#!/bin/sh
# A manifest-backed collector prevents a lost parallel worker from shrinking a
# required corpus into a false green.

BIN=${BIN:-out/bin}
WW=${WW:-$BIN/ww}
TEST_DIR=${TEST_DIR:-test/wcc}
TEST_EXPECTED_MIN=${TEST_EXPECTED_MIN:-337}
TEST_TIMEOUT=${TEST_TIMEOUT:-900s}
TEST_KILL_AFTER=${TEST_KILL_AFTER:-10s}
TEST_WORKER=${TEST_WORKER:-$0}

# Required-suite results describe the toolchain under BIN, not ambient driver
# overrides from a developer shell. Individual tests still set overrides
# explicitly when that behavior is what they exercise.
unset WW_W6C WW_W6A WW_W6L WW_LIB WW_SRCLIB WW_PKGCACHE

atomic_record() {
	record_path=$1
	record_text=$2
	record_tmp=$record_path.tmp.$$
	if ! printf '%s\n' "$record_text" > "$record_tmp"; then
		rm -f "$record_tmp"
		return 1
	fi
	if ! mv "$record_tmp" "$record_path"; then
		rm -f "$record_tmp"
		return 1
	fi
	return 0
}

# Worker mode owns one result prefix. A result becomes visible only after every
# capture and timing file is complete, so the collector cannot accept a torn
# terminal record as a finished test.
if [ "${1:-}" = "--one" ]; then
	[ "$#" -eq 3 ] || exit 64
	prefix=$2
	t=$3
	name=${t##*/}
	name=${name%.c}
	short=${name#[0-9][0-9][0-9]_}
	bin=$BIN/test_$short

	if ! atomic_record "$prefix.started" "WWTEST_STARTED 1"; then
		exit 70
	fi
	if ! : > "$prefix.out" || ! : > "$prefix.err"; then
		exit 70
	fi

	t0=$(date +%s.%N)
	outcome=pass
	rc=0
	if [ ! -x "$bin" ]; then
		printf 'unwired test: no executable %s\n' "$bin" > "$prefix.err"
		outcome=harness_error
		rc=127
	else
		# The child records its own status before the timeout wrapper exits.
		# That distinguishes a real child exit 124/137 from expiration, and
		# child exits 125-127 from timeout's reserved infrastructure codes.
		command_status=$prefix.command-status
		timeout_diag=$prefix.timeout-diag
		if LC_ALL=C timeout --verbose --kill-after="$TEST_KILL_AFTER" \
		    "$TEST_TIMEOUT" sh -c '
			"$1" > "$2" 2> "$3"
			command_rc=$?
			status_tmp=$4.tmp.$$
			printf "%s\n" "$command_rc" > "$status_tmp" || exit 70
			mv "$status_tmp" "$4" || exit 70
		' sh "$bin" "$prefix.out" "$prefix.err" "$command_status" \
		    2> "$timeout_diag"; then
			timeout_rc=0
		else
			timeout_rc=$?
		fi
		deadline_expired=0
		if grep -Eq '^timeout: sending signal (TERM|KILL) to command ' \
		    "$timeout_diag"; then
			deadline_expired=1
		fi
		if [ -s "$timeout_diag" ]; then
			cat "$timeout_diag" >> "$prefix.err"
		fi
		if [ "$deadline_expired" -eq 1 ]; then
			outcome=timeout
			rc=$timeout_rc
		elif [ -f "$command_status" ]; then
			rc="" extra=""
			IFS=' ' read -r rc extra < "$command_status"
			case $rc in
			''|*[!0-9]*) rc_valid=0 ;;
			*) rc_valid=1 ;;
			esac
			if [ "$rc_valid" -ne 1 ] || [ -n "$extra" ] \
			   || [ "$timeout_rc" -ne 0 ]; then
				printf 'timeout wrapper returned %s with child status %s\n' \
				    "$timeout_rc" "${rc:-missing}" >> "$prefix.err"
				outcome=harness_error
				rc=125
			elif [ "$rc" -eq 0 ]; then
				outcome=pass
			elif [ "$rc" -eq 77 ]; then
				outcome=skip
			else
				outcome=fail
			fi
		else
			printf 'timeout wrapper produced no child status (rc=%s)\n' \
			    "$timeout_rc" >> "$prefix.err"
			outcome=harness_error
			case $timeout_rc in
			125|126|127) rc=$timeout_rc ;;
			*) rc=125 ;;
			esac
		fi
	fi
	t1=$(date +%s.%N)
	dur_tmp=$prefix.dur.tmp.$$
	if ! awk -v a="$t0" -v b="$t1" -v n="$name" \
	    'BEGIN{printf "%.3f\t%s\n", b-a, n}' > "$dur_tmp"; then
		rm -f "$dur_tmp"
		exit 70
	fi
	if ! mv "$dur_tmp" "$prefix.dur"; then
		rm -f "$dur_tmp"
		exit 70
	fi
	if ! atomic_record "$prefix.result" "WWTEST_RESULT 1 $outcome $rc"; then
		exit 70
	fi
	exit 0
fi

RESULTS=$(mktemp -d "${TMPDIR:-/tmp}/wwtest.XXXXXX") || exit 2
trap 'rm -rf "$RESULTS"' EXIT
trap 'exit 1' HUP INT TERM

ALL_MANIFEST=$RESULTS/all.manifest
PHASE1_MANIFEST=$RESULTS/phase1.manifest
PHASE2_MANIFEST=$RESULTS/phase2.manifest
RUN1_MANIFEST=$RESULTS/run1.manifest
RUN2_MANIFEST=$RESULTS/run2.manifest
SELECTED_NAMES=$RESULTS/selected.names
CACHED_NAMES=$RESULTS/cached.names
ALL_NAMES=$RESULTS/all.names
for manifest in "$ALL_MANIFEST" "$PHASE1_MANIFEST" "$PHASE2_MANIFEST" \
    "$RUN1_MANIFEST" "$RUN2_MANIFEST" "$SELECTED_NAMES" \
    "$CACHED_NAMES" "$ALL_NAMES"; do
	if ! : > "$manifest"; then
		printf 'HARNESS suite (cannot initialize manifest %s)\n' "$manifest"
		exit 2
	fi
done

is_phase2() {
	case $1 in
	950_*|990_*|991_*|992_*|993_*|994_*|995_*|996_*|997_*) return 0 ;;
	esac
	return 1
}

is_cached_gate() {
	case $1 in
	950_selfcheck.c|989_lib_byteid.c|\
	990_*|991_*|992_*|993_*|994_*|995_*|997_*) return 0 ;;
	esac
	return 1
}

for t in "$TEST_DIR"/*.c; do
	[ -f "$t" ] || continue
	printf '%s\n' "$t" >> "$ALL_MANIFEST"
	name=${t##*/}
	name=${name%.c}
	printf '%s\n' "$name" >> "$ALL_NAMES"
	if is_phase2 "${t##*/}"; then
		printf '%s\n' "$t" >> "$PHASE2_MANIFEST"
	else
		printf '%s\n' "$t" >> "$PHASE1_MANIFEST"
	fi
done

discovered=$(awk 'END{print NR + 0}' "$ALL_MANIFEST")
excluded=0
preflight_error=0
harness_error=0

case $TEST_EXPECTED_MIN in
''|*[!0-9]*)
	printf 'HARNESS suite (invalid TEST_EXPECTED_MIN: %s)\n' "$TEST_EXPECTED_MIN"
	preflight_error=1
	harness_error=$((harness_error + 1))
	;;
*)
	if [ "$discovered" -eq 0 ]; then
		echo 'HARNESS suite (empty corpus)'
		preflight_error=1
		harness_error=$((harness_error + 1))
	elif [ "$discovered" -lt "$TEST_EXPECTED_MIN" ]; then
		printf 'HARNESS suite (corpus shrank: %d < %d)\n' \
		    "$discovered" "$TEST_EXPECTED_MIN"
		preflight_error=1
		harness_error=$((harness_error + 1))
	fi
	;;
esac

duplicate_names=$(sort "$ALL_NAMES" | uniq -d)
if [ -n "$duplicate_names" ]; then
	echo 'HARNESS suite (duplicate test identifiers)'
	printf '%s\n' "$duplicate_names"
	preflight_error=1
	harness_error=$((harness_error + 1))
fi

JOBS=${JOBS:-$(nproc 2>/dev/null || echo 4)}
case $JOBS in
''|*[!0-9]*|0)
	printf 'HARNESS suite (invalid JOBS: %s)\n' "$JOBS"
	preflight_error=1
	harness_error=$((harness_error + 1))
	;;
esac
if ! command -v timeout >/dev/null 2>&1; then
	echo 'HARNESS suite (GNU timeout is unavailable)'
	preflight_error=1
	harness_error=$((harness_error + 1))
fi
valid_duration() {
	awk -v duration="$1" 'BEGIN {
		if (duration !~ /^[0-9]+([.][0-9]+)?[smhd]?$/) exit 1
		sub(/[smhd]$/, "", duration)
		exit !(duration + 0 > 0)
	}'
}
if ! valid_duration "$TEST_TIMEOUT"; then
	printf 'HARNESS suite (invalid TEST_TIMEOUT: %s)\n' "$TEST_TIMEOUT"
	preflight_error=1
	harness_error=$((harness_error + 1))
fi
if ! valid_duration "$TEST_KILL_AFTER"; then
	printf 'HARNESS suite (invalid TEST_KILL_AFTER: %s)\n' "$TEST_KILL_AFTER"
	preflight_error=1
	harness_error=$((harness_error + 1))
fi

OUT_DIR=$(dirname "$BIN")
CACHE_DIR=$OUT_DIR/.testcache
CACHE_FILE=$CACHE_DIR/last-green

# The cache includes nonignored untracked inputs because a local fixture or
# source file can change a gate even before it is added to the index.
testcache_key() {
	cache_inputs=$RESULTS/cache-inputs
	if ! : > "$cache_inputs"; then
		return 1
	fi
	# Hash the effective built tools, runtime archive, and every executable
	# that the last-green policy may replace with a cached result.
	for b in ww w6c w6a w6l wwdump ww_ww w6c_ww w6a_ww w6l_ww wwdump_ww; do
		printf '%s\0' "$BIN/$b" >> "$cache_inputs" || return 1
	done
	printf '%s\0' "$OUT_DIR/lib/libwwrt.a" >> "$cache_inputs" || return 1
	while IFS= read -r t; do
		[ -n "$t" ] || continue
		if is_cached_gate "${t##*/}"; then
			name=${t##*/}
			name=${name%.c}
			short=${name#[0-9][0-9][0-9]_}
			printf '%s\0' "$BIN/test_$short" >> "$cache_inputs" || return 1
		fi
		printf '%s\0' "$t" >> "$cache_inputs" || return 1
	done < "$ALL_MANIFEST"
	if ! git ls-files --cached -z -- \
	    selfhost lib cmd rt examples test/wcc test/run test/run_test.sh Makefile \
	    >> "$cache_inputs"; then
		return 1
	fi
	# Generated package-cache state is not fixture input; hashing it would make
	# a gate mutate its own key. Other untracked files under data/ remain input.
	if ! git ls-files --others --exclude-standard -z -- \
	    selfhost lib cmd rt examples test/wcc test/run test/run_test.sh Makefile \
	    ':(exclude,glob)test/wcc/data/**/.pkgcache/**' >> "$cache_inputs"; then
		return 1
	fi
	if ! sort -z -u "$cache_inputs" -o "$cache_inputs"; then
		return 1
	fi
	for setting in \
	    "TEST_DIR=$TEST_DIR" \
	    "TEST_EXPECTED_MIN=$TEST_EXPECTED_MIN" \
	    "TEST_TIMEOUT=$TEST_TIMEOUT" \
	    "TEST_KILL_AFTER=$TEST_KILL_AFTER" \
	    "TEST_WORKER=$TEST_WORKER" \
	    "JOBS=$JOBS"; do
		setting_key=$(printf '%s' "$setting" | md5sum) || return 1
		printf 'setting %s\n' "${setting_key%% *}"
	done
	# The index can name an unstaged deletion. Preserve that state in the
	# stream instead of letting md5sum abort and silently disable caching.
	xargs -0 -r sh -c '
		for cache_file do
			path_key=$(printf "%s" "$cache_file" | md5sum) || exit
			path_key=${path_key%% *}
			if [ -f "$cache_file" ]; then
				content_key=$(md5sum < "$cache_file") || exit
				content_key=${content_key%% *}
				mode=-
				[ -x "$cache_file" ] && mode=x
				printf "file %s %s %s\n" \
				    "$path_key" "$mode" "$content_key"
			elif [ ! -e "$cache_file" ] && [ ! -L "$cache_file" ]; then
				printf "missing %s\n" "$path_key"
			else
				printf "unsupported cache input %s\n" "$path_key" >&2
				exit 1
			fi
		done
	' sh < "$cache_inputs"
}

KEY=""
if [ "${UNIT:-0}" != "1" ] && [ "$TEST_WORKER" = "$0" ]; then
	if key_stream=$(testcache_key 2>/dev/null); then
		KEY=$(printf '%s\n' "$key_stream" | md5sum | cut -d' ' -f1)
	fi
fi
CACHE_HIT=0
if [ "${COMMIT:-0}" = "1" ] && [ -n "$KEY" ] && [ -f "$CACHE_FILE" ] \
   && [ "$(cat "$CACHE_FILE" 2>/dev/null)" = "$KEY" ]; then
	CACHE_HIT=1
fi

emit_cached() {
	cached_name=$1
	cached_prefix=$RESULTS/$cached_name
	if ! atomic_record "$cached_prefix.result" "WWTEST_RESULT 1 cached 0"; then
		return 1
	fi
	printf '%s\n' "$cached_name" >> "$CACHED_NAMES"
	return 0
}

plan_manifest() {
	plan_input=$1
	plan_run=$2
	while IFS= read -r t; do
		[ -n "$t" ] || continue
		name=${t##*/}
		name=${name%.c}
		printf '%s\n' "$name" >> "$SELECTED_NAMES"
		if [ "$CACHE_HIT" = 1 ] && is_cached_gate "${t##*/}"; then
			if ! emit_cached "$name"; then
				printf 'HARNESS %s (cannot record cached result)\n' "$name"
				harness_error=$((harness_error + 1))
			fi
		else
			printf '%s\n' "$t" >> "$plan_run"
		fi
	done < "$plan_input"
}

if [ "$preflight_error" -eq 0 ]; then
	plan_manifest "$PHASE1_MANIFEST" "$RUN1_MANIFEST"
	if [ "${UNIT:-0}" = "1" ]; then
		while IFS= read -r t; do
			[ -n "$t" ] || continue
			name=${t##*/}
			name=${name%.c}
			printf 'excluded %s (test-unit phase-2)\n' "$name"
			excluded=$((excluded + 1))
		done < "$PHASE2_MANIFEST"
	else
		plan_manifest "$PHASE2_MANIFEST" "$RUN2_MANIFEST"
	fi
fi

launch_manifest() {
	launch_file=$1
	[ -s "$launch_file" ] || return 0
	while IFS= read -r t; do
		name=${t##*/}
		name=${name%.c}
		printf '%s/%s\0%s\0' "$RESULTS" "$name" "$t"
	done < "$launch_file" | \
		xargs -0 -r -n2 -P "$JOBS" "$TEST_WORKER" --one
}

run_start=$(date +%s.%N)
launcher_error=0
p1_end=$run_start
p2par_start=""
p2par_end=""
if [ "$preflight_error" -eq 0 ]; then
	if launch_manifest "$RUN1_MANIFEST"; then
		:
	else
		rc=$?
		printf 'HARNESS launcher (phase 1 xargs rc=%d)\n' "$rc"
		launcher_error=1
		harness_error=$((harness_error + 1))
	fi
	p1_end=$(date +%s.%N)
	if [ "${UNIT:-0}" != "1" ] && [ -s "$RUN2_MANIFEST" ]; then
		p2par_start=$(date +%s.%N)
		if launch_manifest "$RUN2_MANIFEST"; then
			:
		else
			rc=$?
			printf 'HARNESS launcher (phase 2 xargs rc=%d)\n' "$rc"
			launcher_error=1
			harness_error=$((harness_error + 1))
		fi
		p2par_end=$(date +%s.%N)
	fi
fi

emit_output() {
	output_prefix=$1
	output_name=$2
	if [ -s "$output_prefix.out" ]; then
		printf '%s\n' "--- stdout: $output_name ---"
		cat "$output_prefix.out"
	fi
	if [ -s "$output_prefix.err" ]; then
		printf '%s\n' "--- stderr: $output_name ---"
		cat "$output_prefix.err"
	fi
}

started=0
completed=0
pass=0
fail=0
skip=0
timed_out=0
cached=0
worker_harness_error=0

if [ "$preflight_error" -eq 0 ]; then
	while IFS= read -r name; do
		[ -n "$name" ] || continue
		prefix=$RESULTS/$name
		expect_cached=0
		if grep -Fqx "$name" "$CACHED_NAMES"; then
			expect_cached=1
		fi
		has_started=0
		if [ -f "$prefix.started" ]; then
			if grep -qx 'WWTEST_STARTED 1' "$prefix.started"; then
				has_started=1
				started=$((started + 1))
			else
				printf 'HARNESS %s (malformed started record)\n' "$name"
				harness_error=$((harness_error + 1))
			fi
		fi

		if [ ! -f "$prefix.result" ]; then
			if [ "$has_started" -eq 1 ]; then
				printf 'HARNESS %s (started but no terminal result)\n' "$name"
			else
				printf 'HARNESS %s (worker never started)\n' "$name"
			fi
			harness_error=$((harness_error + 1))
			continue
		fi

		magic="" version="" outcome="" result_rc="" extra=""
		IFS=' ' read -r magic version outcome result_rc extra < "$prefix.result"
		if [ "$magic" != WWTEST_RESULT ] || [ "$version" != 1 ] \
		   || [ -z "$outcome" ] || [ -z "$result_rc" ] || [ -n "$extra" ]; then
			printf 'HARNESS %s (malformed terminal result)\n' "$name"
			harness_error=$((harness_error + 1))
			continue
		fi
		case $result_rc in
		''|*[!0-9]*)
			printf 'HARNESS %s (nonnumeric terminal rc)\n' "$name"
			harness_error=$((harness_error + 1))
			continue
			;;
		esac

		if [ "$outcome" = cached ]; then
			if [ "$expect_cached" -ne 1 ] || [ "$has_started" -ne 0 ] \
			   || [ "$result_rc" != 0 ]; then
				printf 'HARNESS %s (invalid cached result)\n' "$name"
				harness_error=$((harness_error + 1))
				continue
			fi
			printf 'cached %s\n' "$name"
			cached=$((cached + 1))
			continue
		fi

		capture_error=0
		for suffix in out err; do
			if [ ! -f "$prefix.$suffix" ]; then
				printf 'HARNESS %s (missing %s capture)\n' "$name" "$suffix"
				capture_error=1
			fi
		done
		if [ ! -s "$prefix.dur" ]; then
			printf 'HARNESS %s (missing duration record)\n' "$name"
			capture_error=1
		fi
		if [ "$capture_error" -ne 0 ]; then
			harness_error=$((harness_error + 1))
			continue
		fi

		completed=$((completed + 1))
		if [ "$expect_cached" -eq 1 ] || [ "$has_started" -ne 1 ]; then
			printf 'HARNESS %s (terminal result without matching start)\n' "$name"
			harness_error=$((harness_error + 1))
		fi
		case $outcome:$result_rc in
		pass:0)
			printf 'ok   %s\n' "$name"
			pass=$((pass + 1))
			;;
		skip:77)
			printf 'skip %s (rc=77)\n' "$name"
			skip=$((skip + 1))
			if [ ! -s "$prefix.out" ] && [ ! -s "$prefix.err" ]; then
				echo 'skip reason: exit 77 (no output)'
			fi
			;;
		timeout:124|timeout:137)
			printf 'TIMEOUT %s\n' "$name"
			timed_out=$((timed_out + 1))
			;;
		fail:*)
			printf 'FAIL %s (rc=%s)\n' "$name" "$result_rc"
			fail=$((fail + 1))
			;;
		harness_error:125|harness_error:126|harness_error:127)
			printf 'HARNESS %s (worker rc=%s)\n' "$name" "$result_rc"
			worker_harness_error=$((worker_harness_error + 1))
			harness_error=$((harness_error + 1))
			;;
		*)
			printf 'HARNESS %s (invalid outcome/rc %s/%s)\n' \
			    "$name" "$outcome" "$result_rc"
			worker_harness_error=$((worker_harness_error + 1))
			harness_error=$((harness_error + 1))
			;;
		esac
		emit_output "$prefix" "$name"
	done < "$SELECTED_NAMES"

	for record in "$RESULTS"/*.result "$RESULTS"/*.started; do
		[ -f "$record" ] || continue
		record_name=${record##*/}
		record_name=${record_name%.result}
		record_name=${record_name%.started}
		if ! grep -Fqx "$record_name" "$SELECTED_NAMES"; then
			printf 'HARNESS %s (unexpected worker record)\n' "$record_name"
			harness_error=$((harness_error + 1))
		fi
	done
fi

selected=$((discovered - excluded))
planned=$((selected - cached))
terminal=$((pass + fail + skip + timed_out + worker_harness_error))
if [ "$preflight_error" -eq 0 ]; then
	if [ "$started" -ne "$planned" ]; then
		printf 'HARNESS accounting (started %d != planned %d)\n' \
		    "$started" "$planned"
		harness_error=$((harness_error + 1))
	fi
	if [ "$completed" -ne "$planned" ]; then
		printf 'HARNESS accounting (completed %d != planned %d)\n' \
		    "$completed" "$planned"
		harness_error=$((harness_error + 1))
	fi
	if [ "$terminal" -ne "$completed" ]; then
		printf 'HARNESS accounting (terminal outcomes %d != completed %d)\n' \
		    "$terminal" "$completed"
		harness_error=$((harness_error + 1))
	fi
	if [ $((completed + cached + excluded)) -ne "$discovered" ]; then
		printf 'HARNESS accounting (discovered corpus is incomplete)\n'
		harness_error=$((harness_error + 1))
	fi
fi

# A cache decision is valid only if its complete input fingerprint is still
# current after all workers have finished. This applies to hits as well as to
# newly published keys.
if [ "${UNIT:-0}" != "1" ] && [ -n "$KEY" ]; then
	end_key=""
	if end_stream=$(testcache_key 2>/dev/null); then
		end_key=$(printf '%s\n' "$end_stream" | md5sum | cut -d' ' -f1)
	fi
	if [ -z "$end_key" ] || [ "$end_key" != "$KEY" ]; then
		echo 'HARNESS cache (inputs changed during test run)'
		harness_error=$((harness_error + 1))
	fi
fi

not_run=$((discovered - completed - cached))
run_end=$(date +%s.%N)
echo '--- timing: per-test (sec, desc) ---'
cat "$RESULTS"/*.dur 2>/dev/null | sort -rn
echo '--- timing: phase walls (sec) ---'
awk -v rs="$run_start" -v p1e="$p1_end" \
    -v ppar_s="$p2par_start" -v ppar_e="$p2par_end" \
    -v re="$run_end" 'BEGIN{
	printf "phase1_parallel\t%.3f\n", p1e - rs
	if (ppar_e != "") printf "phase2_parallel\t%.3f\n", ppar_e - ppar_s
	printf "total\t%.3f\n", re - rs
}'

printf 'summary: discovered=%d started=%d completed=%d pass=%d fail=%d skip=%d timeout=%d harness_error=%d cached=%d not_run=%d\n' \
    "$discovered" "$started" "$completed" "$pass" "$fail" "$skip" \
    "$timed_out" "$harness_error" "$cached" "$not_run"

green=1
if [ "$preflight_error" -ne 0 ] || [ "$launcher_error" -ne 0 ] \
   || [ "$fail" -ne 0 ] || [ "$skip" -ne 0 ] || [ "$timed_out" -ne 0 ] \
   || [ "$harness_error" -ne 0 ]; then
	green=0
fi
if [ "$preflight_error" -eq 0 ] && [ "$not_run" -ne "$excluded" ]; then
	green=0
fi

if [ "$green" -eq 1 ] && [ "${UNIT:-0}" != "1" ] \
   && [ "$CACHE_HIT" != 1 ] && [ -n "$KEY" ]; then
	if ! mkdir -p "$CACHE_DIR" \
	   || ! atomic_record "$CACHE_FILE" "$KEY"; then
		echo 'HARNESS cache (cannot publish last-green key)'
		exit 1
	fi
fi

[ "$green" -eq 1 ] || exit 1
exit 0
