@@ -22,6 +22,16 @@ die() {
2222 exit 1
2323}
2424
25+ if (( $# == 1 )) && [[ $1 == --help || $1 == -h ]]; then
26+ usage
27+ exit 0
28+ fi
29+ for argument in " $@ " ; do
30+ if [[ $argument == --help || $argument == -h ]]; then
31+ die " --help must be used as the only argument"
32+ fi
33+ done
34+
2535BASE_REF=" "
2636VECTOR_V1_REF=" "
2737HEAD_REF=" "
@@ -31,10 +41,6 @@ HEAD_SEEN=0
3141
3242while (( $# > 0 )) ; do
3343 case " $1 " in
34- --help|-h)
35- usage
36- exit 0
37- ;;
3844 --base-ref)
3945 (( BASE_SEEN == 0 )) || die " duplicate --base-ref"
4046 (( $# >= 2 )) || die " --base-ref requires a value"
@@ -77,18 +83,16 @@ FIXTURE_TOOL="$REPO_ROOT/tests/compat/vector_storage_fixture.py"
7783[[ -f $FIXTURE_TOOL ]] || die " fixture tool is missing: $FIXTURE_TOOL "
7884
7985for tool in awk bash basename c++ cargo cat cc cp date dirname find git grep mkdir mktemp \
80- python3 realpath rm sed sha256sum stat wc; do
86+ pgrep python3 realpath rm sed sha256sum stat wc; do
8187 command -v " $tool " > /dev/null 2>&1 || die " required Linux tool is missing: $tool "
8288done
8389
84- if command -v sccache > /dev/null 2>&1 ; then
85- export RUSTC_WRAPPER=sccache
86- export CC=" sccache cc"
87- export CXX=" sccache c++"
88- printf ' BUILD_CACHE sccache=%s\n' " $( command -v sccache) "
89- else
90- printf ' BUILD_CACHE sccache=unavailable\n'
91- fi
90+ unset RUSTC_WRAPPER RUSTC_WORKSPACE_WRAPPER CARGO_BUILD_RUSTC_WRAPPER
91+ unset SCCACHE_DIR SCCACHE_CACHE_SIZE SCCACHE_ENDPOINT SCCACHE_BUCKET SCCACHE_REGION
92+ unset SCCACHE_S3_USE_SSL SCCACHE_REDIS SCCACHE_MEMCACHED
93+ export CC=" $( command -v cc) "
94+ export CXX=" $( command -v c++) "
95+ printf ' BUILD_CACHE mode=disabled cc=%s cxx=%s\n' " $CC " " $CXX "
9296
9397if [[ -d $REPO_ROOT /.git ]]; then
9498 COMMON_GIT_DIR=$( realpath -- " $REPO_ROOT /.git" )
@@ -111,6 +115,25 @@ else
111115fi
112116[[ -d $COMMON_GIT_DIR ]] || die " common Git directory is missing: $COMMON_GIT_DIR "
113117
118+ path_is_within () {
119+ local candidate=$1
120+ local authority=$2
121+ [[ $candidate == " $authority " || $candidate == " $authority " /* ]]
122+ }
123+
124+ declare -a REGISTERED_WORKTREE_ROOTS=()
125+ while IFS= read -r record; do
126+ [[ $record == worktree\ * ]] || continue
127+ worktree_root=${record# worktree }
128+ if [[ $worktree_root =~ ^[A-Za-z]:[/\\ ] ]]; then
129+ command -v wslpath > /dev/null 2>&1 || die " wslpath is required for Windows worktree paths"
130+ worktree_root=$( wslpath -u " $worktree_root " )
131+ fi
132+ worktree_root=$( realpath -- " $worktree_root " )
133+ REGISTERED_WORKTREE_ROOTS+=(" $worktree_root " )
134+ done < <( git --git-dir=" $COMMON_GIT_DIR " worktree list --porcelain)
135+ (( ${# REGISTERED_WORKTREE_ROOTS[@]} > 0 )) || die " Git reported zero registered worktrees"
136+
114137resolve_exact_commit () {
115138 git --git-dir=" $COMMON_GIT_DIR " rev-parse --verify --end-of-options " $1 ^{commit}"
116139}
@@ -122,13 +145,25 @@ HEAD_SHA=$(resolve_exact_commit "$HEAD_REF") || die "cannot resolve Head commit:
122145[[ $VECTOR_V1_SHA == " $EXPECTED_VECTOR_V1_REF " ]] || die " Vector-v1 resolves to $VECTOR_V1_SHA , expected $EXPECTED_VECTOR_V1_REF "
123146[[ $HEAD_SHA == " ${HEAD_REF,,} " ]] || die " Head input must name the exact resolved commit: input=$HEAD_REF resolved=$HEAD_SHA "
124147
125- TMP_BASE=$( realpath -- " ${TMPDIR:-/ tmp} " )
148+ TMP_CANDIDATE=${TMPDIR:-/ tmp}
149+ [[ -d $TMP_CANDIDATE ]] || die " TMPDIR is not an existing directory: $TMP_CANDIDATE "
150+ TMP_BASE=$( realpath -- " $TMP_CANDIDATE " )
151+ for worktree_root in " ${REGISTERED_WORKTREE_ROOTS[@]} " ; do
152+ path_is_within " $TMP_BASE " " $worktree_root " && \
153+ die " TMPDIR must be outside every Git worktree: tmp=$TMP_BASE worktree=$worktree_root "
154+ done
155+ path_is_within " $TMP_BASE " " $REPO_ROOT " && die " TMPDIR must be outside REPO_ROOT: $TMP_BASE "
126156TEMP_ROOT=$( mktemp -d " $TMP_BASE /kiwi-vector-storage-compat.XXXXXX" )
127157TEMP_ROOT=$( realpath -- " $TEMP_ROOT " )
128158[[ $( dirname -- " $TEMP_ROOT " ) == " $TMP_BASE " ]] || die " temporary root escaped TMPDIR: $TEMP_ROOT "
129159[[ $( basename -- " $TEMP_ROOT " ) == kiwi-vector-storage-compat.* ]] || die " unexpected temporary root basename: $TEMP_ROOT "
160+ for worktree_root in " ${REGISTERED_WORKTREE_ROOTS[@]} " ; do
161+ path_is_within " $TEMP_ROOT " " $worktree_root " && \
162+ die " temporary root is inside a Git worktree: temp=$TEMP_ROOT worktree=$worktree_root "
163+ done
130164
131165declare -a WORKTREES=()
166+ declare -a BACKGROUND_PIDS=()
132167declare -a EXECUTED_GATES=()
133168declare -A GATE_SET=()
134169ACTIVE_GATE=" "
@@ -153,11 +188,26 @@ safe_remove() {
153188cleanup () {
154189 local status=$?
155190 local cleanup_failed=0
191+ local -a lingering_temp_pids=()
156192 trap - EXIT INT TERM
157193 set +e
158194 if [[ -n $ACTIVE_GATE ]]; then
159195 printf ' GATE %s FAIL duration=%ss\n' " $ACTIVE_GATE " " $(( $(date +% s) - ACTIVE_GATE_STARTED )) " >&2
160196 fi
197+ for pid in " ${BACKGROUND_PIDS[@]} " ; do
198+ if kill -0 " $pid " 2> /dev/null; then
199+ printf ' CLEANUP FAIL runner_background_pid_alive=%s\n' " $pid " >&2
200+ cleanup_failed=1
201+ fi
202+ done
203+ while IFS= read -r pid; do
204+ [[ -n $pid && $pid != $$ && $pid != $PPID ]] || continue
205+ lingering_temp_pids+=(" $pid " )
206+ done < <( pgrep -f -- " $TEMP_ROOT " 2> /dev/null || true)
207+ if (( ${# lingering_temp_pids[@]} > 0 )) ; then
208+ printf ' CLEANUP FAIL temp_root_processes=%s\n' " ${lingering_temp_pids[*]} " >&2
209+ cleanup_failed=1
210+ fi
161211 for (( index= ${# WORKTREES[@]} - 1 ; index >= 0 ; index-- )) ; do
162212 worktree=${WORKTREES[$index]}
163213 assert_under_temp " $worktree " || cleanup_failed=1
@@ -184,7 +234,8 @@ cleanup() {
184234 if (( cleanup_failed != 0 )) ; then
185235 status=1
186236 else
187- printf ' CLEANUP PASS temp_root_removed=true worktrees_removed=%d tracked_processes=0\n' " ${# WORKTREES[@]} "
237+ printf ' CLEANUP PASS temp_root_removed=true worktrees_removed=%d tracked_processes=%d lingering_temp_processes=0\n' \
238+ " ${# WORKTREES[@]} " " ${# BACKGROUND_PIDS[@]} "
188239 fi
189240 exit " $status "
190241}
@@ -304,6 +355,22 @@ copy_fixture() {
304355 cp -a --reflink=auto -- " $source " " $target "
305356}
306357
358+ copy_temp_file () {
359+ local source=$1
360+ local target=$2
361+ local target_parent
362+ target_parent=$( dirname -- " $target " )
363+ assert_under_temp " $source "
364+ assert_under_temp " $target "
365+ assert_under_temp " $target_parent "
366+ [[ -f $source && ! -L $source ]] || die " temporary copy source must be a regular file: $source "
367+ mkdir -p -- " $target_parent "
368+ [[ ! -e $target ]] || die " temporary copy target already exists: $target "
369+ cp --preserve=mode,timestamps -- " $source " " $target "
370+ [[ $( sha256sum -- " $source " | awk ' {print $1}' ) == $( sha256sum -- " $target " | awk ' {print $1}' ) ]] || \
371+ die " temporary file copy hash mismatch: source=$source target=$target "
372+ }
373+
307374printf ' PROVENANCE base=%s vector_v1=%s head=%s common_git_dir=%s temp_root=%s\n' \
308375 " $BASE_SHA " " $VECTOR_V1_SHA " " $HEAD_SHA " " $COMMON_GIT_DIR " " $TEMP_ROOT "
309376
@@ -345,13 +412,11 @@ VECTOR_STORAGE_EXE=$(copy_group_executable vector-storage "$VECTOR_WORKTREE" "$V
345412HEAD_BUILD_LOG=$( build_test_group head " $HEAD_WORKTREE " " $HEAD_TARGET " \
346413 storage/test-fault-injection \
347414 storage:vector_storage_compat_external raft:vector_storage_snapshot_compat_external \
348- storage:checkpoint_test raft:snapshot_roundtrip_test)
415+ raft:snapshot_roundtrip_test)
349416HEAD_STORAGE_EXE=$( copy_group_executable head-storage " $HEAD_WORKTREE " " $HEAD_ARTIFACTS " \
350417 " $HEAD_BUILD_LOG " vector_storage_compat_external)
351418HEAD_SNAPSHOT_EXE=$( copy_group_executable head-snapshot " $HEAD_WORKTREE " " $HEAD_ARTIFACTS " \
352419 " $HEAD_BUILD_LOG " vector_storage_snapshot_compat_external)
353- HEAD_CHECKPOINT_EXE=$( copy_group_executable head-checkpoint " $HEAD_WORKTREE " " $HEAD_ARTIFACTS " \
354- " $HEAD_BUILD_LOG " checkpoint_test)
355420HEAD_ROUNDTRIP_EXE=$( copy_group_executable head-roundtrip " $HEAD_WORKTREE " " $HEAD_ARTIFACTS " \
356421 " $HEAD_BUILD_LOG " snapshot_roundtrip_test)
357422
@@ -404,11 +469,21 @@ for source_profile in base vector; do
404469 fi
405470 for fault in " ${faults[@]} " ; do
406471 case_root=" $TEMP_ROOT /cases/retry-$source_profile -$fault "
472+ authority_root=" $TEMP_ROOT /cases/authority-$source_profile -$fault "
407473 copy_fixture " $source_root " " $case_root "
408- run_exact_test " $HEAD_STORAGE_EXE " migrate_fault_retry_external \
409- KIWI_COMPAT_ROOT=" $case_root " KIWI_COMPAT_PROFILE=" $source_profile " KIWI_COMPAT_FAULT=" $fault "
410- safe_remove " $case_root "
474+ run_exact_test " $HEAD_STORAGE_EXE " inject_fault_and_assert_interrupted_external \
475+ KIWI_COMPAT_ROOT=" $case_root " KIWI_COMPAT_AUTHORITY_ROOT=" $authority_root " \
476+ KIWI_COMPAT_PROFILE=" $source_profile " KIWI_COMPAT_FAULT=" $fault "
477+ if [[ $source_profile == base ]]; then
478+ run_exact_test " $BASE_STORAGE_EXE " reopen_fixture KIWI_COMPAT_ROOT=" $authority_root "
479+ else
480+ run_exact_test " $VECTOR_STORAGE_EXE " reopen_fixture KIWI_COMPAT_ROOT=" $authority_root "
481+ fi
411482 (( PHASE_COUNT += 1 ))
483+ run_exact_test " $HEAD_STORAGE_EXE " resume_after_asserted_fault_external \
484+ KIWI_COMPAT_ROOT=" $case_root " KIWI_COMPAT_PROFILE=" $source_profile "
485+ safe_remove " $case_root "
486+ safe_remove " $authority_root "
412487 done
413488done
414489[[ $PHASE_COUNT -eq 30 ]] || die " migration phase execution count mismatch: $PHASE_COUNT "
@@ -462,14 +537,44 @@ run_exact_test "$HEAD_SNAPSHOT_EXE" restore_exact_base_v1_archive_external \
462537pass_gate base_v1_snapshot_restores_on_head
463538
464539begin_gate v1_snapshot_with_unknown_or_vector_schema_is_rejected
465- run_exact_test " $HEAD_CHECKPOINT_EXE " base_v1_snapshot_with_vector_data_cf_fails_before_pause
466- run_exact_test " $HEAD_CHECKPOINT_EXE " base_v1_snapshot_with_vector_meta_fails_before_pause
467- run_exact_test " $HEAD_CHECKPOINT_EXE " base_v1_snapshot_with_unknown_cf_fails_before_pause
540+ for mutation in unknown-cf vector-cf vector-meta; do
541+ MUTATED_BASE_ARCHIVE=" $TEMP_ROOT /snapshots/base-v1-$mutation .snapshot"
542+ MUTATED_BASE_META=" $TEMP_ROOT /snapshots/base-v1-$mutation .meta.json"
543+ MUTATION_ROOT=" $TEMP_ROOT /snapshots/base-v1-$mutation -unpack"
544+ MUTATION_TARGET=" $TEMP_ROOT /snapshots/base-v1-$mutation -target"
545+ MUTATION_WORK=" $TEMP_ROOT /snapshots/base-v1-$mutation -work"
546+ copy_temp_file " $BASE_ARCHIVE " " $MUTATED_BASE_ARCHIVE "
547+ copy_temp_file " $BASE_SNAPSHOT_META " " $MUTATED_BASE_META "
548+ run_exact_test " $HEAD_SNAPSHOT_EXE " reject_mutated_exact_base_v1_archive_external \
549+ KIWI_COMPAT_ARCHIVE=" $MUTATED_BASE_ARCHIVE " \
550+ KIWI_COMPAT_SNAPSHOT_META=" $MUTATED_BASE_META " \
551+ KIWI_COMPAT_MUTATION=" $mutation " KIWI_COMPAT_MUTATION_ROOT=" $MUTATION_ROOT " \
552+ KIWI_COMPAT_TARGET=" $MUTATION_TARGET " KIWI_COMPAT_SNAPSHOT_WORK=" $MUTATION_WORK "
553+ done
468554pass_gate v1_snapshot_with_unknown_or_vector_schema_is_rejected
469555
470556begin_gate head_v2_snapshot_reopens_with_exact_manifest_pairing
471- run_exact_test " $HEAD_CHECKPOINT_EXE " v2_snapshot_requires_exact_root_manifest_digest
472- run_exact_test " $HEAD_CHECKPOINT_EXE " v2_snapshot_requires_every_instance_manifest_digest_and_incarnation
557+ HEAD_V2_SOURCE=" $TEMP_ROOT /snapshots/head-v2-source"
558+ HEAD_V2_TARGET=" $TEMP_ROOT /snapshots/head-v2-target"
559+ HEAD_V2_BUILD_WORK=" $TEMP_ROOT /snapshots/head-v2-build-work"
560+ HEAD_V2_INSTALL_WORK=" $TEMP_ROOT /snapshots/head-v2-install-work"
561+ HEAD_V2_NEGATIVE_ROOT=" $TEMP_ROOT /snapshots/head-v2-negative"
562+ HEAD_V2_ARCHIVE=" $TEMP_ROOT /snapshots/head-v2.snapshot"
563+ HEAD_V2_META=" $TEMP_ROOT /snapshots/head-v2.meta.json"
564+ run_exact_test " $HEAD_SNAPSHOT_EXE " head_v2_two_instance_exact_pairing_external \
565+ KIWI_COMPAT_HEAD_SOURCE=" $HEAD_V2_SOURCE " KIWI_COMPAT_TARGET=" $HEAD_V2_TARGET " \
566+ KIWI_COMPAT_BUILD_WORK=" $HEAD_V2_BUILD_WORK " \
567+ KIWI_COMPAT_SNAPSHOT_WORK=" $HEAD_V2_INSTALL_WORK " \
568+ KIWI_COMPAT_NEGATIVE_ROOT=" $HEAD_V2_NEGATIVE_ROOT " \
569+ KIWI_COMPAT_HEAD_ARCHIVE=" $HEAD_V2_ARCHIVE " \
570+ KIWI_COMPAT_HEAD_SNAPSHOT_META=" $HEAD_V2_META "
571+ [[ -s $HEAD_V2_ARCHIVE ]] || die " Head v2 snapshot archive is empty"
572+ [[ -s $HEAD_V2_META ]] || die " Head v2 SnapshotMeta sidecar is empty"
573+ printf ' SNAPSHOT head_v2_sha256=%s bytes=%s meta_sha256=%s meta_bytes=%s instances=2 negative_cases=4\n' \
574+ " $( sha256sum -- " $HEAD_V2_ARCHIVE " | awk ' {print $1}' ) " \
575+ " $( stat -c %s -- " $HEAD_V2_ARCHIVE " ) " \
576+ " $( sha256sum -- " $HEAD_V2_META " | awk ' {print $1}' ) " \
577+ " $( stat -c %s -- " $HEAD_V2_META " ) "
473578run_exact_test " $HEAD_ROUNDTRIP_EXE " cursor_snapshot_roundtrip
474579pass_gate head_v2_snapshot_reopens_with_exact_manifest_pairing
475580
0 commit comments