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.
nudt-compiler-cpp/2026test.sh

814 lines
25 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
set -u
set -o pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
TEST_ROOT="./2026test"
OUTPUT_DIR="./2026test_results"
COMPILER="./build/bin/compiler"
# 本地开发:自动使用包装器限制内存,防止编译器 bug 触发 OOM
if [[ -x "./scripts/compiler-wrapper.sh" ]]; then
COMPILER="./scripts/compiler-wrapper.sh"
fi
VERIFY_SCRIPT="./scripts/verify_asm.sh"
TIME_ORI_FILE="$OUTPUT_DIR/time_ori.txt"
TIME_OPT_FILE="$OUTPUT_DIR/time_opt.txt"
MAX_CASES=0
STOP_ON_FIRST_FAILURE=false
START_FROM=1
KEEP_OLD=true
KEEP_ORI=false
OPTIMIZE=true
CATEGORY="all"
SKIP_LIST=""
VERBOSE=false
TIMEOUT_MS=300000
JOBS=$(nproc 2>/dev/null || echo 4)
total_time_sum=0
time_cases_count=0
SUCCESS=0
FAILED=0
SKIPPED=0
RUNTIME_OBJ="./build/test_runtime/sylib.o"
show_help() {
cat << 'EOF'
用法: ./2026test.sh [选项]
说明:
自动化执行 2026test 文件夹中的所有测试用例。
支持功能测试(functional)、隐含功能测试(h_functional)、性能测试(performance)。
使用编译器生成 AArch64 汇编,交叉编译链接后通过 qemu-aarch64 运行验证。
自动记录每个测试集的纯运行时间(qemu执行时间)并生成基线文件。
注意: 计时仅包含程序在qemu中的执行时间不包含编译和汇编链接时间。
选项:
-h, --help 显示此帮助信息
-n, --max N 最多运行 N 个测试用例 (0=不限制,默认: 0)
-s, --start-from N 从第 N 个测试用例开始 (默认: 1)
-x, --stop-on-fail 遇到第一个失败即停止
-k, --keep 保留旧输出目录,不删除
--keep-ori 保留现有 time_ori.txt 基线文件,不从 time_opt.txt 复制替换
-O, --optimize 启用编译器优化 (默认启用)
-O0, --no-optimize 禁用编译器优化
-c, --category CAT 指定测试类别: functional|h_functional|performance|all (默认: all)
--skip N1,N2,... 跳过指定编号的测试用例 (逗号分隔)
-v, --verbose 显示详细输出
-o, --output-dir DIR 指定输出目录 (默认: ./2026test_results)
-t, --timeout MS 单个测试超时时间(毫秒) (默认: 300000)
-j, --jobs N 并行任务数 (默认: nproc, 设为1恢复串行)
示例:
./2026test.sh # 运行所有测试 (默认启用优化)
./2026test.sh -c functional # 仅运行功能测试
./2026test.sh -c performance # 仅运行性能测试
./2026test.sh -n 10 # 只运行前10个测试
./2026test.sh -s 5 # 从第5个测试开始
./2026test.sh --skip 3,7,15 # 跳过第3、7、15个测试
./2026test.sh -c functional -n 5 -v # 功能测试前5个详细模式
./2026test.sh -O0 # 不启用优化
./2026test.sh -x # 失败即停止
./2026test.sh -c functional -s 10 -n 5 # 功能测试从第10个开始运行5个
EOF
}
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
-n|--max)
MAX_CASES="$2"
shift 2
;;
-s|--start-from)
START_FROM="$2"
shift 2
;;
-x|--stop-on-fail)
STOP_ON_FIRST_FAILURE=true
shift
;;
-k|--keep)
KEEP_OLD=true
shift
;;
--keep-ori)
KEEP_ORI=true
shift
;;
-O|--optimize)
OPTIMIZE=true
shift
;;
-O0|--no-optimize)
OPTIMIZE=false
shift
;;
-c|--category)
CATEGORY="$2"
if [[ "$CATEGORY" != "functional" && "$CATEGORY" != "h_functional" && "$CATEGORY" != "performance" && "$CATEGORY" != "all" ]]; then
echo -e "${RED}错误: 类别必须是 functional|h_functional|performance|all${NC}"
exit 1
fi
shift 2
;;
--skip)
SKIP_LIST="$2"
shift 2
;;
-v|--verbose)
VERBOSE=true
shift
;;
-o|--output-dir)
OUTPUT_DIR="$2"
shift 2
;;
-t|--timeout)
TIMEOUT_MS="$2"
shift 2
;;
-j|--jobs)
JOBS="$2"
if ! [[ "$JOBS" =~ ^[0-9]+$ ]] || [[ "$JOBS" -lt 1 ]]; then
echo -e "${RED}错误: --jobs 需要正整数${NC}"
exit 1
fi
shift 2
;;
*)
echo -e "${RED}错误: 未知选项 $1${NC}"
show_help
exit 1
;;
esac
done
}
parse_args "$@"
check_prerequisites() {
local missing=0
if [[ ! -x "$COMPILER" ]]; then
echo -e "${RED}错误: 编译器不可执行: $COMPILER${NC}"
echo -e "${YELLOW}提示: 请先构建项目:${NC}"
echo -e "${YELLOW} cmake -S . -B build -DCMAKE_BUILD_TYPE=Release${NC}"
echo -e "${YELLOW} cmake --build build -j \"\$(nproc)\"${NC}"
missing=1
fi
if [[ ! -d "$TEST_ROOT" ]]; then
echo -e "${RED}错误: 测试目录不存在: $TEST_ROOT${NC}"
missing=1
fi
if ! command -v aarch64-linux-gnu-gcc >/dev/null 2>&1; then
echo -e "${RED}错误: 未找到 aarch64-linux-gnu-gcc无法汇编/链接${NC}"
echo -e "${YELLOW}提示: sudo apt install gcc-aarch64-linux-gnu${NC}"
missing=1
fi
if ! command -v qemu-aarch64 >/dev/null 2>&1; then
echo -e "${RED}错误: 未找到 qemu-aarch64无法运行生成的可执行文件${NC}"
echo -e "${YELLOW}提示: sudo apt install qemu-user${NC}"
missing=1
fi
local runtime_found=false
if [[ -f "./sylib/sylib.c" ]]; then
runtime_found=true
elif [[ -n "${SYSY_RUNTIME:-}" ]] && [[ -f "$SYSY_RUNTIME" ]]; then
runtime_found=true
else
local found
found=$(find . -path './build' -prune -o -path './.git' -prune -o -type f -name 'sylib.c' -print 2>/dev/null | head -n 1)
if [[ -n "$found" ]]; then
runtime_found=true
fi
fi
if [[ "$runtime_found" != "true" ]]; then
echo -e "${RED}错误: 未找到运行时库 sylib.c${NC}"
echo -e "${YELLOW}提示: 可通过环境变量 SYSY_RUNTIME 指定路径${NC}"
missing=1
fi
if [[ $missing -eq 1 ]]; then
exit 1
fi
}
check_prerequisites
if ! [[ "$START_FROM" =~ ^[0-9]+$ ]] || [[ "$START_FROM" -lt 1 ]]; then
echo -e "${RED}错误: --start-from 需要正整数${NC}"
exit 1
fi
if ! [[ "$MAX_CASES" =~ ^[0-9]+$ ]]; then
echo -e "${RED}错误: --max 需要非负整数${NC}"
exit 1
fi
declare -A SKIP_SET
if [[ -n "$SKIP_LIST" ]]; then
IFS=',' read -ra SKIP_ITEMS <<< "$SKIP_LIST"
for item in "${SKIP_ITEMS[@]}"; do
item=$(echo "$item" | xargs)
if [[ "$item" =~ ^[0-9]+$ ]]; then
SKIP_SET[$item]=1
fi
done
fi
if [[ "$KEEP_OLD" != "true" ]]; then
if [[ -f "$TIME_OPT_FILE" ]]; then
cp "$TIME_OPT_FILE" /tmp/_time_opt_backup_2026test.txt
fi
if [[ "$KEEP_ORI" == "true" && -f "$TIME_ORI_FILE" ]]; then
cp "$TIME_ORI_FILE" /tmp/_time_ori_backup_2026test.txt
fi
rm -rf "$OUTPUT_DIR"
fi
mkdir -p "$OUTPUT_DIR"
if [[ "$KEEP_ORI" == "true" ]]; then
if [[ -f /tmp/_time_ori_backup_2026test.txt ]]; then
cp /tmp/_time_ori_backup_2026test.txt "$TIME_ORI_FILE"
rm -f /tmp/_time_ori_backup_2026test.txt
echo -e "${BLUE}保留现有基线文件: time_ori.txt (--keep-ori)${NC}"
elif [[ -f "$TIME_ORI_FILE" ]]; then
echo -e "${BLUE}保留现有基线文件: time_ori.txt (--keep-ori)${NC}"
else
echo -e "${YELLOW}--keep-ori 已指定但未找到现有 time_ori.txt本次测试将无基线对比${NC}"
fi
elif [[ -f /tmp/_time_opt_backup_2026test.txt ]]; then
cp /tmp/_time_opt_backup_2026test.txt "$TIME_ORI_FILE"
rm -f /tmp/_time_opt_backup_2026test.txt
echo -e "${BLUE}已将上次优化时间文件复制为基线: time_opt.txt -> time_ori.txt${NC}"
elif [[ -f "$TIME_OPT_FILE" ]]; then
cp "$TIME_OPT_FILE" "$TIME_ORI_FILE"
echo -e "${BLUE}已将上次优化时间文件复制为基线: time_opt.txt -> time_ori.txt${NC}"
else
echo -e "${YELLOW}未找到上次优化时间文件 time_opt.txt本次测试将无基线对比${NC}"
fi
LOG_FILE="$OUTPUT_DIR/2026test_batch.log"
FAIL_FILE="$OUTPUT_DIR/failed_cases.txt"
ERROR_LOG_FILE="$OUTPUT_DIR/error_log.txt"
TIME_SUMMARY_FILE="$OUTPUT_DIR/time_summary.txt"
DETAIL_TIME_FILE="$OUTPUT_DIR/detail_time.txt"
declare -A ORI_TIME_MAP
if [[ -f "$TIME_ORI_FILE" ]]; then
while IFS='|' read -r entry_name entry_start entry_end entry_time; do
entry_name=$(echo "$entry_name" | xargs)
entry_time=$(echo "$entry_time" | grep -oP '\d+(?=ms)')
if [[ -n "$entry_name" && -n "$entry_time" ]]; then
ORI_TIME_MAP["$entry_name"]="$entry_time"
fi
done < "$TIME_ORI_FILE"
fi
lookup_ori_time() {
local key="$1"
echo "${ORI_TIME_MAP[$key]:-}"
}
format_time_comparison() {
local current_ms="$1"
local ori_ms="$2"
if [[ -z "$ori_ms" ]]; then
echo "${current_ms}ms"
return
fi
local diff=$((current_ms - ori_ms))
local pct=0
if [[ "$ori_ms" -gt 0 ]]; then
pct=$((diff * 100 / ori_ms))
fi
if [[ "$diff" -lt 0 ]]; then
echo "${current_ms}ms (${GREEN}${diff}ms / ${pct}%${NC})"
elif [[ "$diff" -gt 0 ]]; then
echo "${current_ms}ms (${RED}+${diff}ms / +${pct}%${NC})"
else
echo "${current_ms}ms (${BLUE}0ms / 0%${NC})"
fi
}
if [[ "$KEEP_OLD" != "true" ]]; then
: > "$LOG_FILE"
: > "$FAIL_FILE"
: > "$ERROR_LOG_FILE"
: > "$TIME_SUMMARY_FILE"
: > "$DETAIL_TIME_FILE"
fi
{
echo "2026test 批量测试日志 - $(date '+%Y-%m-%d %H:%M:%S')"
echo "TEST_ROOT=$TEST_ROOT"
echo "OUTPUT_DIR=$OUTPUT_DIR"
echo "CATEGORY=$CATEGORY"
echo "OPTIMIZE=$OPTIMIZE"
echo "MAX_CASES=$MAX_CASES"
echo "START_FROM=$START_FROM"
echo "SKIP_LIST=$SKIP_LIST"
echo "================================================"
} >> "$LOG_FILE"
collect_sy_files() {
local dirs=()
if [[ "$CATEGORY" == "all" ]]; then
dirs=("functional" "h_functional" "performance")
else
dirs=("$CATEGORY")
fi
for dir in "${dirs[@]}"; do
local full_dir="$TEST_ROOT/$dir"
if [[ -d "$full_dir" ]]; then
find "$full_dir" -type f -name '*.sy' -print0 | sort -z
fi
done
}
mapfile -d '' -t ALL_CASES < <(collect_sy_files)
TOTAL_FOUND=${#ALL_CASES[@]}
if [[ $TOTAL_FOUND -eq 0 ]]; then
echo -e "${YELLOW}未找到任何 .sy 用例,请检查目录: $TEST_ROOT${NC}"
exit 0
fi
get_timestamp_ms() {
date +%s%3N 2>/dev/null || date +%s000
}
get_category_name() {
local rel_path="$1"
local dir_name
dir_name=$(dirname "$rel_path")
dir_name=$(basename "$dir_name")
echo "$dir_name"
}
find_runtime_src() {
if [[ -n "${SYSY_RUNTIME:-}" ]] && [[ -f "$SYSY_RUNTIME" ]]; then
printf '%s\n' "$SYSY_RUNTIME"
return 0
fi
local candidates=("./sylib/sylib.c" "./sylib.c" "./runtime/sylib.c" "./lib/sylib.c")
for candidate in "${candidates[@]}"; do
if [[ -f "$candidate" ]]; then
printf '%s\n' "$candidate"
return 0
fi
done
local found
found=$(find . -path './build' -prune -o -path './.git' -prune -o -type f -name 'sylib.c' -print 2>/dev/null | head -n 1)
if [[ -n "$found" ]]; then
printf '%s\n' "$found"
return 0
fi
return 1
}
RUNTIME_SRC="$(find_runtime_src || true)"
if [[ -z "$RUNTIME_SRC" ]]; then
echo -e "${RED}错误: 未找到运行时库源码 sylib.c${NC}"
exit 1
fi
runtime_cache_dir="./build/test_runtime"
RUNTIME_OBJ="$runtime_cache_dir/sylib.o"
mkdir -p "$runtime_cache_dir"
if [[ ! -f "$RUNTIME_OBJ" ]] || [[ "$RUNTIME_SRC" -nt "$RUNTIME_OBJ" ]]; then
aarch64-linux-gnu-gcc -O2 -c "$RUNTIME_SRC" -o "$RUNTIME_OBJ"
fi
echo -e "${BLUE}========================================================${NC}"
echo -e "${BLUE} 2026test 批量测试${NC}"
echo -e "${BLUE}========================================================${NC}"
echo -e "${BLUE}测试根目录: $TEST_ROOT${NC}"
echo -e "${BLUE}测试类别: $CATEGORY${NC}"
echo -e "${BLUE}找到用例数: $TOTAL_FOUND${NC}"
echo -e "${BLUE}输出目录: $OUTPUT_DIR${NC}"
echo -e "${BLUE}编译器优化: $OPTIMIZE${NC}"
echo -e "${BLUE}计时方式: 仅qemu运行时间(不含编译/汇编)${NC}"
if [[ "$START_FROM" -gt 1 ]]; then
echo -e "${BLUE}起始用例: $START_FROM${NC}"
fi
if [[ "$MAX_CASES" -gt 0 ]]; then
echo -e "${BLUE}最大用例数: $MAX_CASES${NC}"
fi
if [[ "${#SKIP_SET[@]}" -gt 0 ]] 2>/dev/null; then
echo -e "${BLUE}跳过编号: ${!SKIP_SET[*]}${NC}"
fi
if [[ -f "$TIME_ORI_FILE" ]]; then
echo -e "${BLUE}基线对比: $TIME_ORI_FILE (已加载)${NC}"
if [[ "$KEEP_ORI" == "true" ]]; then
echo -e "${BLUE}基线模式: 保留现有 (--keep-ori)${NC}"
else
echo -e "${BLUE}基线模式: 从time_opt.txt复制替换${NC}"
fi
else
echo -e "${YELLOW}基线对比: 无 (首次运行或无time_opt.txt)${NC}"
fi
echo -e "${BLUE}========================================================${NC}"
echo ""
# ============================================================
# 输出比较:规范化后 diff替代 python3降低进程启动开销
# ============================================================
canon_compare() {
local expected="$1" actual="$2"
diff -q \
<(sed 's/\r$//; s/[[:space:]]*$//' "$expected" \
| awk '{lines[NR]=$0} END{last=NR; while(last>0&&lines[last]=="")last--; for(i=1;i<=last;i++)print lines[i]}') \
<(sed 's/\r$//; s/[[:space:]]*$//' "$actual" \
| awk '{lines[NR]=$0} END{last=NR; while(last>0&&lines[last]=="")last--; for(i=1;i<=last;i++)print lines[i]}') \
> /dev/null 2>&1
}
# ============================================================
# Worker: 处理单个测试用例,结果写入 $OUTPUT_DIR/.results/$idx
# ============================================================
run_one_test() {
local idx="$1" file="$2"
local result_file="$OUTPUT_DIR/.results/$idx"
local rel_path="${file#$TEST_ROOT/}"
local filename="$(basename "$file")"
local base_name="${filename%.sy}"
local rel_dir="$(dirname "$rel_path")"
local input_dir="$TEST_ROOT/$rel_dir"
local category_name
category_name=$(get_category_name "$rel_path")
local case_out_dir="$OUTPUT_DIR/$rel_dir"
mkdir -p "$case_out_dir"
rm -f "$case_out_dir/$base_name.s" "$case_out_dir/$base_name.o" \
"$case_out_dir/$base_name" "$case_out_dir/$base_name.stdout" \
"$case_out_dir/$base_name.actual.out"
local asm_file="$case_out_dir/$base_name.s"
local exe="$case_out_dir/$base_name"
local stdin_file="$input_dir/$base_name.in"
local expected_file="$input_dir/$base_name.out"
local stdout_file="$case_out_dir/$base_name.stdout"
local actual_file="$case_out_dir/$base_name.actual.out"
local baseline_entry="${category_name}/${base_name}"
local status="FAILED"
local error_type=""
local elapsed_ms=0
local start_human=""
local end_human=""
# 编译
local compile_ok=true
set +e
if [[ "$OPTIMIZE" == "true" ]]; then
timeout --signal=KILL "$((TIMEOUT_MS / 1000))" "$COMPILER" -O --emit-asm "$file" > "$asm_file" 2>/dev/null
else
timeout --signal=KILL "$((TIMEOUT_MS / 1000))" "$COMPILER" --emit-asm "$file" > "$asm_file" 2>/dev/null
fi
local compile_code=$?
set -e
if [[ $compile_code -ne 0 ]]; then
compile_ok=false
fi
if $compile_ok; then
set +e
aarch64-linux-gnu-gcc "$asm_file" "$RUNTIME_OBJ" -o "$exe" 2>/dev/null
local link_code=$?
set -e
if [[ $link_code -ne 0 ]]; then
compile_ok=false
fi
fi
if ! $compile_ok; then
printf 'STATUS=%s\nERROR=%s\nELAPSED=%s\nSTART=%s\nEND=%s\nFILE=%s\nBASELINE=%s\n' \
"COMPILE_FAIL" "compile/link" "$elapsed_ms" "$start_human" "$end_human" "$rel_path" "$baseline_entry" > "$result_file"
return 0
fi
# 运行
start_human=$(date '+%Y-%m-%d %H:%M:%S.%3N')
local exec_start_ms
exec_start_ms=$(get_timestamp_ms)
set +e
# qemu 执行加 timeout 防护(默认 60s防止无限循环卡死整个测试
local qemu_timeout=60
if [[ -f "$stdin_file" ]]; then
timeout --signal=KILL "$qemu_timeout" qemu-aarch64 -L /usr/aarch64-linux-gnu -s 104857600 "$exe" < "$stdin_file" > "$stdout_file" 2>/dev/null
else
timeout --signal=KILL "$qemu_timeout" qemu-aarch64 -L /usr/aarch64-linux-gnu -s 104857600 "$exe" < /dev/null > "$stdout_file" 2>/dev/null
fi
local exit_status=$?
set -e
local exec_end_ms
exec_end_ms=$(get_timestamp_ms)
end_human=$(date '+%Y-%m-%d %H:%M:%S.%3N')
elapsed_ms=$((exec_end_ms - exec_start_ms))
# 构造 actual.out
{
cat "$stdout_file"
if [[ -s "$stdout_file" ]] && (( $(tail -c 1 "$stdout_file" | wc -l) == 0 )); then
printf '\n'
fi
printf '%s\n' "$exit_status"
} > "$actual_file"
# 比较输出
local output_ok=true
if [[ -f "$expected_file" ]]; then
if ! canon_compare "$expected_file" "$actual_file"; then
output_ok=false
fi
fi
if $output_ok; then
status="SUCCESS"
else
status="OUTPUT_MISMATCH"
error_type="output"
fi
printf 'STATUS=%s\nERROR=%s\nELAPSED=%s\nSTART=%s\nEND=%s\nFILE=%s\nBASELINE=%s\n' \
"$status" "$error_type" "$elapsed_ms" "$start_human" "$end_human" "$rel_path" "$baseline_entry" > "$result_file"
}
# ============================================================
# 构建过滤后的测试队列
# ============================================================
declare -a TEST_QUEUE=()
declare -a SKIP_QUEUE=()
total=0
executed=0
for file in "${ALL_CASES[@]}"; do
total=$((total + 1))
if [[ $total -lt $START_FROM ]]; then
continue
fi
if [[ $MAX_CASES -gt 0 && $executed -ge $MAX_CASES ]]; then
break
fi
if [[ ${SKIP_SET[$total]+_} ]]; then
SKIP_QUEUE+=("$total|$file")
continue
fi
executed=$((executed + 1))
TEST_QUEUE+=("$total|$file")
done
SKIPPED=${#SKIP_QUEUE[@]}
# ============================================================
# 执行测试
# ============================================================
mkdir -p "$OUTPUT_DIR/.results"
wall_start=$(get_timestamp_ms)
_parallel_mode=false
if [[ $JOBS -gt 1 && ${#TEST_QUEUE[@]} -gt 1 ]]; then
_parallel_mode=true
if [[ "$STOP_ON_FIRST_FAILURE" == "true" ]]; then
echo -e "${YELLOW}注意: 并行模式下 --stop-on-fail 不生效(所有测试已并行启动)${NC}"
fi
echo -e "${BLUE}并行执行 ${#TEST_QUEUE[@]} 个测试 (${JOBS} 并发)...${NC}"
echo ""
export OUTPUT_DIR TEST_ROOT COMPILER RUNTIME_OBJ TIMEOUT_MS OPTIMIZE
export -f run_one_test get_category_name get_timestamp_ms canon_compare
printf '%s\n' "${TEST_QUEUE[@]}" | xargs -P "$JOBS" -L 1 bash -c '
IFS="|" read -r idx file <<< "$1"
run_one_test "$idx" "$file"
' _
else
# 串行模式:逐条执行,即时输出
for item in "${TEST_QUEUE[@]}"; do
IFS='|' read -r idx file <<< "$item"
printf "${YELLOW}[%s] %s ... ${NC}" "$idx" "$(basename "$file")"
run_one_test "$idx" "$file"
_rf="$OUTPUT_DIR/.results/$idx"
if [[ -f "$_rf" ]]; then
_st=$(grep '^STATUS=' "$_rf" | cut -d= -f2)
_el=$(grep '^ELAPSED=' "$_rf" | cut -d= -f2)
case "$_st" in
SUCCESS) echo -e "${GREEN}成功${NC} (${_el}ms)" ;;
COMPILE_FAIL) echo -e "${RED}编译/链接失败${NC}" ;;
OUTPUT_MISMATCH) echo -e "${RED}输出不匹配${NC} (${_el}ms)" ;;
*) echo -e "${RED}异常${NC}" ;;
esac
else
echo -e "${RED}超时/崩溃${NC}"
fi
if [[ "$STOP_ON_FIRST_FAILURE" == "true" ]]; then
if [[ -f "$_rf" ]]; then
_st=$(grep '^STATUS=' "$_rf" | cut -d= -f2)
if [[ "$_st" != "SUCCESS" ]]; then
echo -e "${RED}在第一个失败处停止${NC}"
break
fi
else
echo -e "${RED}在第一个失败处停止${NC}"
break
fi
fi
done
fi
wall_end=$(get_timestamp_ms)
wall_elapsed=$((wall_end - wall_start))
# ============================================================
# 汇总结果
# ============================================================
SUCCESS=0
FAILED=0
total_time_sum=0
time_cases_count=0
declare -a BASELINE_ENTRIES
# 打印跳过项
for item in "${SKIP_QUEUE[@]}"; do
IFS='|' read -r idx file <<< "$item"
echo -e "${CYAN}[$idx] $(basename "$file") ... 跳过${NC}"
echo "[SKIPPED] $file (user skip)" >> "$LOG_FILE"
done
# 收集测试结果并打印
for item in "${TEST_QUEUE[@]}"; do
IFS='|' read -r idx file <<< "$item"
result_file="$OUTPUT_DIR/.results/$idx"
if [[ ! -f "$result_file" ]]; then
FAILED=$((FAILED + 1))
echo "$file" >> "$FAIL_FILE"
if $_parallel_mode; then
echo -e "${RED}[$idx] $(basename "$file") ... 超时/崩溃${NC}"
fi
echo "[FAILED] $file (timeout/crash)" >> "$LOG_FILE"
{
echo "========================================"
echo "测试失败: $file"
echo "原因: 超时或崩溃"
echo "时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "========================================"
} >> "$ERROR_LOG_FILE"
continue
fi
status="" error="" elapsed_ms="" start_human="" end_human="" rel_path="" baseline_entry=""
while IFS='=' read -r key value; do
case "$key" in
STATUS) status="$value" ;;
ERROR) error="$value" ;;
ELAPSED) elapsed_ms="$value" ;;
START) start_human="$value" ;;
END) end_human="$value" ;;
FILE) rel_path="$value" ;;
BASELINE) baseline_entry="$value" ;;
esac
done < "$result_file"
case "$status" in
SUCCESS)
SUCCESS=$((SUCCESS + 1))
if [[ "$elapsed_ms" =~ ^[0-9]+$ ]]; then
total_time_sum=$((total_time_sum + elapsed_ms))
time_cases_count=$((time_cases_count + 1))
fi
ori_time=$(lookup_ori_time "$baseline_entry")
time_display=$(format_time_comparison "$elapsed_ms" "$ori_time")
if $_parallel_mode; then
if [[ "$VERBOSE" == "true" ]]; then
echo -e "${GREEN}[$idx] $(basename "$file") ... 成功${NC} | ${start_human}${end_human} | ${time_display}"
else
echo -e "${GREEN}[$idx] $(basename "$file") ... 成功${NC} ($time_display)"
fi
fi
echo "[SUCCESS] $file | start=$start_human | end=$end_human | exec=${elapsed_ms}ms" >> "$LOG_FILE"
echo "$rel_path: ${elapsed_ms}ms" >> "$TIME_SUMMARY_FILE"
echo "$baseline_entry | $start_human | $end_human | ${elapsed_ms}ms" >> "$DETAIL_TIME_FILE"
BASELINE_ENTRIES+=("$baseline_entry ${elapsed_ms}ms")
;;
COMPILE_FAIL)
FAILED=$((FAILED + 1))
echo "$file" >> "$FAIL_FILE"
if $_parallel_mode; then
echo -e "${RED}[$idx] $(basename "$file") ... 编译/链接失败${NC}"
fi
echo "[FAILED] $file (compile/link error)" >> "$LOG_FILE"
{
echo "========================================"
echo "测试失败: $file"
echo "原因: 编译或链接失败"
echo "时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "========================================"
} >> "$ERROR_LOG_FILE"
;;
OUTPUT_MISMATCH)
FAILED=$((FAILED + 1))
echo "$file" >> "$FAIL_FILE"
if $_parallel_mode; then
echo -e "${RED}[$idx] $(basename "$file") ... 失败${NC} (运行${elapsed_ms}ms, 输出不匹配)"
fi
echo "[FAILED] $file (output mismatch) | start=$start_human | end=$end_human | exec=${elapsed_ms}ms" >> "$LOG_FILE"
{
echo "========================================"
echo "测试失败: $file"
echo "原因: 输出不匹配"
echo "运行时间: ${elapsed_ms}ms"
echo "开始时间: $start_human"
echo "结束时间: $end_human"
echo "时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "========================================"
} >> "$ERROR_LOG_FILE"
;;
esac
done
: > "$TIME_OPT_FILE"
for entry in "${BASELINE_ENTRIES[@]}"; do
local_name=$(echo "$entry" | sed 's/ [0-9]*ms$//')
local_time=$(echo "$entry" | grep -oP '\d+(?=ms$)')
if [[ -n "$local_name" && -n "$local_time" ]]; then
echo "$local_name | | | ${local_time}ms" >> "$TIME_OPT_FILE"
fi
done
RATE="0.00"
EXECUTED=${#TEST_QUEUE[@]}
if [[ $EXECUTED -gt 0 ]]; then
RATE=$(awk -v s="$SUCCESS" -v t="$EXECUTED" 'BEGIN { printf "%.2f", (s*100.0)/t }')
fi
echo ""
echo -e "${BLUE}========================================================${NC}"
echo -e "${BLUE} 2026test 批量测试完成${NC}"
echo -e "${BLUE}========================================================${NC}"
echo -e "${BLUE}总用例数: $TOTAL_FOUND${NC}"
echo -e "${BLUE}执行用例: $EXECUTED${NC}"
echo -e "${GREEN}成功: $SUCCESS${NC}"
echo -e "${RED}失败: $FAILED${NC}"
echo -e "${CYAN}跳过: $SKIPPED${NC}"
echo -e "${BLUE}成功率: ${RATE}%${NC}"
echo -e "${BLUE}总耗时: ${wall_elapsed}ms${NC}"
if [[ $time_cases_count -gt 0 ]]; then
avg_time=$((total_time_sum / time_cases_count))
echo -e "${BLUE}平均运行时间: ${avg_time}ms (基于 ${time_cases_count} 个成功用例)${NC}"
echo -e "${BLUE}总运行时间: ${total_time_sum}ms${NC}"
fi
echo ""
echo -e "${BLUE}日志文件: $LOG_FILE${NC}"
echo -e "${BLUE}时间汇总: $TIME_SUMMARY_FILE${NC}"
echo -e "${BLUE}详细时间: $DETAIL_TIME_FILE${NC}"
echo -e "${BLUE}基线时间: $TIME_ORI_FILE${NC}"
echo -e "${BLUE}本次时间: $TIME_OPT_FILE${NC}"
if [[ $FAILED -gt 0 ]]; then
echo -e "${RED}失败清单: $FAIL_FILE${NC}"
echo -e "${RED}错误日志: $ERROR_LOG_FILE${NC}"
fi
echo -e "${BLUE}========================================================${NC}"
if [[ $FAILED -gt 0 ]]; then
exit 1
fi
exit 0