#!/usr/bin/env bash set -uo pipefail repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) cd "$repo_root" cases_dir="test/test_case" result_root="test/test_result/run_tests" asm_dir="$result_root/asm" compiler="./build/bin/compiler" cross_gcc="aarch64-linux-gnu-gcc" qemu_bin="qemu-aarch64" qemu_sysroot="/usr/aarch64-linux-gnu" usage() { cat <<'EOF' 用法: ./test/run_tests.sh [case ...] 参数: case 可选。支持以下两种写法: - test/test_case/foo.sy - foo (自动解析为 test/test_case/foo.sy) 行为: - 批量编译 test/test_case/*.sy 为 AArch64 汇编并链接可执行文件 - 若存在同名 .in,则将其作为标准输入喂给程序 - 采集程序标准输出,并将退出码追加为最后一行 - 与同名 .out 做精确 diff,比对结果写入 test/test_result/run_tests/ EOF } require_tool() { local tool=$1 if ! command -v "$tool" >/dev/null 2>&1; then echo "未找到依赖工具: $tool" >&2 exit 1 fi } resolve_case() { local arg=$1 if [[ -f "$arg" ]]; then printf '%s\n' "$arg" return 0 fi if [[ -f "$cases_dir/$arg.sy" ]]; then printf '%s\n' "$cases_dir/$arg.sy" return 0 fi return 1 } append_exit_code() { local stdout_file=$1 local actual_file=$2 local status=$3 : > "$actual_file" if [[ -f "$stdout_file" ]]; then cat "$stdout_file" >> "$actual_file" if [[ -s "$stdout_file" ]]; then local last_byte last_byte=$(tail -c 1 "$stdout_file" | od -An -t x1 | tr -d ' \n') if [[ "$last_byte" != "0a" ]]; then printf '\n' >> "$actual_file" fi fi fi printf '%s\n' "$status" >> "$actual_file" } run_case() { local input_sy=$1 local base stem expected input_txt case_dir asm_file exe_file local compile_log link_log stderr_log stdout_log actual_out diff_log local status base=$(basename "$input_sy") stem=${base%.sy} expected="${input_sy%.sy}.out" input_txt="${input_sy%.sy}.in" case_dir="$result_root/$stem" asm_file="$asm_dir/$stem.s" exe_file="$asm_dir/$stem" compile_log="$case_dir/compiler.log" link_log="$case_dir/link.log" stderr_log="$case_dir/stderr.log" stdout_log="$case_dir/stdout.log" actual_out="$case_dir/actual.out" diff_log="$case_dir/diff.log" rm -rf "$case_dir" mkdir -p "$case_dir" rm -f "$asm_file" "$exe_file" if [[ ! -f "$expected" ]]; then echo "[FAIL] $stem: 缺少预期输出文件 $expected" ((missing_expected_count += 1)) failed_cases+=("$stem") return fi if ! "$compiler" --emit-asm "$input_sy" >"$asm_file" 2>"$compile_log"; then echo "[FAIL] $stem: 编译失败,详见 $compile_log" ((compile_fail_count += 1)) failed_cases+=("$stem") return fi if ! "$cross_gcc" "$asm_file" -o "$exe_file" >"$link_log" 2>&1; then echo "[FAIL] $stem: 链接失败,详见 $link_log" ((link_fail_count += 1)) failed_cases+=("$stem") return fi : > "$stdout_log" : > "$stderr_log" if [[ -f "$input_txt" ]]; then "$qemu_bin" -L "$qemu_sysroot" "$exe_file" <"$input_txt" >"$stdout_log" 2>"$stderr_log" status=$? else "$qemu_bin" -L "$qemu_sysroot" "$exe_file" >"$stdout_log" 2>"$stderr_log" status=$? fi append_exit_code "$stdout_log" "$actual_out" "$status" if diff -u "$expected" "$actual_out" >"$diff_log"; then rm -f "$diff_log" echo "[PASS] $stem" ((pass_count += 1)) else echo "[FAIL] $stem: 输出不匹配,详见 $diff_log" ((diff_fail_count += 1)) failed_cases+=("$stem") fi } if [[ ${1:-} == "-h" || ${1:-} == "--help" ]]; then usage exit 0 fi if [[ ! -x "$compiler" ]]; then echo "未找到编译器: $compiler ,请先构建。" >&2 exit 1 fi require_tool "$cross_gcc" require_tool "$qemu_bin" mkdir -p "$asm_dir" declare -a cases=() declare -a failed_cases=() if [[ $# -gt 0 ]]; then for arg in "$@"; do if ! resolved=$(resolve_case "$arg"); then echo "未找到测试用例: $arg" >&2 exit 1 fi cases+=("$resolved") done else while IFS= read -r -d '' file; do cases+=("$file") done < <(find "$cases_dir" -maxdepth 1 -type f -name '*.sy' -print0 | sort -z) fi if [[ ${#cases[@]} -eq 0 ]]; then echo "未找到任何 .sy 测试用例。" >&2 exit 1 fi pass_count=0 compile_fail_count=0 link_fail_count=0 diff_fail_count=0 missing_expected_count=0 for case_path in "${cases[@]}"; do run_case "$case_path" done total_count=${#cases[@]} fail_count=$((compile_fail_count + link_fail_count + diff_fail_count + missing_expected_count)) echo echo "测试完成: $total_count 个用例" echo "通过: $pass_count" echo "失败: $fail_count" echo " 编译失败: $compile_fail_count" echo " 链接失败: $link_fail_count" echo " 输出不匹配: $diff_fail_count" echo " 缺少预期输出: $missing_expected_count" if [[ ${#failed_cases[@]} -gt 0 ]]; then echo "失败用例: ${failed_cases[*]}" exit 1 fi exit 0