diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c3ee033f..454cef24b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -301,14 +301,22 @@ jobs: # reason. They run after a failed first pass (not after a failed build), so the report sees # every pass, and they leave the HTML report upload to the first pass, whose artifact names # (assembly, OS, framework) they would repeat. + # + # Every pass ends a hung module inside its step under a session --timeout; the listing below, + # written only when the main pass fails, narrows the rows in flight (quality-gates.md). - name: Test - run: dotnet test --configuration Release --no-build --max-parallel-test-modules ${{ matrix.test-modules }} -- --report-trx --results-directory "${{ github.workspace }}/test-results/main" --treenode-filter "/*/*/*/*[(Category!=ServiceElection)&(Category!=NativeAot)]" --minimum-expected-tests 1 + id: test + run: dotnet test --configuration Release --no-build --max-parallel-test-modules ${{ matrix.test-modules }} -- --report-trx --results-directory "${{ github.workspace }}/test-results/main" --treenode-filter "/*/*/*/*[(Category!=ServiceElection)&(Category!=NativeAot)]" --minimum-expected-tests 1 --timeout 25m env: TUNIT_ARTIFACT_RETENTION_DAYS: 7 + - name: List tests (main, after a failed pass) + if: ${{ !cancelled() && steps.test.outcome == 'failure' }} + run: dotnet test --configuration Release --no-build --list-tests -- --treenode-filter "/*/*/*/*[(Category!=ServiceElection)&(Category!=NativeAot)]" > "${{ github.workspace }}/test-results/main/listed-tests.txt" + - name: Test (service elections) if: ${{ !cancelled() && steps.build.outcome == 'success' }} - run: dotnet test --project "${{ github.workspace }}/tests/OpenCode.Sdk.Tests/OpenCode.Sdk.Tests.csproj" --configuration Release --no-build --max-parallel-test-modules 1 -- --report-trx --results-directory "${{ github.workspace }}/test-results/service-elections" --treenode-filter "/*/*/*/*[Category=ServiceElection]" --minimum-expected-tests 1 + run: dotnet test --project "${{ github.workspace }}/tests/OpenCode.Sdk.Tests/OpenCode.Sdk.Tests.csproj" --configuration Release --no-build --max-parallel-test-modules 1 -- --report-trx --results-directory "${{ github.workspace }}/test-results/service-elections" --treenode-filter "/*/*/*/*[Category=ServiceElection]" --minimum-expected-tests 1 --timeout 25m env: TUNIT_ARTIFACT_RETENTION_DAYS: 7 TUNIT_DISABLE_ARTIFACT_UPLOAD: true @@ -318,7 +326,7 @@ jobs: # host's native toolchain and runs the host's own runtime identifier. - name: Test (native AOT) if: ${{ !cancelled() && steps.build.outcome == 'success' }} - run: dotnet test --project "${{ github.workspace }}/tests/OpenCode.Sdk.Tools.Tests/OpenCode.Sdk.Tools.Tests.csproj" --configuration Release --no-build --max-parallel-test-modules 1 -- --report-trx --results-directory "${{ github.workspace }}/test-results/native-aot" --treenode-filter "/*/*/*/*[Category=NativeAot]" --minimum-expected-tests 1 + run: dotnet test --project "${{ github.workspace }}/tests/OpenCode.Sdk.Tools.Tests/OpenCode.Sdk.Tools.Tests.csproj" --configuration Release --no-build --max-parallel-test-modules 1 -- --report-trx --results-directory "${{ github.workspace }}/test-results/native-aot" --treenode-filter "/*/*/*/*[Category=NativeAot]" --minimum-expected-tests 1 --timeout 25m env: TUNIT_ARTIFACT_RETENTION_DAYS: 7 TUNIT_DISABLE_ARTIFACT_UPLOAD: true diff --git a/docs/engineering/quality-gates.md b/docs/engineering/quality-gates.md index ec100c9f3..335290bdb 100644 --- a/docs/engineering/quality-gates.md +++ b/docs/engineering/quality-gates.md @@ -230,6 +230,13 @@ no unexpected or duplicate modules. The roster is deliberately explicit rather t cannot shrink together with the coverage it guards — so adding or removing a test project or a test target framework updates it in the same change. +Every CI test pass runs under a 25-minute session `--timeout`, about four times the slowest +measured module (`OpenCode.Sdk.Tests` on net472 on Windows, under six minutes), so a hung module +fails inside its step instead of at the job timeout. An expired session exits with code 7 and keeps +the TRX of the rows that finished, but it does not name the rows still in flight. When the main +pass fails, the job also writes that pass's test listing to `test-results/main/listed-tests.txt`, +and the rows in flight are among those listed but not recorded. + ## Clone gate The detector ignores using directives; a baseline entry that matches no clone fails the gate like a new clone does.