#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" VERIFY_SCRIPT="$REPO_ROOT/scripts/verify_asm.sh" BUILD_DIR="$REPO_ROOT/build_lab3" RUN_ROOT="$REPO_ROOT/output/logs/lab3" LAST_RUN_FILE="$RUN_ROOT/last_run.txt" LAST_FAILED_FILE="$RUN_ROOT/last_failed.txt" RUN_NAME="lab3_$(date +%Y%m%d_%H%M%S)" RUN_DIR="$RUN_ROOT/$RUN_NAME" WHOLE_LOG="$RUN_DIR/whole.log" FAIL_DIR="$RUN_DIR/failures" LEGACY_SAVE_ASM=false FAILED_ONLY=false FALLBACK_TO_FULL=false RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' TEST_DIRS=() TEST_FILES=() while [[ $# -gt 0 ]]; do case "$1" in --save-asm) LEGACY_SAVE_ASM=true ;; --failed-only) FAILED_ONLY=true ;; *) if [[ -f "$1" ]]; then TEST_FILES+=("$1") else TEST_DIRS+=("$1") fi ;; esac shift done mkdir -p "$RUN_DIR" : > "$WHOLE_LOG" printf '%s\n' "$RUN_DIR" > "$LAST_RUN_FILE" log_plain() { printf '%s\n' "$*" printf '%s\n' "$*" >> "$WHOLE_LOG" } log_color() { local color="$1" shift local message="$*" printf '%b%s%b\n' "$color" "$message" "$NC" printf '%s\n' "$message" >> "$WHOLE_LOG" } append_file_to_whole_log() { local title="$1" local file="$2" { printf '\n===== %s =====\n' "$title" cat "$file" printf '\n' } >> "$WHOLE_LOG" } cleanup_tmp_dir() { local dir="$1" if [[ -d "$dir" ]]; then rm -rf "$dir" fi } discover_default_test_dirs() { local roots=( "$REPO_ROOT/test/test_case" "$REPO_ROOT/test/class_test_case" ) local root for root in "${roots[@]}"; do [[ -d "$root" ]] || continue find "$root" -mindepth 1 -maxdepth 1 -type d -print0 done | sort -z } prune_empty_run_dirs() { if [[ -d "$RUN_DIR/.tmp" ]]; then rmdir "$RUN_DIR/.tmp" 2>/dev/null || true fi if [[ -d "$FAIL_DIR" ]]; then rmdir "$FAIL_DIR" 2>/dev/null || true fi } now_ns() { date +%s%N } format_duration_ns() { local ns="$1" local sec=$((ns / 1000000000)) local ms=$(((ns % 1000000000) / 1000000)) printf '%d.%03ds' "$sec" "$ms" } is_transient_io_failure() { local log_file="$1" [[ -f "$log_file" ]] || return 1 grep -Eq \ 'Permission denied|Text file busy|Device or resource busy|Stale file handle|Input/output error|Resource temporarily unavailable|Read-only file system' \ "$log_file" } test_one() { local sy_file="$1" local rel="$2" local safe_name="${rel//\//_}" local case_key="${safe_name%.sy}" local tmp_dir="$RUN_DIR/.tmp/$case_key" local fail_case_dir="$FAIL_DIR/$case_key" local case_log="$tmp_dir/error.log" local attempt=1 cleanup_tmp_dir "$fail_case_dir" while true; do cleanup_tmp_dir "$tmp_dir" mkdir -p "$tmp_dir" if "$VERIFY_SCRIPT" "$sy_file" "$tmp_dir" --run > "$case_log" 2>&1; then cleanup_tmp_dir "$tmp_dir" return 0 fi if [[ $attempt -eq 1 ]] && is_transient_io_failure "$case_log"; then log_color "$YELLOW" "RETRY $rel (transient I/O failure)" attempt=$((attempt + 1)) continue fi break done mkdir -p "$FAIL_DIR" mv "$tmp_dir" "$fail_case_dir" append_file_to_whole_log "$rel" "$fail_case_dir/error.log" return 1 } run_case() { local sy_file="$1" local rel local case_start_ns local case_end_ns local case_elapsed_ns rel="$(realpath --relative-to="$REPO_ROOT" "$sy_file")" case_start_ns=$(now_ns) if test_one "$sy_file" "$rel"; then case_end_ns=$(now_ns) case_elapsed_ns=$((case_end_ns - case_start_ns)) log_color "$GREEN" "PASS $rel [$(format_duration_ns "$case_elapsed_ns")]" PASS=$((PASS + 1)) else case_end_ns=$(now_ns) case_elapsed_ns=$((case_end_ns - case_start_ns)) log_color "$RED" "FAIL $rel [$(format_duration_ns "$case_elapsed_ns")]" FAIL=$((FAIL + 1)) FAIL_LIST+=("$rel") fi } TOTAL_START_NS=$(now_ns) if [[ "$FAILED_ONLY" == true ]]; then if [[ -f "$LAST_FAILED_FILE" ]]; then while IFS= read -r sy_file; do [[ -n "$sy_file" ]] || continue [[ -f "$sy_file" ]] || continue TEST_FILES+=("$sy_file") done < "$LAST_FAILED_FILE" fi if [[ ${#TEST_FILES[@]} -eq 0 ]]; then FALLBACK_TO_FULL=true FAILED_ONLY=false fi fi if [[ "$FAILED_ONLY" == false && ${#TEST_DIRS[@]} -eq 0 && ${#TEST_FILES[@]} -eq 0 ]]; then while IFS= read -r -d '' test_dir; do TEST_DIRS+=("$test_dir") done < <(discover_default_test_dirs) fi log_plain "Run directory: $RUN_DIR" log_plain "Whole log: $WHOLE_LOG" if [[ "$LEGACY_SAVE_ASM" == true ]]; then log_color "$YELLOW" "Warning: --save-asm is deprecated; successful case artifacts will still be deleted." fi if [[ "$FAILED_ONLY" == true ]]; then log_plain "Mode: rerun cached failed cases only" fi if [[ "$FALLBACK_TO_FULL" == true ]]; then log_color "$YELLOW" "No cached failed cases found, fallback to full suite." fi if [[ ! -f "$VERIFY_SCRIPT" ]]; then log_color "$RED" "missing verify script: $VERIFY_SCRIPT" exit 1 fi for tool in llc aarch64-linux-gnu-gcc qemu-aarch64; do if ! command -v "$tool" >/dev/null 2>&1; then log_color "$RED" "missing required tool: $tool" exit 1 fi done log_plain "==> [1/2] Configure and build compiler" BUILD_START_NS=$(now_ns) if ! cmake -S "$REPO_ROOT" -B "$BUILD_DIR" >> "$WHOLE_LOG" 2>&1; then log_color "$RED" "CMake configure failed. See $WHOLE_LOG" exit 1 fi if ! cmake --build "$BUILD_DIR" -j "$(nproc)" >> "$WHOLE_LOG" 2>&1; then log_color "$RED" "Compiler build failed. See $WHOLE_LOG" exit 1 fi BUILD_END_NS=$(now_ns) BUILD_ELAPSED_NS=$((BUILD_END_NS - BUILD_START_NS)) log_plain "==> [2/2] Run ASM validation suite" VALIDATION_START_NS=$(now_ns) PASS=0 FAIL=0 FAIL_LIST=() if [[ "$FAILED_ONLY" == true ]]; then for sy_file in "${TEST_FILES[@]}"; do run_case "$sy_file" done else for sy_file in "${TEST_FILES[@]}"; do run_case "$sy_file" done for test_dir in "${TEST_DIRS[@]}"; do if [[ ! -d "$test_dir" ]]; then log_color "$YELLOW" "skip missing dir: $test_dir" continue fi while IFS= read -r -d '' sy_file; do run_case "$sy_file" done < <(find "$test_dir" -maxdepth 1 -type f -name '*.sy' -print0 | sort -z) done fi rm -f "$LAST_FAILED_FILE" if [[ ${#FAIL_LIST[@]} -gt 0 ]]; then for f in "${FAIL_LIST[@]}"; do printf '%s/%s\n' "$REPO_ROOT" "$f" >> "$LAST_FAILED_FILE" done fi prune_empty_run_dirs VALIDATION_END_NS=$(now_ns) VALIDATION_ELAPSED_NS=$((VALIDATION_END_NS - VALIDATION_START_NS)) TOTAL_END_NS=$(now_ns) TOTAL_ELAPSED_NS=$((TOTAL_END_NS - TOTAL_START_NS)) log_plain "" log_plain "summary: ${PASS} PASS / ${FAIL} FAIL / total $((PASS + FAIL))" log_plain "build elapsed: $(format_duration_ns "$BUILD_ELAPSED_NS")" log_plain "validation elapsed: $(format_duration_ns "$VALIDATION_ELAPSED_NS")" log_plain "total elapsed: $(format_duration_ns "$TOTAL_ELAPSED_NS")" if [[ ${#FAIL_LIST[@]} -gt 0 ]]; then log_plain "failed cases:" for f in "${FAIL_LIST[@]}"; do safe_name="${f//\//_}" log_plain "- $f" log_plain " artifacts: $FAIL_DIR/${safe_name%.sy}" done else log_plain "all successful case artifacts have been deleted automatically." fi log_plain "whole log saved to: $WHOLE_LOG" [[ $FAIL -eq 0 ]]