|
|
#!/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
|