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