mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-10 03:14:14 -04:00
ci: run cross-platform suites and synthetic gate in parallel
This commit is contained in:
1 parent
761cee7c1f
commit
c989ba6fd0
4 files changed
+129
-12
No files matched your search
@@ -9,9 +9,19 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
unit-tests:
|
||||
name: Unit tests (${{ matrix.project }})
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
project:
|
||||
- OpenNest.Tests
|
||||
- OpenNest.Engine.Tests
|
||||
- OpenNest.IO.Tests
|
||||
- OpenNest.Server.Tests
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
@@ -19,16 +29,41 @@ jobs:
|
||||
- uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
- name: Run OpenNest.Tests
|
||||
run: dotnet test OpenNest.Tests/OpenNest.Tests.csproj
|
||||
- name: Run OpenNest.Engine.Tests
|
||||
run: dotnet test OpenNest.Engine.Tests/OpenNest.Engine.Tests.csproj
|
||||
- name: Run ${{ matrix.project }}
|
||||
run: dotnet test "${{ matrix.project }}/${{ matrix.project }}.csproj"
|
||||
|
||||
synthetic-nests:
|
||||
name: Six synthetic Irregular nests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
- name: Test fail-closed final CI check
|
||||
run: python3 scripts/test_ci_results.py -v
|
||||
- name: Check six synthetic Irregular nests
|
||||
timeout-minutes: 6
|
||||
run: |
|
||||
python3 scripts/test_check_synthetic_nests.py -v
|
||||
python3 scripts/check-synthetic-nests.py --parallel 2
|
||||
- name: Run OpenNest.IO.Tests
|
||||
run: dotnet test OpenNest.IO.Tests/OpenNest.IO.Tests.csproj
|
||||
- name: Run OpenNest.Server.Tests
|
||||
run: dotnet test OpenNest.Server.Tests/OpenNest.Server.Tests.csproj
|
||||
|
||||
# Preserve the existing check name for required-status consumers.
|
||||
tests:
|
||||
name: tests
|
||||
needs: [unit-tests, synthetic-nests]
|
||||
if: ${{ always() }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Require every test job to succeed
|
||||
env:
|
||||
UNIT_TEST_RESULT: ${{ needs.unit-tests.result }}
|
||||
SYNTHETIC_RESULT: ${{ needs.synthetic-nests.result }}
|
||||
run: bash scripts/check-ci-results.sh "$UNIT_TEST_RESULT" "$SYNTHETIC_RESULT"
|
||||
@@ -73,9 +73,21 @@ The unit checks use labeled synthetic CSV rows and a real watchdog process-tree
|
||||
probe. The end-to-end command uses actual benchmark results and the production
|
||||
independent layout validator. This gate supplements, never replaces, the existing
|
||||
geometry, contract, cancellation, sequencing, posting and cross-platform unit suites.
|
||||
GitHub's cross-platform workflow runs both the checker tests and the six-fixture gate.
|
||||
Windows runtime acceptance is separate; a Linux pass does not certify Windows tree
|
||||
termination or desktop/shop interactions.
|
||||
GitHub's cross-platform workflow runs both the checker tests and the six-fixture gate
|
||||
in a separate job, concurrently with four independent unit-suite jobs (main, Engine,
|
||||
IO and Server). Unit jobs retain the default Debug configuration and do not cancel
|
||||
siblings on failure. The final `tests` check requires the entire unit matrix and
|
||||
synthetic job to succeed; failed, cancelled or skipped dependencies cannot produce
|
||||
a green final check. This preserves the existing check name without changing branch
|
||||
protection or removing tests. `python3 scripts/test_ci_results.py -v` exercises the
|
||||
same fail-closed aggregate command used by CI. The six-minute step limit and
|
||||
`--parallel 2` synthetic-runner limit are unchanged.
|
||||
|
||||
This fan-out removes inter-suite serialization, not individual geometry-test work.
|
||||
It uses additional independent runners and repeats restore/build setup; actual wall
|
||||
time depends on the slowest job and runner queueing. It is not a CPU speed claim or
|
||||
a reason to skip correctness fixtures. Windows runtime acceptance is separate; a
|
||||
Linux pass does not certify Windows tree termination or desktop/shop interactions.
|
||||
|
||||
## Validity is not speed
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
# The stable final CI check succeeds only when both required job groups succeeded.
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -ne 2 ]]; then
|
||||
printf 'Expected unit-test and synthetic-nest job results.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$1" != success || "$2" != success ]]; then
|
||||
printf 'Required CI jobs did not both succeed (unit=%s, synthetic=%s).\n' "$1" "$2" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'All required unit suites and the synthetic-nest gate passed.\n'
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fail-closed tests for the exact command used by the final CI check."""
|
||||
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import unittest
|
||||
|
||||
|
||||
CHECK = Path(__file__).resolve().with_name("check-ci-results.sh")
|
||||
|
||||
|
||||
class CIResultsTests(unittest.TestCase):
|
||||
def check(self, *results):
|
||||
return subprocess.run(
|
||||
["bash", str(CHECK), *results],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5,
|
||||
)
|
||||
|
||||
def test_both_required_jobs_successful(self):
|
||||
self.assertEqual(self.check("success", "success").returncode, 0)
|
||||
|
||||
def test_unit_matrix_failure_blocks_aggregate(self):
|
||||
self.assertNotEqual(self.check("failure", "success").returncode, 0)
|
||||
|
||||
def test_synthetic_failure_blocks_aggregate(self):
|
||||
self.assertNotEqual(self.check("success", "failure").returncode, 0)
|
||||
|
||||
def test_cancelled_dependency_blocks_aggregate(self):
|
||||
for results in [("cancelled", "success"), ("success", "cancelled")]:
|
||||
with self.subTest(results=results):
|
||||
self.assertNotEqual(self.check(*results).returncode, 0)
|
||||
|
||||
def test_skipped_dependency_blocks_aggregate(self):
|
||||
for results in [("skipped", "success"), ("success", "skipped")]:
|
||||
with self.subTest(results=results):
|
||||
self.assertNotEqual(self.check(*results).returncode, 0)
|
||||
|
||||
def test_empty_unknown_and_nonliteral_results_block_aggregate(self):
|
||||
for result in ["", "unknown", "SUCCESS", "success failure", "success\nsuccess"]:
|
||||
with self.subTest(result=result):
|
||||
self.assertNotEqual(self.check(result, "success").returncode, 0)
|
||||
self.assertNotEqual(self.check("success", result).returncode, 0)
|
||||
|
||||
def test_missing_required_result_blocks_aggregate(self):
|
||||
self.assertNotEqual(self.check().returncode, 0)
|
||||
self.assertNotEqual(self.check("success").returncode, 0)
|
||||
|
||||
def test_extra_result_blocks_aggregate(self):
|
||||
self.assertNotEqual(self.check("success", "success", "success").returncode, 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user