1<?php 2/* Copyright 2012-present Facebook, Inc. 3 * Licensed under the Apache License, Version 2.0 */ 4 5class triggerTestCase extends WatchmanTestCase { 6 function needsLiveConnection() { 7 return true; 8 } 9 10 function getGlobalConfig() { 11 return array( 12 // We need to run our own instance so that we can look in 13 // the log file 14 'dummy' => true 15 ); 16 } 17 18 function doesTriggerDataMatchFileList($root, array $files) { 19 $trigger_json = $root . DIRECTORY_SEPARATOR . 'trigger.json'; 20 21 if (!file_exists($trigger_json)) { 22 return array(false, 'no such file'); 23 } 24 25 // Validate that the json input is properly formatted 26 $expect = array(); 27 foreach ($files as $file) { 28 $expect[] = array( 29 'name' => $file, 30 'exists' => true 31 ); 32 } 33 usort($expect, function ($a, $b) { 34 return strcmp($a['name'], $b['name']); 35 }); 36 37 $lines = 0; 38 $got = array(); 39 foreach (@file($trigger_json) as $line) { 40 $lines++; 41 $list = json_decode($line, true); 42 // Filter out the unpredictable data from lstat() 43 $list = array_map(function ($ent) { 44 return array( 45 'name' => $ent['name'], 46 'exists' => $ent['exists'] 47 ); 48 }, $list); 49 50 usort($list, function ($a, $b) { 51 return strcmp($a['name'], $b['name']); 52 }); 53 54 foreach ($list as $ele) { 55 $got[] = $ele; 56 } 57 } 58 $got = array_unique($got, SORT_REGULAR); 59 60 if ($expect === $got) { 61 return array(true, 'matches'); 62 } 63 64 return array(false, "expect: ".json_encode($expect) . 65 " got: " . json_encode($got)); 66 } 67 68 function validateTriggerOutput($root, array $files, $context) { 69 $trigger_log = $root . DIRECTORY_SEPARATOR . "trigger.log"; 70 $trigger_json = $root . DIRECTORY_SEPARATOR . "trigger.json"; 71 72 $this->waitFor(function () use ($root, $files, $trigger_log) { 73 if (file_exists($trigger_log)) { 74 $dat = file_get_contents($trigger_log); 75 $n = 0; 76 foreach ($files as $file) { 77 if (strpos($dat, $file) !== false) { 78 $n++; 79 } 80 } 81 return $n == count($files); 82 } 83 return false; 84 }, 5, function () use ($root, $files, $context, $trigger_log) { 85 return sprintf( 86 "[$context] $trigger_log should contain %s, has %s", 87 json_encode($files), 88 file_get_contents($trigger_log) 89 ); 90 }); 91 92 $logdata = file_get_contents($trigger_log); 93 foreach ($files as $file) { 94 $this->assertRegex( 95 "/$file/m", 96 $logdata, 97 "[$context] got the right filename in $trigger_log" 98 ); 99 } 100 101 $self = $this; 102 $this->waitFor(function () use ($root, $files, $self) { 103 list ($ok, $why) = $self->doesTriggerDataMatchFileList($root, $files); 104 return $ok; 105 }, 5, function () use ($root, $files, $context, $self, $trigger_json) { 106 list ($ok, $why) = $self->doesTriggerDataMatchFileList($root, $files); 107 return sprintf( 108 "[$context] trigger.json holds valid json for %s: %s", 109 json_encode($files), 110 $why 111 ); 112 }); 113 } 114 115 function testLegacyTrigger() { 116 $dir = new WatchmanDirectoryFixture(); 117 $root = $dir->getPath(); 118 119 touch("$root/foo.c"); 120 touch("$root/b ar.c"); 121 touch("$root/bar.txt"); 122 123 file_put_contents("$root/.watchmanconfig", json_encode( 124 array('settle' => 200) 125 )); 126 127 $out = $this->watch($root); 128 129 $this->assertFileList($root, array( 130 '.watchmanconfig', 'b ar.c', 'bar.txt', 'foo.c' 131 )); 132 133 $res = $this->trigger($root, 134 'test', '*.c', '--', PHP_BINARY, 135 dirname(__FILE__) . DIRECTORY_SEPARATOR . '_trig.php', 136 $root . DIRECTORY_SEPARATOR . "trigger.log"); 137 $this->assertEqual('created', idx($res, 'disposition')); 138 139 $this->trigger($root, 140 'other', '*.c', '--', PHP_BINARY, 141 dirname(__FILE__) . DIRECTORY_SEPARATOR . '_trigjson.php', 142 $root . DIRECTORY_SEPARATOR . "trigger.json"); 143 144 $trig_list = array( 145 array( 146 'append_files' => true, 147 'name' => 'other', 148 'command' => array( 149 PHP_BINARY, 150 dirname(__FILE__) . DIRECTORY_SEPARATOR . '_trigjson.php', 151 $root . DIRECTORY_SEPARATOR . "trigger.json" 152 ), 153 'expression' => array( 154 'anyof', 155 array('match', '*.c', 'wholename') 156 ), 157 'stdin' => array('name', 'exists', 'new', 'size', 'mode'), 158 ), 159 array( 160 'append_files' => true, 161 'name' => 'test', 162 'command' => array( 163 PHP_BINARY, 164 dirname(__FILE__) . DIRECTORY_SEPARATOR . '_trig.php', 165 $root . DIRECTORY_SEPARATOR . "trigger.log" 166 ), 167 'expression' => array( 168 'anyof', 169 array('match', '*.c', 'wholename') 170 ), 171 'stdin' => array('name', 'exists', 'new', 'size', 'mode'), 172 ), 173 ); 174 175 $this->assertTriggerList($root, $trig_list); 176 177 178 $this->startLogging('debug'); 179 180 $this->suspendWatchman(); 181 touch("$root/foo.c"); 182 touch("$root/b ar.c"); 183 $this->resumeWatchman(); 184 185 $this->watchmanCommand('log', 'debug', 'waiting for spawnp ' . __LINE__); 186 $this->assertWaitForLog('/posix_spawnp: test/'); 187 $this->assertWaitForLog('/posix_spawnp: other/'); 188 $this->assertWaitForLogOutput('/WOOT from trig/'); 189 190 $this->stopLogging(); 191 192 $this->validateTriggerOutput($root, array('foo.c', 'b ar.c'), 'initial'); 193 194 foreach (array('foo.c', 'b ar.c') as $file) { 195 // Validate that we observe the updates correctly 196 // (that we're handling the since portion of the query) 197 $this->suspendWatchman(); 198 w_unlink("$root/trigger.log"); 199 w_unlink("$root/trigger.json"); 200 touch("$root/$file"); 201 $this->resumeWatchman(); 202 $this->validateTriggerOutput($root, array($file), "single $file"); 203 } 204 205 w_unlink("$root/trigger.log"); 206 w_unlink("$root/trigger.json"); 207 208 // When running under valgrind, there may be pending events. 209 // Let's give things a chance to finish dispatching before proceeding 210 $this->waitForNoThrow(function () use ($root) { 211 return file_exists("$root/trigger.log"); 212 }, 1); 213 214 @w_unlink("$root/trigger.log"); 215 @w_unlink("$root/trigger.json"); 216 217 $this->startLogging('debug'); 218 219 // trigger a recrawl 220 $this->watchmanCommand('debug-recrawl', $root); 221 222 // make sure the triggers didn't get deleted 223 $this->assertTriggerList($root, $trig_list); 224 225 $this->watchmanCommand('log', 'debug', 'waiting for spawnp ' . __LINE__); 226 $this->assertWaitForLog('/posix_spawnp/', 5); 227 $this->stopLogging(); 228 229 // and that the right data was seen 230 $this->validateTriggerOutput($root, 231 array('foo.c', 'b ar.c'), 'after recrawl'); 232 233 $res = $this->trigger($root, 'other', '*.c', '--', 'true'); 234 $this->assertEqual('replaced', idx($res, 'disposition')); 235 236 $res = $this->trigger($root, 'other', '*.c', '--', 'true'); 237 $this->assertEqual('already_defined', idx($res, 'disposition')); 238 239 $res = $this->watchmanCommand('trigger-del', $root, 'test'); 240 $this->assertEqual(true, $res['deleted']); 241 $this->assertEqual('test', $res['trigger']); 242 243 $triggers = $this->watchmanCommand('trigger-list', $root); 244 $this->assertEqual(1, count($triggers['triggers'])); 245 246 $res = $this->watchmanCommand('trigger-del', $root, 'other'); 247 $this->assertEqual(true, $res['deleted']); 248 $this->assertEqual('other', $res['trigger']); 249 250 $triggers = $this->watchmanCommand('trigger-list', $root); 251 $this->assertEqual(0, count($triggers['triggers'])); 252 } 253 254} 255 256// vim:ts=2:sw=2:et: 257