t7527: create test for fsmonitor--daemon

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff Hostetler committed Mar 25, 2022 at 18:03 UTC a00cdff81a2d578405cf0f68072da07eee462b93
1 file changed +494
t/t7527-builtin-fsmonitor.sh new
+494
@@ -0,0 +1,494 @@
1 +#!/bin/sh
2 +
3 +test_description='built-in file system watcher'
4 +
5 +. ./test-lib.sh
6 +
7 +if ! test_have_prereq FSMONITOR_DAEMON
8 +then
9 + skip_all="fsmonitor--daemon is not supported on this platform"
10 + test_done
11 +fi
12 +
13 +stop_daemon_delete_repo () {
14 + r=$1 &&
15 + test_might_fail git -C $r fsmonitor--daemon stop &&
16 + rm -rf $1
17 +}
18 +
19 +start_daemon () {
20 + r= tf= t2= tk= &&
21 +
22 + while test "$#" -ne 0
23 + do
24 + case "$1" in
25 + -C)
26 + r="-C ${2?}"
27 + shift
28 + ;;
29 + --tf)
30 + tf="${2?}"
31 + shift
32 + ;;
33 + --t2)
34 + t2="${2?}"
35 + shift
36 + ;;
37 + --tk)
38 + tk="${2?}"
39 + shift
40 + ;;
41 + -*)
42 + BUG "error: unknown option: '$1'"
43 + ;;
44 + *)
45 + BUG "error: unbound argument: '$1'"
46 + ;;
47 + esac
48 + shift
49 + done &&
50 +
51 + (
52 + if test -n "$tf"
53 + then
54 + GIT_TRACE_FSMONITOR="$tf"
55 + export GIT_TRACE_FSMONITOR
56 + fi &&
57 +
58 + if test -n "$t2"
59 + then
60 + GIT_TRACE2_PERF="$t2"
61 + export GIT_TRACE2_PERF
62 + fi &&
63 +
64 + if test -n "$tk"
65 + then
66 + GIT_TEST_FSMONITOR_TOKEN="$tk"
67 + export GIT_TEST_FSMONITOR_TOKEN
68 + fi &&
69 +
70 + git $r fsmonitor--daemon start &&
71 + git $r fsmonitor--daemon status
72 + )
73 +}
74 +
75 +# Is a Trace2 data event present with the given catetory and key?
76 +# We do not care what the value is.
77 +#
78 +have_t2_data_event () {
79 + c=$1 &&
80 + k=$2 &&
81 +
82 + grep -e '"event":"data".*"category":"'"$c"'".*"key":"'"$k"'"'
83 +}
84 +
85 +test_expect_success 'explicit daemon start and stop' '
86 + test_when_finished "stop_daemon_delete_repo test_explicit" &&
87 +
88 + git init test_explicit &&
89 + start_daemon -C test_explicit &&
90 +
91 + git -C test_explicit fsmonitor--daemon stop &&
92 + test_must_fail git -C test_explicit fsmonitor--daemon status
93 +'
94 +
95 +test_expect_success 'implicit daemon start' '
96 + test_when_finished "stop_daemon_delete_repo test_implicit" &&
97 +
98 + git init test_implicit &&
99 + test_must_fail git -C test_implicit fsmonitor--daemon status &&
100 +
101 + # query will implicitly start the daemon.
102 + #
103 + # for test-script simplicity, we send a V1 timestamp rather than
104 + # a V2 token. either way, the daemon response to any query contains
105 + # a new V2 token. (the daemon may complain that we sent a V1 request,
106 + # but this test case is only concerned with whether the daemon was
107 + # implicitly started.)
108 +
109 + GIT_TRACE2_EVENT="$PWD/.git/trace" \
110 + test-tool -C test_implicit fsmonitor-client query --token 0 >actual &&
111 + nul_to_q <actual >actual.filtered &&
112 + grep "builtin:" actual.filtered &&
113 +
114 + # confirm that a daemon was started in the background.
115 + #
116 + # since the mechanism for starting the background daemon is platform
117 + # dependent, just confirm that the foreground command received a
118 + # response from the daemon.
119 +
120 + have_t2_data_event fsm_client query/response-length <.git/trace &&
121 +
122 + git -C test_implicit fsmonitor--daemon status &&
123 + git -C test_implicit fsmonitor--daemon stop &&
124 + test_must_fail git -C test_implicit fsmonitor--daemon status
125 +'
126 +
127 +test_expect_success 'implicit daemon stop (delete .git)' '
128 + test_when_finished "stop_daemon_delete_repo test_implicit_1" &&
129 +
130 + git init test_implicit_1 &&
131 +
132 + start_daemon -C test_implicit_1 &&
133 +
134 + # deleting the .git directory will implicitly stop the daemon.
135 + rm -rf test_implicit_1/.git &&
136 +
137 + # [1] Create an empty .git directory so that the following Git
138 + # command will stay relative to the `-C` directory.
139 + #
140 + # Without this, the Git command will override the requested
141 + # -C argument and crawl out to the containing Git source tree.
142 + # This would make the test result dependent upon whether we
143 + # were using fsmonitor on our development worktree.
144 + #
145 + sleep 1 &&
146 + mkdir test_implicit_1/.git &&
147 +
148 + test_must_fail git -C test_implicit_1 fsmonitor--daemon status
149 +'
150 +
151 +test_expect_success 'implicit daemon stop (rename .git)' '
152 + test_when_finished "stop_daemon_delete_repo test_implicit_2" &&
153 +
154 + git init test_implicit_2 &&
155 +
156 + start_daemon -C test_implicit_2 &&
157 +
158 + # renaming the .git directory will implicitly stop the daemon.
159 + mv test_implicit_2/.git test_implicit_2/.xxx &&
160 +
161 + # See [1] above.
162 + #
163 + sleep 1 &&
164 + mkdir test_implicit_2/.git &&
165 +
166 + test_must_fail git -C test_implicit_2 fsmonitor--daemon status
167 +'
168 +
169 +test_expect_success 'cannot start multiple daemons' '
170 + test_when_finished "stop_daemon_delete_repo test_multiple" &&
171 +
172 + git init test_multiple &&
173 +
174 + start_daemon -C test_multiple &&
175 +
176 + test_must_fail git -C test_multiple fsmonitor--daemon start 2>actual &&
177 + grep "fsmonitor--daemon is already running" actual &&
178 +
179 + git -C test_multiple fsmonitor--daemon stop &&
180 + test_must_fail git -C test_multiple fsmonitor--daemon status
181 +'
182 +
183 +# These tests use the main repo in the trash directory
184 +
185 +test_expect_success 'setup' '
186 + >tracked &&
187 + >modified &&
188 + >delete &&
189 + >rename &&
190 + mkdir dir1 &&
191 + >dir1/tracked &&
192 + >dir1/modified &&
193 + >dir1/delete &&
194 + >dir1/rename &&
195 + mkdir dir2 &&
196 + >dir2/tracked &&
197 + >dir2/modified &&
198 + >dir2/delete &&
199 + >dir2/rename &&
200 + mkdir dirtorename &&
201 + >dirtorename/a &&
202 + >dirtorename/b &&
203 +
204 + cat >.gitignore <<-\EOF &&
205 + .gitignore
206 + expect*
207 + actual*
208 + EOF
209 +
210 + git -c core.fsmonitor=false add . &&
211 + test_tick &&
212 + git -c core.fsmonitor=false commit -m initial &&
213 +
214 + git config core.fsmonitor true
215 +'
216 +
217 +# The test already explicitly stopped (or tried to stop) the daemon.
218 +# This is here in case something else fails first.
219 +#
220 +redundant_stop_daemon () {
221 + test_might_fail git fsmonitor--daemon stop
222 +}
223 +
224 +test_expect_success 'update-index implicitly starts daemon' '
225 + test_when_finished redundant_stop_daemon &&
226 +
227 + test_must_fail git fsmonitor--daemon status &&
228 +
229 + GIT_TRACE2_EVENT="$PWD/.git/trace_implicit_1" \
230 + git update-index --fsmonitor &&
231 +
232 + git fsmonitor--daemon status &&
233 + test_might_fail git fsmonitor--daemon stop &&
234 +
235 + # Confirm that the trace2 log contains a record of the
236 + # daemon starting.
237 + test_subcommand git fsmonitor--daemon start <.git/trace_implicit_1
238 +'
239 +
240 +test_expect_success 'status implicitly starts daemon' '
241 + test_when_finished redundant_stop_daemon &&
242 +
243 + test_must_fail git fsmonitor--daemon status &&
244 +
245 + GIT_TRACE2_EVENT="$PWD/.git/trace_implicit_2" \
246 + git status >actual &&
247 +
248 + git fsmonitor--daemon status &&
249 + test_might_fail git fsmonitor--daemon stop &&
250 +
251 + # Confirm that the trace2 log contains a record of the
252 + # daemon starting.
253 + test_subcommand git fsmonitor--daemon start <.git/trace_implicit_2
254 +'
255 +
256 +edit_files () {
257 + echo 1 >modified &&
258 + echo 2 >dir1/modified &&
259 + echo 3 >dir2/modified &&
260 + >dir1/untracked
261 +}
262 +
263 +delete_files () {
264 + rm -f delete &&
265 + rm -f dir1/delete &&
266 + rm -f dir2/delete
267 +}
268 +
269 +create_files () {
270 + echo 1 >new &&
271 + echo 2 >dir1/new &&
272 + echo 3 >dir2/new
273 +}
274 +
275 +rename_files () {
276 + mv rename renamed &&
277 + mv dir1/rename dir1/renamed &&
278 + mv dir2/rename dir2/renamed
279 +}
280 +
281 +file_to_directory () {
282 + rm -f delete &&
283 + mkdir delete &&
284 + echo 1 >delete/new
285 +}
286 +
287 +directory_to_file () {
288 + rm -rf dir1 &&
289 + echo 1 >dir1
290 +}
291 +
292 +# The next few test cases confirm that our fsmonitor daemon sees each type
293 +# of OS filesystem notification that we care about. At this layer we just
294 +# ensure we are getting the OS notifications and do not try to confirm what
295 +# is reported by `git status`.
296 +#
297 +# We run a simple query after modifying the filesystem just to introduce
298 +# a bit of a delay so that the trace logging from the daemon has time to
299 +# get flushed to disk.
300 +#
301 +# We `reset` and `clean` at the bottom of each test (and before stopping the
302 +# daemon) because these commands might implicitly restart the daemon.
303 +
304 +clean_up_repo_and_stop_daemon () {
305 + git reset --hard HEAD &&
306 + git clean -fd &&
307 + test_might_fail git fsmonitor--daemon stop &&
308 + rm -f .git/trace
309 +}
310 +
311 +test_expect_success 'edit some files' '
312 + test_when_finished clean_up_repo_and_stop_daemon &&
313 +
314 + start_daemon --tf "$PWD/.git/trace" &&
315 +
316 + edit_files &&
317 +
318 + test-tool fsmonitor-client query --token 0 &&
319 +
320 + grep "^event: dir1/modified$" .git/trace &&
321 + grep "^event: dir2/modified$" .git/trace &&
322 + grep "^event: modified$" .git/trace &&
323 + grep "^event: dir1/untracked$" .git/trace
324 +'
325 +
326 +test_expect_success 'create some files' '
327 + test_when_finished clean_up_repo_and_stop_daemon &&
328 +
329 + start_daemon --tf "$PWD/.git/trace" &&
330 +
331 + create_files &&
332 +
333 + test-tool fsmonitor-client query --token 0 &&
334 +
335 + grep "^event: dir1/new$" .git/trace &&
336 + grep "^event: dir2/new$" .git/trace &&
337 + grep "^event: new$" .git/trace
338 +'
339 +
340 +test_expect_success 'delete some files' '
341 + test_when_finished clean_up_repo_and_stop_daemon &&
342 +
343 + start_daemon --tf "$PWD/.git/trace" &&
344 +
345 + delete_files &&
346 +
347 + test-tool fsmonitor-client query --token 0 &&
348 +
349 + grep "^event: dir1/delete$" .git/trace &&
350 + grep "^event: dir2/delete$" .git/trace &&
351 + grep "^event: delete$" .git/trace
352 +'
353 +
354 +test_expect_success 'rename some files' '
355 + test_when_finished clean_up_repo_and_stop_daemon &&
356 +
357 + start_daemon --tf "$PWD/.git/trace" &&
358 +
359 + rename_files &&
360 +
361 + test-tool fsmonitor-client query --token 0 &&
362 +
363 + grep "^event: dir1/rename$" .git/trace &&
364 + grep "^event: dir2/rename$" .git/trace &&
365 + grep "^event: rename$" .git/trace &&
366 + grep "^event: dir1/renamed$" .git/trace &&
367 + grep "^event: dir2/renamed$" .git/trace &&
368 + grep "^event: renamed$" .git/trace
369 +'
370 +
371 +test_expect_success 'rename directory' '
372 + test_when_finished clean_up_repo_and_stop_daemon &&
373 +
374 + start_daemon --tf "$PWD/.git/trace" &&
375 +
376 + mv dirtorename dirrenamed &&
377 +
378 + test-tool fsmonitor-client query --token 0 &&
379 +
380 + grep "^event: dirtorename/*$" .git/trace &&
381 + grep "^event: dirrenamed/*$" .git/trace
382 +'
383 +
384 +test_expect_success 'file changes to directory' '
385 + test_when_finished clean_up_repo_and_stop_daemon &&
386 +
387 + start_daemon --tf "$PWD/.git/trace" &&
388 +
389 + file_to_directory &&
390 +
391 + test-tool fsmonitor-client query --token 0 &&
392 +
393 + grep "^event: delete$" .git/trace &&
394 + grep "^event: delete/new$" .git/trace
395 +'
396 +
397 +test_expect_success 'directory changes to a file' '
398 + test_when_finished clean_up_repo_and_stop_daemon &&
399 +
400 + start_daemon --tf "$PWD/.git/trace" &&
401 +
402 + directory_to_file &&
403 +
404 + test-tool fsmonitor-client query --token 0 &&
405 +
406 + grep "^event: dir1$" .git/trace
407 +'
408 +
409 +# The next few test cases exercise the token-resync code. When filesystem
410 +# drops events (because of filesystem velocity or because the daemon isn't
411 +# polling fast enough), we need to discard the cached data (relative to the
412 +# current token) and start collecting events under a new token.
413 +#
414 +# the 'test-tool fsmonitor-client flush' command can be used to send a
415 +# "flush" message to a running daemon and ask it to do a flush/resync.
416 +
417 +test_expect_success 'flush cached data' '
418 + test_when_finished "stop_daemon_delete_repo test_flush" &&
419 +
420 + git init test_flush &&
421 +
422 + start_daemon -C test_flush --tf "$PWD/.git/trace_daemon" --tk true &&
423 +
424 + # The daemon should have an initial token with no events in _0 and
425 + # then a few (probably platform-specific number of) events in _1.
426 + # These should both have the same <token_id>.
427 +
428 + test-tool -C test_flush fsmonitor-client query --token "builtin:test_00000001:0" >actual_0 &&
429 + nul_to_q <actual_0 >actual_q0 &&
430 +
431 + >test_flush/file_1 &&
432 + >test_flush/file_2 &&
433 +
434 + test-tool -C test_flush fsmonitor-client query --token "builtin:test_00000001:0" >actual_1 &&
435 + nul_to_q <actual_1 >actual_q1 &&
436 +
437 + grep "file_1" actual_q1 &&
438 +
439 + # Force a flush. This will change the <token_id>, reset the <seq_nr>, and
440 + # flush the file data. Then create some events and ensure that the file
441 + # again appears in the cache. It should have the new <token_id>.
442 +
443 + test-tool -C test_flush fsmonitor-client flush >flush_0 &&
444 + nul_to_q <flush_0 >flush_q0 &&
445 + grep "^builtin:test_00000002:0Q/Q$" flush_q0 &&
446 +
447 + test-tool -C test_flush fsmonitor-client query --token "builtin:test_00000002:0" >actual_2 &&
448 + nul_to_q <actual_2 >actual_q2 &&
449 +
450 + grep "^builtin:test_00000002:0Q$" actual_q2 &&
451 +
452 + >test_flush/file_3 &&
453 +
454 + test-tool -C test_flush fsmonitor-client query --token "builtin:test_00000002:0" >actual_3 &&
455 + nul_to_q <actual_3 >actual_q3 &&
456 +
457 + grep "file_3" actual_q3
458 +'
459 +
460 +# The next few test cases create repos where the .git directory is NOT
461 +# inside the one of the working directory. That is, where .git is a file
462 +# that points to a directory elsewhere. This happens for submodules and
463 +# non-primary worktrees.
464 +
465 +test_expect_success 'setup worktree base' '
466 + git init wt-base &&
467 + echo 1 >wt-base/file1 &&
468 + git -C wt-base add file1 &&
469 + git -C wt-base commit -m "c1"
470 +'
471 +
472 +test_expect_success 'worktree with .git file' '
473 + git -C wt-base worktree add ../wt-secondary &&
474 +
475 + start_daemon -C wt-secondary \
476 + --tf "$PWD/trace_wt_secondary" \
477 + --t2 "$PWD/trace2_wt_secondary" &&
478 +
479 + git -C wt-secondary fsmonitor--daemon stop &&
480 + test_must_fail git -C wt-secondary fsmonitor--daemon status
481 +'
482 +
483 +# NEEDSWORK: Repeat one of the "edit" tests on wt-secondary and
484 +# confirm that we get the same events and behavior -- that is, that
485 +# fsmonitor--daemon correctly watches BOTH the working directory and
486 +# the external GITDIR directory and behaves the same as when ".git"
487 +# is a directory inside the working directory.
488 +
489 +test_expect_success 'cleanup worktrees' '
490 + stop_daemon_delete_repo wt-secondary &&
491 + stop_daemon_delete_repo wt-base
492 +'
493 +
494 +test_done