{"thread":{"id":"64703","subject":"[PATCH] t7527: fix flaky fsmonitor event tests with retry logic","startedAt":"2025-12-31T23:40:14Z","lastAt":"2026-01-01T00:19:27Z","messageCount":2,"participants":["Paul Tarjan via GitGitGadget"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"532869","messageId":"pull.2150.git.git.1767224411233.gitgitgadget@gmail.com","threadId":"64703","inReplyTo":null,"subject":"[PATCH] t7527: fix flaky fsmonitor event tests with retry logic","fromName":"Paul Tarjan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-12-31T23:40:11Z","receivedAt":"2025-12-31T23:40:14Z","isPatch":true,"sender":{"key":"paul@paultarjan.com","avatar":null},"body":"From: Paul Tarjan <github@paulisageek.com>\n\nThe fsmonitor event tests (edit, create, delete, rename, etc.) were\nflaky because there can be a race between the daemon writing events\nto the trace file and the test's grep commands checking for them.\n\nAdd a retry_grep() helper function (similar to retry_until_success\nin lib-git-p4.sh) that retries grep with a timeout, and use it in\nall event-checking tests to wait for one expected event before\nchecking the rest.\n\nSigned-off-by: Paul Tarjan <github@paulisageek.com>\n---\n    t7527: fix flaky fsmonitor event tests with retry logic\n    \n    This failed in\n    https://github.com/git/git/actions/runs/20628166110/job/59242063331 on\n    an unrelated commit.\n    \n    The fsmonitor event tests (edit, create, delete, rename, etc.) were\n    flaky because there can be a race between the daemon writing events to\n    the trace file and the test's grep commands checking for them.\n    \n    Add a retry_grep() helper function (similar to retry_until_success in\n    lib-git-p4.sh) that retries grep with a timeout, and use it in all\n    event-checking tests to wait for one expected event before checking the\n    rest.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2150%2Fptarjan%2Fclaude%2Ffix-fsmonitor-test-jsXoE-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2150/ptarjan/claude/fix-fsmonitor-test-jsXoE-v1\nPull-Request: https://github.com/git/git/pull/2150\n\n t/t7527-builtin-fsmonitor.sh | 51 +++++++++++++++++++-----------------\n 1 file changed, 27 insertions(+), 24 deletions(-)\n\ndiff --git a/t/t7527-builtin-fsmonitor.sh b/t/t7527-builtin-fsmonitor.sh\nindex 409cd0cd12..68a10a2100 100755\n--- a/t/t7527-builtin-fsmonitor.sh\n+++ b/t/t7527-builtin-fsmonitor.sh\n@@ -408,9 +408,8 @@ move_directory() {\n # ensure we are getting the OS notifications and do not try to confirm what\n # is reported by `git status`.\n #\n-# We run a simple query after modifying the filesystem just to introduce\n-# a bit of a delay so that the trace logging from the daemon has time to\n-# get flushed to disk.\n+# We use retry_grep to handle races between the daemon writing events\n+# to the trace file and our check.\n #\n # We `reset` and `clean` at the bottom of each test (and before stopping the\n # daemon) because these commands might implicitly restart the daemon.\n@@ -422,6 +421,24 @@ clean_up_repo_and_stop_daemon () {\n \trm -f .git/trace\n }\n \n+# Retry a grep up to RETRY_TIMEOUT times until it succeeds.\n+#\n+RETRY_TIMEOUT=5\n+\n+retry_grep () {\n+\tnr_tries_left=$RETRY_TIMEOUT\n+\tuntil grep \"$1\" \"$2\" 2>/dev/null\n+\tdo\n+\t\tif test $nr_tries_left -eq 0\n+\t\tthen\n+\t\t\tgrep \"$1\" \"$2\"\n+\t\t\treturn\n+\t\tfi\n+\t\tnr_tries_left=$(($nr_tries_left - 1))\n+\t\tsleep 1\n+\tdone\n+}\n+\n test_expect_success 'edit some files' '\n \ttest_when_finished clean_up_repo_and_stop_daemon &&\n \n@@ -429,9 +446,7 @@ test_expect_success 'edit some files' '\n \n \tedit_files &&\n \n-\ttest-tool fsmonitor-client query --token 0 &&\n-\n-\tgrep \"^event: dir1/modified$\"  .git/trace &&\n+\tretry_grep \"^event: dir1/modified$\" .git/trace &&\n \tgrep \"^event: dir2/modified$\"  .git/trace &&\n \tgrep \"^event: modified$\"       .git/trace &&\n \tgrep \"^event: dir1/untracked$\" .git/trace\n@@ -444,9 +459,7 @@ test_expect_success 'create some files' '\n \n \tcreate_files &&\n \n-\ttest-tool fsmonitor-client query --token 0 &&\n-\n-\tgrep \"^event: dir1/new$\" .git/trace &&\n+\tretry_grep \"^event: dir1/new$\" .git/trace &&\n \tgrep \"^event: dir2/new$\" .git/trace &&\n \tgrep \"^event: new$\"      .git/trace\n '\n@@ -458,9 +471,7 @@ test_expect_success 'delete some files' '\n \n \tdelete_files &&\n \n-\ttest-tool fsmonitor-client query --token 0 &&\n-\n-\tgrep \"^event: dir1/delete$\" .git/trace &&\n+\tretry_grep \"^event: dir1/delete$\" .git/trace &&\n \tgrep \"^event: dir2/delete$\" .git/trace &&\n \tgrep \"^event: delete$\"      .git/trace\n '\n@@ -472,9 +483,7 @@ test_expect_success 'rename some files' '\n \n \trename_files &&\n \n-\ttest-tool fsmonitor-client query --token 0 &&\n-\n-\tgrep \"^event: dir1/rename$\"  .git/trace &&\n+\tretry_grep \"^event: dir1/rename$\" .git/trace &&\n \tgrep \"^event: dir2/rename$\"  .git/trace &&\n \tgrep \"^event: rename$\"       .git/trace &&\n \tgrep \"^event: dir1/renamed$\" .git/trace &&\n@@ -489,9 +498,7 @@ test_expect_success 'rename directory' '\n \n \tmv dirtorename dirrenamed &&\n \n-\ttest-tool fsmonitor-client query --token 0 &&\n-\n-\tgrep \"^event: dirtorename/*$\" .git/trace &&\n+\tretry_grep \"^event: dirtorename/*$\" .git/trace &&\n \tgrep \"^event: dirrenamed/*$\"  .git/trace\n '\n \n@@ -502,9 +509,7 @@ test_expect_success 'file changes to directory' '\n \n \tfile_to_directory &&\n \n-\ttest-tool fsmonitor-client query --token 0 &&\n-\n-\tgrep \"^event: delete$\"     .git/trace &&\n+\tretry_grep \"^event: delete$\" .git/trace &&\n \tgrep \"^event: delete/new$\" .git/trace\n '\n \n@@ -515,9 +520,7 @@ test_expect_success 'directory changes to a file' '\n \n \tdirectory_to_file &&\n \n-\ttest-tool fsmonitor-client query --token 0 &&\n-\n-\tgrep \"^event: dir1$\" .git/trace\n+\tretry_grep \"^event: dir1$\" .git/trace\n '\n \n # The next few test cases exercise the token-resync code.  When filesystem\n\nbase-commit: 68cb7f9e92a5d8e9824f5b52ac3d0a9d8f653dbe\n-- \ngitgitgadget\n"},{"id":"532870","messageId":"pull.2150.v2.git.git.1767226763360.gitgitgadget@gmail.com","threadId":"64703","inReplyTo":"pull.2150.git.git.1767224411233.gitgitgadget@gmail.com","subject":"[PATCH v2] t7527: fix flaky fsmonitor event tests with retry logic","fromName":"Paul Tarjan via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-01-01T00:19:23Z","receivedAt":"2026-01-01T00:19:27Z","isPatch":true,"sender":{"key":"paul@paultarjan.com","avatar":null},"body":"From: Paul Tarjan <github@paulisageek.com>\n\nThe fsmonitor event tests (edit, create, delete, rename, etc.) were\nflaky because there can be a race between the daemon writing events\nto the trace file and the test's grep commands checking for them.\n\nAdd a retry_grep() helper function (similar to retry_until_success\nin lib-git-p4.sh) that retries grep with a timeout, and use it in\nall event-checking tests to wait for one expected event before\nchecking the rest.\n\nSigned-off-by: Paul Tarjan <github@paulisageek.com>\n---\n    t7527: fix flaky fsmonitor event tests with retry logic\n    \n    This failed in\n    https://github.com/git/git/actions/runs/20628166110/job/59242063331 on\n    an unrelated commit.\n    \n    The fsmonitor event tests (edit, create, delete, rename, etc.) were\n    flaky because there can be a race between the daemon writing events to\n    the trace file and the test's grep commands checking for them.\n    \n    Add a retry_grep() helper function (similar to retry_until_success in\n    lib-git-p4.sh) that retries grep with a timeout, and use it in all\n    event-checking tests to wait for one expected event before checking the\n    rest.\n    \n    Changes since v1:\n    \n     * Use retry_grep for all event checks, not just the first one (any\n       event can be delayed)\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2150%2Fptarjan%2Fclaude%2Ffix-fsmonitor-test-jsXoE-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2150/ptarjan/claude/fix-fsmonitor-test-jsXoE-v2\nPull-Request: https://github.com/git/git/pull/2150\n\nRange-diff vs v1:\n\n 1:  6fafc812e1 ! 1:  c275732e95 t7527: fix flaky fsmonitor event tests with retry logic\n     @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'edit some files' '\n      -\ttest-tool fsmonitor-client query --token 0 &&\n      -\n      -\tgrep \"^event: dir1/modified$\"  .git/trace &&\n     +-\tgrep \"^event: dir2/modified$\"  .git/trace &&\n     +-\tgrep \"^event: modified$\"       .git/trace &&\n     +-\tgrep \"^event: dir1/untracked$\" .git/trace\n      +\tretry_grep \"^event: dir1/modified$\" .git/trace &&\n     - \tgrep \"^event: dir2/modified$\"  .git/trace &&\n     - \tgrep \"^event: modified$\"       .git/trace &&\n     - \tgrep \"^event: dir1/untracked$\" .git/trace\n     ++\tretry_grep \"^event: dir2/modified$\"  .git/trace &&\n     ++\tretry_grep \"^event: modified$\"       .git/trace &&\n     ++\tretry_grep \"^event: dir1/untracked$\" .git/trace\n     + '\n     + \n     + test_expect_success 'create some files' '\n      @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'create some files' '\n       \n       \tcreate_files &&\n     @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'create some files' '\n      -\ttest-tool fsmonitor-client query --token 0 &&\n      -\n      -\tgrep \"^event: dir1/new$\" .git/trace &&\n     +-\tgrep \"^event: dir2/new$\" .git/trace &&\n     +-\tgrep \"^event: new$\"      .git/trace\n      +\tretry_grep \"^event: dir1/new$\" .git/trace &&\n     - \tgrep \"^event: dir2/new$\" .git/trace &&\n     - \tgrep \"^event: new$\"      .git/trace\n     ++\tretry_grep \"^event: dir2/new$\" .git/trace &&\n     ++\tretry_grep \"^event: new$\"      .git/trace\n       '\n     + \n     + test_expect_success 'delete some files' '\n      @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'delete some files' '\n       \n       \tdelete_files &&\n     @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'delete some files' '\n      -\ttest-tool fsmonitor-client query --token 0 &&\n      -\n      -\tgrep \"^event: dir1/delete$\" .git/trace &&\n     +-\tgrep \"^event: dir2/delete$\" .git/trace &&\n     +-\tgrep \"^event: delete$\"      .git/trace\n      +\tretry_grep \"^event: dir1/delete$\" .git/trace &&\n     - \tgrep \"^event: dir2/delete$\" .git/trace &&\n     - \tgrep \"^event: delete$\"      .git/trace\n     ++\tretry_grep \"^event: dir2/delete$\" .git/trace &&\n     ++\tretry_grep \"^event: delete$\"      .git/trace\n       '\n     + \n     + test_expect_success 'rename some files' '\n      @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'rename some files' '\n       \n       \trename_files &&\n     @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'rename some files' '\n      -\ttest-tool fsmonitor-client query --token 0 &&\n      -\n      -\tgrep \"^event: dir1/rename$\"  .git/trace &&\n     +-\tgrep \"^event: dir2/rename$\"  .git/trace &&\n     +-\tgrep \"^event: rename$\"       .git/trace &&\n     +-\tgrep \"^event: dir1/renamed$\" .git/trace &&\n     +-\tgrep \"^event: dir2/renamed$\" .git/trace &&\n     +-\tgrep \"^event: renamed$\"      .git/trace\n      +\tretry_grep \"^event: dir1/rename$\" .git/trace &&\n     - \tgrep \"^event: dir2/rename$\"  .git/trace &&\n     - \tgrep \"^event: rename$\"       .git/trace &&\n     - \tgrep \"^event: dir1/renamed$\" .git/trace &&\n     ++\tretry_grep \"^event: dir2/rename$\"  .git/trace &&\n     ++\tretry_grep \"^event: rename$\"       .git/trace &&\n     ++\tretry_grep \"^event: dir1/renamed$\" .git/trace &&\n     ++\tretry_grep \"^event: dir2/renamed$\" .git/trace &&\n     ++\tretry_grep \"^event: renamed$\"      .git/trace\n     + '\n     + \n     + test_expect_success 'rename directory' '\n      @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'rename directory' '\n       \n       \tmv dirtorename dirrenamed &&\n     @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'rename directory' '\n      -\ttest-tool fsmonitor-client query --token 0 &&\n      -\n      -\tgrep \"^event: dirtorename/*$\" .git/trace &&\n     +-\tgrep \"^event: dirrenamed/*$\"  .git/trace\n      +\tretry_grep \"^event: dirtorename/*$\" .git/trace &&\n     - \tgrep \"^event: dirrenamed/*$\"  .git/trace\n     ++\tretry_grep \"^event: dirrenamed/*$\"  .git/trace\n       '\n       \n     + test_expect_success 'file changes to directory' '\n      @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'file changes to directory' '\n       \n       \tfile_to_directory &&\n     @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'file changes to directory' '\n      -\ttest-tool fsmonitor-client query --token 0 &&\n      -\n      -\tgrep \"^event: delete$\"     .git/trace &&\n     +-\tgrep \"^event: delete/new$\" .git/trace\n      +\tretry_grep \"^event: delete$\" .git/trace &&\n     - \tgrep \"^event: delete/new$\" .git/trace\n     ++\tretry_grep \"^event: delete/new$\" .git/trace\n       '\n       \n     + test_expect_success 'directory changes to a file' '\n      @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'directory changes to a file' '\n       \n       \tdirectory_to_file &&\n\n\n t/t7527-builtin-fsmonitor.sh | 79 +++++++++++++++++++-----------------\n 1 file changed, 41 insertions(+), 38 deletions(-)\n\ndiff --git a/t/t7527-builtin-fsmonitor.sh b/t/t7527-builtin-fsmonitor.sh\nindex 409cd0cd12..e7b4065469 100755\n--- a/t/t7527-builtin-fsmonitor.sh\n+++ b/t/t7527-builtin-fsmonitor.sh\n@@ -408,9 +408,8 @@ move_directory() {\n # ensure we are getting the OS notifications and do not try to confirm what\n # is reported by `git status`.\n #\n-# We run a simple query after modifying the filesystem just to introduce\n-# a bit of a delay so that the trace logging from the daemon has time to\n-# get flushed to disk.\n+# We use retry_grep to handle races between the daemon writing events\n+# to the trace file and our check.\n #\n # We `reset` and `clean` at the bottom of each test (and before stopping the\n # daemon) because these commands might implicitly restart the daemon.\n@@ -422,6 +421,24 @@ clean_up_repo_and_stop_daemon () {\n \trm -f .git/trace\n }\n \n+# Retry a grep up to RETRY_TIMEOUT times until it succeeds.\n+#\n+RETRY_TIMEOUT=5\n+\n+retry_grep () {\n+\tnr_tries_left=$RETRY_TIMEOUT\n+\tuntil grep \"$1\" \"$2\" 2>/dev/null\n+\tdo\n+\t\tif test $nr_tries_left -eq 0\n+\t\tthen\n+\t\t\tgrep \"$1\" \"$2\"\n+\t\t\treturn\n+\t\tfi\n+\t\tnr_tries_left=$(($nr_tries_left - 1))\n+\t\tsleep 1\n+\tdone\n+}\n+\n test_expect_success 'edit some files' '\n \ttest_when_finished clean_up_repo_and_stop_daemon &&\n \n@@ -429,12 +446,10 @@ test_expect_success 'edit some files' '\n \n \tedit_files &&\n \n-\ttest-tool fsmonitor-client query --token 0 &&\n-\n-\tgrep \"^event: dir1/modified$\"  .git/trace &&\n-\tgrep \"^event: dir2/modified$\"  .git/trace &&\n-\tgrep \"^event: modified$\"       .git/trace &&\n-\tgrep \"^event: dir1/untracked$\" .git/trace\n+\tretry_grep \"^event: dir1/modified$\" .git/trace &&\n+\tretry_grep \"^event: dir2/modified$\"  .git/trace &&\n+\tretry_grep \"^event: modified$\"       .git/trace &&\n+\tretry_grep \"^event: dir1/untracked$\" .git/trace\n '\n \n test_expect_success 'create some files' '\n@@ -444,11 +459,9 @@ test_expect_success 'create some files' '\n \n \tcreate_files &&\n \n-\ttest-tool fsmonitor-client query --token 0 &&\n-\n-\tgrep \"^event: dir1/new$\" .git/trace &&\n-\tgrep \"^event: dir2/new$\" .git/trace &&\n-\tgrep \"^event: new$\"      .git/trace\n+\tretry_grep \"^event: dir1/new$\" .git/trace &&\n+\tretry_grep \"^event: dir2/new$\" .git/trace &&\n+\tretry_grep \"^event: new$\"      .git/trace\n '\n \n test_expect_success 'delete some files' '\n@@ -458,11 +471,9 @@ test_expect_success 'delete some files' '\n \n \tdelete_files &&\n \n-\ttest-tool fsmonitor-client query --token 0 &&\n-\n-\tgrep \"^event: dir1/delete$\" .git/trace &&\n-\tgrep \"^event: dir2/delete$\" .git/trace &&\n-\tgrep \"^event: delete$\"      .git/trace\n+\tretry_grep \"^event: dir1/delete$\" .git/trace &&\n+\tretry_grep \"^event: dir2/delete$\" .git/trace &&\n+\tretry_grep \"^event: delete$\"      .git/trace\n '\n \n test_expect_success 'rename some files' '\n@@ -472,14 +483,12 @@ test_expect_success 'rename some files' '\n \n \trename_files &&\n \n-\ttest-tool fsmonitor-client query --token 0 &&\n-\n-\tgrep \"^event: dir1/rename$\"  .git/trace &&\n-\tgrep \"^event: dir2/rename$\"  .git/trace &&\n-\tgrep \"^event: rename$\"       .git/trace &&\n-\tgrep \"^event: dir1/renamed$\" .git/trace &&\n-\tgrep \"^event: dir2/renamed$\" .git/trace &&\n-\tgrep \"^event: renamed$\"      .git/trace\n+\tretry_grep \"^event: dir1/rename$\" .git/trace &&\n+\tretry_grep \"^event: dir2/rename$\"  .git/trace &&\n+\tretry_grep \"^event: rename$\"       .git/trace &&\n+\tretry_grep \"^event: dir1/renamed$\" .git/trace &&\n+\tretry_grep \"^event: dir2/renamed$\" .git/trace &&\n+\tretry_grep \"^event: renamed$\"      .git/trace\n '\n \n test_expect_success 'rename directory' '\n@@ -489,10 +498,8 @@ test_expect_success 'rename directory' '\n \n \tmv dirtorename dirrenamed &&\n \n-\ttest-tool fsmonitor-client query --token 0 &&\n-\n-\tgrep \"^event: dirtorename/*$\" .git/trace &&\n-\tgrep \"^event: dirrenamed/*$\"  .git/trace\n+\tretry_grep \"^event: dirtorename/*$\" .git/trace &&\n+\tretry_grep \"^event: dirrenamed/*$\"  .git/trace\n '\n \n test_expect_success 'file changes to directory' '\n@@ -502,10 +509,8 @@ test_expect_success 'file changes to directory' '\n \n \tfile_to_directory &&\n \n-\ttest-tool fsmonitor-client query --token 0 &&\n-\n-\tgrep \"^event: delete$\"     .git/trace &&\n-\tgrep \"^event: delete/new$\" .git/trace\n+\tretry_grep \"^event: delete$\" .git/trace &&\n+\tretry_grep \"^event: delete/new$\" .git/trace\n '\n \n test_expect_success 'directory changes to a file' '\n@@ -515,9 +520,7 @@ test_expect_success 'directory changes to a file' '\n \n \tdirectory_to_file &&\n \n-\ttest-tool fsmonitor-client query --token 0 &&\n-\n-\tgrep \"^event: dir1$\" .git/trace\n+\tretry_grep \"^event: dir1$\" .git/trace\n '\n \n # The next few test cases exercise the token-resync code.  When filesystem\n\nbase-commit: 68cb7f9e92a5d8e9824f5b52ac3d0a9d8f653dbe\n-- \ngitgitgadget\n"}]}