完成了循环交换

derder
安峻邑 11 hours ago
parent 409d2e7e3a
commit d2882fb69a

@ -0,0 +1,639 @@
#!/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"
VERIFY_SCRIPT="./scripts/verify_asm.sh"
BASELINE_FILE="./基线.txt"
MAX_CASES=0
STOP_ON_FIRST_FAILURE=false
START_FROM=1
KEEP_OLD=false
OPTIMIZE=true
CATEGORY="all"
SKIP_LIST=""
VERBOSE=false
TIMEOUT_MS=300000
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 保留旧输出目录,不删除
-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)
示例:
./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
;;
-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
;;
*)
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
rm -rf "$OUTPUT_DIR"
fi
mkdir -p "$OUTPUT_DIR"
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"
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
echo -e "${BLUE}基线文件: $BASELINE_FILE${NC}"
echo -e "${BLUE}========================================================${NC}"
echo ""
declare -a BASELINE_ENTRIES=()
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
SKIPPED=$((SKIPPED + 1))
rel_path="${file#$TEST_ROOT/}"
echo -e "${CYAN}[$TOTAL] $(basename "$file") ... 跳过${NC}"
echo "[SKIPPED] $file (user skip)" >> "$LOG_FILE"
continue
fi
EXECUTED=$((EXECUTED + 1))
rel_path="${file#$TEST_ROOT/}"
filename="$(basename "$file")"
base_name="${filename%.sy}"
rel_dir="$(dirname "$rel_path")"
input_dir="$TEST_ROOT/$rel_dir"
category_name=$(get_category_name "$rel_path")
case_out_dir="$OUTPUT_DIR/$rel_dir"
mkdir -p "$case_out_dir"
rm -f "$case_out_dir/$base_name.s"
rm -f "$case_out_dir/$base_name.o"
rm -f "$case_out_dir/$base_name"
rm -f "$case_out_dir/$base_name.stdout"
rm -f "$case_out_dir/$base_name.actual.out"
if [[ "$VERBOSE" == "true" ]]; then
echo -e "${YELLOW}[$TOTAL] $category_name/$filename ... ${NC}"
else
echo -ne "${YELLOW}[$TOTAL] $category_name/$filename ... ${NC}"
fi
asm_file="$case_out_dir/$base_name.s"
exe="$case_out_dir/$base_name"
stdin_file="$input_dir/$base_name.in"
expected_file="$input_dir/$base_name.out"
stdout_file="$case_out_dir/$base_name.stdout"
actual_file="$case_out_dir/$base_name.actual.out"
compile_ok=true
set +e
if [[ "$OPTIMIZE" == "true" ]]; then
"$COMPILER" -O --emit-asm "$file" > "$asm_file" 2>/dev/null
else
"$COMPILER" --emit-asm "$file" > "$asm_file" 2>/dev/null
fi
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
link_code=$?
set -e
if [[ $link_code -ne 0 ]]; then
compile_ok=false
fi
fi
if ! $compile_ok; then
FAILED=$((FAILED + 1))
echo "$file" >> "$FAIL_FILE"
echo -e "${RED}编译/链接失败${NC}"
echo "[FAILED] $file (compile/link error)" >> "$LOG_FILE"
{
echo "========================================"
echo "测试失败: $file"
echo "原因: 编译或链接失败"
echo "时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "========================================"
} >> "$ERROR_LOG_FILE"
if [[ "$STOP_ON_FIRST_FAILURE" == "true" ]]; then
echo -e "${RED}========================================================${NC}"
echo -e "${RED}在第一个失败处停止测试${NC}"
echo -e "${RED}失败文件: $file${NC}"
echo -e "${RED}日志: $LOG_FILE${NC}"
echo -e "${RED}错误日志: $ERROR_LOG_FILE${NC}"
echo -e "${RED}========================================================${NC}"
break
fi
continue
fi
exec_start_ms=$(get_timestamp_ms)
exec_start_human=$(date '+%Y-%m-%d %H:%M:%S.%3N')
set +e
if [[ -f "$stdin_file" ]]; then
qemu-aarch64 -L /usr/aarch64-linux-gnu -s 104857600 "$exe" < "$stdin_file" > "$stdout_file" 2>/dev/null
else
qemu-aarch64 -L /usr/aarch64-linux-gnu -s 104857600 "$exe" < /dev/null > "$stdout_file" 2>/dev/null
fi
exit_status=$?
set -e
exec_end_ms=$(get_timestamp_ms)
exec_end_human=$(date '+%Y-%m-%d %H:%M:%S.%3N')
exec_elapsed_ms=$((exec_end_ms - exec_start_ms))
{
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"
output_ok=true
if [[ -f "$expected_file" ]]; then
if command -v python3 >/dev/null 2>&1; then
if ! python3 - "$expected_file" "$actual_file" <<'PY' >/dev/null 2>&1
import sys
from pathlib import Path
def canon(path: str) -> bytes:
data = Path(path).read_bytes()
data = data.replace(b'\r\n', b'\n')
while data.endswith(b'\n'):
data = data[:-1]
lines = data.split(b'\n')
lines = [line.rstrip() for line in lines]
return b'\n'.join(lines)
sys.exit(0 if canon(sys.argv[1]) == canon(sys.argv[2]) else 1)
PY
then
output_ok=false
fi
else
local_expected="/tmp/_test_expected_$$"
local_actual="/tmp/_test_actual_$$"
tr -d '\r' < "$expected_file" > "$local_expected"
tr -d '\r' < "$actual_file" > "$local_actual"
if ! diff -u "$local_expected" "$local_actual" > /dev/null 2>&1; then
output_ok=false
fi
rm -f "$local_expected" "$local_actual"
fi
fi
baseline_entry="${category_name}/${base_name}"
if $output_ok; then
SUCCESS=$((SUCCESS + 1))
if [[ "$exec_elapsed_ms" =~ ^[0-9]+$ ]]; then
total_time_sum=$((total_time_sum + exec_elapsed_ms))
time_cases_count=$((time_cases_count + 1))
fi
if [[ "$VERBOSE" == "true" ]]; then
echo -e " ${GREEN}成功${NC} | 开始: $exec_start_human | 结束: $exec_end_human | 运行: ${exec_elapsed_ms}ms"
else
echo -e "${GREEN}成功${NC} (${exec_elapsed_ms}ms)"
fi
echo "[SUCCESS] $file | start=$exec_start_human | end=$exec_end_human | exec=${exec_elapsed_ms}ms" >> "$LOG_FILE"
echo "$rel_path: ${exec_elapsed_ms}ms" >> "$TIME_SUMMARY_FILE"
echo "$baseline_entry | $exec_start_human | $exec_end_human | ${exec_elapsed_ms}ms" >> "$DETAIL_TIME_FILE"
BASELINE_ENTRIES+=("$baseline_entry ${exec_elapsed_ms}ms")
else
FAILED=$((FAILED + 1))
echo "$file" >> "$FAIL_FILE"
if [[ "$VERBOSE" == "true" ]]; then
echo -e " ${RED}失败${NC} | 开始: $exec_start_human | 结束: $exec_end_human | 运行: ${exec_elapsed_ms}ms | 输出不匹配"
else
echo -e "${RED}失败${NC} (运行${exec_elapsed_ms}ms, 输出不匹配)"
fi
echo "[FAILED] $file (output mismatch) | start=$exec_start_human | end=$exec_end_human | exec=${exec_elapsed_ms}ms" >> "$LOG_FILE"
{
echo "========================================"
echo "测试失败: $file"
echo "原因: 输出不匹配"
echo "运行时间: ${exec_elapsed_ms}ms"
echo "开始时间: $exec_start_human"
echo "结束时间: $exec_end_human"
echo "时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "========================================"
} >> "$ERROR_LOG_FILE"
if [[ "$STOP_ON_FIRST_FAILURE" == "true" ]]; then
echo -e "${RED}========================================================${NC}"
echo -e "${RED}在第一个失败处停止测试${NC}"
echo -e "${RED}失败文件: $file${NC}"
echo -e "${RED}日志: $LOG_FILE${NC}"
echo -e "${RED}错误日志: $ERROR_LOG_FILE${NC}"
echo -e "${RED}========================================================${NC}"
break
fi
fi
done
{
echo ""
echo "========================================================"
echo "2026test 批量测试报告"
echo "========================================================"
echo "生成时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "测试类别: $CATEGORY"
echo "编译器优化: $OPTIMIZE"
echo "计时说明: 仅qemu运行时间(不含编译/汇编链接)"
echo "========================================================"
echo ""
echo "统计信息:"
echo " 总用例数: $TOTAL_FOUND"
echo " 执行用例: $EXECUTED"
echo " 成功: $SUCCESS"
echo " 失败: $FAILED"
echo " 跳过: $SKIPPED"
if [[ $EXECUTED -gt 0 ]]; then
local_rate=$(awk -v s="$SUCCESS" -v t="$EXECUTED" 'BEGIN { printf "%.2f", (s*100.0)/t }')
echo " 成功率: ${local_rate}%"
fi
echo ""
echo "--------------------------------------------------------"
printf "%-50s %15s\n" "测试集标识" "运行时长(ms)"
echo "--------------------------------------------------------"
for entry in "${BASELINE_ENTRIES[@]}"; do
local_name=$(echo "$entry" | sed 's/ [0-9]*ms$//')
local_time=$(echo "$entry" | grep -oP '\d+(?=ms$)')
printf "%-50s %15s\n" "$local_name" "$local_time"
done
echo "--------------------------------------------------------"
if [[ $time_cases_count -gt 0 ]]; then
avg_time=$((total_time_sum / time_cases_count))
echo ""
echo "平均运行时间: ${avg_time}ms (基于 ${time_cases_count} 个成功用例)"
echo "总运行时间: ${total_time_sum}ms"
fi
echo ""
echo "========================================================"
} > "$BASELINE_FILE"
RATE="0.00"
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}"
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}基线文件: $BASELINE_FILE${NC}"
echo -e "${BLUE}日志文件: $LOG_FILE${NC}"
echo -e "${BLUE}时间汇总: $TIME_SUMMARY_FILE${NC}"
echo -e "${BLUE}详细时间: $DETAIL_TIME_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

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save