Files
erofs-freebsd-out-tree/tests/pre15/lib/runner.sh
T
2026-08-18 09:20:44 +02:00

752 lines
22 KiB
Bash

#!/bin/sh
PRE15_RC_DUT_FAIL=10
PRE15_RC_RUNNER_FAIL=20
PRE15_RC_INFRA_BLOCKED=21
PRE15_RC_STOP=22
pre15_require_paths()
{
: "${PRE15_LIB_DIR:?PRE15_LIB_DIR is required}"
PRE15_DUT=${PRE15_DUT:-$(CDPATH= cd -- "$PRE15_LIB_DIR/../../.." && pwd -P)}
PRE15_ROOT=${PRE15_ROOT:-$(CDPATH= cd -- "$PRE15_DUT/.." && pwd -P)}
PRE15_SCHEMA=${PRE15_SCHEMA:-$PRE15_DUT/tests/pre15/EVIDENCE-SCHEMA.json}
PRE15_FREEBSD_SRC=${PRE15_FREEBSD_SRC:-${FREEBSD_SRC:-/work/build/freebsd-src}}
PRE15_EVIDENCE_ROOT=${PRE15_EVIDENCE_ROOT:-${TMPDIR:-/tmp}/erofs-pre15-evidence}
export PRE15_DUT PRE15_ROOT PRE15_SCHEMA PRE15_FREEBSD_SRC PRE15_EVIDENCE_ROOT
}
pre15_utc_now()
{
date -u '+%Y-%m-%dT%H:%M:%SZ'
}
pre15_tree_sha256()
{
python3 - "$1" <<'PY'
from __future__ import annotations
import hashlib
import os
from pathlib import Path
import stat
import sys
root = Path(sys.argv[1])
if not root.is_dir():
raise SystemExit(f"tree root is not a directory: {root}")
digest = hashlib.sha256()
for current, directories, files in os.walk(root, followlinks=False):
directories.sort(key=os.fsencode)
files.sort(key=os.fsencode)
current_path = Path(current)
names = [(name, True) for name in directories]
names.extend((name, False) for name in files)
names.sort(key=lambda item: os.fsencode(item[0]))
for name, is_directory in names:
path = current_path / name
metadata = path.lstat()
relative = os.fsencode(path.relative_to(root))
if stat.S_ISLNK(metadata.st_mode):
kind = b"L"
elif is_directory:
kind = b"D"
elif stat.S_ISREG(metadata.st_mode):
kind = b"F"
else:
raise SystemExit(f"unsupported tree entry: {path}")
digest.update(kind + b"\0" + relative + b"\0")
digest.update(f"{stat.S_IMODE(metadata.st_mode):04o}\0".encode("ascii"))
if kind == b"L":
digest.update(os.fsencode(os.readlink(path)) + b"\0")
elif kind == b"F":
digest.update(f"{metadata.st_size}\0".encode("ascii"))
with path.open("rb") as source:
for chunk in iter(lambda: source.read(1024 * 1024), b""):
digest.update(chunk)
digest.update(b"\0")
print(digest.hexdigest())
PY
}
pre15_tree_metadata_sha256()
{
python3 - "$1" <<'PY'
from __future__ import annotations
import hashlib
import os
from pathlib import Path
import stat
import sys
root = Path(sys.argv[1])
if not root.is_dir():
raise SystemExit(f"tree root is not a directory: {root}")
digest = hashlib.sha256()
for current, directories, files in os.walk(root, followlinks=False):
directories.sort(key=os.fsencode)
files.sort(key=os.fsencode)
current_path = Path(current)
names = [(name, True) for name in directories]
names.extend((name, False) for name in files)
names.sort(key=lambda item: os.fsencode(item[0]))
for name, is_directory in names:
path = current_path / name
metadata = path.lstat()
relative = os.fsencode(path.relative_to(root))
if stat.S_ISLNK(metadata.st_mode):
kind = b"L"
elif is_directory:
kind = b"D"
elif stat.S_ISREG(metadata.st_mode):
kind = b"F"
else:
raise SystemExit(f"unsupported tree entry: {path}")
digest.update(kind + b"\0" + relative + b"\0")
fields = (
stat.S_IMODE(metadata.st_mode),
metadata.st_size,
metadata.st_mtime_ns,
metadata.st_ctime_ns,
metadata.st_dev,
metadata.st_ino,
)
digest.update(("\0".join(map(str, fields)) + "\0").encode("ascii"))
if kind == b"L":
digest.update(os.fsencode(os.readlink(path)) + b"\0")
print(digest.hexdigest())
PY
}
pre15_fail_usage()
{
printf 'RUNNER_FAIL: %s\n' "$*" >&2
exit "$PRE15_RC_RUNNER_FAIL"
}
pre15_target_reached()
{
: "${PRE15_TARGET_MARKER_FILE:?target marker path is unavailable}"
printf 'reached\n' > "$PRE15_TARGET_MARKER_FILE"
}
pre15_set_reason()
{
: "${PRE15_REASON_FILE:?reason path is unavailable}"
printf '%s\n' "$*" > "$PRE15_REASON_FILE"
}
pre15_dut_fail()
{
pre15_set_reason "$*"
exit "$PRE15_RC_DUT_FAIL"
}
pre15_runner_fail()
{
pre15_set_reason "$*"
exit "$PRE15_RC_RUNNER_FAIL"
}
pre15_infra_blocked()
{
pre15_set_reason "$*"
exit "$PRE15_RC_INFRA_BLOCKED"
}
pre15_gate_stop()
{
pre15_set_reason "$*"
exit "$PRE15_RC_STOP"
}
pre15_register_resource()
{
: "${PRE15_OWNERSHIP_FILE:?ownership manifest is unavailable}"
pre15_resource_kind=$1
pre15_resource_value=$2
pre15_resource_label=$3
case "$pre15_resource_kind$pre15_resource_value$pre15_resource_label" in
*" "*|*"
"*) pre15_runner_fail "resource records cannot contain tabs or newlines" ;;
esac
printf '%s\t%s\t%s\n' \
"$pre15_resource_kind" "$pre15_resource_value" "$pre15_resource_label" \
>> "$PRE15_OWNERSHIP_FILE"
}
pre15_own_path()
{
pre15_register_resource path "$1" "${2:-temporary path}"
}
pre15_own_pid()
{
case "$1" in
''|*[!0-9]*) pre15_runner_fail "invalid owned PID: $1" ;;
esac
pre15_register_resource pid "$1" "${2:-process}"
}
pre15_own_port()
{
case "$1" in
''|*[!0-9]*) pre15_runner_fail "invalid owned port: $1" ;;
esac
pre15_register_resource port "$1" "${2:-forwarded port}"
}
pre15_own_ssh_control()
{
pre15_register_resource ssh-control "$1" "${2:-SSH ControlMaster}"
}
pre15_own_base_image()
{
pre15_base_path=$1
test -f "$pre15_base_path" || pre15_infra_blocked "base image is absent: $pre15_base_path"
pre15_base_hash=$(sha256sum "$pre15_base_path" | awk '{print $1}')
pre15_register_resource base-image "$pre15_base_path" "$pre15_base_hash"
}
pre15_own_guest_mount()
{
case "$1" in
/*) ;;
*) pre15_runner_fail "guest mountpoint must be absolute: $1" ;;
esac
pre15_register_resource guest-mount "$1" "${2:-guest mount}"
}
pre15_own_guest_md()
{
case "$1" in
md[0-9]*) ;;
*) pre15_runner_fail "invalid guest md unit: $1" ;;
esac
pre15_register_resource guest-md "$1" "${2:-guest md}"
}
pre15_own_guest_kld()
{
case "$1" in
[A-Za-z0-9_.-]*) ;;
*) pre15_runner_fail "invalid guest KLD name: $1" ;;
esac
pre15_register_resource guest-kld "$1" "${2:-guest KLD}"
}
pre15_record_fixture()
{
pre15_fixture_label=$1
pre15_fixture_path=$2
test -f "$pre15_fixture_path" || pre15_runner_fail \
"fixture is absent: $pre15_fixture_path"
pre15_fixture_hash=$(sha256sum "$pre15_fixture_path" | awk '{print $1}')
printf '%s\t%s\t%s\n' \
"$pre15_fixture_hash" "$pre15_fixture_label" "$pre15_fixture_path" \
>> "$PRE15_FIXTURE_HASHES"
}
pre15_record_module()
{
pre15_module_path=$1
test -f "$pre15_module_path" || pre15_runner_fail \
"module is absent: $pre15_module_path"
pre15_module_digest=$(sha256sum "$pre15_module_path" | awk '{print $1}')
printf '%s\t%s\n' "$pre15_module_digest" "$pre15_module_path" \
> "$PRE15_MODULE_HASH"
}
pre15_port_is_free()
{
python3 - "$1" <<'PY'
import socket
import sys
port = int(sys.argv[1])
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.settimeout(0.2)
raise SystemExit(0 if sock.connect_ex(("127.0.0.1", port)) != 0 else 1)
PY
}
pre15_guest_ssh()
{
: "${PRE15_QEMU_SSH_PORT:?SSH port is unavailable}"
: "${PRE15_QEMU_SSH_USER:?SSH user is unavailable}"
: "${PRE15_QEMU_SSH_KEY:?SSH key is unavailable}"
ssh -o BatchMode=yes -o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 \
-i "$PRE15_QEMU_SSH_KEY" -p "$PRE15_QEMU_SSH_PORT" \
"$PRE15_QEMU_SSH_USER@127.0.0.1" "$@"
}
pre15_guest_ssh_bounded()
{
pre15_guest_deadline=${PRE15_GUEST_COMMAND_TIMEOUT:-60}
case "$pre15_guest_deadline" in
''|*[!0-9]*|0) pre15_runner_fail "invalid guest command deadline: $pre15_guest_deadline" ;;
esac
timeout -k 5 "$pre15_guest_deadline" ssh \
-o BatchMode=yes -o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 \
-i "$PRE15_QEMU_SSH_KEY" -p "$PRE15_QEMU_SSH_PORT" \
"$PRE15_QEMU_SSH_USER@127.0.0.1" "$@"
}
pre15_cleanup_note()
{
printf '%s\n' "$*" >> "$PRE15_CLEANUP_LOG"
}
pre15_cleanup_fail()
{
PRE15_CLEANUP_STATUS_RESULT=FAIL
pre15_cleanup_note "FAIL $*"
}
pre15_cleanup_pid()
{
pre15_cleanup_pid_value=$1
pre15_cleanup_pid_label=$2
if ! kill -0 "$pre15_cleanup_pid_value" 2>/dev/null; then
pre15_cleanup_note "PASS pid already exited: $pre15_cleanup_pid_value ($pre15_cleanup_pid_label)"
return
fi
if ! kill -TERM "$pre15_cleanup_pid_value" 2>/dev/null; then
pre15_cleanup_fail "could not TERM PID $pre15_cleanup_pid_value ($pre15_cleanup_pid_label)"
return
fi
pre15_cleanup_wait=0
while kill -0 "$pre15_cleanup_pid_value" 2>/dev/null && \
test "$pre15_cleanup_wait" -lt 50; do
sleep 0.1
pre15_cleanup_wait=$((pre15_cleanup_wait + 1))
done
if kill -0 "$pre15_cleanup_pid_value" 2>/dev/null; then
if ! kill -KILL "$pre15_cleanup_pid_value" 2>/dev/null; then
pre15_cleanup_fail "could not KILL PID $pre15_cleanup_pid_value ($pre15_cleanup_pid_label)"
return
fi
sleep 0.2
fi
if kill -0 "$pre15_cleanup_pid_value" 2>/dev/null; then
pre15_cleanup_fail "PID survived cleanup: $pre15_cleanup_pid_value ($pre15_cleanup_pid_label)"
else
pre15_cleanup_note "PASS stopped PID $pre15_cleanup_pid_value ($pre15_cleanup_pid_label)"
fi
}
pre15_cleanup_guest()
{
pre15_guest_kind=$1
pre15_guest_value=$2
case "$pre15_guest_kind" in
guest-mount)
if pre15_guest_ssh_bounded umount "$pre15_guest_value" >> "$PRE15_CLEANUP_LOG" 2>&1; then
pre15_cleanup_note "PASS unmounted guest path $pre15_guest_value"
else
pre15_cleanup_fail "guest unmount failed: $pre15_guest_value"
fi
;;
guest-md)
if pre15_guest_ssh_bounded mdconfig -d -u "$pre15_guest_value" >> "$PRE15_CLEANUP_LOG" 2>&1; then
pre15_cleanup_note "PASS detached guest md $pre15_guest_value"
else
pre15_cleanup_fail "guest md detach failed: $pre15_guest_value"
fi
;;
guest-kld)
if pre15_guest_ssh_bounded kldunload "$pre15_guest_value" >> "$PRE15_CLEANUP_LOG" 2>&1; then
pre15_cleanup_note "PASS unloaded guest KLD $pre15_guest_value"
else
pre15_cleanup_fail "guest KLD unload failed: $pre15_guest_value"
fi
;;
esac
}
pre15_cleanup_owned()
{
PRE15_CLEANUP_STATUS_RESULT=PASS
: > "$PRE15_CLEANUP_LOG"
pre15_reverse_file=$PRE15_RUN_DIR/.ownership-reverse.$$
awk '{ line[NR] = $0 } END { for (i = NR; i > 0; --i) print line[i] }' \
"$PRE15_OWNERSHIP_FILE" > "$pre15_reverse_file"
while IFS="$(printf '\t')" read -r pre15_kind pre15_value pre15_label; do
test -n "$pre15_kind" || continue
case "$pre15_kind" in
guest-mount|guest-md|guest-kld)
if test -n "${PRE15_QEMU_SSH_PORT:-}"; then
pre15_cleanup_guest "$pre15_kind" "$pre15_value"
else
pre15_cleanup_fail "guest resource has no SSH endpoint: $pre15_kind $pre15_value"
fi
;;
ssh-control)
if test -S "$pre15_value"; then
if timeout -k 5 "${PRE15_CLEANUP_COMMAND_TIMEOUT:-15}" \
ssh -S "$pre15_value" -O exit \
-p "${PRE15_QEMU_SSH_PORT:-22}" \
"${PRE15_QEMU_SSH_USER:-root}@127.0.0.1" \
>> "$PRE15_CLEANUP_LOG" 2>&1; then
pre15_cleanup_note "PASS closed SSH control $pre15_value"
else
pre15_cleanup_fail "could not close SSH control $pre15_value"
fi
fi
if test -e "$pre15_value" && ! rm -f -- "$pre15_value"; then
pre15_cleanup_fail "could not remove SSH control $pre15_value"
fi
;;
pid)
pre15_cleanup_pid "$pre15_value" "$pre15_label"
;;
port)
if pre15_port_is_free "$pre15_value"; then
pre15_cleanup_note "PASS port is free: $pre15_value"
else
pre15_cleanup_fail "port remains in use: $pre15_value"
fi
;;
path)
case "$pre15_value" in
"$PRE15_RUN_DIR"/*)
if test -e "$pre15_value" && ! rm -rf -- "$pre15_value"; then
pre15_cleanup_fail "could not remove owned path $pre15_value"
else
pre15_cleanup_note "PASS removed owned path $pre15_value"
fi
;;
*) pre15_cleanup_fail "refused to remove path outside run directory: $pre15_value" ;;
esac
;;
base-image)
if test ! -f "$pre15_value"; then
pre15_cleanup_fail "base image disappeared: $pre15_value"
else
pre15_cleanup_base_hash=$(sha256sum "$pre15_value" | awk '{print $1}')
if test "$pre15_cleanup_base_hash" = "$pre15_label"; then
pre15_cleanup_note "PASS base image unchanged: $pre15_value"
else
pre15_cleanup_fail "base image hash changed: $pre15_value"
fi
fi
;;
*) pre15_cleanup_fail "unknown ownership kind: $pre15_kind" ;;
esac
done < "$pre15_reverse_file"
rm -f -- "$pre15_reverse_file"
if grep -q '^FAIL ' "$PRE15_CLEANUP_LOG"; then
PRE15_CLEANUP_STATUS_RESULT=FAIL
fi
}
pre15_reason_text()
{
if test -s "$PRE15_REASON_FILE"; then
head -n 1 "$PRE15_REASON_FILE"
else
printf '%s\n' "$1"
fi
}
pre15_classify()
{
pre15_classify_rc=$1
pre15_classify_target=$2
pre15_classify_cleanup=$3
PRE15_TIMED_OUT_RESULT=0
if test "$pre15_classify_cleanup" != PASS; then
PRE15_STATUS_RESULT=RUNNER_FAIL
PRE15_ORIGIN_RESULT=runner
PRE15_REASON_RESULT='owned resource cleanup failed'
return
fi
case "$pre15_classify_rc" in
0)
if test "$pre15_classify_target" = reached; then
PRE15_STATUS_RESULT=PASS
PRE15_ORIGIN_RESULT=none
PRE15_REASON_RESULT='target reached and all oracles passed'
else
PRE15_STATUS_RESULT=RUNNER_FAIL
PRE15_ORIGIN_RESULT=runner
PRE15_REASON_RESULT='command exited zero without a target marker'
fi
;;
"$PRE15_RC_DUT_FAIL")
if test "$pre15_classify_target" = reached; then
PRE15_STATUS_RESULT=DUT_FAIL
PRE15_ORIGIN_RESULT=dut
PRE15_REASON_RESULT=$(pre15_reason_text 'DUT oracle mismatch')
else
PRE15_STATUS_RESULT=RUNNER_FAIL
PRE15_ORIGIN_RESULT=runner
PRE15_REASON_RESULT='DUT_FAIL was requested before the target marker'
fi
;;
"$PRE15_RC_INFRA_BLOCKED")
PRE15_STATUS_RESULT=INFRA_BLOCKED
PRE15_ORIGIN_RESULT=infrastructure
PRE15_REASON_RESULT=$(pre15_reason_text 'infrastructure prerequisite failed')
;;
"$PRE15_RC_STOP")
PRE15_STATUS_RESULT=STOP
PRE15_ORIGIN_RESULT=gate
PRE15_REASON_RESULT=$(pre15_reason_text 'gate oracle returned STOP')
;;
124|137)
PRE15_STATUS_RESULT=RUNNER_FAIL
PRE15_ORIGIN_RESULT=runner
PRE15_REASON_RESULT="hard timeout after ${PRE15_DEADLINE}s"
PRE15_TIMED_OUT_RESULT=1
;;
*)
PRE15_STATUS_RESULT=RUNNER_FAIL
PRE15_ORIGIN_RESULT=runner
PRE15_REASON_RESULT=$(pre15_reason_text "command exited $pre15_classify_rc")
;;
esac
}
pre15_write_identity()
{
PRE15_DUT_HEAD=$(git -C "$PRE15_ROOT" rev-parse HEAD)
PRE15_DUT_TREE=$(git -C "$PRE15_ROOT" rev-parse HEAD:repo-pre-15)
PRE15_LINUX_SOURCE=$(git -C "$PRE15_ROOT" rev-parse HEAD:src-linux)
pre15_freebsd_git_root=$(git -C "$PRE15_FREEBSD_SRC" \
rev-parse --show-toplevel 2>/dev/null || true)
if test -n "$pre15_freebsd_git_root" && \
test "$(CDPATH= cd -- "$pre15_freebsd_git_root" && pwd -P)" = \
"$(CDPATH= cd -- "$PRE15_FREEBSD_SRC" && pwd -P)"; then
pre15_freebsd_head=$(git -C "$PRE15_FREEBSD_SRC" rev-parse HEAD)
pre15_freebsd_dirty=$(git -C "$PRE15_FREEBSD_SRC" status --short | sha256sum | awk '{print $1}')
PRE15_FREEBSD_SOURCE="$pre15_freebsd_head dirty-sha256:$pre15_freebsd_dirty"
else
pre15_freebsd_metadata=$(pre15_tree_metadata_sha256 "$PRE15_FREEBSD_SRC")
if test -n "${PRE15_FREEBSD_TREE_SHA256:-}" || \
test -n "${PRE15_FREEBSD_TREE_METADATA_SHA256:-}"; then
case "${PRE15_FREEBSD_TREE_SHA256:-}" in
????????????????????????????????????????????????????????????????)
case "$PRE15_FREEBSD_TREE_SHA256" in
*[!0-9a-f]*) pre15_fail_usage 'cached FreeBSD tree SHA256 is invalid' ;;
esac
;;
*) pre15_fail_usage 'cached FreeBSD tree SHA256 is invalid' ;;
esac
case "${PRE15_FREEBSD_TREE_METADATA_SHA256:-}" in
????????????????????????????????????????????????????????????????)
case "$PRE15_FREEBSD_TREE_METADATA_SHA256" in
*[!0-9a-f]*) pre15_fail_usage 'cached FreeBSD metadata SHA256 is invalid' ;;
esac
;;
*) pre15_fail_usage 'cached FreeBSD metadata SHA256 is invalid' ;;
esac
test "$pre15_freebsd_metadata" = \
"$PRE15_FREEBSD_TREE_METADATA_SHA256" || \
pre15_fail_usage 'FreeBSD source changed after its content snapshot'
pre15_freebsd_tree=$PRE15_FREEBSD_TREE_SHA256
else
pre15_freebsd_tree=$(pre15_tree_sha256 "$PRE15_FREEBSD_SRC")
fi
PRE15_FREEBSD_SOURCE="tree-sha256:$pre15_freebsd_tree metadata-sha256:$pre15_freebsd_metadata non-git-snapshot"
fi
PRE15_WORKTREE_DIFF_SHA256=$(git -C "$PRE15_ROOT" diff --binary HEAD -- repo-pre-15 | sha256sum | awk '{print $1}')
export PRE15_DUT_HEAD PRE15_DUT_TREE PRE15_LINUX_SOURCE PRE15_FREEBSD_SOURCE
export PRE15_WORKTREE_DIFF_SHA256
PRE15_IDENTITY_HOST=$(uname -a)
PRE15_IDENTITY_GIT_STATUS=$(git -C "$PRE15_ROOT" status --short | sha256sum | awk '{print $1}')
export PRE15_IDENTITY_HOST PRE15_IDENTITY_GIT_STATUS
python3 - "$PRE15_IDENTITY_PATH" <<'PY'
import json
import os
from pathlib import Path
import platform
import sys
identity = {
"host_uname": os.environ["PRE15_IDENTITY_HOST"],
"python": platform.python_version(),
"git_status_sha256": os.environ["PRE15_IDENTITY_GIT_STATUS"],
"freebsd_source_path": os.environ["PRE15_FREEBSD_SRC"],
"freebsd_source_identity": os.environ["PRE15_FREEBSD_SOURCE"],
}
with Path(sys.argv[1]).open("x", encoding="ascii") as output:
json.dump(identity, output, ensure_ascii=True, indent=2, sort_keys=True)
output.write("\n")
PY
}
pre15_new_run()
{
umask 077
mkdir -p "$PRE15_EVIDENCE_ROOT"
pre15_run_stamp=$(date -u '+%Y%m%dT%H%M%SZ')
pre15_run_attempt=0
while :; do
PRE15_RUN_ID="$pre15_run_stamp-$PRE15_MODE-$PRE15_CASE_ID-$$-$pre15_run_attempt"
PRE15_RUN_DIR=$PRE15_EVIDENCE_ROOT/$PRE15_RUN_ID
if mkdir "$PRE15_RUN_DIR" 2>/dev/null; then
break
fi
pre15_run_attempt=$((pre15_run_attempt + 1))
test "$pre15_run_attempt" -lt 100 || pre15_fail_usage \
"could not allocate immutable run directory"
done
PRE15_STDOUT_PATH=$PRE15_RUN_DIR/stdout.log
PRE15_STDERR_PATH=$PRE15_RUN_DIR/stderr.log
PRE15_TARGET_MARKER_FILE=$PRE15_RUN_DIR/target.marker
PRE15_REASON_FILE=$PRE15_RUN_DIR/reason.txt
PRE15_OWNERSHIP_FILE=$PRE15_RUN_DIR/ownership.tsv
PRE15_CLEANUP_LOG=$PRE15_RUN_DIR/cleanup.log
PRE15_FIXTURE_HASHES=$PRE15_RUN_DIR/fixture-hashes.tsv
PRE15_MODULE_HASH=$PRE15_RUN_DIR/module-hash.tsv
PRE15_ARGV_PATH=$PRE15_RUN_DIR/command-argv.json
PRE15_IDENTITY_PATH=$PRE15_RUN_DIR/identity.json
PRE15_MANIFEST_PATH=$PRE15_RUN_DIR/manifest.json
PRE15_CASE_TMP=$PRE15_RUN_DIR/case-tmp
: > "$PRE15_STDOUT_PATH"
: > "$PRE15_STDERR_PATH"
: > "$PRE15_OWNERSHIP_FILE"
: > "$PRE15_CLEANUP_LOG"
: > "$PRE15_FIXTURE_HASHES"
: > "$PRE15_MODULE_HASH"
mkdir "$PRE15_CASE_TMP"
pre15_own_path "$PRE15_CASE_TMP" 'case temporary directory'
export PRE15_RUN_ID PRE15_RUN_DIR PRE15_STDOUT_PATH PRE15_STDERR_PATH
export PRE15_TARGET_MARKER_FILE PRE15_REASON_FILE PRE15_OWNERSHIP_FILE
export PRE15_CLEANUP_LOG PRE15_FIXTURE_HASHES PRE15_MODULE_HASH
export PRE15_ARGV_PATH PRE15_IDENTITY_PATH PRE15_MANIFEST_PATH PRE15_CASE_TMP
}
pre15_freeze_run()
{
find "$PRE15_RUN_DIR" -type f -exec chmod 0444 {} +
find "$PRE15_RUN_DIR" -type d -exec chmod 0555 {} +
}
pre15_parent_signal()
{
pre15_cleanup_owned
exit "$PRE15_RC_RUNNER_FAIL"
}
pre15_run_command()
{
pre15_require_paths
PRE15_MODE=$1
PRE15_CASE_ID=$2
PRE15_DEADLINE=$3
shift 3
case "$PRE15_CASE_ID" in
''|*[!A-Za-z0-9._-]*) pre15_fail_usage "invalid case ID: $PRE15_CASE_ID" ;;
esac
case "$PRE15_DEADLINE" in
''|*[!0-9]*|0) pre15_fail_usage "invalid deadline: $PRE15_DEADLINE" ;;
esac
command -v timeout >/dev/null 2>&1 || pre15_fail_usage 'timeout is required'
command -v python3 >/dev/null 2>&1 || pre15_fail_usage 'python3 is required'
test -f "$PRE15_SCHEMA" || pre15_fail_usage "schema is absent: $PRE15_SCHEMA"
pre15_new_run
pre15_write_identity
pre15_write_argv_json "$PRE15_ARGV_PATH" "$@"
PRE15_START_UTC=$(pre15_utc_now)
export PRE15_MODE PRE15_CASE_ID PRE15_DEADLINE PRE15_START_UTC
trap pre15_parent_signal HUP INT TERM
if timeout -k "${PRE15_TIMEOUT_KILL_AFTER:-10}" "$PRE15_DEADLINE" "$@" \
> "$PRE15_STDOUT_PATH" 2> "$PRE15_STDERR_PATH"; then
PRE15_EXIT_CODE=0
else
PRE15_EXIT_CODE=$?
fi
trap - HUP INT TERM
if test -f "$PRE15_TARGET_MARKER_FILE" && \
test "$(cat "$PRE15_TARGET_MARKER_FILE")" = reached; then
PRE15_TARGET_STATE=reached
else
PRE15_TARGET_STATE=not_reached
fi
pre15_cleanup_owned
PRE15_CLEANUP_STATUS=$PRE15_CLEANUP_STATUS_RESULT
pre15_classify "$PRE15_EXIT_CODE" "$PRE15_TARGET_STATE" "$PRE15_CLEANUP_STATUS"
PRE15_STATUS=$PRE15_STATUS_RESULT
PRE15_FAILURE_ORIGIN=$PRE15_ORIGIN_RESULT
PRE15_REASON=$PRE15_REASON_RESULT
PRE15_TIMED_OUT=$PRE15_TIMED_OUT_RESULT
PRE15_END_UTC=$(pre15_utc_now)
export PRE15_EXIT_CODE PRE15_TARGET_STATE PRE15_CLEANUP_STATUS PRE15_STATUS
export PRE15_FAILURE_ORIGIN PRE15_REASON PRE15_TIMED_OUT PRE15_END_UTC
pre15_write_manifest
pre15_validate_manifest "$PRE15_MANIFEST_PATH" "$PRE15_SCHEMA"
printf 'PRE15_RESULT status=%s cleanup=%s run_id=%s evidence=%s\n' \
"$PRE15_STATUS" "$PRE15_CLEANUP_STATUS" "$PRE15_RUN_ID" \
"$PRE15_MANIFEST_PATH"
pre15_freeze_run
case "$PRE15_STATUS" in
PASS) return 0 ;;
DUT_FAIL) return "$PRE15_RC_DUT_FAIL" ;;
INFRA_BLOCKED) return "$PRE15_RC_INFRA_BLOCKED" ;;
STOP) return "$PRE15_RC_STOP" ;;
*) return "$PRE15_RC_RUNNER_FAIL" ;;
esac
}
pre15_control_run()
{
pre15_control_dir=$1
pre15_control_deadline=$2
shift 2
(
PRE15_RUN_DIR=$pre15_control_dir
PRE15_TARGET_MARKER_FILE=$pre15_control_dir/target.marker
PRE15_REASON_FILE=$pre15_control_dir/reason.txt
PRE15_OWNERSHIP_FILE=$pre15_control_dir/ownership.tsv
PRE15_CLEANUP_LOG=$pre15_control_dir/cleanup.log
PRE15_DEADLINE=$pre15_control_deadline
export PRE15_RUN_DIR PRE15_TARGET_MARKER_FILE PRE15_REASON_FILE
export PRE15_OWNERSHIP_FILE PRE15_CLEANUP_LOG PRE15_DEADLINE
mkdir "$pre15_control_dir"
: > "$PRE15_OWNERSHIP_FILE"
: > "$PRE15_CLEANUP_LOG"
if timeout -k 2 "$pre15_control_deadline" "$@" \
> "$pre15_control_dir/stdout.log" \
2> "$pre15_control_dir/stderr.log"; then
pre15_control_rc=0
else
pre15_control_rc=$?
fi
if test -f "$PRE15_TARGET_MARKER_FILE" && \
test "$(cat "$PRE15_TARGET_MARKER_FILE")" = reached; then
pre15_control_target=reached
else
pre15_control_target=not_reached
fi
pre15_cleanup_owned
pre15_classify "$pre15_control_rc" "$pre15_control_target" \
"$PRE15_CLEANUP_STATUS_RESULT"
printf '%s\t%s\t%s\t%s\n' "$PRE15_STATUS_RESULT" \
"$PRE15_ORIGIN_RESULT" "$PRE15_CLEANUP_STATUS_RESULT" \
"$PRE15_TIMED_OUT_RESULT" > "$pre15_control_dir/result.tsv"
)
}
pre15_find_case()
{
pre15_case_id=$1
case "$pre15_case_id" in
''|*[!A-Za-z0-9._-]*) pre15_fail_usage "invalid case ID: $pre15_case_id" ;;
esac
pre15_case_path=$PRE15_DUT/tests/pre15/cases/$pre15_case_id.sh
test -f "$pre15_case_path" || pre15_fail_usage "unknown case: $pre15_case_id"
printf '%s\n' "$pre15_case_path"
}