ci: run unit tests on Windows and macOS (#14443)

* ci: run unit tests on Windows and macOS

The Unit Tests CI job only ran on Linux, so platform-specific bugs
invisible to a Linux build could land undetected (e.g. the MSVC-only
NfpPlacer crash fixed in #14267). The full non-[NotWorking] suite already
builds and passes on every shipped arch, so this wires them into CI.

A reusable unit_tests.yml, called once per built arch, downloads that
arch's test artifact and runs ctest. Each build leg builds the test
executables and uploads them; a single publish_test_results job on Linux
aggregates the JUnit results into one check.

Coverage: Linux x86_64 + aarch64, Windows x64 + arm64, macOS arm64.
macOS x86_64 is deferred (cross-built on arm64, needs Rosetta).

- build_release_vs.bat: "tests" token enables BUILD_TESTS
- build_release_macos.sh: -T builds and runs tests; ORCA_TESTS_BUILD_ONLY
  builds them without running (used by CI)
- scripts/run_unit_tests.sh: parameterized test dir and build config

Addresses #11273.

* ci: bump actions/checkout to v7 in reusable unit_tests workflow

Match the actions/checkout v6->v7 bump (#14517) that upstream applied to
the inline test job this reusable workflow replaces.
This commit is contained in:
Kris Austin
2026-07-08 09:00:35 -05:00
committed by GitHub
parent b58025575d
commit 781ecdc2c1
6 changed files with 196 additions and 60 deletions

View File

@@ -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:

View File

@@ -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

73
.github/workflows/unit_tests.yml vendored Normal file
View File

@@ -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 }}

View File

@@ -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..."

View File

@@ -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

View File

@@ -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[@]}"