#!/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" 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 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) 示例: ./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 ;; *) 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 "" 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 ori_time=$(lookup_ori_time "$baseline_entry") time_display=$(format_time_comparison "$exec_elapsed_ms" "$ori_time") if [[ "$VERBOSE" == "true" ]]; then echo -e " ${GREEN}成功${NC} | 开始: $exec_start_human | 结束: $exec_end_human | 运行: $time_display" else echo -e "${GREEN}成功${NC} ($time_display)" 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 : > "$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" 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}日志文件: $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