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