|
|
#!/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
|
|
|
|
|
|
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
|
|
|
|
|
|
total=0
|
|
|
passed=0
|
|
|
failed=0
|
|
|
skipped=0
|
|
|
fail_list=()
|
|
|
|
|
|
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"
|
|
|
|
|
|
# 生成 IR
|
|
|
if ! timeout 30 "$compiler" --emit-ir "$sy" > "$out_file" 2>/dev/null; then
|
|
|
echo " [SKIP-IR] $sy (编译器报错或超时)"
|
|
|
return 2
|
|
|
fi
|
|
|
|
|
|
# 需要 llc + clang
|
|
|
if ! command -v llc >/dev/null 2>&1 || ! command -v clang >/dev/null 2>&1; then
|
|
|
echo " [SKIP-IR] $sy (缺少 llc/clang)"
|
|
|
return 2
|
|
|
fi
|
|
|
|
|
|
local obj="$out_dir/$stem.o"
|
|
|
local exe="$out_dir/$stem"
|
|
|
|
|
|
if ! llc -filetype=obj "$out_file" -o "$obj" 2>/dev/null; then
|
|
|
echo " [SKIP-IR] $sy (llc 编译失败)"
|
|
|
return 2
|
|
|
fi
|
|
|
if ! clang -no-pie "$obj" sylib/sylib.c -o "$exe" -lm 2>/dev/null; then
|
|
|
echo " [SKIP-IR] $sy (clang 链接失败)"
|
|
|
return 2
|
|
|
fi
|
|
|
|
|
|
set +e
|
|
|
# performance 用例给更长的超时时间
|
|
|
local run_timeout=30
|
|
|
if [[ "$sy" == *"performance"* ]]; then
|
|
|
run_timeout=300
|
|
|
fi
|
|
|
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
|
|
|
|
|
|
# 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"
|
|
|
|
|
|
# 生成汇编
|
|
|
if ! timeout 30 "$compiler" --emit-asm "$sy" > "$asm_file" 2>/dev/null; then
|
|
|
echo " [SKIP-ASM] $sy (编译器报错或超时)"
|
|
|
return 2
|
|
|
fi
|
|
|
|
|
|
if ! command -v aarch64-linux-gnu-gcc >/dev/null 2>&1; then
|
|
|
echo " [SKIP-ASM] $sy (缺少 aarch64-linux-gnu-gcc)"
|
|
|
return 2
|
|
|
fi
|
|
|
|
|
|
if ! timeout 30 aarch64-linux-gnu-gcc "$asm_file" sylib/sylib.c -o "$exe" -static 2>/dev/null; then
|
|
|
echo " [SKIP-ASM] $sy (汇编/链接失败)"
|
|
|
return 2
|
|
|
fi
|
|
|
|
|
|
if ! command -v qemu-aarch64 >/dev/null 2>&1; then
|
|
|
echo " [SKIP-ASM] $sy (缺少 qemu-aarch64)"
|
|
|
return 2
|
|
|
fi
|
|
|
|
|
|
set +e
|
|
|
# performance 用例给更长的超时时间
|
|
|
local run_timeout=30
|
|
|
if [[ "$sy" == *"performance"* ]]; then
|
|
|
run_timeout=300
|
|
|
fi
|
|
|
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
|
|
|
|
|
|
# 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 ""
|
|
|
|
|
|
# 收集所有测试文件
|
|
|
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"
|
|
|
passed=$((passed + 1))
|
|
|
elif [[ $rc -eq 1 ]]; then
|
|
|
echo " [FAIL-IR] $sy"
|
|
|
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"
|
|
|
if [[ "$mode" == "asm" ]]; then
|
|
|
passed=$((passed + 1))
|
|
|
fi
|
|
|
elif [[ $rc -eq 1 ]]; then
|
|
|
echo " [FAIL-ASM] $sy"
|
|
|
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 ""
|
|
|
|
|
|
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
|