.NET 11 adds run-level --timeout and --maximum-failed-tests policies to dotnet test in Microsoft Testing Platform mode. Unlike options passed to one test application, these budgets control the entire multi-project invocation. They let CI stop a hung repository or a cascading failure storm at a predictable boundary.
Table of Contents
Why per-project limits are not enough
A repository-level invocation can discover and execute many test applications. A timeout inside one project may stop that project while other projects continue. A maximum-failures extension configured separately in every assembly can also produce inconsistent behavior when CI changes parallelism or discovery order.
The .NET 11 options sit before the -- separator and apply to the whole dotnet test run. The timeout aborts the overall invocation. The failure budget counts failures across all test applications and stops scheduling when the threshold is reached. This makes the repository command the policy boundary.
Run-level budgets are particularly useful for pull-request validation, where the goal is fast feedback. A broken shared fixture or environment can otherwise generate hundreds of equivalent failures, consume the whole runner allocation, and hide the first actionable error.
Confirm Microsoft Testing Platform mode
The new policies apply when dotnet test runs through Microsoft Testing Platform. .NET 11 can select the runner through global.json or the DOTNET_TEST_RUNNER environment variable. Do not add the options to CI until the selected runner is explicit and visible in logs.
env:
DOTNET_TEST_RUNNER: Microsoft.Testing.Platform
steps:
- name: Record SDK and runner
run: |
dotnet --info
echo "DOTNET_TEST_RUNNER=$DOTNET_TEST_RUNNER"
Pin the .NET 11 Preview SDK in an isolated evaluation branch or image. The existing production .NET migration checklist explains why SDK selection must be deterministic across developer machines and CI; apply the same discipline to Preview evaluation.
Set a whole-run timeout
The run-level timeout is an outer deadline. It protects against deadlocked tests, hung containers, stalled discovery, and a repository whose aggregate duration exceeds its CI service-level objective.
dotnet test Repo.Tests.slnx \
--configuration Release \
--no-build \
--timeout 20m
Choose the value from observed healthy duration, not from the CI job’s maximum. If healthy pull-request runs finish in six to nine minutes, a 20-minute test deadline gives room for normal variance while stopping abnormal work well before a 60-minute runner timeout.
Keep an independent job timeout a few minutes larger so the test process has time to terminate and artifact-upload steps can run. If both timeouts expire together, the CI platform may kill the job before logs and test results are collected.
Cap the failure storm
--maximum-failed-tests stops a noisy run after a repository-wide threshold. Microsoft documents exit code 13 when this policy ends the run. Treat that code as a failed test stage, while preserving the distinction in telemetry so the team can see that the result is intentionally incomplete.
dotnet test Repo.Tests.slnx \
--configuration Release \
--no-build \
--timeout 20m \
--maximum-failed-tests 25
A value of one gives fast fail but often hides independent failures caused by the same change. A very high value saves little time. Start from failure-history data: enough failures to reveal whether several subsystems broke, low enough to prevent repeated fixture or connection errors from flooding the run.
Do not use the threshold to normalize a flaky suite. A run that stops at 25 failures is not “mostly passed.” The policy reduces waste; every observed failure still blocks the change.
Keep diagnostics when the run stops early
An early stop is useful only if the first failures remain actionable. Write results and logs to a stable artifacts directory, and run artifact upload with the CI platform’s “always” condition. .NET 11 also adds --artifacts-path support for dotnet test in MTP mode.
- name: Test with repository budgets
run: >-
dotnet test Repo.Tests.slnx
--configuration Release
--no-build
--artifacts-path artifacts/test
--timeout 20m
--maximum-failed-tests 25
- name: Upload test artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: artifacts/test
if-no-files-found: warn
Capture the command exit code before running cleanup or formatting commands that might overwrite it. Summarize whether the run completed, hit the failure budget, hit the timeout, or was canceled externally. These are different operational signals even though they all fail the pull request.
Layer budgets instead of replacing test controls
The repository timeout is not a replacement for test-level timeouts. A single test that should finish in two seconds should not be allowed to consume the entire 20-minute run budget. Keep focused timeouts near external calls, container startup, browser actions, and other operations with known bounds.
- Operation timeout: protects one network or process operation.
- Test timeout: identifies one hung test with a useful name.
- Run timeout: protects the complete repository invocation.
- Job timeout: protects the runner, cleanup, and artifact workflow.
Keep cancellation cooperative. Tests and fixtures should accept cancellation where supported, terminate child processes, dispose containers, and avoid unbounded retry loops. An outer timeout is a final containment boundary, not a substitute for clean test infrastructure.
Roll the policy into CI safely
Begin in observation mode. Record healthy p50 and p95 durations, failure counts, and artifact completeness for several days. Choose initial budgets above healthy variance, then enable them on pull requests before scheduled full-suite runs.
Run a deliberate canary: add a quarantined test project that blocks until cancellation and another that emits more failures than the threshold. Verify the run stops, returns the expected classification, uploads partial results, and does not leave child processes on a self-hosted runner. Remove the canary from normal discovery after the proof.
Keep nightly or release validation broader when it serves a different purpose. A pull-request budget optimized for feedback speed may be too aggressive for serial integration tests or multiple target frameworks. Policy should follow the stage, not be copied blindly.
CI checklist
- Pin the .NET 11 SDK in an isolated evaluation environment.
- Select Microsoft Testing Platform explicitly.
- Measure healthy whole-run duration and failure counts.
- Set the test deadline below the CI job timeout.
- Choose a failure budget that preserves useful diversity.
- Upload partial logs and results with an always condition.
- Classify timeout, failure-budget, cancellation, and normal failure separately.
- Canary both policies before enforcing them on the main branch.
The value of these .NET 11 options is not a shorter command. It is one repository-level contract: CI will gather enough evidence to diagnose a change, but it will not spend unlimited time or output proving that the run is already broken.