
One test in t7422 asserts that `git submodule status --recursive` properly handles SIGPIPE. This test is flaky though and may sometimes not see a SIGPIPE at all: expecting success of 7422.18 'git submodule status --recursive propagates SIGPIPE': { git submodule status --recursive 2>err; echo $?>status; } | grep -q X/S && test_must_be_empty err && test_match_signal 13 "$(cat status)" ++ git submodule status --recursive ++ grep -q X/S ++ echo 0 ++ test_must_be_empty err ++ test 1 -ne 1 ++ test_path_is_file err ++ test 1 -ne 1 ++ test -f err ++ test -s err +++ cat status ++ test_match_signal 13 0 ++ test 0 = 141 ++ test 0 = 269 ++ return 1 error: last command exited with $?=1 not ok 18 - git submodule status --recursive propagates SIGPIPE The issue is caused by a race between git-submodule(1) and grep(1): 1. git-submodule(1) (or its child process) writes the first X/S line we're trying to match. 2. grep(1) matches the line. 3a. grep(1) exits, closing the pipe. 3b. git-submodule(1) (or its child process) writes the rest of its lines. Steps 3a and 3b happen at the same time without any guarantees. If 3a happens first, we get SIGPIPE. Otherwise, we don't and the test fails. Fix the issue by generating a couple thousand nested submodules and matching on the first nested submodule. This ensures that the recursive git-submodule(1) process completely fills its stdout buffer, which makes subsequent writes block until the downstream consumer of the pipe either reads more or closes it. To verify that this works as expected one can apply the following patch to the preimage of this commit, which used to reliably trigger the race: diff --git a/t/t7422-submodule-output.sh b/t/t7422-submodule-output.sh index 3c5177cc30..df6001f8a0 100755 --- a/t/t7422-submodule-output.sh +++ b/t/t7422-submodule-output.sh @@ -202,7 +202,7 @@ test_expect_success !MINGW 'git submodule status --recursive propagates SIGPIPE' cd repo && GIT_ALLOW_PROTOCOL=file git submodule add "$(pwd)"/../submodule && { git submodule status --recursive 2>err; echo $?>status; } | - grep -q recursive-submodule-path-1 && + { sleep 1 && grep -q recursive-submodule-path-1 && sleep 1; } && test_must_be_empty err && test_match_signal 13 "$(cat status)" ) With the pipe-stuffing workaround the test runs successfully. Helped-by: Jeff King <peff@peff.net> Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
212 lines
4.5 KiB
Bash
Executable File
212 lines
4.5 KiB
Bash
Executable File
#!/bin/sh
|
|
|
|
test_description='submodule --cached, --quiet etc. output'
|
|
|
|
. ./test-lib.sh
|
|
. "$TEST_DIRECTORY"/lib-t3100.sh
|
|
|
|
setup_sub () {
|
|
local d="$1" &&
|
|
shift &&
|
|
git $@ clone . "$d" &&
|
|
git $@ submodule add ./"$d"
|
|
}
|
|
|
|
normalize_status () {
|
|
sed -e 's/-g[0-9a-f]*/-gHASH/'
|
|
}
|
|
|
|
test_expect_success 'setup' '
|
|
test_commit A &&
|
|
test_commit B &&
|
|
setup_sub S &&
|
|
setup_sub S.D &&
|
|
setup_sub S.C &&
|
|
setup_sub S.C.D &&
|
|
setup_sub X &&
|
|
git add S* &&
|
|
test_commit C &&
|
|
|
|
# recursive in X/
|
|
git -C X pull &&
|
|
GIT_ALLOW_PROTOCOL=file git -C X submodule update --init &&
|
|
|
|
# dirty
|
|
for d in S.D X/S.D
|
|
do
|
|
echo dirty >"$d"/A.t || return 1
|
|
done &&
|
|
|
|
# commit (for --cached)
|
|
for d in S.C* X/S.C*
|
|
do
|
|
git -C "$d" reset --hard A || return 1
|
|
done &&
|
|
|
|
# dirty
|
|
for d in S*.D X/S*.D
|
|
do
|
|
echo dirty >"$d/C2.t" || return 1
|
|
done &&
|
|
|
|
for ref in A B C
|
|
do
|
|
# Not different with SHA-1 and SHA-256, just (ab)using
|
|
# test_oid_cache as a variable bag to avoid using
|
|
# $(git rev-parse ...).
|
|
oid=$(git rev-parse $ref) &&
|
|
test_oid_cache <<-EOF || return 1
|
|
$ref sha1:$oid
|
|
$ref sha256:$oid
|
|
EOF
|
|
done
|
|
'
|
|
|
|
for opts in "" "status"
|
|
do
|
|
test_expect_success "git submodule $opts" '
|
|
sed -e "s/^>//" >expect <<-EOF &&
|
|
> $(test_oid B) S (B)
|
|
>+$(test_oid A) S.C (A)
|
|
>+$(test_oid A) S.C.D (A)
|
|
> $(test_oid B) S.D (B)
|
|
>+$(test_oid C) X (C)
|
|
EOF
|
|
git submodule $opts >actual.raw &&
|
|
normalize_status <actual.raw >actual &&
|
|
test_cmp expect actual
|
|
'
|
|
done
|
|
|
|
for opts in \
|
|
"status --recursive"
|
|
do
|
|
test_expect_success "git submodule $opts" '
|
|
sed -e "s/^>//" >expect <<-EOF &&
|
|
> $(test_oid B) S (B)
|
|
>+$(test_oid A) S.C (A)
|
|
>+$(test_oid A) S.C.D (A)
|
|
> $(test_oid B) S.D (B)
|
|
>+$(test_oid C) X (C)
|
|
> $(test_oid B) X/S (B)
|
|
>+$(test_oid A) X/S.C (A)
|
|
>+$(test_oid A) X/S.C.D (A)
|
|
> $(test_oid B) X/S.D (B)
|
|
> $(test_oid B) X/X (B)
|
|
EOF
|
|
git submodule $opts >actual.raw &&
|
|
normalize_status <actual.raw >actual &&
|
|
test_cmp expect actual
|
|
'
|
|
done
|
|
|
|
for opts in \
|
|
"--quiet" \
|
|
"--quiet status" \
|
|
"status --quiet"
|
|
do
|
|
test_expect_success "git submodule $opts" '
|
|
git submodule $opts >out &&
|
|
test_must_be_empty out
|
|
'
|
|
done
|
|
|
|
for opts in \
|
|
"--cached" \
|
|
"--cached status" \
|
|
"status --cached"
|
|
do
|
|
test_expect_success "git submodule $opts" '
|
|
sed -e "s/^>//" >expect <<-EOF &&
|
|
> $(test_oid B) S (B)
|
|
>+$(test_oid B) S.C (B)
|
|
>+$(test_oid B) S.C.D (B)
|
|
> $(test_oid B) S.D (B)
|
|
>+$(test_oid B) X (B)
|
|
EOF
|
|
git submodule $opts >actual.raw &&
|
|
normalize_status <actual.raw >actual &&
|
|
test_cmp expect actual
|
|
'
|
|
done
|
|
|
|
for opts in \
|
|
"--cached --quiet" \
|
|
"--cached --quiet status" \
|
|
"--cached status --quiet" \
|
|
"--quiet status --cached" \
|
|
"status --cached --quiet"
|
|
do
|
|
test_expect_success "git submodule $opts" '
|
|
git submodule $opts >out &&
|
|
test_must_be_empty out
|
|
'
|
|
done
|
|
|
|
for opts in \
|
|
"status --cached --recursive" \
|
|
"--cached status --recursive"
|
|
do
|
|
test_expect_success "git submodule $opts" '
|
|
sed -e "s/^>//" >expect <<-EOF &&
|
|
> $(test_oid B) S (B)
|
|
>+$(test_oid B) S.C (B)
|
|
>+$(test_oid B) S.C.D (B)
|
|
> $(test_oid B) S.D (B)
|
|
>+$(test_oid B) X (B)
|
|
> $(test_oid B) X/S (B)
|
|
>+$(test_oid B) X/S.C (B)
|
|
>+$(test_oid B) X/S.C.D (B)
|
|
> $(test_oid B) X/S.D (B)
|
|
> $(test_oid B) X/X (B)
|
|
EOF
|
|
git submodule $opts >actual.raw &&
|
|
normalize_status <actual.raw >actual &&
|
|
test_cmp expect actual
|
|
'
|
|
done
|
|
|
|
test_expect_success !MINGW 'git submodule status --recursive propagates SIGPIPE' '
|
|
# The test setup is somewhat involved because triggering a SIGPIPE is
|
|
# racy with buffered pipes. To avoid the raciness we thus need to make
|
|
# sure that the subprocess in question fills the buffers completely,
|
|
# which requires a couple thousand submodules in total.
|
|
test_when_finished "rm -rf submodule repo" &&
|
|
git init submodule &&
|
|
(
|
|
cd submodule &&
|
|
test_commit initial &&
|
|
|
|
COMMIT=$(git rev-parse HEAD) &&
|
|
for i in $(test_seq 2000)
|
|
do
|
|
printf "[submodule \"sm-$i\"]\npath = recursive-submodule-path-$i\n" "$i" ||
|
|
return 1
|
|
done >gitmodules &&
|
|
BLOB=$(git hash-object -w --stdin <gitmodules) &&
|
|
|
|
printf "100644 blob $BLOB\t.gitmodules\n" >tree &&
|
|
for i in $(test_seq 2000)
|
|
do
|
|
printf "160000 commit $COMMIT\trecursive-submodule-path-%d\n" "$i" ||
|
|
return 1
|
|
done >>tree &&
|
|
TREE=$(git mktree <tree) &&
|
|
|
|
COMMIT=$(git commit-tree "$TREE") &&
|
|
git reset --hard "$COMMIT"
|
|
) &&
|
|
|
|
git init repo &&
|
|
(
|
|
cd repo &&
|
|
GIT_ALLOW_PROTOCOL=file git submodule add "$(pwd)"/../submodule &&
|
|
{ git submodule status --recursive 2>err; echo $?>status; } |
|
|
grep -q recursive-submodule-path-1 &&
|
|
test_must_be_empty err &&
|
|
test_match_signal 13 "$(cat status)"
|
|
)
|
|
'
|
|
|
|
test_done
|