diff --git a/.github/workflows/build_all.yml b/.github/workflows/build_all.yml index 376ef74b1f..49e6f58fd6 100644 --- a/.github/workflows/build_all.yml +++ b/.github/workflows/build_all.yml @@ -122,54 +122,73 @@ jobs: arch: universal macos-combine-only: true secrets: inherit - unit_tests: - name: Unit Tests - # Tests are built on the aarch64 leg by default (faster GitHub arm runner), - # so run them there; self-hosted builds them on the amd64 server instead. - runs-on: ${{ vars.SELF_HOSTED && 'orca-lnx-server' || 'ubuntu-24.04-arm' }} + # One test job per built arch, on the runner that built it. + unit_tests_linux_x86_64: + name: Linux x86_64 needs: build_linux if: ${{ !cancelled() && success() }} + uses: ./.github/workflows/unit_tests.yml + with: + os: ${{ vars.SELF_HOSTED && 'orca-lnx-server' || 'ubuntu-24.04' }} + artifact: ${{ github.sha }}-tests-linux-x86_64 + unit_tests_linux_aarch64: + name: Linux aarch64 + needs: build_linux + if: ${{ !cancelled() && success() }} + uses: ./.github/workflows/unit_tests.yml + with: + os: ubuntu-24.04-arm + artifact: ${{ github.sha }}-tests-linux-aarch64 + unit_tests_windows_x64: + name: Windows x64 + needs: build_windows + if: ${{ !cancelled() && success() }} + uses: ./.github/workflows/unit_tests.yml + with: + os: ${{ vars.SELF_HOSTED && 'orca-win-server' || 'windows-latest' }} + artifact: ${{ github.sha }}-tests-windows-x64 + unit_tests_windows_arm64: + name: Windows arm64 + needs: build_windows + if: ${{ !cancelled() && success() }} + uses: ./.github/workflows/unit_tests.yml + with: + os: windows-11-arm + artifact: ${{ github.sha }}-tests-windows-arm64 + test-dir: build-arm64/tests + unit_tests_macos_arm64: + name: macOS arm64 + needs: build_macos_arch + if: ${{ !cancelled() && success() }} + uses: ./.github/workflows/unit_tests.yml + with: + os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-14' }} + artifact: ${{ github.sha }}-tests-macos-arm64 + test-dir: build/arm64/tests + publish_test_results: + name: Publish Test Results + needs: [unit_tests_linux_x86_64, unit_tests_linux_aarch64, unit_tests_windows_x64, unit_tests_windows_arm64, unit_tests_macos_arm64] + if: ${{ !cancelled() }} + runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v7 - with: - sparse-checkout: | - .github - scripts - tests - - name: Apt-Install Dependencies - if: ${{ !vars.SELF_HOSTED }} - uses: ./.github/actions/apt-install-deps - - name: Restore Test Artifact + - name: Download Test Results uses: actions/download-artifact@v8 with: - name: ${{ github.sha }}-tests - - uses: lukka/get-cmake@latest - with: - cmakeVersion: "~4.3.0" # use most recent 4.3.x version - useLocalCache: true # <--= Use the local cache (default is 'false'). - useCloudCache: true - - name: Unpackage and Run Unit Tests - timeout-minutes: 20 - run: | - tar -xvf build_tests.tar - scripts/run_unit_tests.sh - - name: Upload Test Logs - uses: actions/upload-artifact@v7 - if: ${{ failure() }} - with: - name: unit-test-logs - path: build/tests/**/*.log + pattern: test-results-* + path: test-results + # Best-effort: a read-only token (e.g. fork PRs) can't write the check, so + # don't let a publish failure fail the run. The test jobs gate correctness. - name: Publish Test Results - if: always() + continue-on-error: true uses: EnricoMi/publish-unit-test-result-action@v2 with: - files: "ctest_results.xml" - - name: Delete Test Artifact + files: "test-results/**/*.xml" + - name: Delete Test Results if: success() uses: geekyeggo/delete-artifact@v6 with: - name: ${{ github.sha }}-tests + name: test-results-* + failOnError: false flatpak: name: "Flatpak" container: diff --git a/.github/workflows/build_orca.yml b/.github/workflows/build_orca.yml index ffe4c35f8d..21c65283a9 100644 --- a/.github/workflows/build_orca.yml +++ b/.github/workflows/build_orca.yml @@ -141,8 +141,26 @@ jobs: - name: Build slicer mac if: runner.os == 'macOS' && !inputs.macos-combine-only working-directory: ${{ github.workspace }} + # arm64 only: build the tests here; the unit_tests_macos job runs them. + env: + ORCA_TESTS_BUILD_ONLY: ${{ inputs.arch == 'arm64' && '1' || '' }} run: | - ./build_release_macos.sh -s -n -x ${{ !vars.SELF_HOSTED && '-1' || '' }} -a ${{ inputs.arch }} -t 10.15 + ./build_release_macos.sh -s -n -x ${{ !vars.SELF_HOSTED && '-1' || '' }} -a ${{ inputs.arch }} -t 10.15 ${{ inputs.arch == 'arm64' && '-T' || '' }} + + - name: Pack unit tests mac + if: runner.os == 'macOS' && !inputs.macos-combine-only && inputs.arch == 'arm64' + working-directory: ${{ github.workspace }} + run: tar -cvf build_tests.tar build/arm64/tests + + - name: Upload Test Artifact mac + if: runner.os == 'macOS' && !inputs.macos-combine-only && inputs.arch == 'arm64' + uses: actions/upload-artifact@v7 + with: + name: ${{ github.sha }}-tests-macos-arm64 + overwrite: true + path: build_tests.tar + retention-days: 5 + if-no-files-found: error - name: Pack macOS app bundle ${{ inputs.arch }} if: runner.os == 'macOS' && !inputs.macos-combine-only @@ -335,11 +353,28 @@ jobs: # env: # WindowsSdkDir: 'C:\Program Files (x86)\Windows Kits\10\' # WindowsSDKVersion: '10.0.26100.0\' + # "tests" builds the unit tests too; the unit_tests_windows_* jobs run them. run: | $arch = "${{ inputs.arch }}" - if ($arch -eq "arm64") { .\build_release_vs.bat slicer arm64 } else { .\build_release_vs.bat slicer } + if ($arch -eq "arm64") { .\build_release_vs.bat slicer arm64 tests } else { .\build_release_vs.bat slicer tests } shell: pwsh + - name: Pack unit tests Win + if: runner.os == 'Windows' + working-directory: ${{ github.workspace }} + shell: pwsh + run: tar -cvf build_tests.tar ${{ env.BUILD_DIR }}/tests + + - name: Upload Test Artifact Win + if: runner.os == 'Windows' + uses: actions/upload-artifact@v7 + with: + name: ${{ github.sha }}-tests-windows-${{ inputs.arch }} + overwrite: true + path: build_tests.tar + retention-days: 5 + if-no-files-found: error + # NSIS is x86-only; it runs (and the installer it emits runs) under ARM64's # x86 emulation, packaging the native arm64 payload from build-arm64. - name: Create installer Win @@ -452,26 +487,22 @@ jobs: if: runner.os == 'Linux' shell: bash run: | - # Build + tar the unit tests (-t) only on the leg that runs them: the - # aarch64 leg by default (faster GitHub arm runner), or amd64 when using - # self-hosted runners (no arm self-hosted server). unit_tests downloads - # this tarball. The profile validator is built with -s, so amd64 keeps it. - tests=${{ (!vars.SELF_HOSTED && inputs.arch == 'aarch64') || (vars.SELF_HOSTED && inputs.arch != 'aarch64') }} - if $tests; then flags=-istrlL; else flags=-isrlL; fi - ./build_linux.sh "$flags" + # Build + tar the unit tests (-t) on both Linux legs so each arch + # (x86_64 + aarch64) gets tested by its own unit_tests_linux_* job. + ./build_linux.sh -istrlL ./scripts/check_appimage_libs.sh ./build/package ./build/package/bin/orca-slicer appimage=./build/OrcaSlicer_Linux_AppImage${{ env.ubuntu-ver-str }}${{ env.arch_suffix }}_${{ env.ver }}.AppImage mv -n ./build/OrcaSlicer_Linux_V${{ env.ver_pure }}.AppImage "$appimage" chmod +x "$appimage" - if $tests; then tar -cvpf build_tests.tar build/tests; fi + tar -cvf build_tests.tar build/tests # Use tar because upload-artifacts won't always preserve directory structure # and doesn't preserve file permissions - name: Upload Test Artifact - if: runner.os == 'Linux' && ((!vars.SELF_HOSTED && inputs.arch == 'aarch64') || (vars.SELF_HOSTED && inputs.arch != 'aarch64')) + if: runner.os == 'Linux' uses: actions/upload-artifact@v7 with: - name: ${{ github.sha }}-tests + name: ${{ github.sha }}-tests-linux-${{ inputs.arch == 'aarch64' && 'aarch64' || 'x86_64' }} overwrite: true path: build_tests.tar retention-days: 5 diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml new file mode 100644 index 0000000000..0fe91c7440 --- /dev/null +++ b/.github/workflows/unit_tests.yml @@ -0,0 +1,73 @@ +name: Unit Tests + +# Download a platform's test artifact, run ctest, and upload the JUnit +# results for aggregation. Called once per arch from build_all.yml. +on: + workflow_call: + inputs: + os: + required: true + type: string + artifact: + description: Test artifact uploaded by the build leg + required: true + type: string + test-dir: + description: Built tests dir; defaults to build/tests, override for arch-separated builds + required: false + type: string + default: build/tests + +jobs: + unit_tests: + # Static "Unit Tests"; the per-arch label is the caller's job name, so the + # graph shows e.g. "Windows x64 / Unit Tests". + name: Unit Tests + runs-on: ${{ inputs.os }} + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + # tests/data is referenced by absolute path (TEST_DATA_DIR). + sparse-checkout: | + .github + scripts + tests + - name: Apt-Install Dependencies + if: runner.os == 'Linux' && !vars.SELF_HOSTED + uses: ./.github/actions/apt-install-deps + - name: Restore Test Artifact + uses: actions/download-artifact@v8 + with: + name: ${{ inputs.artifact }} + - uses: lukka/get-cmake@latest + with: + cmakeVersion: "~4.3.0" # use most recent 4.3.x version + useLocalCache: true + useCloudCache: true + - name: Unpackage and Run Unit Tests + timeout-minutes: 20 + shell: bash + run: | + tar -xvf build_tests.tar + # Multi-config generators (Windows/macOS) need a config; Linux is single-config. + scripts/run_unit_tests.sh "${{ inputs.test-dir }}" "${{ runner.os != 'Linux' && 'Release' || '' }}" + - name: Upload Test Logs + if: ${{ failure() }} + uses: actions/upload-artifact@v7 + with: + name: unit-test-logs-${{ inputs.artifact }} + path: ${{ inputs.test-dir }}/**/*.log + - name: Upload Test Results + if: always() + uses: actions/upload-artifact@v7 + with: + name: test-results-${{ inputs.artifact }} + path: ctest_results.xml + retention-days: 5 + if-no-files-found: warn + - name: Delete Test Artifact + if: success() + uses: geekyeggo/delete-artifact@v6 + with: + name: ${{ inputs.artifact }} diff --git a/build_release_macos.sh b/build_release_macos.sh index 8d417695ad..1a86d2c515 100755 --- a/build_release_macos.sh +++ b/build_release_macos.sh @@ -59,7 +59,7 @@ while getopts ":dpa:snt:xbc:i:1Tuh" opt; do echo " -c: Set CMake build configuration, default is Release" echo " -i: Add a prefix to ignore during CMake dependency discovery (repeatable), defaults to /opt/local:/usr/local:/opt/homebrew" echo " -1: Use single job for building" - echo " -T: Build and run tests" + echo " -T: Build and run tests (set ORCA_TESTS_BUILD_ONLY=1 to build without running)" exit 0 ;; * ) @@ -209,13 +209,10 @@ function build_slicer() { cmake --build . --config "$BUILD_CONFIG" --target "$SLICER_BUILD_TARGET" ) - if [ "1." == "$BUILD_TESTS". ]; then - echo "Running tests for $_ARCH..." - ( - set -x - cd "$PROJECT_BUILD_DIR" - ctest --build-config "$BUILD_CONFIG" --output-on-failure - ) + # -T also runs the tests; ORCA_TESTS_BUILD_ONLY=1 builds them without + # running, so CI can build here and run them in a dedicated job. + if [ "1." == "$BUILD_TESTS". ] && [ "1." != "$ORCA_TESTS_BUILD_ONLY". ]; then + "$PROJECT_DIR/scripts/run_unit_tests.sh" "build/$_ARCH/tests" "$BUILD_CONFIG" fi echo "Verify localization with gettext..." diff --git a/build_release_vs.bat b/build_release_vs.bat index 0c1334d5f7..3288beda4c 100644 --- a/build_release_vs.bat +++ b/build_release_vs.bat @@ -20,6 +20,12 @@ for %%a in (%*) do ( if "%%a"=="-x" set USE_NINJA=1 ) +@REM Check for unit-tests option ("tests") +set BUILD_TESTS=OFF +for %%a in (%*) do ( + if /I "%%a"=="tests" set BUILD_TESTS=ON +) + if "%USE_NINJA%"=="1" ( echo Using Ninja Multi-Config generator set CMAKE_GENERATOR="Ninja Multi-Config" @@ -145,10 +151,10 @@ cd %build_dir% echo on set CMAKE_POLICY_VERSION_MINIMUM=3.5 if "%USE_NINJA%"=="1" ( - cmake .. -G %CMAKE_GENERATOR% -DORCA_TOOLS=ON %SIG_FLAG% -DCMAKE_BUILD_TYPE=%build_type% + cmake .. -G %CMAKE_GENERATOR% -DORCA_TOOLS=ON %SIG_FLAG% -DBUILD_TESTS=%BUILD_TESTS% -DCMAKE_BUILD_TYPE=%build_type% cmake --build . --config %build_type% --target ALL_BUILD ) else ( - cmake .. -G %CMAKE_GENERATOR% -A %arch% -DORCA_TOOLS=ON %SIG_FLAG% -DCMAKE_BUILD_TYPE=%build_type% + cmake .. -G %CMAKE_GENERATOR% -A %arch% -DORCA_TOOLS=ON %SIG_FLAG% -DBUILD_TESTS=%BUILD_TESTS% -DCMAKE_BUILD_TYPE=%build_type% cmake --build . --config %build_type% --target ALL_BUILD -- -m ) @echo off diff --git a/scripts/run_unit_tests.sh b/scripts/run_unit_tests.sh index bd9e969227..f6a01dd3e0 100755 --- a/scripts/run_unit_tests.sh +++ b/scripts/run_unit_tests.sh @@ -4,11 +4,21 @@ # It should only require the directories build/tests, scripts/, and tests/ to function, # and cmake (with ctest) installed. # (otherwise, update the workflow too, but try to avoid to keep things self-contained) +# +# Usage: run_unit_tests.sh [TEST_DIR] [BUILD_CONFIG] +# TEST_DIR directory containing the built tests (default: build/tests) +# BUILD_CONFIG configuration to run; required for multi-config generators +# (Windows/macOS), harmless/omitted for single-config (Linux). ROOT_DIR="$(dirname "$0")/.." cd "${ROOT_DIR}" || exit 1 +TEST_DIR="${1:-build/tests}" +BUILD_CONFIG="${2:-}" + # Run the whole suite, excluding tests tagged [NotWorking]. # --no-tests=error fails the job if the filter matches nothing (instead of passing green). -ctest --test-dir build/tests -LE "NotWorking" --no-tests=error --output-junit "$(pwd)/ctest_results.xml" --output-on-failure -j +args=(--test-dir "${TEST_DIR}" -LE "NotWorking" --no-tests=error --output-junit "$(pwd)/ctest_results.xml" --output-on-failure -j) +[ -n "${BUILD_CONFIG}" ] && args+=(--build-config "${BUILD_CONFIG}") +ctest "${args[@]}"