Skip to content

test: add a concurrency stress suite - #376

Merged
Chris-Wolfgang merged 1 commit into
mainfrom
test/concurrency-stress
Sep 26, 2026
Merged

Chris-Wolfgang merged 1 commit into
mainfrom
test/concurrency-stress

Conversation

@Chris-Wolfgang

Copy link
Copy Markdown
Owner

Summary

This PR is the Try-Pattern slice of #175: a new tests/Wolfgang.TryPattern.Tests.Concurrency project (net10.0) that runs concurrent callers through the real thread pool.

Test Invariant
Run_Func_when_called_concurrently_each_caller_gets_its_own_value N concurrent Try.Run<T> callers each get their own value
Run_Action_when_failing_concurrently_each_caller_gets_its_own_message Concurrent failures each carry their own ErrorMessage
RunAsync_Func_when_success_and_failure_interleave_outcomes_stay_with_their_caller Mixed success and failure, with Task.Yield so continuations resume on other pool threads
RunAsync_Action_when_cancellation_races_scheduling_… A cancel racing Task.Run has exactly two legal outcomes: canceled and the action never ran, or ran once and succeeded
RunAsync_Func_when_cancellation_races_the_token_check_… The same invariant for RunAsync<T>'s ThrowIfCancellationRequested-then-invoke window

Why not Coyote: Try is static and Result is immutable, so there is no shared state for a schedule explorer to find. This follows IAsyncEnumerable-Extensions' concurrency suite, which made the same call.

STRESS_ITERATIONS scales the rounds. The default of 100 runs in every PR's Stage 1 in about 0.5 s. The weekly schedule comes in a separate PR, because workflow files are protected.

Verification

  • dotnet build -c Release -p:TreatWarningsAsErrors=true: 0 errors.
  • Default 100 rounds: 5/5 passed. Both race outcomes actually occurred: the cancel beat Task.Run in 19 of 100 rounds and beat the token check in 3 of 100.
  • STRESS_ITERATIONS=5000: 5/5 passed in about 2 s. The cancel won in 2458 and 46 rounds respectively.
  • Coverage with coverlet.runsettings: Wolfgang.TryPattern.Tests.Concurrency is at 100%. There are no failure-only branches; each test records its outcomes and asserts once.

Refs #175

🤖 Generated with Claude Code

tests/Wolfgang.TryPattern.Tests.Concurrency (net10.0) runs concurrent
callers through the real thread pool:

- Run / RunAsync callers never see each other's value or error message,
  including async continuations that resume on another pool thread.
- A cancellation racing RunAsync(Action)'s Task.Run, or RunAsync<T>'s
  token check, has exactly two legal outcomes: canceled and the delegate
  never ran, or the delegate ran once and the call succeeded.

STRESS_ITERATIONS scales the rounds (default 100, so it runs in every PR
at ~0.5 s). Both race outcomes are observed at the default. No
failure-only branches, so the assembly is at 100% line coverage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant