diff --git a/.ci/check-format.sh b/.ci/check-format.sh new file mode 100755 index 00000000..f6c87a93 --- /dev/null +++ b/.ci/check-format.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +set -u -o pipefail + +ret=0 + +# Check C/C++ files with clang-format +while IFS= read -r -d '' file; do + clang-format-18 -n --Werror "${file}" || ret=1 +done < <(git ls-files -z '*.c' '*.cpp' '*.h' ':!:*/submodule/*') + +# Check shell scripts with shfmt +while IFS= read -r file; do + diff <(cat "${file}") <(shfmt -i 4 -bn -ci -sr "${file}") + if [ $? -ne 0 ]; then + echo "${file}" "mismatch" + ret=1 + fi +done < <(git ls-files '*.sh') + +exit ${ret} diff --git a/.ci/check-newline.sh b/.ci/check-newline.sh new file mode 100755 index 00000000..f8a81b45 --- /dev/null +++ b/.ci/check-newline.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -e -u -o pipefail + +ret=0 +show=0 +# Reference: https://medium.com/@alexey.inkin/how-to-force-newline-at-end-of-files-and-why-you-should-do-it-fdf76d1d090e +while IFS= read -rd '' f; do + if file --mime-encoding "$f" | grep -qv binary; then + tail -c1 < "$f" | read -r _ || show=1 + if [ $show -eq 1 ]; then + echo "Warning: No newline at end of file $f" + ret=1 + show=0 + fi + fi +done < <(git ls-files -z app kernel lib arch include) + +exit $ret diff --git a/.ci/ci-tools.sh b/.ci/ci-tools.sh index d3316a7a..927633b7 100755 --- a/.ci/ci-tools.sh +++ b/.ci/ci-tools.sh @@ -7,7 +7,7 @@ SCRIPT_NAME=$(basename "$0") COMMAND=${1:-help} show_help() { - cat < [options] Commands: @@ -37,140 +37,140 @@ EOF # Data collection function collect_data() { - local toolchain=${1:-unknown} - local test_output=${2:-} - local functional_output=${3:-} - - if [ -z "$test_output" ]; then - echo "Error: test_output required" - exit 1 - fi - - mkdir -p test-results - echo "$toolchain" >test-results/toolchain - - # Extract app data - echo "$test_output" | grep "APP_STATUS:" | sed 's/APP_STATUS://' >test-results/apps_data || touch test-results/apps_data - - # Debug: Check if functional output was received - if [ -n "$functional_output" ]; then - echo "[DEBUG] Functional output received (length: ${#functional_output})" - local func_test_count=$(echo "$functional_output" | grep -c "FUNCTIONAL_TEST:" || echo "0") - local func_crit_count=$(echo "$functional_output" | grep -c "FUNCTIONAL_CRITERIA:" || echo "0") - echo "[DEBUG] Found $func_test_count FUNCTIONAL_TEST lines" - echo "[DEBUG] Found $func_crit_count FUNCTIONAL_CRITERIA lines" - - # Show last 50 lines to see if parseable output is present - echo "[DEBUG] Last 50 lines of functional output:" - echo "$functional_output" | tail -50 - else - echo "[DEBUG] No functional output received" - fi - - # Extract functional data (both overall and criteria) - if [ -n "$functional_output" ]; then - echo "$functional_output" | grep "FUNCTIONAL_TEST:" | sed 's/FUNCTIONAL_TEST://' >test-results/functional_data || touch test-results/functional_data - echo "$functional_output" | grep "FUNCTIONAL_CRITERIA:" | sed 's/FUNCTIONAL_CRITERIA://' >test-results/functional_criteria_data || touch test-results/functional_criteria_data - else - touch test-results/functional_data - touch test-results/functional_criteria_data - fi - - # Determine exit codes - test_exit=0 - functional_exit=0 - echo "$test_output" | grep -q "validation FAILED" && test_exit=1 - [ -n "$functional_output" ] && echo "$functional_output" | grep -q "functional tests FAILED" && functional_exit=1 - - echo "$test_exit" >test-results/crash_exit_code - echo "$functional_exit" >test-results/functional_exit_code - - echo "Test data collected for $toolchain toolchain" + local toolchain=${1:-unknown} + local test_output=${2:-} + local functional_output=${3:-} + + if [ -z "$test_output" ]; then + echo "Error: test_output required" + exit 1 + fi + + mkdir -p test-results + echo "$toolchain" > test-results/toolchain + + # Extract app data + echo "$test_output" | grep "APP_STATUS:" | sed 's/APP_STATUS://' > test-results/apps_data || touch test-results/apps_data + + # Debug: Check if functional output was received + if [ -n "$functional_output" ]; then + echo "[DEBUG] Functional output received (length: ${#functional_output})" + local func_test_count=$(echo "$functional_output" | grep -c "FUNCTIONAL_TEST:" || echo "0") + local func_crit_count=$(echo "$functional_output" | grep -c "FUNCTIONAL_CRITERIA:" || echo "0") + echo "[DEBUG] Found $func_test_count FUNCTIONAL_TEST lines" + echo "[DEBUG] Found $func_crit_count FUNCTIONAL_CRITERIA lines" + + # Show last 50 lines to see if parseable output is present + echo "[DEBUG] Last 50 lines of functional output:" + echo "$functional_output" | tail -50 + else + echo "[DEBUG] No functional output received" + fi + + # Extract functional data (both overall and criteria) + if [ -n "$functional_output" ]; then + echo "$functional_output" | grep "FUNCTIONAL_TEST:" | sed 's/FUNCTIONAL_TEST://' > test-results/functional_data || touch test-results/functional_data + echo "$functional_output" | grep "FUNCTIONAL_CRITERIA:" | sed 's/FUNCTIONAL_CRITERIA://' > test-results/functional_criteria_data || touch test-results/functional_criteria_data + else + touch test-results/functional_data + touch test-results/functional_criteria_data + fi + + # Determine exit codes + test_exit=0 + functional_exit=0 + echo "$test_output" | grep -q "validation FAILED" && test_exit=1 + [ -n "$functional_output" ] && echo "$functional_output" | grep -q "functional tests FAILED" && functional_exit=1 + + echo "$test_exit" > test-results/crash_exit_code + echo "$functional_exit" > test-results/functional_exit_code + + echo "Test data collected for $toolchain toolchain" } # Result aggregation function aggregate_results() { - local results_dir=${1:-all-test-results} - local output_file=${2:-test-summary.toml} - - if [ ! -d "$results_dir" ]; then - echo "Error: Results directory not found: $results_dir" - exit 1 - fi - - # Initialize status - gnu_build="failed" gnu_crash="failed" gnu_functional="failed" - llvm_build="failed" llvm_crash="failed" llvm_functional="failed" - overall="failed" - apps_data="" functional_data="" functional_criteria_data="" - - # Process artifacts - for artifact_dir in "$results_dir"/test-results-*; do - [ ! -d "$artifact_dir" ] && continue - - toolchain=$(cat "$artifact_dir/toolchain" 2>/dev/null || echo "unknown") - crash_exit=$(cat "$artifact_dir/crash_exit_code" 2>/dev/null || echo "1") - functional_exit=$(cat "$artifact_dir/functional_exit_code" 2>/dev/null || echo "1") - - build_status="passed" - # Handle skipped tests - if [ "$crash_exit" = "skipped" ]; then - crash_status="skipped" - else - crash_status=$([ "$crash_exit" = "0" ] && echo "passed" || echo "failed") - fi - - if [ "$functional_exit" = "skipped" ]; then - functional_status="skipped" - else - functional_status=$([ "$functional_exit" = "0" ] && echo "passed" || echo "failed") - fi - - case "$toolchain" in - "gnu") - gnu_build="$build_status" - gnu_crash="$crash_status" - gnu_functional="$functional_status" - ;; - "llvm") - llvm_build="$build_status" - llvm_crash="$crash_status" - llvm_functional="$functional_status" - ;; - esac - - # Collect data with toolchain prefix - if [ -f "$artifact_dir/apps_data" ] && [ -s "$artifact_dir/apps_data" ]; then - while read -r line; do - [ -n "$line" ] && apps_data="$apps_data ${toolchain}_${line}" - done <"$artifact_dir/apps_data" - fi - - if [ -f "$artifact_dir/functional_data" ] && [ -s "$artifact_dir/functional_data" ]; then - while read -r line; do - [ -n "$line" ] && functional_data="$functional_data ${toolchain}_${line}" - done <"$artifact_dir/functional_data" - fi - - if [ -f "$artifact_dir/functional_criteria_data" ] && [ -s "$artifact_dir/functional_criteria_data" ]; then - while read -r line; do - [ -n "$line" ] && functional_criteria_data="$functional_criteria_data ${toolchain}_${line}" - done <"$artifact_dir/functional_criteria_data" - fi - done - - # Overall status - only GNU needs to fully pass, LLVM can be skipped - if [ "$gnu_build" = "passed" ] && [ "$gnu_crash" = "passed" ] && [ "$gnu_functional" = "passed" ] && - [ "$llvm_build" = "passed" ]; then - overall="passed" - fi - - apps_data=$(echo "$apps_data" | xargs) - functional_data=$(echo "$functional_data" | xargs) - functional_criteria_data=$(echo "$functional_criteria_data" | xargs) - - # Generate TOML - cat >"$output_file" < /dev/null || echo "unknown") + crash_exit=$(cat "$artifact_dir/crash_exit_code" 2> /dev/null || echo "1") + functional_exit=$(cat "$artifact_dir/functional_exit_code" 2> /dev/null || echo "1") + + build_status="passed" + # Handle skipped tests + if [ "$crash_exit" = "skipped" ]; then + crash_status="skipped" + else + crash_status=$([ "$crash_exit" = "0" ] && echo "passed" || echo "failed") + fi + + if [ "$functional_exit" = "skipped" ]; then + functional_status="skipped" + else + functional_status=$([ "$functional_exit" = "0" ] && echo "passed" || echo "failed") + fi + + case "$toolchain" in + "gnu") + gnu_build="$build_status" + gnu_crash="$crash_status" + gnu_functional="$functional_status" + ;; + "llvm") + llvm_build="$build_status" + llvm_crash="$crash_status" + llvm_functional="$functional_status" + ;; + esac + + # Collect data with toolchain prefix + if [ -f "$artifact_dir/apps_data" ] && [ -s "$artifact_dir/apps_data" ]; then + while read -r line; do + [ -n "$line" ] && apps_data="$apps_data ${toolchain}_${line}" + done < "$artifact_dir/apps_data" + fi + + if [ -f "$artifact_dir/functional_data" ] && [ -s "$artifact_dir/functional_data" ]; then + while read -r line; do + [ -n "$line" ] && functional_data="$functional_data ${toolchain}_${line}" + done < "$artifact_dir/functional_data" + fi + + if [ -f "$artifact_dir/functional_criteria_data" ] && [ -s "$artifact_dir/functional_criteria_data" ]; then + while read -r line; do + [ -n "$line" ] && functional_criteria_data="$functional_criteria_data ${toolchain}_${line}" + done < "$artifact_dir/functional_criteria_data" + fi + done + + # Overall status - only GNU needs to fully pass, LLVM can be skipped + if [ "$gnu_build" = "passed" ] && [ "$gnu_crash" = "passed" ] && [ "$gnu_functional" = "passed" ] \ + && [ "$llvm_build" = "passed" ]; then + overall="passed" + fi + + apps_data=$(echo "$apps_data" | xargs) + functional_data=$(echo "$functional_data" | xargs) + functional_criteria_data=$(echo "$functional_criteria_data" | xargs) + + # Generate TOML + cat > "$output_file" << EOF [summary] status = "$overall" timestamp = "$(date -Iseconds)" @@ -190,44 +190,44 @@ crash = "$llvm_crash" functional = "$llvm_functional" EOF - # Add apps sections - if [ -n "$apps_data" ]; then - echo "" >>"$output_file" - echo "[gnu.apps]" >>"$output_file" - echo "$apps_data" | tr ' ' '\n' | grep "^gnu_" | sed 's/gnu_//; s/\(.*\)=\(.*\)/"\1" = "\2"/' >>"$output_file" || true - echo "" >>"$output_file" - echo "[llvm.apps]" >>"$output_file" - echo "$apps_data" | tr ' ' '\n' | grep "^llvm_" | sed 's/llvm_//; s/\(.*\)=\(.*\)/"\1" = "\2"/' >>"$output_file" || true - fi - - # Add functional sections - if [ -n "$functional_data" ]; then - echo "" >>"$output_file" - echo "[gnu.functional_tests]" >>"$output_file" - echo "$functional_data" | tr ' ' '\n' | grep "^gnu_" | sed 's/gnu_//; s/\(.*\)=\(.*\)/"\1" = "\2"/' >>"$output_file" || true - echo "" >>"$output_file" - echo "[llvm.functional_tests]" >>"$output_file" - echo "$functional_data" | tr ' ' '\n' | grep "^llvm_" | sed 's/llvm_//; s/\(.*\)=\(.*\)/"\1" = "\2"/' >>"$output_file" || true - fi - - # Add functional criteria sections - if [ -n "$functional_criteria_data" ]; then - echo "" >>"$output_file" - echo "[gnu.functional_criteria]" >>"$output_file" - echo "$functional_criteria_data" | tr ' ' '\n' | grep "^gnu_" | sed 's/gnu_//; s/\(.*\)=\(.*\)/"\1" = "\2"/' >>"$output_file" || true - echo "" >>"$output_file" - echo "[llvm.functional_criteria]" >>"$output_file" - echo "$functional_criteria_data" | tr ' ' '\n' | grep "^llvm_" | sed 's/llvm_//; s/\(.*\)=\(.*\)/"\1" = "\2"/' >>"$output_file" || true - fi - - echo "Results aggregated into $output_file" - [ "$overall" = "passed" ] && exit 0 || exit 1 + # Add apps sections + if [ -n "$apps_data" ]; then + echo "" >> "$output_file" + echo "[gnu.apps]" >> "$output_file" + echo "$apps_data" | tr ' ' '\n' | grep "^gnu_" | sed 's/gnu_//; s/\(.*\)=\(.*\)/"\1" = "\2"/' >> "$output_file" || true + echo "" >> "$output_file" + echo "[llvm.apps]" >> "$output_file" + echo "$apps_data" | tr ' ' '\n' | grep "^llvm_" | sed 's/llvm_//; s/\(.*\)=\(.*\)/"\1" = "\2"/' >> "$output_file" || true + fi + + # Add functional sections + if [ -n "$functional_data" ]; then + echo "" >> "$output_file" + echo "[gnu.functional_tests]" >> "$output_file" + echo "$functional_data" | tr ' ' '\n' | grep "^gnu_" | sed 's/gnu_//; s/\(.*\)=\(.*\)/"\1" = "\2"/' >> "$output_file" || true + echo "" >> "$output_file" + echo "[llvm.functional_tests]" >> "$output_file" + echo "$functional_data" | tr ' ' '\n' | grep "^llvm_" | sed 's/llvm_//; s/\(.*\)=\(.*\)/"\1" = "\2"/' >> "$output_file" || true + fi + + # Add functional criteria sections + if [ -n "$functional_criteria_data" ]; then + echo "" >> "$output_file" + echo "[gnu.functional_criteria]" >> "$output_file" + echo "$functional_criteria_data" | tr ' ' '\n' | grep "^gnu_" | sed 's/gnu_//; s/\(.*\)=\(.*\)/"\1" = "\2"/' >> "$output_file" || true + echo "" >> "$output_file" + echo "[llvm.functional_criteria]" >> "$output_file" + echo "$functional_criteria_data" | tr ' ' '\n' | grep "^llvm_" | sed 's/llvm_//; s/\(.*\)=\(.*\)/"\1" = "\2"/' >> "$output_file" || true + fi + + echo "Results aggregated into $output_file" + [ "$overall" = "passed" ] && exit 0 || exit 1 } # TOML parsing helpers get_value() { - local section=$1 key=$2 file=$3 - awk -v section="$section" -v key="$key" ' + local section=$1 key=$2 file=$3 + awk -v section="$section" -v key="$key" ' BEGIN { in_section = 0 } /^\[.*\]/ { in_section = 0; if ($0 == "[" section "]") in_section = 1 } in_section && /^[a-zA-Z_]/ { @@ -236,8 +236,8 @@ get_value() { } get_section_data() { - local section=$1 file=$2 - awk -v section="$section" ' + local section=$1 file=$2 + awk -v section="$section" ' BEGIN { in_section = 0 } /^\[.*\]/ { in_section = 0; if ($0 == "[" section "]") in_section = 1 } in_section && /=/ && !/^\[/ { gsub(/^[ \t]+/, ""); print $0 } @@ -246,44 +246,44 @@ get_section_data() { # Extract key from TOML line (strips quotes) get_toml_key() { - echo "$1" | sed 's/^"\([^"]*\)" = ".*"$/\1/' + echo "$1" | sed 's/^"\([^"]*\)" = ".*"$/\1/' } # Extract value from TOML line (strips quotes) get_toml_value() { - echo "$1" | sed 's/^"[^"]*" = "\(.*\)"$/\1/' + echo "$1" | sed 's/^"[^"]*" = "\(.*\)"$/\1/' } get_symbol() { - case $1 in - "passed") echo "✅" ;; - "failed") echo "❌" ;; - "skipped") echo "⏭️" ;; - *) echo "⚠️" ;; - esac + case $1 in + "passed") echo "✅" ;; + "failed") echo "❌" ;; + "skipped") echo "⏭️" ;; + *) echo "⚠️" ;; + esac } # PR comment formatting format_comment() { - local toml_file=${1:-test-summary.toml} - - if [ ! -f "$toml_file" ]; then - echo "Error: TOML file not found: $toml_file" - exit 1 - fi - - # Extract basic info - overall_status=$(get_value "summary" "status" "$toml_file") - timestamp=$(get_value "summary" "timestamp" "$toml_file") - gnu_build=$(get_value "gnu" "build" "$toml_file") - gnu_crash=$(get_value "gnu" "crash" "$toml_file") - gnu_functional=$(get_value "gnu" "functional" "$toml_file") - llvm_build=$(get_value "llvm" "build" "$toml_file") - llvm_crash=$(get_value "llvm" "crash" "$toml_file") - llvm_functional=$(get_value "llvm" "functional" "$toml_file") - - # Generate comment - cat <"$temp_file" - gh pr comment "$pr_number" --body-file "$temp_file" - rm -f "$temp_file" - echo "PR comment posted successfully" + local toml_file=${1:-test-summary.toml} + local pr_number=${2:-$GITHUB_PR_NUMBER} + + if [ -z "$pr_number" ]; then + echo "Error: PR number not provided" + exit 1 + fi + + if [ ! -f "$toml_file" ]; then + echo "Error: TOML file not found: $toml_file" + exit 1 + fi + + if [ -z "$GITHUB_TOKEN" ]; then + echo "Error: GITHUB_TOKEN environment variable not set" + exit 1 + fi + + comment_body=$(format_comment "$toml_file") + temp_file=$(mktemp) + echo "$comment_body" > "$temp_file" + gh pr comment "$pr_number" --body-file "$temp_file" + rm -f "$temp_file" + echo "PR comment posted successfully" } # Print report print_report() { - local toml_file=${1:-test-summary.toml} - - if [ ! -f "$toml_file" ]; then - echo "Error: TOML file not found: $toml_file" - exit 1 - fi - - echo "=========================================" - echo "Linmo CI Test Report" - echo "=========================================" - echo "" - cat "$toml_file" - echo "" - echo "=========================================" - echo "Report generated from: $toml_file" - echo "=========================================" + local toml_file=${1:-test-summary.toml} + + if [ ! -f "$toml_file" ]; then + echo "Error: TOML file not found: $toml_file" + exit 1 + fi + + echo "=========================================" + echo "Linmo CI Test Report" + echo "=========================================" + echo "" + cat "$toml_file" + echo "" + echo "=========================================" + echo "Report generated from: $toml_file" + echo "=========================================" } # Main command dispatcher case "$COMMAND" in -"collect-data") - shift - collect_data "$@" - ;; -"aggregate") - shift - aggregate_results "$@" - ;; -"format-comment") - shift - format_comment "$@" - ;; -"post-comment") - shift - post_comment "$@" - ;; -"print-report") - shift - print_report "$@" - ;; -"help" | "--help" | "-h") - show_help - ;; -*) - echo "Error: Unknown command '$COMMAND'" - echo "Use '$SCRIPT_NAME help' for usage information" - exit 1 - ;; + "collect-data") + shift + collect_data "$@" + ;; + "aggregate") + shift + aggregate_results "$@" + ;; + "format-comment") + shift + format_comment "$@" + ;; + "post-comment") + shift + post_comment "$@" + ;; + "print-report") + shift + print_report "$@" + ;; + "help" | "--help" | "-h") + show_help + ;; + *) + echo "Error: Unknown command '$COMMAND'" + echo "Use '$SCRIPT_NAME help' for usage information" + exit 1 + ;; esac diff --git a/.ci/run-app-tests.sh b/.ci/run-app-tests.sh index b21211f6..dcf4f040 100755 --- a/.ci/run-app-tests.sh +++ b/.ci/run-app-tests.sh @@ -7,53 +7,53 @@ TOOLCHAIN_TYPE=${TOOLCHAIN_TYPE:-gnu} # Test a single app: build, run, check # Returns: 0=passed, 1=failed, 2=build_failed test_app() { - local app=$1 + local app=$1 - echo "=== Testing $app ($TOOLCHAIN_TYPE) ===" + echo "=== Testing $app ($TOOLCHAIN_TYPE) ===" - # Build phase - echo "[+] Building..." - make clean >/dev/null 2>&1 - if ! make "$app" TOOLCHAIN_TYPE="$TOOLCHAIN_TYPE" >/dev/null 2>&1; then - echo "[!] Build failed" - return 2 - fi + # Build phase + echo "[+] Building..." + make clean > /dev/null 2>&1 + if ! make "$app" TOOLCHAIN_TYPE="$TOOLCHAIN_TYPE" > /dev/null 2>&1; then + echo "[!] Build failed" + return 2 + fi - # Run phase - echo "[+] Running in QEMU (timeout: ${TIMEOUT}s)..." - local output exit_code - output=$(timeout ${TIMEOUT}s qemu-system-riscv32 -nographic -machine virt -bios none -kernel build/image.elf 2>&1) - exit_code=$? + # Run phase + echo "[+] Running in QEMU (timeout: ${TIMEOUT}s)..." + local output exit_code + output=$(timeout ${TIMEOUT}s qemu-system-riscv32 -nographic -machine virt -bios none -kernel build/image.elf 2>&1) + exit_code=$? - # Check phase - if echo "$output" | grep -qiE "(trap|exception|fault|panic|illegal|segfault)"; then - echo "[!] Crash detected" - return 1 - elif [ $exit_code -eq 124 ] || [ $exit_code -eq 0 ]; then - echo "[✓] Passed" - return 0 - else - echo "[!] Exit code $exit_code" - return 1 - fi + # Check phase + if echo "$output" | grep -qiE "(trap|exception|fault|panic|illegal|segfault)"; then + echo "[!] Crash detected" + return 1 + elif [ $exit_code -eq 124 ] || [ $exit_code -eq 0 ]; then + echo "[✓] Passed" + return 0 + else + echo "[!] Exit code $exit_code" + return 1 + fi } # Auto-discover apps if none provided if [ $# -eq 0 ]; then - APPS=$(find app/ -name "*.c" -exec basename {} .c \; | sort | tr '\n' ' ') - echo "[+] Auto-discovered apps: $APPS" + APPS=$(find app/ -name "*.c" -exec basename {} .c \; | sort | tr '\n' ' ') + echo "[+] Auto-discovered apps: $APPS" else - APPS="$@" + APPS="$@" fi # Filter excluded apps EXCLUDED_APPS="" if [ -n "$EXCLUDED_APPS" ]; then - FILTERED_APPS="" - for app in $APPS; do - [[ ! " $EXCLUDED_APPS " =~ " $app " ]] && FILTERED_APPS="$FILTERED_APPS $app" - done - APPS="$FILTERED_APPS" + FILTERED_APPS="" + for app in $APPS; do + [[ ! " $EXCLUDED_APPS " =~ " $app " ]] && FILTERED_APPS="$FILTERED_APPS $app" + done + APPS="$FILTERED_APPS" fi echo "[+] Testing apps: $APPS" @@ -67,13 +67,13 @@ BUILD_FAILED_APPS="" # Test each app for app in $APPS; do - test_app "$app" - case $? in - 0) PASSED_APPS="$PASSED_APPS $app" ;; - 1) FAILED_APPS="$FAILED_APPS $app" ;; - 2) BUILD_FAILED_APPS="$BUILD_FAILED_APPS $app" ;; - esac - echo "" + test_app "$app" + case $? in + 0) PASSED_APPS="$PASSED_APPS $app" ;; + 1) FAILED_APPS="$FAILED_APPS $app" ;; + 2) BUILD_FAILED_APPS="$BUILD_FAILED_APPS $app" ;; + esac + echo "" done # Summary @@ -86,21 +86,21 @@ echo "=== STEP 2 APP TEST RESULTS ===" echo "" echo "=== PARSEABLE_OUTPUT ===" for app in $APPS; do - if echo "$PASSED_APPS" | grep -qw "$app"; then - echo "APP_STATUS:$app=passed" - elif echo "$FAILED_APPS" | grep -qw "$app"; then - echo "APP_STATUS:$app=failed" - elif echo "$BUILD_FAILED_APPS" | grep -qw "$app"; then - echo "APP_STATUS:$app=build_failed" - fi + if echo "$PASSED_APPS" | grep -qw "$app"; then + echo "APP_STATUS:$app=passed" + elif echo "$FAILED_APPS" | grep -qw "$app"; then + echo "APP_STATUS:$app=failed" + elif echo "$BUILD_FAILED_APPS" | grep -qw "$app"; then + echo "APP_STATUS:$app=build_failed" + fi done # Exit status if [ -n "$FAILED_APPS" ] || [ -n "$BUILD_FAILED_APPS" ]; then - echo "" - echo "[!] Step 2 validation FAILED" - exit 1 + echo "" + echo "[!] Step 2 validation FAILED" + exit 1 else - echo "" - echo "[+] Step 2 validation PASSED" + echo "" + echo "[+] Step 2 validation PASSED" fi diff --git a/.ci/run-functional-tests.sh b/.ci/run-functional-tests.sh index c06fded5..b32c0b79 100755 --- a/.ci/run-functional-tests.sh +++ b/.ci/run-functional-tests.sh @@ -25,115 +25,115 @@ declare -A CRITERIA_RESULTS # Test a single functional test: build, run, validate criteria # Returns: 0=passed, 1=failed, 2=build_failed, 3=unknown_test test_functional_app() { - local test=$1 - - echo "=== Functional Test: $test ===" - - # Check if test is defined - if [ -z "${FUNCTIONAL_TESTS[$test]}" ]; then - echo "[!] Unknown test (not in FUNCTIONAL_TESTS)" - return 3 - fi - - # Build phase - echo "[+] Building..." - make clean >/dev/null 2>&1 - if ! make "$test" TOOLCHAIN_TYPE="$TOOLCHAIN_TYPE" >/dev/null 2>&1; then - echo "[!] Build failed" - - # Mark all criteria as build_failed - local expected_passes="${FUNCTIONAL_TESTS[$test]}" - IFS=',' read -ra PASS_CRITERIA <<<"$expected_passes" - for criteria in "${PASS_CRITERIA[@]}"; do - local criteria_key=$(echo "$criteria" | sed 's/: PASS//g' | tr '[:upper:]' '[:lower:]' | tr ' ' '_') - CRITERIA_RESULTS["$test:$criteria_key"]="build_failed" - done - - return 2 - fi - - # Run phase - echo "[+] Running (timeout: ${TIMEOUT}s)..." - local output exit_code - output=$(timeout ${TIMEOUT}s qemu-system-riscv32 -nographic -machine virt -bios none -kernel build/image.elf 2>&1) - exit_code=$? - - # Debug: Show first 500 chars of output - if [ -n "$output" ]; then - echo "[DEBUG] Output preview (first 500 chars):" - echo "$output" | head -c 500 - echo "" - else - echo "[DEBUG] No output captured from QEMU" - fi - - # Parse expected criteria - local expected_passes="${FUNCTIONAL_TESTS[$test]}" - IFS=',' read -ra PASS_CRITERIA <<<"$expected_passes" - - # Check for crashes first - if echo "$output" | grep -qiE "(trap|exception|fault|panic|illegal|segfault)"; then - echo "[!] Crash detected" - - # Mark all criteria as crashed - for criteria in "${PASS_CRITERIA[@]}"; do - local criteria_key=$(echo "$criteria" | sed 's/: PASS//g' | tr '[:upper:]' '[:lower:]' | tr ' ' '_') - CRITERIA_RESULTS["$test:$criteria_key"]="crashed" - done - - return 1 - fi - - # Check exit code - if [ $exit_code -eq 124 ]; then - echo "[!] Timeout (test hung)" - - # Mark all criteria as timeout - for criteria in "${PASS_CRITERIA[@]}"; do - local criteria_key=$(echo "$criteria" | sed 's/: PASS//g' | tr '[:upper:]' '[:lower:]' | tr ' ' '_') - CRITERIA_RESULTS["$test:$criteria_key"]="timeout" - done - - return 1 - elif [ $exit_code -ne 0 ]; then - echo "[!] Exit code $exit_code" - - # Mark all criteria as failed - for criteria in "${PASS_CRITERIA[@]}"; do - local criteria_key=$(echo "$criteria" | sed 's/: PASS//g' | tr '[:upper:]' '[:lower:]' | tr ' ' '_') - CRITERIA_RESULTS["$test:$criteria_key"]="failed" - done - - return 1 - fi - - # Validate criteria - echo "[+] Checking PASS criteria:" - local all_passes_found=true - local missing_passes="" - - for criteria in "${PASS_CRITERIA[@]}"; do - local criteria_key=$(echo "$criteria" | sed 's/: PASS//g' | tr '[:upper:]' '[:lower:]' | tr ' ' '_') - - if echo "$output" | grep -qF "$criteria"; then - echo " ✓ Found: $criteria" - CRITERIA_RESULTS["$test:$criteria_key"]="passed" - else - echo " ✗ Missing: $criteria" - all_passes_found=false - missing_passes="$missing_passes '$criteria'" - CRITERIA_RESULTS["$test:$criteria_key"]="failed" - fi - done - - # Determine result - if [ "$all_passes_found" = true ]; then - echo "[✓] All criteria passed" - return 0 - else - echo "[!] Missing criteria:$missing_passes" - return 1 - fi + local test=$1 + + echo "=== Functional Test: $test ===" + + # Check if test is defined + if [ -z "${FUNCTIONAL_TESTS[$test]}" ]; then + echo "[!] Unknown test (not in FUNCTIONAL_TESTS)" + return 3 + fi + + # Build phase + echo "[+] Building..." + make clean > /dev/null 2>&1 + if ! make "$test" TOOLCHAIN_TYPE="$TOOLCHAIN_TYPE" > /dev/null 2>&1; then + echo "[!] Build failed" + + # Mark all criteria as build_failed + local expected_passes="${FUNCTIONAL_TESTS[$test]}" + IFS=',' read -ra PASS_CRITERIA <<< "$expected_passes" + for criteria in "${PASS_CRITERIA[@]}"; do + local criteria_key=$(echo "$criteria" | sed 's/: PASS//g' | tr '[:upper:]' '[:lower:]' | tr ' ' '_') + CRITERIA_RESULTS["$test:$criteria_key"]="build_failed" + done + + return 2 + fi + + # Run phase + echo "[+] Running (timeout: ${TIMEOUT}s)..." + local output exit_code + output=$(timeout ${TIMEOUT}s qemu-system-riscv32 -nographic -machine virt -bios none -kernel build/image.elf 2>&1) + exit_code=$? + + # Debug: Show first 500 chars of output + if [ -n "$output" ]; then + echo "[DEBUG] Output preview (first 500 chars):" + echo "$output" | head -c 500 + echo "" + else + echo "[DEBUG] No output captured from QEMU" + fi + + # Parse expected criteria + local expected_passes="${FUNCTIONAL_TESTS[$test]}" + IFS=',' read -ra PASS_CRITERIA <<< "$expected_passes" + + # Check for crashes first + if echo "$output" | grep -qiE "(trap|exception|fault|panic|illegal|segfault)"; then + echo "[!] Crash detected" + + # Mark all criteria as crashed + for criteria in "${PASS_CRITERIA[@]}"; do + local criteria_key=$(echo "$criteria" | sed 's/: PASS//g' | tr '[:upper:]' '[:lower:]' | tr ' ' '_') + CRITERIA_RESULTS["$test:$criteria_key"]="crashed" + done + + return 1 + fi + + # Check exit code + if [ $exit_code -eq 124 ]; then + echo "[!] Timeout (test hung)" + + # Mark all criteria as timeout + for criteria in "${PASS_CRITERIA[@]}"; do + local criteria_key=$(echo "$criteria" | sed 's/: PASS//g' | tr '[:upper:]' '[:lower:]' | tr ' ' '_') + CRITERIA_RESULTS["$test:$criteria_key"]="timeout" + done + + return 1 + elif [ $exit_code -ne 0 ]; then + echo "[!] Exit code $exit_code" + + # Mark all criteria as failed + for criteria in "${PASS_CRITERIA[@]}"; do + local criteria_key=$(echo "$criteria" | sed 's/: PASS//g' | tr '[:upper:]' '[:lower:]' | tr ' ' '_') + CRITERIA_RESULTS["$test:$criteria_key"]="failed" + done + + return 1 + fi + + # Validate criteria + echo "[+] Checking PASS criteria:" + local all_passes_found=true + local missing_passes="" + + for criteria in "${PASS_CRITERIA[@]}"; do + local criteria_key=$(echo "$criteria" | sed 's/: PASS//g' | tr '[:upper:]' '[:lower:]' | tr ' ' '_') + + if echo "$output" | grep -qF "$criteria"; then + echo " ✓ Found: $criteria" + CRITERIA_RESULTS["$test:$criteria_key"]="passed" + else + echo " ✗ Missing: $criteria" + all_passes_found=false + missing_passes="$missing_passes '$criteria'" + CRITERIA_RESULTS["$test:$criteria_key"]="failed" + fi + done + + # Determine result + if [ "$all_passes_found" = true ]; then + echo "[✓] All criteria passed" + return 0 + else + echo "[!] Missing criteria:$missing_passes" + return 1 + fi } echo "[+] Linmo Functional Test Suite (Step 3)" @@ -143,11 +143,11 @@ echo "" # Get list of tests to run if [ $# -eq 0 ]; then - TESTS_TO_RUN=$(echo "${!FUNCTIONAL_TESTS[@]}" | tr ' ' '\n' | sort | tr '\n' ' ') - echo "[+] Running all functional tests: $TESTS_TO_RUN" + TESTS_TO_RUN=$(echo "${!FUNCTIONAL_TESTS[@]}" | tr ' ' '\n' | sort | tr '\n' ' ') + echo "[+] Running all functional tests: $TESTS_TO_RUN" else - TESTS_TO_RUN="$@" - echo "[+] Running specified tests: $TESTS_TO_RUN" + TESTS_TO_RUN="$@" + echo "[+] Running specified tests: $TESTS_TO_RUN" fi echo "" @@ -160,16 +160,16 @@ TOTAL_TESTS=0 # Test each app for test in $TESTS_TO_RUN; do - TOTAL_TESTS=$((TOTAL_TESTS + 1)) - - test_functional_app "$test" - case $? in - 0) PASSED_TESTS="$PASSED_TESTS $test" ;; - 1) FAILED_TESTS="$FAILED_TESTS $test" ;; - 2) BUILD_FAILED_TESTS="$BUILD_FAILED_TESTS $test" ;; - 3) FAILED_TESTS="$FAILED_TESTS $test" ;; - esac - echo "" + TOTAL_TESTS=$((TOTAL_TESTS + 1)) + + test_functional_app "$test" + case $? in + 0) PASSED_TESTS="$PASSED_TESTS $test" ;; + 1) FAILED_TESTS="$FAILED_TESTS $test" ;; + 2) BUILD_FAILED_TESTS="$BUILD_FAILED_TESTS $test" ;; + 3) FAILED_TESTS="$FAILED_TESTS $test" ;; + esac + echo "" done # Summary @@ -185,37 +185,37 @@ echo "" echo "[DEBUG] About to emit parseable output for $TOTAL_TESTS tests" echo "=== PARSEABLE_OUTPUT ===" for test in $TESTS_TO_RUN; do - # Output overall test result - if echo "$PASSED_TESTS" | grep -qw "$test"; then - echo "FUNCTIONAL_TEST:$test=passed" - elif echo "$FAILED_TESTS" | grep -qw "$test"; then - echo "FUNCTIONAL_TEST:$test=failed" - elif echo "$BUILD_FAILED_TESTS" | grep -qw "$test"; then - echo "FUNCTIONAL_TEST:$test=build_failed" - fi - - # Output individual criteria results - if [ -n "${FUNCTIONAL_TESTS[$test]}" ]; then - expected_passes="${FUNCTIONAL_TESTS[$test]}" - IFS=',' read -ra PASS_CRITERIA <<<"$expected_passes" - for criteria in "${PASS_CRITERIA[@]}"; do - criteria_key=$(echo "$criteria" | sed 's/: PASS//g' | tr '[:upper:]' '[:lower:]' | tr ' ' '_') - - if [ -n "${CRITERIA_RESULTS["$test:$criteria_key"]}" ]; then - echo "FUNCTIONAL_CRITERIA:$test:$criteria_key=${CRITERIA_RESULTS["$test:$criteria_key"]}" - fi - done - fi + # Output overall test result + if echo "$PASSED_TESTS" | grep -qw "$test"; then + echo "FUNCTIONAL_TEST:$test=passed" + elif echo "$FAILED_TESTS" | grep -qw "$test"; then + echo "FUNCTIONAL_TEST:$test=failed" + elif echo "$BUILD_FAILED_TESTS" | grep -qw "$test"; then + echo "FUNCTIONAL_TEST:$test=build_failed" + fi + + # Output individual criteria results + if [ -n "${FUNCTIONAL_TESTS[$test]}" ]; then + expected_passes="${FUNCTIONAL_TESTS[$test]}" + IFS=',' read -ra PASS_CRITERIA <<< "$expected_passes" + for criteria in "${PASS_CRITERIA[@]}"; do + criteria_key=$(echo "$criteria" | sed 's/: PASS//g' | tr '[:upper:]' '[:lower:]' | tr ' ' '_') + + if [ -n "${CRITERIA_RESULTS["$test:$criteria_key"]}" ]; then + echo "FUNCTIONAL_CRITERIA:$test:$criteria_key=${CRITERIA_RESULTS["$test:$criteria_key"]}" + fi + done + fi done echo "[DEBUG] Finished emitting parseable output" # Exit status if [ -n "$FAILED_TESTS" ] || [ -n "$BUILD_FAILED_TESTS" ]; then - echo "" - echo "[!] Step 3 functional tests FAILED" - exit 1 + echo "" + echo "[!] Step 3 functional tests FAILED" + exit 1 else - echo "" - echo "[+] Step 3 functional tests PASSED" + echo "" + echo "[+] Step 3 functional tests PASSED" fi diff --git a/.ci/setup-toolchain.sh b/.ci/setup-toolchain.sh index d52f8ed4..11bb26ad 100755 --- a/.ci/setup-toolchain.sh +++ b/.ci/setup-toolchain.sh @@ -9,54 +9,54 @@ TOOLCHAIN_TYPE="${1:-gnu}" # Setup RISC-V toolchain (unified for GNU and LLVM) setup_toolchain() { - local toolchain=$1 - local compiler_name - - # Determine compiler package name - case "$toolchain" in - gnu) compiler_name="gcc" ;; - llvm) compiler_name="llvm" ;; - *) - echo "Error: Unknown toolchain type '$toolchain'" - echo "Usage: $0 [gnu|llvm]" - exit 1 - ;; - esac - - echo "[+] Setting up ${toolchain^^} RISC-V toolchain..." - - # Construct download URL - local archive="riscv32-elf-${TOOLCHAIN_OS}-${compiler_name}.tar.xz" - #local archive="riscv32-elf-${TOOLCHAIN_OS}-${compiler_name}-nightly-${TOOLCHAIN_VERSION}-nightly.tar.xz" - local url="${TOOLCHAIN_REPO}/releases/download/${TOOLCHAIN_VERSION}/${archive}" - - # Download and extract - echo "[+] Downloading $archive..." - wget -q "$url" - - echo "[+] Extracting toolchain..." - tar -xf "$archive" - - # Export to GitHub Actions environment - echo "[+] Exporting toolchain to PATH..." - echo "$PWD/riscv/bin" >>"$GITHUB_PATH" - echo "CROSS_COMPILE=riscv32-unknown-elf-" >>"$GITHUB_ENV" - echo "TOOLCHAIN_TYPE=$toolchain" >>"$GITHUB_ENV" - - # Cleanup - rm -f "$archive" - - echo "[+] Toolchain setup complete: $toolchain" + local toolchain=$1 + local compiler_name + + # Determine compiler package name + case "$toolchain" in + gnu) compiler_name="gcc" ;; + llvm) compiler_name="llvm" ;; + *) + echo "Error: Unknown toolchain type '$toolchain'" + echo "Usage: $0 [gnu|llvm]" + exit 1 + ;; + esac + + echo "[+] Setting up ${toolchain^^} RISC-V toolchain..." + + # Construct download URL + local archive="riscv32-elf-${TOOLCHAIN_OS}-${compiler_name}.tar.xz" + #local archive="riscv32-elf-${TOOLCHAIN_OS}-${compiler_name}-nightly-${TOOLCHAIN_VERSION}-nightly.tar.xz" + local url="${TOOLCHAIN_REPO}/releases/download/${TOOLCHAIN_VERSION}/${archive}" + + # Download and extract + echo "[+] Downloading $archive..." + wget -q "$url" + + echo "[+] Extracting toolchain..." + tar -xf "$archive" + + # Export to GitHub Actions environment + echo "[+] Exporting toolchain to PATH..." + echo "$PWD/riscv/bin" >> "$GITHUB_PATH" + echo "CROSS_COMPILE=riscv32-unknown-elf-" >> "$GITHUB_ENV" + echo "TOOLCHAIN_TYPE=$toolchain" >> "$GITHUB_ENV" + + # Cleanup + rm -f "$archive" + + echo "[+] Toolchain setup complete: $toolchain" } # Validate toolchain type and setup case "$TOOLCHAIN_TYPE" in -gnu | llvm) - setup_toolchain "$TOOLCHAIN_TYPE" - ;; -*) - echo "Error: Unknown toolchain type '$TOOLCHAIN_TYPE'" - echo "Usage: $0 [gnu|llvm]" - exit 1 - ;; + gnu | llvm) + setup_toolchain "$TOOLCHAIN_TYPE" + ;; + *) + echo "Error: Unknown toolchain type '$TOOLCHAIN_TYPE'" + echo "Usage: $0 [gnu|llvm]" + exit 1 + ;; esac diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..21de05aa --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +# Top-level EditorConfig file +root = true + +# Shell script-specific settings +[*.sh] +indent_style = space +indent_size = 4 +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true +function_next_line = true +switch_case_indent = true +space_redirects = true +binary_next_line = true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f30c16b1..24148f07 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,13 @@ jobs: - name: Install base dependencies run: | sudo apt-get update - sudo apt-get install -y build-essential qemu-system-riscv32 wget + sudo apt-get install -y build-essential qemu-system-riscv32 wget clang-format-18 shfmt + + - name: Check code formatting + run: .ci/check-format.sh + + - name: Check newline at end of files + run: .ci/check-newline.sh - name: Setup ${{ matrix.toolchain }} toolchain run: .ci/setup-toolchain.sh ${{ matrix.toolchain }}