Skip to content

Commit 1264105

Browse files
derrickstoleeNeil KaingaCopilot
committed
t5798: test parallel POST failure handling
Parallel requests need to preserve the sequential path's behavior for configuration boundaries, authentication, throttling, cache fallback, corrupt responses, and request headers. Extend the protocol test server with targeted failure modes. Verify that parallel POST refreshes authentication, honors Retry-After, falls back from cache 404 responses only when permitted, retries a one-time corrupt pack, and reports permanent corruption as an index-pack failure. Also cover absent and invalid thread configuration, cookie-enabled sequential fallback, configured headers, multiple participating workers, and a timeout-protected child-pipe stress case. Helped-by: GPT-5.6 Sol Co-authored-by: Neil Kainga <t-neilkainga@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Neil Kainga <t-neilkainga@microsoft.com> Signed-off-by: Derrick Stolee <stolee@gmail.com>
1 parent 02c76fc commit 1264105

2 files changed

Lines changed: 314 additions & 5 deletions

File tree

‎t/helper/test-gvfs-protocol.c‎

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,14 @@ static int mayhem_try_auth(struct req *req, enum worker_result *wr_out)
309309
{
310310
*wr_out = WR_OK;
311311

312+
if (string_list_has_string(&mayhem_list, "http_401_1") &&
313+
mayhem_child == 0) {
314+
logmayhem("http_401_1");
315+
*wr_out = send_http_error(1, 401, "Unauthorized", -1,
316+
WR_MAYHEM);
317+
return 1;
318+
}
319+
312320
if (string_list_has_string(&mayhem_list, "http_401")) {
313321
struct string_list_item *item;
314322
int has_auth = 0;
@@ -906,7 +914,28 @@ static enum worker_result send_packfile_from_buffer(const struct strbuf *packfil
906914
goto done;
907915
}
908916

909-
if (write_in_full(1, packfile->buf, packfile->len) < 0) {
917+
if ((string_list_has_string(&mayhem_list, "bad_post_pack_sha") ||
918+
(string_list_has_string(&mayhem_list, "bad_post_pack_sha_1") &&
919+
mayhem_child == 0)) &&
920+
packfile->len) {
921+
char byte = packfile->buf[packfile->len - 1] ^ 0xff;
922+
923+
logmayhem("bad_post_pack_sha%s",
924+
string_list_has_string(&mayhem_list,
925+
"bad_post_pack_sha_1") ?
926+
"_1" : "");
927+
if (write_in_full(1, packfile->buf, packfile->len - 1) < 0 ||
928+
write_in_full(1, &byte, 1) < 0) {
929+
logerror("unable to write corrupt response body");
930+
wr = WR_IO_ERROR;
931+
goto done;
932+
}
933+
if (string_list_has_string(&mayhem_list,
934+
"bad_post_pack_sha_1")) {
935+
wr = WR_MAYHEM | WR_HANGUP;
936+
goto done;
937+
}
938+
} else if (write_in_full(1, packfile->buf, packfile->len) < 0) {
910939
logerror("unable to write response content body");
911940
wr = WR_IO_ERROR;
912941
goto done;
@@ -1555,15 +1584,14 @@ static enum worker_result req__read(struct req *req, int fd)
15551584
done:
15561585

15571586
/*
1558-
* Log the X-Session-Id header if present (for testing purposes).
1587+
* Log selected test headers if present.
15591588
*/
15601589
{
15611590
struct string_list_item *item;
15621591
for_each_string_list_item(item, &req->header_list) {
1563-
if (starts_with(item->string, "X-Session-Id:")) {
1592+
if (starts_with(item->string, "X-Session-Id:") ||
1593+
starts_with(item->string, "X-Test-Header:"))
15641594
loginfo("Received header: %s", item->string);
1565-
break;
1566-
}
15671595
}
15681596
}
15691597

@@ -1600,6 +1628,11 @@ static enum worker_result dispatch(struct req *req)
16001628
enum worker_result wr;
16011629

16021630
if (strstr(req->uri_base.buf, MY_SERVER_TYPE__CACHE)) {
1631+
if (string_list_has_string(&mayhem_list, "cache_http_404")) {
1632+
logmayhem("cache_http_404");
1633+
return send_http_error(1, 404, "Not Found", -1,
1634+
WR_MAYHEM);
1635+
}
16031636
if (string_list_has_string(&mayhem_list, "cache_http_503")) {
16041637
logmayhem("cache_http_503");
16051638
return send_http_error(1, 503, "Service Unavailable", 2,

‎t/t5798-gvfs-helper-post-threads.sh‎

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ and to exercise both code paths in do__http_post__fetch_oidset().
1212

1313
. "$TEST_DIRECTORY"/lib-gvfs-helper.sh
1414

15+
test_lazy_prereq TIMEOUT '
16+
type timeout >/dev/null 2>&1
17+
'
18+
1519
parallel_block_size=100
1620

1721
test_expect_success 'create enough blobs for parallel POST' '
@@ -105,6 +109,112 @@ verify_parallel_post_workers () {
105109
test "$nr_workers" -gt 1
106110
}
107111

112+
do_post_corrupt_pack () {
113+
test_must_fail \
114+
git -C "$REPO_T1" gvfs-helper \
115+
--cache-server=disable \
116+
--remote=origin \
117+
--no-progress \
118+
post \
119+
--block-size="$parallel_block_size" \
120+
--max-retries=0 \
121+
<"$OIDS_BLOBS_FILE" >OUT.output 2>OUT.stderr &&
122+
123+
test_grep "error: post: index-pack failed" OUT.stderr
124+
}
125+
126+
for value in unset 0 negative
127+
do
128+
test_expect_success "postThreads=$value uses sequential mode" '
129+
test_when_finished "per_test_cleanup" &&
130+
start_gvfs_protocol_server &&
131+
if test "'$value'" = unset
132+
then
133+
git -C "$REPO_T1" config --unset-all \
134+
gvfs.postThreads || :
135+
elif test "'$value'" = negative
136+
then
137+
git -C "$REPO_T1" config gvfs.postThreads -1
138+
else
139+
git -C "$REPO_T1" config gvfs.postThreads 0
140+
fi &&
141+
142+
GIT_TRACE2_EVENT="$(pwd)/trace-$test_count.txt" &&
143+
export GIT_TRACE2_EVENT &&
144+
145+
do_post_blobs_small_blocks &&
146+
147+
stop_gvfs_protocol_server &&
148+
test_trace2_data gvfs-helper post/fetch_mode 1 \
149+
<"trace-$test_count.txt"
150+
'
151+
done
152+
153+
test_expect_success 'malformed postThreads is rejected' '
154+
test_when_finished "git -C \"$REPO_T1\" config --unset-all \
155+
gvfs.postThreads" &&
156+
git -C "$REPO_T1" config gvfs.postThreads invalid &&
157+
158+
test_must_fail git -C "$REPO_T1" gvfs-helper \
159+
--cache-server=disable \
160+
--remote=origin \
161+
--no-progress \
162+
post \
163+
<"$OIDS_BLOBS_FILE" >OUT.output 2>OUT.stderr &&
164+
test_grep "bad numeric config value" OUT.stderr
165+
'
166+
167+
test_expect_success PTHREADS 'small block size uses sequential POST' '
168+
test_when_finished "per_test_cleanup" &&
169+
start_gvfs_protocol_server &&
170+
git -C "$REPO_T1" config gvfs.postThreads 4 &&
171+
172+
GIT_TRACE2_EVENT="$(pwd)/trace-$test_count.txt" &&
173+
export GIT_TRACE2_EVENT &&
174+
175+
git -C "$REPO_T1" gvfs-helper \
176+
--cache-server=disable \
177+
--remote=origin \
178+
--no-progress \
179+
post \
180+
--block-size=$(($parallel_block_size - 1)) \
181+
<"$OIDS_BLOBS_FILE" >OUT.output 2>OUT.stderr &&
182+
183+
test_must_be_empty OUT.stderr &&
184+
verify_objects_in_shared_cache "$OIDS_BLOBS_FILE" &&
185+
stop_gvfs_protocol_server &&
186+
test_trace2_data gvfs-helper post/fetch_mode 1 \
187+
<"trace-$test_count.txt"
188+
'
189+
190+
test_expect_success PTHREADS 'cookie configuration uses sequential POST' '
191+
test_when_finished "per_test_cleanup" &&
192+
test_when_finished "rm -f cookies" &&
193+
>"cookies" &&
194+
start_gvfs_protocol_server &&
195+
git -C "$REPO_T1" config gvfs.postThreads 4 &&
196+
197+
GIT_TRACE2_EVENT="$(pwd)/trace-$test_count.txt" &&
198+
export GIT_TRACE2_EVENT &&
199+
200+
git -C "$REPO_T1" \
201+
-c http.cookieFile="$(pwd)/cookies" \
202+
-c http.saveCookies=true \
203+
gvfs-helper \
204+
--cache-server=disable \
205+
--remote=origin \
206+
--no-progress \
207+
post \
208+
--block-size="$parallel_block_size" \
209+
<"$OIDS_BLOBS_FILE" >OUT.output 2>OUT.stderr &&
210+
211+
test_must_be_empty OUT.stderr &&
212+
verify_objects_in_shared_cache "$OIDS_BLOBS_FILE" &&
213+
stop_gvfs_protocol_server &&
214+
test_trace2_data gvfs-helper post/fetch_mode 1 \
215+
<"trace-$test_count.txt"
216+
'
217+
108218
for threads in 1 4
109219
do
110220
if test "$threads" = "1"
@@ -193,4 +303,170 @@ do
193303
'
194304
done
195305

306+
test_expect_success PTHREADS,TIMEOUT 'parallel POST does not deadlock' '
307+
test_when_finished "per_test_cleanup" &&
308+
start_gvfs_protocol_server &&
309+
git -C "$REPO_T1" config gvfs.postThreads 4 &&
310+
311+
GIT_TRACE2_EVENT="$(pwd)/trace-$test_count.txt" &&
312+
export GIT_TRACE2_EVENT &&
313+
314+
timeout 30 git -C "$REPO_T1" gvfs-helper \
315+
--cache-server=disable \
316+
--remote=origin \
317+
--no-progress \
318+
post \
319+
--block-size="$parallel_block_size" \
320+
<"$OIDS_BLOBS_FILE" >OUT.output 2>OUT.stderr &&
321+
322+
test_must_be_empty OUT.stderr &&
323+
verify_objects_in_shared_cache "$OIDS_BLOBS_FILE" &&
324+
stop_gvfs_protocol_server &&
325+
test_trace2_data gvfs-helper post/fetch_mode 4 \
326+
<"trace-$test_count.txt"
327+
'
328+
329+
test_expect_success PTHREADS 'parallel POST reports index-pack failure' '
330+
test_when_finished "per_test_cleanup" &&
331+
start_gvfs_protocol_server_with_mayhem bad_post_pack_sha &&
332+
git -C "$REPO_T1" config gvfs.postThreads 4 &&
333+
334+
GIT_TRACE2_EVENT="$(pwd)/trace-$test_count.txt" &&
335+
export GIT_TRACE2_EVENT &&
336+
337+
do_post_corrupt_pack &&
338+
339+
stop_gvfs_protocol_server &&
340+
test_grep "bad_post_pack_sha" "$SERVER_LOG" &&
341+
test_trace2_data gvfs-helper post/fetch_mode 4 \
342+
<"trace-$test_count.txt"
343+
'
344+
345+
test_expect_success PTHREADS 'parallel POST retries a corrupt pack' '
346+
test_when_finished "per_test_cleanup" &&
347+
start_gvfs_protocol_server_with_mayhem bad_post_pack_sha_1 &&
348+
git -C "$REPO_T1" config gvfs.postThreads 4 &&
349+
350+
GIT_TRACE2_EVENT="$(pwd)/trace-$test_count.txt" &&
351+
export GIT_TRACE2_EVENT &&
352+
353+
do_post_blobs_small_blocks &&
354+
355+
stop_gvfs_protocol_server &&
356+
test_grep "bad_post_pack_sha_1" "$SERVER_LOG" &&
357+
test_trace2_data gvfs-helper post/fetch_mode 4 \
358+
<"trace-$test_count.txt"
359+
'
360+
361+
test_expect_success PTHREADS 'parallel POST retries a transient HTTP error' '
362+
test_when_finished "per_test_cleanup" &&
363+
start_gvfs_protocol_server_with_mayhem http_429_1 &&
364+
git -C "$REPO_T1" config gvfs.postThreads 4 &&
365+
366+
GIT_TRACE2_EVENT="$(pwd)/trace-$test_count.txt" &&
367+
export GIT_TRACE2_EVENT &&
368+
369+
do_post_blobs_small_blocks &&
370+
371+
stop_gvfs_protocol_server &&
372+
test_grep "http_429_1" "$SERVER_LOG" &&
373+
test_trace2_data gvfs-helper post/fetch_mode 4 \
374+
<"trace-$test_count.txt"
375+
'
376+
377+
test_expect_success PTHREADS 'parallel POST retries authentication' '
378+
test_when_finished "per_test_cleanup" &&
379+
start_gvfs_protocol_server_with_mayhem http_401_1 &&
380+
git -C "$REPO_T1" config gvfs.postThreads 4 &&
381+
382+
GIT_TRACE2_EVENT="$(pwd)/trace-$test_count.txt" &&
383+
export GIT_TRACE2_EVENT &&
384+
385+
do_post_blobs_small_blocks &&
386+
387+
stop_gvfs_protocol_server &&
388+
test_grep "http_401_1" "$SERVER_LOG" &&
389+
test_trace2_data gvfs-helper post/fetch_mode 4 \
390+
<"trace-$test_count.txt" &&
391+
test_trace2_data gvfs-helper post/auth_retry 1 \
392+
<"trace-$test_count.txt"
393+
'
394+
395+
test_expect_success PTHREADS 'parallel POST falls back after cache 404' '
396+
test_when_finished "per_test_cleanup" &&
397+
start_gvfs_protocol_server_with_mayhem cache_http_404 &&
398+
git -C "$REPO_T1" config gvfs.postThreads 4 &&
399+
400+
GIT_TRACE2_EVENT="$(pwd)/trace-$test_count.txt" &&
401+
export GIT_TRACE2_EVENT &&
402+
403+
git -C "$REPO_T1" gvfs-helper \
404+
--cache-server=trust \
405+
--remote=origin \
406+
--fallback \
407+
--no-progress \
408+
post \
409+
--block-size="$parallel_block_size" \
410+
<"$OIDS_BLOBS_FILE" >OUT.output 2>OUT.stderr &&
411+
412+
test_must_be_empty OUT.stderr &&
413+
verify_objects_in_shared_cache "$OIDS_BLOBS_FILE" &&
414+
stop_gvfs_protocol_server &&
415+
test_grep "cache_http_404" "$SERVER_LOG" &&
416+
test_trace2_data gvfs-helper post/fetch_mode 4 \
417+
<"trace-$test_count.txt"
418+
'
419+
420+
test_expect_success PTHREADS 'parallel POST honors --no-fallback' '
421+
test_when_finished "per_test_cleanup" &&
422+
start_gvfs_protocol_server_with_mayhem cache_http_404 &&
423+
git -C "$REPO_T1" config gvfs.postThreads 4 &&
424+
425+
GIT_TRACE2_EVENT="$(pwd)/trace-$test_count.txt" &&
426+
export GIT_TRACE2_EVENT &&
427+
428+
test_must_fail \
429+
git -C "$REPO_T1" gvfs-helper \
430+
--cache-server=trust \
431+
--remote=origin \
432+
--no-fallback \
433+
--no-progress \
434+
post \
435+
--block-size="$parallel_block_size" \
436+
<"$OIDS_BLOBS_FILE" >OUT.output 2>OUT.stderr &&
437+
438+
test_grep "error: post: (http:404)" OUT.stderr &&
439+
stop_gvfs_protocol_server &&
440+
test_grep "cache_http_404" "$SERVER_LOG" &&
441+
test_trace2_data gvfs-helper post/fetch_mode 4 \
442+
<"trace-$test_count.txt"
443+
'
444+
445+
test_expect_success PTHREADS 'parallel POST preserves configured headers' '
446+
test_when_finished "per_test_cleanup" &&
447+
start_gvfs_protocol_server &&
448+
git -C "$REPO_T1" config gvfs.postThreads 4 &&
449+
450+
GIT_TRACE2_EVENT="$(pwd)/trace-$test_count.txt" &&
451+
export GIT_TRACE2_EVENT &&
452+
453+
git -C "$REPO_T1" \
454+
-c http.extraHeader="X-Test-Header: parallel" \
455+
-c gvfs.sessionkey=test.id \
456+
-c test.id=parallel-session \
457+
gvfs-helper \
458+
--cache-server=disable \
459+
--remote=origin \
460+
--no-progress \
461+
post \
462+
--block-size="$parallel_block_size" \
463+
<"$OIDS_BLOBS_FILE" >OUT.output 2>OUT.stderr &&
464+
465+
test_must_be_empty OUT.stderr &&
466+
stop_gvfs_protocol_server &&
467+
test_grep "X-Test-Header: parallel" "$SERVER_LOG" &&
468+
test_grep "X-Session-Id:.*parallel-session:.*-P" "$SERVER_LOG" &&
469+
verify_parallel_post_workers "trace-$test_count.txt"
470+
'
471+
196472
test_done

0 commit comments

Comments
 (0)