You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

340 lines
9.3 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/usr/bin/env bash
# 批量回归测试脚本:对 test/test_case 下全部 .sy 用例执行 IR 语义验证。
# 用法:./scripts/run_all_tests.sh [--ir | --asm | --both]
#
# 默认只测 IR通过 llc + clang 编译运行)。
# --asm 只测汇编(需要 aarch64-linux-gnu-gcc + qemu-aarch64
# --both 同时测 IR 和汇编。
set -uo pipefail
now_ns() {
date +%s%N
}
format_ns() {
local ns=$1
local ms=$((ns / 1000000))
local sec=$((ms / 1000))
local rem_ms=$((ms % 1000))
printf '%d.%03ds' "$sec" "$rem_ms"
}
mode="ir"
if [[ "${1:-}" == "--asm" ]]; then
mode="asm"
elif [[ "${1:-}" == "--both" ]]; then
mode="both"
fi
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
cd "$ROOT_DIR"
compiler="./build/bin/compiler"
if [[ ! -x "$compiler" ]]; then
echo "❌ 未找到编译器: $compiler" >&2
echo "请先构建cmake -S . -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build -j \$(nproc)" >&2
exit 1
fi
find_tool() {
local name
for name in "$@"; do
if command -v "$name" >/dev/null 2>&1; then
command -v "$name"
return 0
fi
done
return 1
}
LLC_CMD=$(find_tool llc llc-20 || true)
CLANG_CMD=$(find_tool clang clang-20 || true)
total=0
passed=0
failed=0
skipped=0
fail_list=()
RUN_LAST_TOTAL_NS=0
RUN_LAST_BREAKDOWN=""
batch_start_ns=$(now_ns)
run_ir_test() {
local sy="$1"
local dir
dir=$(dirname "$sy")
local stem
stem=$(basename "$sy" .sy)
local out_dir="test/test_result/ir_batch"
mkdir -p "$out_dir"
local out_file="$out_dir/$stem.ll"
local stdin_file="$dir/$stem.in"
local expected_file="$dir/$stem.out"
local stdout_file="$out_dir/$stem.stdout"
local actual_file="$out_dir/$stem.actual.out"
local start_ns
start_ns=$(now_ns)
# 生成 IR
local emit_start_ns
emit_start_ns=$(now_ns)
if ! timeout 30 "$compiler" --emit-ir "$sy" > "$out_file" 2>/dev/null; then
RUN_LAST_TOTAL_NS=$(( $(now_ns) - start_ns ))
RUN_LAST_BREAKDOWN="emit=$(format_ns $((RUN_LAST_TOTAL_NS)))"
echo " [SKIP-IR] $sy (编译器报错或超时)"
return 2
fi
local emit_ns=$(( $(now_ns) - emit_start_ns ))
# 需要 llc + clang
if [[ -z "$LLC_CMD" || -z "$CLANG_CMD" ]]; then
RUN_LAST_TOTAL_NS=$(( $(now_ns) - start_ns ))
RUN_LAST_BREAKDOWN="emit=$(format_ns "$emit_ns")"
echo " [SKIP-IR] $sy (缺少 llc/llc-20 或 clang/clang-20)"
return 2
fi
local obj="$out_dir/$stem.o"
local exe="$out_dir/$stem"
local lower_link_start_ns
lower_link_start_ns=$(now_ns)
if ! "$LLC_CMD" -filetype=obj "$out_file" -o "$obj" 2>/dev/null; then
RUN_LAST_TOTAL_NS=$(( $(now_ns) - start_ns ))
RUN_LAST_BREAKDOWN="emit=$(format_ns "$emit_ns")"
echo " [SKIP-IR] $sy ($LLC_CMD 编译失败)"
return 2
fi
if ! "$CLANG_CMD" -no-pie "$obj" sylib/sylib.c -o "$exe" -lm -pthread 2>/dev/null; then
RUN_LAST_TOTAL_NS=$(( $(now_ns) - start_ns ))
RUN_LAST_BREAKDOWN="emit=$(format_ns "$emit_ns") lower+link=$(format_ns $(( $(now_ns) - lower_link_start_ns )))"
echo " [SKIP-IR] $sy ($CLANG_CMD 链接失败)"
return 2
fi
local lower_link_ns=$(( $(now_ns) - lower_link_start_ns ))
set +e
# performance 用例给更长的超时时间
local run_timeout=3000
if [[ "$sy" == *"performance"* ]]; then
run_timeout=3000
fi
local run_start_ns
run_start_ns=$(now_ns)
if [[ -f "$stdin_file" ]]; then
timeout $run_timeout "$exe" < "$stdin_file" > "$stdout_file" 2>/dev/null
else
timeout $run_timeout "$exe" > "$stdout_file" 2>/dev/null
fi
local status=$?
set +e
local run_ns=$(( $(now_ns) - run_start_ns ))
RUN_LAST_TOTAL_NS=$(( $(now_ns) - start_ns ))
RUN_LAST_BREAKDOWN="emit=$(format_ns "$emit_ns") lower+link=$(format_ns "$lower_link_ns") run=$(format_ns "$run_ns")"
# timeout 返回 124 表示超时,标记为 SKIP
if [[ $status -eq 124 ]]; then
echo " [SKIP-IR] $sy (运行超时)"
return 2
fi
# 组装实际输出
{
cat "$stdout_file"
if [[ -s "$stdout_file" ]] && (( $(tail -c 1 "$stdout_file" | wc -l) == 0 )); then
printf '\n'
fi
printf '%s\n' "$status"
} > "$actual_file"
if [[ ! -f "$expected_file" ]]; then
echo " [SKIP-IR] $sy (无预期输出)"
return 2
fi
if diff -q <(sed -e 's/\r$//' -e '$a\\' "$expected_file") \
<(sed -e 's/\r$//' -e '$a\\' "$actual_file") >/dev/null 2>&1; then
return 0
else
return 1
fi
}
run_asm_test() {
local sy="$1"
local dir
dir=$(dirname "$sy")
local stem
stem=$(basename "$sy" .sy)
local out_dir="test/test_result/asm_batch"
mkdir -p "$out_dir"
local asm_file="$out_dir/$stem.s"
local stdin_file="$dir/$stem.in"
local expected_file="$dir/$stem.out"
local stdout_file="$out_dir/$stem.stdout"
local actual_file="$out_dir/$stem.actual.out"
local exe="$out_dir/$stem"
local start_ns
start_ns=$(now_ns)
# 生成汇编
local emit_start_ns
emit_start_ns=$(now_ns)
if ! timeout 30 "$compiler" --emit-asm "$sy" > "$asm_file" 2>/dev/null; then
RUN_LAST_TOTAL_NS=$(( $(now_ns) - start_ns ))
RUN_LAST_BREAKDOWN="emit=$(format_ns "$RUN_LAST_TOTAL_NS")"
echo " [SKIP-ASM] $sy (编译器报错或超时)"
return 2
fi
local emit_ns=$(( $(now_ns) - emit_start_ns ))
if ! command -v aarch64-linux-gnu-gcc >/dev/null 2>&1; then
RUN_LAST_TOTAL_NS=$(( $(now_ns) - start_ns ))
RUN_LAST_BREAKDOWN="emit=$(format_ns "$emit_ns")"
echo " [SKIP-ASM] $sy (缺少 aarch64-linux-gnu-gcc)"
return 2
fi
local link_start_ns
link_start_ns=$(now_ns)
if ! timeout 30 aarch64-linux-gnu-gcc "$asm_file" sylib/sylib.c -o "$exe" -static -pthread 2>/dev/null; then
RUN_LAST_TOTAL_NS=$(( $(now_ns) - start_ns ))
RUN_LAST_BREAKDOWN="emit=$(format_ns "$emit_ns") asm+link=$(format_ns $(( $(now_ns) - link_start_ns )))"
echo " [SKIP-ASM] $sy (汇编/链接失败)"
return 2
fi
local link_ns=$(( $(now_ns) - link_start_ns ))
if ! command -v qemu-aarch64 >/dev/null 2>&1; then
RUN_LAST_TOTAL_NS=$(( $(now_ns) - start_ns ))
RUN_LAST_BREAKDOWN="emit=$(format_ns "$emit_ns") asm+link=$(format_ns "$link_ns")"
echo " [SKIP-ASM] $sy (缺少 qemu-aarch64)"
return 2
fi
set +e
# performance 用例给更长的超时时间
local run_timeout=30
if [[ "$sy" == *"performance"* ]]; then
run_timeout=1000
fi
local run_start_ns
run_start_ns=$(now_ns)
if [[ -f "$stdin_file" ]]; then
timeout $run_timeout qemu-aarch64 -L /usr/aarch64-linux-gnu "$exe" < "$stdin_file" > "$stdout_file" 2>/dev/null
else
timeout $run_timeout qemu-aarch64 -L /usr/aarch64-linux-gnu "$exe" > "$stdout_file" 2>/dev/null
fi
local status=$?
set +e
local run_ns=$(( $(now_ns) - run_start_ns ))
RUN_LAST_TOTAL_NS=$(( $(now_ns) - start_ns ))
RUN_LAST_BREAKDOWN="emit=$(format_ns "$emit_ns") asm+link=$(format_ns "$link_ns") run=$(format_ns "$run_ns")"
# timeout 返回 124 表示超时,标记为 SKIP
if [[ $status -eq 124 ]]; then
echo " [SKIP-ASM] $sy (运行超时)"
return 2
fi
{
cat "$stdout_file"
if [[ -s "$stdout_file" ]] && (( $(tail -c 1 "$stdout_file" | wc -l) == 0 )); then
printf '\n'
fi
printf '%s\n' "$status"
} > "$actual_file"
if [[ ! -f "$expected_file" ]]; then
echo " [SKIP-ASM] $sy (无预期输出)"
return 2
fi
if diff -q <(sed -e 's/\r$//' -e '$a\\' "$expected_file") \
<(sed -e 's/\r$//' -e '$a\\' "$actual_file") >/dev/null 2>&1; then
return 0
else
return 1
fi
}
echo "========================================"
echo " Lab4 批量回归测试 (mode: $mode)"
echo "========================================"
echo " LLVM tools: $LLC_CMD / $CLANG_CMD"
echo ""
# 收集所有测试文件
mapfile -t test_files < <(find test/test_case -name '*.sy' | sort)
for sy in "${test_files[@]}"; do
total=$((total + 1))
if [[ "$mode" == "ir" || "$mode" == "both" ]]; then
run_ir_test "$sy"
rc=$?
if [[ $rc -eq 0 ]]; then
echo " [PASS-IR] $sy ($(format_ns "$RUN_LAST_TOTAL_NS"); $RUN_LAST_BREAKDOWN)"
passed=$((passed + 1))
elif [[ $rc -eq 1 ]]; then
echo " [FAIL-IR] $sy ($(format_ns "$RUN_LAST_TOTAL_NS"); $RUN_LAST_BREAKDOWN)"
failed=$((failed + 1))
fail_list+=("$sy (IR)")
else
skipped=$((skipped + 1))
fi
fi
if [[ "$mode" == "asm" || "$mode" == "both" ]]; then
run_asm_test "$sy"
rc=$?
if [[ $rc -eq 0 ]]; then
echo " [PASS-ASM] $sy ($(format_ns "$RUN_LAST_TOTAL_NS"); $RUN_LAST_BREAKDOWN)"
if [[ "$mode" == "asm" ]]; then
passed=$((passed + 1))
fi
elif [[ $rc -eq 1 ]]; then
echo " [FAIL-ASM] $sy ($(format_ns "$RUN_LAST_TOTAL_NS"); $RUN_LAST_BREAKDOWN)"
if [[ "$mode" == "asm" ]]; then
failed=$((failed + 1))
fi
fail_list+=("$sy (ASM)")
else
if [[ "$mode" == "asm" ]]; then
skipped=$((skipped + 1))
fi
fi
fi
done
echo ""
echo "========================================"
echo " 测试结果汇总"
echo "========================================"
echo " 总计: $total"
echo " 通过: $passed"
echo " 失败: $failed"
echo " 跳过: $skipped"
echo " 总耗时: $(format_ns $(( $(now_ns) - batch_start_ns )))"
echo ""
if [[ ${#fail_list[@]} -gt 0 ]]; then
echo " 失败用例:"
for f in "${fail_list[@]}"; do
echo " - $f"
done
echo ""
fi
if [[ $failed -gt 0 ]]; then
echo "❌ 存在失败用例"
exit 1
else
echo "✅ 全部通过(跳过 $skipped 个)"
exit 0
fi