diff --git a/.ci/run-functional-tests.sh b/.ci/run-functional-tests.sh index b32c0b79..14b1afed 100755 --- a/.ci/run-functional-tests.sh +++ b/.ci/run-functional-tests.sh @@ -1,5 +1,8 @@ #!/bin/bash +# Strict error handling +set -euo pipefail + # Configuration TIMEOUT=30 TOOLCHAIN_TYPE=${TOOLCHAIN_TYPE:-gnu} @@ -7,7 +10,7 @@ TOOLCHAIN_TYPE=${TOOLCHAIN_TYPE:-gnu} # Define functional tests and their expected PASS criteria declare -A FUNCTIONAL_TESTS FUNCTIONAL_TESTS["mutex"]="Fairness: PASS,Mutual Exclusion: PASS,Data Consistency: PASS,Overall: PASS" -FUNCTIONAL_TESTS["semaphore"]="All tests PASSED!" +FUNCTIONAL_TESTS["semaphore"]="Overall: PASS" #FUNCTIONAL_TESTS["test64"]="Unsigned Multiply: PASS,Unsigned Divide: PASS,Signed Multiply: PASS,Signed Divide: PASS,Left Shifts: PASS,Logical Right Shifts: PASS,Arithmetic Right Shifts: PASS,Overall: PASS" #FUNCTIONAL_TESTS["suspend"]="Suspend: PASS,Resume: PASS,Self-Suspend: PASS,Overall: PASS" @@ -37,7 +40,7 @@ test_functional_app() { # Build phase echo "[+] Building..." - make clean > /dev/null 2>&1 + make clean > /dev/null 2>&1 || true # Clean previous build artifacts (failures ignored) if ! make "$test" TOOLCHAIN_TYPE="$TOOLCHAIN_TYPE" > /dev/null 2>&1; then echo "[!] Build failed" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 24148f07..86ba3465 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,24 +8,34 @@ on: branches: - main +# Cancel in-progress runs for the same PR/branch +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +# Least-privilege permissions +permissions: + contents: read + actions: write # Required for actions/cache@v4 to save/restore cache + jobs: - matrix-tests: + # Fast-running lint job to catch formatting issues early + lint: runs-on: ubuntu-24.04 - name: Test on ${{ matrix.toolchain }} toolchain + name: Code Quality Checks + timeout-minutes: 10 - strategy: - fail-fast: false - matrix: - toolchain: [gnu, llvm] + env: + CLANG_FORMAT_VERSION: 18 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - - name: Install base dependencies + - name: Install linting tools run: | sudo apt-get update - sudo apt-get install -y build-essential qemu-system-riscv32 wget clang-format-18 shfmt + sudo apt-get install -y --no-install-recommends clang-format-${{ env.CLANG_FORMAT_VERSION }} shfmt - name: Check code formatting run: .ci/check-format.sh @@ -33,9 +43,50 @@ jobs: - name: Check newline at end of files run: .ci/check-newline.sh + # Build and test matrix - runs in parallel after lint passes + # NOTE: LLVM toolchain performs build-only validation (no runtime tests) + # to verify cross-toolchain compilation compatibility. GNU toolchain + # runs the full test suite including application and functional tests. + matrix-tests: + runs-on: ubuntu-24.04 + name: Test on ${{ matrix.toolchain }} toolchain + needs: lint + timeout-minutes: 30 + + strategy: + fail-fast: false + matrix: + toolchain: [gnu, llvm] + + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Cache toolchain + uses: actions/cache@v4 + id: cache-toolchain + with: + path: riscv + key: ${{ runner.os }}-${{ matrix.toolchain }}-toolchain-${{ hashFiles('.ci/setup-toolchain.sh') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.toolchain }}-toolchain- + + - name: Install build dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends build-essential qemu-system-riscv32 wget + - name: Setup ${{ matrix.toolchain }} toolchain + if: steps.cache-toolchain.outputs.cache-hit != 'true' run: .ci/setup-toolchain.sh ${{ matrix.toolchain }} + - name: Configure toolchain environment + if: steps.cache-toolchain.outputs.cache-hit == 'true' + run: | + echo "$PWD/riscv/bin" >> "$GITHUB_PATH" + echo "CROSS_COMPILE=riscv32-unknown-elf-" >> "$GITHUB_ENV" + echo "TOOLCHAIN_TYPE=${{ matrix.toolchain }}" >> "$GITHUB_ENV" + - name: Verify toolchain installation run: | if [ "${{ matrix.toolchain }}" = "gnu" ]; then @@ -49,8 +100,9 @@ jobs: - name: Build Kernel run: | + set -euo pipefail make clean - make + make -j$(nproc) env: TOOLCHAIN_TYPE: ${{ matrix.toolchain }} @@ -59,6 +111,7 @@ jobs: continue-on-error: true if: matrix.toolchain == 'gnu' run: | + set -euo pipefail output=$(.ci/run-app-tests.sh 2>&1) || true echo "TEST_OUTPUT<> $GITHUB_OUTPUT echo "$output" >> $GITHUB_OUTPUT @@ -71,6 +124,7 @@ jobs: continue-on-error: true if: matrix.toolchain == 'gnu' run: | + set -euo pipefail output=$(.ci/run-functional-tests.sh 2>&1) || true echo "FUNCTIONAL_TEST_OUTPUT<> $GITHUB_OUTPUT echo "$output" >> $GITHUB_OUTPUT @@ -81,6 +135,7 @@ jobs: - name: Collect Test Data if: always() run: | + set -euo pipefail if [ "${{ matrix.toolchain }}" = "llvm" ]; then # LLVM: Build-only validation, skip tests mkdir -p test-results @@ -103,7 +158,7 @@ jobs: echo "mutex:mutual_exclusion=skipped" >> test-results/functional_criteria_data echo "mutex:data_consistency=skipped" >> test-results/functional_criteria_data echo "mutex:overall=skipped" >> test-results/functional_criteria_data - echo "semaphore:all_tests_passed!=skipped" >> test-results/functional_criteria_data + echo "semaphore:overall=skipped" >> test-results/functional_criteria_data echo "LLVM toolchain: Build validation only (tests skipped)" else @@ -117,13 +172,18 @@ jobs: with: name: test-results-${{ matrix.toolchain }} path: test-results/ - retention-days: 1 + retention-days: 3 + if-no-files-found: warn # Comprehensive test summary with detailed reporting test-summary: runs-on: ubuntu-24.04 - needs: matrix-tests + needs: [lint, matrix-tests] if: always() + timeout-minutes: 15 + permissions: + contents: read + pull-requests: write steps: - name: Checkout @@ -136,41 +196,66 @@ jobs: path: all-test-results/ - name: Generate Test Summary + id: generate_summary continue-on-error: true run: | + echo "Aggregating test results..." .ci/ci-tools.sh aggregate all-test-results test-summary.toml - cat test-summary.toml + + if [ -f test-summary.toml ]; then + echo "summary_generated=true" >> $GITHUB_OUTPUT + echo "Test Summary:" + cat test-summary.toml + else + echo "summary_generated=false" >> $GITHUB_OUTPUT + echo "⚠️ Warning: test-summary.toml not generated" + fi - name: Upload Test Summary - if: always() + if: always() && steps.generate_summary.outputs.summary_generated == 'true' uses: actions/upload-artifact@v4 with: name: test-summary path: test-summary.toml retention-days: 30 + if-no-files-found: error - name: Comment PR with Formatted Summary - if: always() && github.event_name == 'pull_request' + if: always() && github.event_name == 'pull_request' && steps.generate_summary.outputs.summary_generated == 'true' continue-on-error: true run: | + echo "Posting summary to PR #${{ github.event.number }}..." .ci/ci-tools.sh post-comment test-summary.toml ${{ github.event.number }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Final Status Check run: | + set -euo pipefail + echo "=== Final CI Status Check ===" + if [ ! -f test-summary.toml ]; then - echo "Error: test-summary.toml not found" + echo "❌ Error: test-summary.toml not found" + echo "CI infrastructure issue - check test aggregation step" exit 1 fi overall_status=$(grep -A 1 '^\[summary\]' test-summary.toml | grep 'status =' | cut -d'"' -f2) echo "Overall test status: $overall_status" + # Extract failure details if available + if [ "$overall_status" != "passed" ]; then + echo "" + echo "=== Failure Details ===" + grep -E '(failed|error|skipped)' test-summary.toml || true + fi + if [ "$overall_status" = "passed" ]; then - echo "✅ All tests passed" + echo "" + echo "✅ All tests passed successfully" exit 0 else - echo "❌ Tests failed" + echo "" + echo "❌ Tests failed - see details above" exit 1 fi diff --git a/app/semaphore.c b/app/semaphore.c index 5558215d..8188350c 100644 --- a/app/semaphore.c +++ b/app/semaphore.c @@ -175,9 +175,9 @@ void print_test_results(void) printf("Total tests: %d\n", tests_passed + tests_failed); if (tests_failed == 0) { - printf("All tests PASSED!\n"); + printf("Overall: PASS\n"); } else { - printf("Some tests FAILED!\n"); + printf("Overall: FAIL\n"); } }