Skip to content

perf: replace LINQ with loops in test execution hot paths (#4855)#4910

Closed
thomhurst wants to merge 1 commit into
mainfrom
perf/linq-hot-paths
Closed

perf: replace LINQ with loops in test execution hot paths (#4855)#4910
thomhurst wants to merge 1 commit into
mainfrom
perf/linq-hot-paths

Conversation

@thomhurst

Copy link
Copy Markdown
Owner

Summary

  • Replace LINQ chains (Where().ToList(), .Values.ToArray(), .Any()) with simple foreach loops in test execution hot paths to reduce allocations
  • Merge two-pass filter+check patterns into single-pass loops in TestContext.Dependencies
  • Pre-allocate List<Task> with known capacity in ObjectLifecycleService

Closes #4855

Changes

TUnit.Engine/Services/TestDependencyResolver.cs

  • uniqueDependencies.Values.ToArray() → pre-allocated ResolvedDependency[] with manual copy (2 occurrences)
  • tests.Where(t => ...).ToList()foreach loop with conditional Add

TUnit.Core/TestContext.Dependencies.cs

  • GetTests(predicate): merged .Where(predicate).ToList() + .Any(x => x.Result == null) two-pass into single-pass foreach loop
  • GetTests(testName) and GetTests(testName, classType): removed redundant .ToList() on TestContext[] return; replaced .Any() check with foreach loop

TUnit.Engine/Services/ObjectLifecycleService.cs

  • new List<Task>()new List<Task>(objectsAtDepth.Count) to pre-allocate with known capacity

Test plan

  • dotnet build TUnit.Engine/TUnit.Engine.csproj — builds successfully (0 warnings, 0 errors)
  • dotnet build TUnit.Core/TUnit.Core.csproj — builds successfully (0 warnings, 0 errors)
  • CI passes all existing tests (no behavioral changes, only allocation reduction)

Replace LINQ chains (Where/ToList/ToArray/Any) with simple foreach loops
in hot paths to reduce allocations during test discovery and execution:

- TestDependencyResolver: Replace .Values.ToArray() with pre-allocated
  array copy; replace Where().ToList() with foreach loop
- TestContext.Dependencies: Merge Where().ToList() + Any() two-pass into
  single-pass foreach loop; eliminate redundant .ToList() on array returns
- ObjectLifecycleService: Pre-allocate List<Task> with known capacity
@claude

claude Bot commented Feb 19, 2026

Copy link
Copy Markdown
Contributor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.


This is a clean, well-executed performance optimization. A few notes on what was verified:

Correctness: All LINQ-to-loop conversions faithfully replicate the original behavior:

  • The single-pass foreach with hasUnfinished flag in TestContext.Dependencies.cs correctly replicates the original two-pass .Where(predicate).ToList() + .Any(x => x.Result == null) — the flag is scoped inside the predicate check, so it only tracks unfinished tests that matched the filter.
  • The manual array filling in TestDependencyResolver.cs uses uniqueDependencies.Count as both the array size and the implicit iteration count, so no IndexOutOfRangeException is possible.
  • The objectsAtDepth.Count pre-allocation in ObjectLifecycleService.cs is a safe hint — List<T>.Add would resize gracefully if the count were ever wrong.

CLAUDE.md compliance:

  • Rule 1 (Dual-Mode): Not applicable — all changed files operate post-metadata-collection in the execution/dependency-resolution path, which is the documented unified code path.
  • Rule 4 (Performance First): The PR is directly aligned with this rule. Eliminating intermediate LINQ allocations in hot paths is exactly what the rule asks for.
  • Rule 5 (AOT Compatible): No reflection introduced; no [DynamicallyAccessedMembers] needed.
  • No .Result/.GetAwaiter().GetResult() blocking calls introduced.

@thomhurst thomhurst closed this Feb 19, 2026
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.

perf: replace LINQ with loops in test execution hot paths

1 participant