Skip to content

Commit f4e5366

Browse files
committed
t5567: test lib-httpd CGI helpers under concurrent requests
Add a test that exercises the cgi-lib.sh primitives directly and the apply-one-time-script.sh helper built on them, without a web server so the races can be forced. The primitive tests fire several concurrent workers and assert that cgi_claim and cgi_first_request pick exactly one winner; the end-to-end test uses FIFOs to hold one request inside the helper while a second claims and removes the marker, which is the overlap that used to answer HTTP 500. Signed-off-by: Michael Montalbo <mmontalbo@gmail.com>
1 parent 1535e75 commit f4e5366

2 files changed

Lines changed: 140 additions & 0 deletions

File tree

t/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,7 @@ integration_tests = [
707707
't5564-http-proxy.sh',
708708
't5565-push-multiple.sh',
709709
't5566-push-group.sh',
710+
't5567-one-time-script.sh',
710711
't5570-git-daemon.sh',
711712
't5571-pre-push-hook.sh',
712713
't5572-pull-submodule.sh',

t/t5567-one-time-script.sh

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#!/bin/sh
2+
3+
test_description='lib-httpd CGI helpers are safe under concurrent requests
4+
5+
The web server runs requests concurrently, and a single Git operation can have
6+
two requests to one endpoint in flight at once (for example a partial fetch
7+
that lazily fetches a missing promisor object). The CGI helpers under
8+
t/lib-httpd therefore keep any cross-request state through the primitives in
9+
cgi-lib.sh, which must remain safe when several requests race. Exercise those
10+
primitives directly, and exercise the apply-one-time-script.sh helper that is
11+
built on them, without a real web server so the races can be forced.
12+
'
13+
14+
. ./test-lib.sh
15+
16+
CGI_LIB="$TEST_DIRECTORY/lib-httpd/cgi-lib.sh"
17+
export CGI_LIB
18+
19+
test_expect_success 'set up concurrency runner' '
20+
# race N CMD: run N concurrent copies of "CMD <i>" and wait for all.
21+
# Keeping the "&" inside a helper script (rather than a loop in a test
22+
# body) is both chainlint-clean and gives each worker its own process.
23+
write_script race <<-\EOF
24+
n=$1
25+
shift
26+
i=0
27+
while test $i -lt "$n"
28+
do
29+
i=$((i + 1))
30+
"$@" "$i" &
31+
done
32+
wait
33+
EOF
34+
'
35+
36+
test_expect_success 'cgi_claim is won by exactly one concurrent request' '
37+
rm -rf wins && mkdir wins &&
38+
>marker &&
39+
write_script claim-worker <<-\EOF &&
40+
. "$CGI_LIB" &&
41+
cgi_claim marker &&
42+
>"wins/$1"
43+
EOF
44+
45+
./race 8 ./claim-worker &&
46+
47+
echo 1 >expect &&
48+
ls wins | wc -l | tr -d " " >actual &&
49+
test_cmp expect actual
50+
'
51+
52+
test_expect_success 'cgi_first_request succeeds exactly once' '
53+
rm -rf wins state && mkdir wins &&
54+
write_script first-worker <<-\EOF &&
55+
. "$CGI_LIB" &&
56+
cgi_first_request state &&
57+
>"wins/$1"
58+
EOF
59+
60+
./race 8 ./first-worker &&
61+
62+
echo 1 >expect &&
63+
ls wins | wc -l | tr -d " " >actual &&
64+
test_cmp expect actual
65+
'
66+
67+
test_expect_success PIPE 'apply-one-time-script tolerates a concurrent request' '
68+
rm -rf workdir fakebin &&
69+
mkdir workdir fakebin &&
70+
cp "$TEST_DIRECTORY"/lib-httpd/cgi-lib.sh workdir/ &&
71+
ENTERED="$PWD/entered" &&
72+
GATE="$PWD/gate" &&
73+
DONE="$PWD/done" &&
74+
export ENTERED GATE DONE &&
75+
mkfifo "$ENTERED" "$GATE" "$DONE" &&
76+
77+
# Stand in for git-http-backend. The modify role returns a response
78+
# containing "packfile", which the one-time script rewrites. The
79+
# passthrough role returns a response that is left untouched, but first
80+
# announces that it has entered the helper and then blocks, so that it
81+
# is still in flight when the modify role claims and removes the marker.
82+
write_script fakebin/git-http-backend <<-\EOF &&
83+
printf "Status: 200 OK\r\n"
84+
printf "Content-Type: application/x-git-result\r\n"
85+
printf "\r\n"
86+
if test "$ROLE" = modify
87+
then
88+
printf "packfile\n"
89+
else
90+
echo entered >"$ENTERED"
91+
read released <"$GATE"
92+
printf "refs\n"
93+
fi
94+
EOF
95+
96+
write_script workdir/one-time-script <<-\EOF &&
97+
if grep packfile "$1" >/dev/null
98+
then
99+
sed "/packfile/q" "$1" &&
100+
printf "REPLACED\n"
101+
else
102+
cat "$1"
103+
fi
104+
EOF
105+
106+
HELPER="$TEST_DIRECTORY/lib-httpd/apply-one-time-script.sh" &&
107+
GIT_EXEC_PATH="$PWD/fakebin" &&
108+
export GIT_EXEC_PATH &&
109+
110+
# Launch the passthrough request in the background. It enters the
111+
# helper, signals us through ENTERED, blocks on GATE inside the fake
112+
# backend, and signals DONE once it has run to completion (even if the
113+
# helper fails, so that a broken helper fails this test instead of
114+
# hanging it).
115+
(
116+
cd workdir &&
117+
{ ROLE=passthrough sh "$HELPER" >../passthrough.out 2>../passthrough.err || true; } &&
118+
echo done >"$DONE" &
119+
) &&
120+
121+
read entered <"$ENTERED" &&
122+
123+
# Run the modifying request to completion while the passthrough request
124+
# is still blocked.
125+
(
126+
cd workdir &&
127+
ROLE=modify sh "$HELPER" >../modify.out 2>../modify.err
128+
) &&
129+
130+
echo released >"$GATE" &&
131+
read finished <"$DONE" &&
132+
133+
test_must_be_empty passthrough.err &&
134+
test_must_be_empty modify.err &&
135+
grep "Status: 200 OK" passthrough.out &&
136+
grep "Status: 200 OK" modify.out
137+
'
138+
139+
test_done

0 commit comments

Comments
 (0)