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