blob: e83bf5f92da1b7d5d216f6e0c5d346223bb12da5 [file] [edit]
set -eo pipefail
ulimit -S -s ${TEST_STACK_SIZE:-8192}
DIFF="diff -au --strip-trailing-cr --color=always"
# Temporary directory to store unnamed output files
TMP_DIR="$(mktemp --tmpdir --directory lean4-test-suite.XXXXXXXXXX)"
trap 'rm -rf "$TMP_DIR"' EXIT
function fail {
echo "TEST FAILED: $1"
exit 1
}
function check_exit_is_success {
if [[ $EXIT != 0 ]]; then
fail "Expected command to succeed, got exit code $EXIT"
fi
}
function check_exit_is_fail {
if [[ $EXIT == 0 ]]; then
fail "Expected command to fail, got exit code $EXIT"
fi
}
function check_exit_is {
if [[ $1 == "nonzero" ]]; then
check_exit_is_fail
elif [[ $EXIT != $1 ]]; then
fail "Expected exit code $1, got exit code $EXIT"
fi
}
function check_out_contains {
if ! grep -Fq "$1" "$CAPTURED.out.produced"; then
fail "Expected output to contain '$1'"
fi
}
function check_out_matches {
if ! grep -Eq "$1" "$CAPTURED.out.produced"; then
fail "Expected output to match regex '$1'"
fi
}
function check_out_file {
if [[ -f "$CAPTURED.out.ignored" ]]; then
echo "Output ignored, skipping check"
elif [[ -f "$CAPTURED.out.expected" ]]; then
$DIFF -- "$CAPTURED.out.expected" "$CAPTURED.out.produced" || fail "Unexpected output"
else
echo -n | $DIFF -- - "$CAPTURED.out.produced" || fail "Unexpected output"
fi
}
# Run a command.
# Sets $EXIT to the exit code.
function run_only {
EXIT=0; (set -x; "${@}") || EXIT="$?"
}
# Run a command, failing if the command fails.
# Sets $EXIT to the exit code.
function run {
run_only "${@}"
check_exit_is_success
}
# Run a command, failing if the command succeeds.
# Sets $EXIT to the exit code.
function run_fail {
run_only "${@}"
check_exit_is_fail
}
# Run a command, failing if the exit code does not match the expected value.
# Sets $EXIT to the exit code.
function run_exit {
EXPECTED="$1"; shift
run_only "${@}"
check_exit_is "$EXPECTED"
}
# Run a command, capturing its output.
# The first argument specifies the output file path.
# If it is empty, a temporary file is used.
# Sets $EXIT to the exit code and $CAPTURED to the output file path.
function capture_only {
# No need to mktemp the file to prevent collisions during parallel test runs
# since $TMP_DIR is already specific to this test run.
CAPTURED="${1:-"$TMP_DIR/tmp"}"; shift
EXIT=0; (set -x; "${@}" > "$CAPTURED.out.produced" 2>&1) || EXIT="$?"
cat "$CAPTURED.out.produced"
}
# Run a command, capturing its output and failing if the command fails.
# Sets $EXIT to the exit code and $CAPTURED to the output file path.
function capture {
capture_only "" "${@}"
check_exit_is_success
}
# Run a command, capturing its output and failing if the command succeeds.
# Sets $EXIT to the exit code and $CAPTURED to the output file path.
function capture_fail {
capture_only "" "${@}"
check_exit_is_fail
}
# Run a command, capturing its output and failing if the command does not match the expected exit code.
# Sets $EXIT to the exit code and $CAPTURED to the output file path.
function capture_exit {
EXPECTED="$1"; shift
capture_only "" "${@}"
check_exit_is "$EXPECTED"
}
# Run a command wrapped with `capture_only` and `measure.py`.
# Additionally wrap with `repeatedly.py` if `TEST_REPEAT` is set,
# respecting `TEST_REPEAT_DROP_HIGHEST` and `TEST_REPEAT_DROP_LOWEST`.
# The first argument specifies the output file path.
# The second argument specifies the topic for the measurements.
# The remaining arguments are the command to run.
# Sets $EXIT to the exit code and $CAPTURED to the output file path.
function capture_and_measure {
local file="$1" topic="$2"; shift 2
if [[ -n "${TEST_REPEAT:-}" ]]; then
capture_only "$file" \
"$TEST_DIR/repeatedly.py" -n "$TEST_REPEAT" \
-H "${TEST_REPEAT_DROP_HIGHEST:-0}" -L "${TEST_REPEAT_DROP_LOWEST:-0}" \
-o "$file.measurements.jsonl" -- \
"$TEST_DIR/measure.py" -t "$topic" -o "$file.measurements.jsonl" -d -- "$@"
else
capture_only "$file" \
"$TEST_DIR/measure.py" -t "$topic" -o "$file.measurements.jsonl" -a -d -- "$@"
fi
}
function source_init {
if [[ -f "$1.init.sh" ]]; then
source "$1.init.sh"
fi
}
# Append `--incr-load=...` to TEST_LEAN_ARGS if `$1`'s parsed header matches
# one of the prebuilt snapshots exactly. The Lean snapshot is built from the
# bytes `import Lean\n`; the Init snapshot from empty input. The file must
# match byte-for-byte at the header level; if it doesn't, the loader takes
# the slow path which costs the snapshot env *plus* a fresh import.
function maybe_use_lean_header_snapshot {
[[ "${LEAN_HEADER_SNAPSHOTS:-}" == 0 ]] && return
[[ -z "${LEAN_HEADER_SNAPSHOTS:-}" && -z "${TEST_CTEST:-}" ]] && return
# File kinds that are not supported yet; we should add `module` snapshots when
# we have ported a significant number of tests
grep -qE '^(module|prelude)$' "$1" && return
local snap=
if awk 'NR==1 { if ($0 != "import Lean") exit 1; n=1; next }
/^import / { exit 1 }
END { if (n != 1) exit 1 }' "$1"; then
snap=${LEAN_HEADER_SNAPSHOT_LEAN:-}
elif ! grep -qE '^import ' "$1"; then
snap=${LEAN_HEADER_SNAPSHOT_INIT:-}
fi
if [[ -n "$snap" ]]; then
TEST_LEAN_ARGS+=("--incr-load=$snap")
fi
}
function run_before {
if [[ -f "$1.before.sh" ]]; then
bash -- "$1.before.sh" || fail "$1.before.sh failed"
fi
}
function run_after {
if [[ -f "$1.after.sh" ]]; then
bash -- "$1.after.sh" || fail "$1.after.sh failed"
fi
}
# mvar suffixes like in `?m.123` are deterministic but prone to change on minor changes, so strip them
function normalize_mvar_suffixes {
# Sed on macOS does not support \w.
perl -p -i -e 's/(\?(\w|_\w+))\.[0-9]+/\1/g' "$CAPTURED.out.produced"
}
# similarly, links to the language reference may have URL components depending on the toolchain, so normalize those
function normalize_reference_urls {
perl -p -i -e 's#https://lean-lang\.org/doc/reference/(v?[0-9.]+(-rc[0-9]+)?|latest)#REFERENCE#g' "$CAPTURED.out.produced"
}
function normalize_measurements {
# Sed on macOS does not support \S.
perl -p -i -e 's/^measurement: (\S+) \S+( \S+)?$/measurement: \1 .../' "$CAPTURED.out.produced"
}
function extract_measurements {
set +o pipefail # grep will exit with 1 if there are no matches
grep -E '^measurement: \S+ \S+( \S+)?$' "$CAPTURED.out.produced" \
| jq -R --arg topic "$1" 'split(" ") | { metric: "\($topic)//\(.[1])", value: .[2]|tonumber, unit: .[3] }' -c \
>> "$CAPTURED.measurements.jsonl"
set -o pipefail
normalize_measurements "$CAPTURED"
}
function set_stack_size_to_maximum {
# On macOS, `ulimit -s unlimited` fails with `Operation not permitted` because
# the hard limit is a certain number, not `unlimited` like on Linux.
echo "Setting stack size to $(ulimit -H -s)"
ulimit -s "$(ulimit -H -s)"
}