test: expand phase 8 fault matrix

This commit is contained in:
2026-07-23 10:11:18 +00:00
parent b6de490b7e
commit 63da21f07d
6 changed files with 1008 additions and 0 deletions
+382
View File
@@ -0,0 +1,382 @@
#!/usr/bin/env bash
set -euo pipefail
source "$(dirname "$0")/lib.sh"
bundle_hash=547cec172aab2f4d418d73d4f5fc0d2794e971b7
bundle_torrent=ZDQ6aW5mb2Q1OmZpbGVzbGQ2Omxlbmd0aGkxNjM4NGU0OnBhdGhsOTphbHBoYS5iaW5lZWQ2Omxlbmd0aGkxNjM4NGU0OnBhdGhsNjpuZXN0ZWQ5OmJyYXZvLmJpbmVlZDY6bGVuZ3RoaTE2Mzg0ZTQ6cGF0aGw2Om5lc3RlZDExOmNoYXJsaWUuYmluZWVkNjpsZW5ndGhpMTYzODRlNDpwYXRobDk6ZGVsdGEuYmluZWVlNDpuYW1lNjpidW5kbGUxMjpwaWVjZSBsZW5ndGhpMTYzODRlNjpwaWVjZXM4MDp9tnus7uyzpCC/N6e+ykpFGF+PPHmZb79ZCq2dp84v4EgIIzMZE7bwEMQCvl+7qybWEDECinYwiKcGKn82sWFjoNungr001vTmtGeLhKBGd2Vl
legacy_bundle_hash=305af159425d76140eea48d20cfcfbb3f8dccf46
shared_hash=03fbcf663bd3fe80c298ca54a55780a6b1d4b43b
shared_torrent=ZDQ6aW5mb2Q1OmZpbGVzbGQ2Omxlbmd0aGkxNjM4NGU0OnBhdGhsNjpuZXN0ZWQ5OmJyYXZvLmJpbmVlZTQ6bmFtZTY6YnVuZGxlMTI6cGllY2UgbGVuZ3RoaTE2Mzg0ZTY6cGllY2VzMjA6eZlvv1kKrZ2nzi/gSAgjMxkTtvBlZQ==
cancel_hash=330be0cb7c2201135a2de63b28e77993745ff688
cancel_torrent=ZDQ6aW5mb2Q2Omxlbmd0aGkzMWU0Om5hbWUxMTpmaXh0dXJlLmJpbjEyOnBpZWNlIGxlbmd0aGkxNjM4NGU2OnBpZWNlczIwOlFlB05MtIUU4oem2MNz5LJW/GI6ZWU=
control_scenario() {
compose_control exec -T control \
python /e2e/scenarios/complex_jobs.py "$@"
}
qb_endpoint() {
case "$1" in
cache-1) printf '%s' http://qb-cache-1:8080/api/v2 ;;
cache-2) printf '%s' http://qb-cache-2:8080/api/v2 ;;
archive-1) printf '%s' http://qb-archive-1:8080/api/v2 ;;
archive-2) printf '%s' http://qb-archive-2:8080/api/v2 ;;
*) printf 'unknown E2E node: %s\n' "$1" >&2; return 2 ;;
esac
}
wait_qb_present() {
local node=$1
local info_hash=$2
local deadline=$((SECONDS + 30))
while (( SECONDS < deadline )); do
local records
records=$(compose_node "$node" exec -T qbittorrent curl -fsS \
"http://127.0.0.1:8080/api/v2/torrents/info?hashes=$info_hash")
if [[ "$records" != "[]" ]]; then
return
fi
sleep 1
done
printf 'timed out waiting for torrent %s on %s\n' \
"$info_hash" "$node" >&2
return 1
}
delete_torrent() {
local node=$1
local info_hash=$2
compose_node "$node" exec -T qbittorrent curl -fsS \
-X POST http://127.0.0.1:8080/api/v2/torrents/delete \
--data-urlencode "hashes=$info_hash" \
--data-urlencode "deleteFiles=true" >/dev/null || true
control_scenario assert-qb \
--endpoint "$(qb_endpoint "$node")" \
--info-hash "$info_hash" \
--absent >/dev/null
}
install_torrent() {
local node=$1
local info_hash=$2
local encoded=$3
local save_path=$4
local selected=$5
local all_indices=$6
compose_node "$node" exec -T qbittorrent /bin/sh -c \
"printf '%s' '$encoded' | base64 -d > /tmp/e2e.torrent"
compose_node "$node" exec -T qbittorrent curl -fsS \
-X POST http://127.0.0.1:8080/api/v2/torrents/add \
-F torrents=@/tmp/e2e.torrent \
-F "savepath=$save_path" \
-F stopped=true >/dev/null
wait_qb_present "$node" "$info_hash"
compose_node "$node" exec -T qbittorrent curl -fsS \
-X POST http://127.0.0.1:8080/api/v2/torrents/filePrio \
--data-urlencode "hash=$info_hash" \
--data-urlencode "id=$all_indices" \
--data-urlencode "priority=0" >/dev/null
compose_node "$node" exec -T qbittorrent curl -fsS \
-X POST http://127.0.0.1:8080/api/v2/torrents/filePrio \
--data-urlencode "hash=$info_hash" \
--data-urlencode "id=$selected" \
--data-urlencode "priority=1" >/dev/null
compose_node "$node" exec -T qbittorrent curl -fsS \
-X POST http://127.0.0.1:8080/api/v2/torrents/recheck \
--data-urlencode "hashes=$info_hash" >/dev/null
}
assert_qb() {
local node=$1
local info_hash=$2
local selected=$3
control_scenario assert-qb \
--endpoint "$(qb_endpoint "$node")" \
--info-hash "$info_hash" \
--selected "$selected" >/dev/null
}
for node in cache-1 cache-2 archive-1 archive-2; do
delete_torrent "$node" "$legacy_bundle_hash"
delete_torrent "$node" "$bundle_hash"
delete_torrent "$node" "$shared_hash"
delete_torrent "$node" "$cancel_hash"
compose_node "$node" exec -T qbittorrent /bin/sh -c \
'rm -f /downloads/bundle/alpha.bin \
/downloads/bundle/nested/bravo.bin \
/downloads/bundle/nested/charlie.bin \
/downloads/bundle/delta.bin \
/downloads/bundle/unknown.keep \
/downloads/fixture.bin; \
rmdir /downloads/bundle/nested /downloads/bundle 2>/dev/null || true'
compose_node "$node" exec -T qbittorrent /bin/sh -c \
'chown -R 1001:1001 /downloads/bundle 2>/dev/null || true'
done
compose_node cache-1 exec -T --user 1001:1001 qbittorrent /bin/sh -c \
'mkdir -p /downloads/bundle/nested; \
dd if=/dev/zero bs=16384 count=1 2>/dev/null | tr "\000" A \
> /downloads/bundle/alpha.bin; \
dd if=/dev/zero bs=16384 count=1 2>/dev/null | tr "\000" C \
> /downloads/bundle/nested/charlie.bin'
install_torrent cache-1 "$bundle_hash" "$bundle_torrent" \
/downloads '0|2' '0|1|2|3'
assert_qb cache-1 "$bundle_hash" '0,2'
compose_node cache-2 exec -T --user 1001:1001 qbittorrent /bin/sh -c \
'mkdir -p /downloads/bundle/nested; \
dd if=/dev/zero bs=16384 count=1 2>/dev/null | tr "\000" B \
> /downloads/bundle/nested/bravo.bin; \
dd if=/dev/zero bs=16384 count=1 2>/dev/null | tr "\000" D \
> /downloads/bundle/delta.bin'
install_torrent cache-2 "$bundle_hash" "$bundle_torrent" \
/downloads '1|3' '0|1|2|3'
assert_qb cache-2 "$bundle_hash" '1,3'
control_scenario stale-preview \
--operation archive \
--source-client cache-1 \
--target-client archive-1 \
--info-hash "$bundle_hash" \
--selected '0' >/dev/null
control_scenario transfer \
--operation archive \
--source-client cache-1 \
--target-client archive-1 \
--info-hash "$bundle_hash" \
--selected '0,2' \
--expected-selection '0,2' \
--expected-delta '0,2'
assert_qb archive-1 "$bundle_hash" '0,2'
control_scenario preview-error \
--operation evict_cache \
--cache-client cache-2 \
--info-hash "$bundle_hash" \
--status 400 \
--contains 'missing file indices'
control_scenario transfer \
--operation archive \
--source-client cache-2 \
--target-client archive-2 \
--info-hash "$bundle_hash" \
--selected '1,3' \
--expected-selection '1,3' \
--expected-delta '1,3'
assert_qb archive-2 "$bundle_hash" '1,3'
compose_node cache-2 exec -T --user 1001:1001 qbittorrent /bin/sh -c \
'dd if=/dev/zero bs=16384 count=1 2>/dev/null | tr "\000" A \
> /downloads/bundle/alpha.bin; \
dd if=/dev/zero bs=16384 count=1 2>/dev/null | tr "\000" C \
> /downloads/bundle/nested/charlie.bin'
compose_node cache-2 exec -T qbittorrent curl -fsS \
-X POST http://127.0.0.1:8080/api/v2/torrents/filePrio \
--data-urlencode "hash=$bundle_hash" \
--data-urlencode 'id=0|1|2|3' \
--data-urlencode priority=1 >/dev/null
compose_node cache-2 exec -T qbittorrent curl -fsS \
-X POST http://127.0.0.1:8080/api/v2/torrents/recheck \
--data-urlencode "hashes=$bundle_hash" >/dev/null
assert_qb cache-2 "$bundle_hash" '0,1,2,3'
control_scenario evict \
--cache-client cache-2 \
--info-hash "$bundle_hash" \
--minimum-coverage-proofs 2
control_scenario assert-qb \
--endpoint "$(qb_endpoint cache-2)" \
--info-hash "$bundle_hash" \
--absent >/dev/null
control_scenario transfer \
--operation unarchive \
--source-client archive-1 \
--target-client cache-2 \
--info-hash "$bundle_hash" \
--selected '0,2' \
--expected-selection '0,2' \
--expected-delta '0,2'
assert_qb cache-2 "$bundle_hash" '0,2'
control_scenario transfer \
--operation unarchive \
--source-client archive-2 \
--target-client cache-2 \
--info-hash "$bundle_hash" \
--selected '1,3' \
--expected-selection '1,3' \
--expected-delta '1,3'
assert_qb cache-2 "$bundle_hash" '0,1,2,3'
control_scenario transfer \
--operation archive \
--source-client cache-2 \
--target-client archive-1 \
--info-hash "$bundle_hash" \
--selected '1,3' \
--expected-selection '1,3' \
--expected-delta '1,3'
assert_qb archive-1 "$bundle_hash" '0,1,2,3'
control_scenario transfer \
--operation archive \
--source-client cache-2 \
--target-client archive-2 \
--info-hash "$bundle_hash" \
--selected '0,2' \
--expected-selection '0,2' \
--expected-delta '0,2'
assert_qb archive-2 "$bundle_hash" '0,1,2,3'
control_scenario preview-error \
--operation archive \
--source-client cache-2 \
--target-client archive-1 \
--info-hash "$bundle_hash" \
--selected '0' \
--status 400 \
--contains 'already contains'
control_scenario evict \
--cache-client cache-1 \
--info-hash "$bundle_hash"
control_scenario transfer \
--operation unarchive \
--source-client archive-1 \
--target-client cache-1 \
--info-hash "$bundle_hash" \
--selected '1' \
--expected-selection '1' \
--expected-delta '1'
assert_qb cache-1 "$bundle_hash" '1'
compose_node cache-1 exec -T --user 1001:1001 qbittorrent /bin/sh -c \
'printf "preserve-unknown\n" > /downloads/bundle/unknown.keep'
install_torrent cache-1 "$shared_hash" "$shared_torrent" \
/downloads '0' '0'
assert_qb cache-1 "$shared_hash" '0'
control_scenario evict \
--cache-client cache-1 \
--info-hash "$bundle_hash"
control_scenario assert-qb \
--endpoint "$(qb_endpoint cache-1)" \
--info-hash "$bundle_hash" \
--absent >/dev/null
assert_qb cache-1 "$shared_hash" '0'
compose_node cache-1 exec -T qbittorrent test -f \
/downloads/bundle/nested/bravo.bin
compose_node cache-1 exec -T qbittorrent test -f \
/downloads/bundle/unknown.keep
compose_node cache-1 exec -T --user 1001:1001 qbittorrent /bin/sh -c \
'printf "archive-control-e2e-happy-path\n" > /downloads/fixture.bin'
install_torrent cache-1 "$cancel_hash" "$cancel_torrent" \
/downloads '0' '0'
assert_qb cache-1 "$cancel_hash" '0'
control_scenario queued-cancel \
--operation archive \
--source-client cache-1 \
--target-client archive-1 \
--info-hash "$cancel_hash"
compose_node cache-1 exec -T qbittorrent test -f /downloads/fixture.bin
cancel_job=$(control_scenario drive \
--operation archive \
--source-client cache-1 \
--target-client archive-2 \
--info-hash "$cancel_hash" \
--through target_materialize)
control_scenario cancel --job-id "$cancel_job"
control_scenario assert-qb \
--endpoint "$(qb_endpoint archive-2)" \
--info-hash "$cancel_hash" \
--absent >/dev/null
if compose_node archive-2 exec -T qbittorrent \
test -e /downloads/fixture.bin; then
printf 'precommit cancellation left a materialized target file\n' >&2
exit 1
fi
assert_qb cache-1 "$cancel_hash" '0'
restart_job=$(control_scenario drive \
--operation archive \
--source-client cache-1 \
--target-client archive-1 \
--info-hash "$cancel_hash" \
--through source_stage)
compose_control restart control
sleep 2
control_scenario resume --job-id "$restart_job"
assert_qb archive-1 "$cancel_hash" '0'
delete_torrent archive-2 "$cancel_hash"
replay_job=$(control_scenario drive \
--operation archive \
--source-client cache-1 \
--target-client archive-2 \
--info-hash "$cancel_hash" \
--through source_stage)
compose_node archive-2 stop client
sleep 1
control_scenario advance \
--job-id "$replay_job" \
--action syncthing_transfer >/dev/null
compose_node archive-2 start client
control_scenario resume --job-id "$replay_job"
control_scenario assert-qb \
--endpoint "$(qb_endpoint archive-2)" \
--info-hash "$cancel_hash" \
--selected '0' \
--no-downloaded >/dev/null
delete_torrent archive-2 "$cancel_hash"
stale_source_job=$(control_scenario create-paused \
--operation archive \
--source-client cache-1 \
--target-client archive-2 \
--info-hash "$cancel_hash")
compose_node cache-1 exec -T qbittorrent curl -fsS \
-X POST http://127.0.0.1:8080/api/v2/torrents/filePrio \
--data-urlencode "hash=$cancel_hash" \
--data-urlencode id=0 \
--data-urlencode priority=0 >/dev/null
control_scenario expect-failure \
--job-id "$stale_source_job" \
--contains 'source resource changed after job confirmation'
compose_node cache-1 exec -T qbittorrent curl -fsS \
-X POST http://127.0.0.1:8080/api/v2/torrents/filePrio \
--data-urlencode "hash=$cancel_hash" \
--data-urlencode id=0 \
--data-urlencode priority=1 >/dev/null
compose_node cache-1 exec -T qbittorrent curl -fsS \
-X POST http://127.0.0.1:8080/api/v2/torrents/recheck \
--data-urlencode "hashes=$cancel_hash" >/dev/null
assert_qb cache-1 "$cancel_hash" '0'
target_appeared_job=$(control_scenario create-paused \
--operation archive \
--source-client cache-1 \
--target-client archive-2 \
--info-hash "$cancel_hash")
compose_node archive-2 exec -T qbittorrent /bin/sh -c \
"printf '%s' '$cancel_torrent' | base64 -d > /tmp/appeared.torrent"
compose_node archive-2 exec -T qbittorrent curl -fsS \
-X POST http://127.0.0.1:8080/api/v2/torrents/add \
-F torrents=@/tmp/appeared.torrent \
-F savepath=/downloads \
-F stopped=true >/dev/null
wait_qb_present archive-2 "$cancel_hash"
control_scenario expect-failure \
--job-id "$target_appeared_job" \
--contains 'target resource appeared after job confirmation'
assert_qb cache-1 "$cancel_hash" '0'
delete_torrent archive-2 "$cancel_hash"
printf '%s\n' \
'complex matrix passed: selective merges, union eviction, hostile retention,' \
'queued/partial cancellation, restart/replay, and hostile state changes'