Skip to content

perf(core): de-LINQ data-source expansion#6110

Merged
thomhurst merged 2 commits into
mainfrom
perf/issue-6104-delinq-datasource
May 28, 2026
Merged

perf(core): de-LINQ data-source expansion#6110
thomhurst merged 2 commits into
mainfrom
perf/issue-6104-delinq-datasource

Conversation

@thomhurst
Copy link
Copy Markdown
Owner

What

De-LINQs the hot data-source expansion paths in TUnit.Core, replacing allocation-heavy LINQ with explicit loops. Row content and order are identical in both source-gen and reflection modes.

  1. Matrix / Combined Cartesian product (MatrixDataSourceAttribute.GetMatrixValues, CombinedDataSourcesAttribute.GetCartesianProduct): the Aggregate(seed, (acc,e)=>acc.SelectMany(x=>e.Select(x.Append))) chains are replaced with an odometer-style product loop that materializes object?[] rows directly. The last dimension varies fastest, exactly matching the previous Aggregate/SelectMany ordering. The downstream row.ToArray() per factory is also removed.
  2. MatrixDataSourceAttribute.GetExclusions: Where/Cast/Select/ToArray -> a single foreach with a null-initialized list (no list allocation when there are no exclusions). The per-row exclusions.Any(...) is also unrolled to a foreach, dropping a per-row closure allocation.
  3. DataGeneratorMetadataCreator: the O(N×M) FirstOrDefault property scan becomes a name -> PropertyMetadata Dictionary (StringComparer.Ordinal) built once, giving O(1) lookups.
  4. MethodDataSourceAttribute.IsAsyncEnumerable: caches the GetInterfaces() probe per Type in a ConcurrentDictionary<Type,bool> instead of re-walking interfaces (which allocates an array) on every call.

Item 5 from the issue (DataSourceHelpers.cs Select((_,i)=>...) closure chains) was already converted to manual for loops in a prior commit — no LINQ remained at those sites.

Why

These run during data-source expansion for every parameterised test. The LINQ chains allocate iterators, closures, and intermediate arrays per row; the property scan is quadratic; GetInterfaces() allocates on every async-enumerable check. The replacements keep behaviour identical while cutting allocations and avoiding repeated work.

Validation

  • TUnit.Core builds clean across netstandard2.0;net8.0;net9.0;net10.0 — 0 warnings / 0 errors.

  • net10.0, both source-gen and reflection modes:

    • MatrixTests 275/275 pass
    • CombinedDataSource* 204/204 pass
    • MixedMatrixTests 864/864 pass
    • MatrixExclusion* 24/24 pass (exercises GetExclusions + IsExcluded)

    These tests assert specific argument values per generated combination, so identical counts across modes confirm both identical expansion and identical ordering.

AOT-compatible (no new reflection; [DynamicallyAccessedMembers(Interfaces)] annotation preserved directly on the GetInterfaces() call site). Minimal allocations. Microsoft.Testing.Platform only.

Closes #6104

Replace allocation-heavy LINQ in the hot data-source expansion paths with
explicit loops. Row content and order are identical in both source-gen and
reflection modes.

- Matrix/Combined Cartesian product: replace the
  `Aggregate(seed, (acc,e)=>acc.SelectMany(x=>e.Select(x.Append)))` chains with
  an odometer-style product loop that materializes `object?[]` rows directly
  (last dimension varies fastest, matching the previous ordering exactly). Also
  removes the downstream `row.ToArray()` per factory.
- Matrix GetExclusions: LINQ Where/Cast/Select/ToArray -> single foreach with a
  null-initialized list (no list alloc when there are no exclusions). The
  per-row `exclusions.Any(...)` is also unrolled to a foreach to drop the
  per-row closure.
- DataGeneratorMetadataCreator: O(N*M) FirstOrDefault property scan -> a
  name->metadata Dictionary (StringComparer.Ordinal) built once for O(1) lookups.
- MethodDataSource.IsAsyncEnumerable: cache the GetInterfaces() probe per Type in
  a ConcurrentDictionary instead of re-walking interfaces on every call.

Validated (net10.0, source-gen + reflection): MatrixTests 275/275,
CombinedDataSource 204/204, MixedMatrixTests 864/864, MatrixExclusion 24/24.
TUnit.Core builds clean across netstandard2.0;net8.0;net9.0;net10.0 (0 warnings).

Closes #6104
@codacy-production
Copy link
Copy Markdown

codacy-production Bot commented May 28, 2026

Not up to standards ⛔

🔴 Issues 1 minor

Alerts:
⚠ 1 issue (≤ 0 issues of at least minor severity)

Results:
1 new issue

Category Results
CodeStyle 1 minor

View in Codacy

🟢 Metrics 0 complexity

Metric Results
Complexity 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

Copy link
Copy Markdown
Contributor

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Overall this is a well-executed performance refactoring. The odometer algorithm, exclusion loop unrolling, and dictionary lookup are all correct. Detailed findings below.


Finding 1 — Minor: should use (MethodDataSourceAttribute.cs ~line 387)

Between the TryGetValue miss and the [type] = result write there is a window where N concurrent threads can all miss the cache, each call type.GetInterfaces() (which allocates an array), and then all write the same value. The result is always correct, but the allocation the cache was meant to avoid can still happen many times in parallel. The idiomatic pattern for a "compute once" cache on ConcurrentDictionary is:

GetOrAdd does not guarantee the factory runs exactly once under contention either, but it dramatically narrows the race and is the expected API for this pattern. Callers searching for this pattern in the codebase (e.g. AssemblyReferenceCache) already use GetOrAdd.

Why it matters: this method is called in a hot parameterized-test expansion path and the whole point of the cache is to avoid the GetInterfaces() allocation; under a parallel test run the current pattern can defeat that goal.


Confirmed correct: odometer ordering

Manually traced [A,B] × [1,2] × [X,Y] through both the old Aggregate/SelectMany and the new odometer. Both produce:

Last dimension varies fastest in both. Single-dimension case ([A,B,C][A],[B],[C]) and zero-dimension case (yields one empty row, matching the old _seed = [[]]] base case) are also correct.


Confirmed correct: combination.ToArray() removal

The old () => Task.FromResult(combination.ToArray()) materialised a lazy IEnumerable<object?>. The new GetCartesianProduct yields a freshly allocated object?[] per iteration, so the ToArray() call inside the lambda was redundant. Removing it is safe.


Confirmed correct: closure capture

LangVersion=preview (set in Directory.Build.props) means C# 5+ per-iteration foreach scoping applies — each loop variable (combination, rowArray) is a distinct variable per iteration. Additionally, the lambdas are consumed immediately by TestBuilder (await methodDataFactory() is called within the same iteration), so even under the old semantics there would be no stale-capture issue.


Confirmed correct: dictionary lookup in DataGeneratorMetadataCreator

The allProperties array originates from the source generator via typeSymbol.GetMembers().OfType<IPropertySymbol>(), which returns only directly declared members — never inherited or shadowed duplicates. So propertiesByName[property.Name] = property never silently overwrites a different entry, and the FirstOrDefault → dictionary behaviour change is safe.


Summary: one suggestion (use GetOrAdd); everything else is correct and the performance intent is well-served.

Copy link
Copy Markdown
Contributor

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Overall this is a well-executed performance refactoring. The odometer algorithm, exclusion loop unrolling, and dictionary lookup are all correct. One minor finding below.


Finding 1 — Minor: IsAsyncEnumerableCache should use GetOrAdd (MethodDataSourceAttribute.cs ~line 387)

The current pattern has a race window between the TryGetValue miss and the [type] = result write:

// Current
if (IsAsyncEnumerableCache.TryGetValue(type, out var cached))
    return cached;
// ... compute result ...
IsAsyncEnumerableCache[type] = result;   // ← N threads can reach here concurrently

Between those two lines, N concurrent threads can all miss the cache, each call type.GetInterfaces() (which allocates an array), and then all write the same value. The result is always correct, but the allocation the cache was intended to avoid can still happen many times in parallel. The idiomatic pattern for a "compute once" cache on ConcurrentDictionary is:

return IsAsyncEnumerableCache.GetOrAdd(type, static t =>
{
    foreach (var i in t.GetInterfaces())
        if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IAsyncEnumerable<>))
            return true;
    return false;
});

GetOrAdd does not guarantee the factory runs exactly once under high contention either, but it dramatically narrows the race and is the expected API for this pattern — AssemblyReferenceCache in TUnit.Engine already uses it. Under a parallel test run the current approach can defeat the very goal the cache was added to achieve.


Confirmed correct: odometer ordering

Manually traced [A,B] x [1,2] x [X,Y] through both the old Aggregate/SelectMany and the new odometer. Both produce the same order:

[A,1,X], [A,1,Y], [A,2,X], [A,2,Y], [B,1,X], [B,1,Y], [B,2,X], [B,2,Y]

Last dimension varies fastest in both. Single-dimension and zero-dimension edge cases are also handled correctly (zero dimensions: one empty row yielded, matching the old _seed = [[]] base case).


Confirmed correct: combination.ToArray() removal

The old () => Task.FromResult(combination.ToArray()) materialised a lazy IEnumerable<object?>. The new GetCartesianProduct yields a freshly allocated object?[] per iteration, so the .ToArray() call inside the lambda was redundant overhead. Removing it is safe and correct.


Confirmed correct: closure capture in foreach lambdas

LangVersion=preview (set in Directory.Build.props) gives C# 5+ per-iteration foreach scoping, so each loop variable (combination, rowArray) is a distinct variable per iteration. Additionally, TestBuilder invokes each data-source lambda immediately (await methodDataFactory() within the same iteration), so there is no deferred-invocation risk regardless.


Confirmed correct: dictionary lookup in DataGeneratorMetadataCreator

allProperties comes from the source generator via typeSymbol.GetMembers().OfType<IPropertySymbol>(), which returns only directly declared members — never inherited or shadowed duplicates. So propertiesByName[property.Name] = property never silently overwrites a different entry, and the FirstOrDefault → dictionary switch is a safe, strictly faster replacement.


Summary: one suggestion (use GetOrAdd for IsAsyncEnumerableCache); everything else is correct and the performance intent is well-served.

Replace the check-then-write pattern (TryGetValue + indexer assignment)
with a single GetOrAdd call using a closure-free static factory, matching
the idiom used elsewhere in the codebase. Under parallel test runs the old
pattern let multiple threads all miss the cache and each run the expensive
type.GetInterfaces() scan; GetOrAdd consolidates this. Interface-matching
semantics (IAsyncEnumerable<>) are unchanged.
Copy link
Copy Markdown
Contributor

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — Follow-up

The previous two review comments both flagged that IsAsyncEnumerableCache should use GetOrAdd instead of the two-step TryGetValue + indexer-write pattern. Looking at the current diff, this has already been implemented correctly — the PR uses GetOrAdd(type, static t => { ... }) with a closure-free static factory. That concern is fully addressed.

Verification pass on all four changes

1. Odometer-style Cartesian product (MatrixDataSourceAttribute, CombinedDataSourcesAttribute)

Traced [A,B] x [1,2] x [X,Y] through both the old Aggregate/SelectMany and the new odometer: both produce [A,1,X], [A,1,Y], [A,2,X], [A,2,Y], [B,1,X], [B,1,Y], [B,2,X], [B,2,Y] — last dimension varies fastest in both, ordering preserved correctly.

Zero-dimension edge case (elements.Count == 0): the early-exit loop doesn't fire, indices = new int[0], the while body executes once yielding an empty row, then position = dimensionCount - 1 = -1 triggers yield break. This matches the old _seed = [[]] base case exactly.

Empty-dimension case (elements[i].Count == 0 for any i): the early-exit loop correctly short-circuits with yield break, matching the old SelectMany over an empty source.

2. combination.ToArray() removal (CombinedDataSourcesAttribute ~line 116)

Safe. The new GetCartesianProduct yields a freshly allocated object?[] per iteration — the old .ToArray() was materialising a lazy IEnumerable; now it's already materialised.

3. GetExclusions lazy-list allocation

return exclusions is null ? [] : [.. exclusions] is correct. When there are no exclusions the common case allocates nothing beyond the returned empty array literal (which the compiler can intern). The [.. exclusions] spread on the non-null path allocates a right-sized array, same as the old .ToArray().

4. DataGeneratorMetadataCreator dictionary lookup

allProperties comes from typeSymbol.GetMembers().OfType\<IPropertySymbol>(), which returns only directly declared members — no duplicates from inheritance or shadowing. The propertiesByName[property.Name] = property assignment is therefore safe (no silent overwrite of a different entry), and the O(N) build + O(1) lookup is a correct, strictly faster replacement for the O(N×M) FirstOrDefault scan.

5. IsAsyncEnumerableCache with GetOrAdd + static factory

The [UnconditionalSuppressMessage("Trimming", "IL2070", ...)] annotation is appropriate: the type parameter carries the [DynamicallyAccessedMembers(Interfaces)] annotation on the outer method, and the static factory only forwards that same Type instance, so interfaces are already preserved by the call-site annotation. AOT-clean.

No remaining concerns

All four optimisations are correct, behaviour-preserving, and well-commented. The PR is ready to merge.

@thomhurst thomhurst enabled auto-merge (squash) May 28, 2026 22:53
@thomhurst thomhurst merged commit 6661234 into main May 28, 2026
13 of 14 checks passed
@thomhurst thomhurst deleted the perf/issue-6104-delinq-datasource branch May 28, 2026 23:16
github-actions Bot pushed a commit to BenjaminMichaelis/DotnetTemplates that referenced this pull request Jun 2, 2026
Updated
[Microsoft.Testing.Extensions.CodeCoverage](https://github.com/microsoft/codecoverage)
from 18.6.2 to 18.7.0.

<details>
<summary>Release notes</summary>

_Sourced from [Microsoft.Testing.Extensions.CodeCoverage's
releases](https://github.com/microsoft/codecoverage/releases)._

No release notes found for this version range.

Commits viewable in [compare
view](https://github.com/microsoft/codecoverage/commits).
</details>

Updated [TUnit](https://github.com/thomhurst/TUnit) from 1.44.39 to
1.48.6.

<details>
<summary>Release notes</summary>

_Sourced from [TUnit's
releases](https://github.com/thomhurst/TUnit/releases)._

## 1.48.6

<!-- Release notes generated using configuration in .github/release.yml
at v1.48.6 -->

## What's Changed
### Other Changes
* fix(sourcegen): fully-qualify Linq calls in params array binding
(#​6140) by @​thomhurst in thomhurst/TUnit#6141
### Dependencies
* chore(deps): update tunit to 1.48.0 by @​thomhurst in
thomhurst/TUnit#6135
* chore(deps): update dependency polyfill to 10.7.1 by @​thomhurst in
thomhurst/TUnit#6137
* chore(deps): update dependency polyfill to 10.7.1 by @​thomhurst in
thomhurst/TUnit#6138
* chore(deps): update verify to 31.19.0 by @​thomhurst in
thomhurst/TUnit#6139


**Full Changelog**:
thomhurst/TUnit@v1.48.0...v1.48.6

## 1.48.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.48.0 -->

## What's Changed
### Other Changes
* feat(html-report): baked-in C# syntax highlighting on Source tab by
@​slang25 in thomhurst/TUnit#6132
* feat(analyzers): suppress VSTHRD200 on test and hook methods by
@​thomhurst in thomhurst/TUnit#6123
* fix(source-gen): correct source location for cross-project inherited
tests by @​slang25 in thomhurst/TUnit#6133
* feat(assertions): add WasCalled to tunit mocks assertions by
@​robertcoltheart in thomhurst/TUnit#6126
* feat(arguments): bind array values to a single array test parameter by
@​thomhurst in thomhurst/TUnit#6122
* fix: populate retry/flaky attempt history in HTML report (#​6119) by
@​thomhurst in thomhurst/TUnit#6124
### Dependencies
* chore(deps): update tunit to 1.47.0 by @​thomhurst in
thomhurst/TUnit#6115
* chore(deps): update dependency
microsoft.visualstudio.threading.analyzers to 17.14.15 by @​thomhurst in
thomhurst/TUnit#6134


**Full Changelog**:
thomhurst/TUnit@v1.47.0...v1.48.0

## 1.47.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.47.0 -->

## What's Changed
### Other Changes
* perf(engine): hoist GetParameters and dict-dedup AfterTestDiscovery
hooks by @​thomhurst in thomhurst/TUnit#6062
* perf(engine): hoist GetParameters and drop LINQ in reflection
discovery by @​thomhurst in thomhurst/TUnit#6063
* perf(engine): cache treenode filter path on TestMetadata by
@​thomhurst in thomhurst/TUnit#6064
* perf: use is T pattern in ReflectionExtensions.HasAttribute fallback
(#​6060) by @​thomhurst in thomhurst/TUnit#6066
* perf: replace OrderBy().ToArray() with Array.Sort in
ConstraintKeyScheduler by @​thomhurst in
thomhurst/TUnit#6067
* perf: pool HashSet in WaitingTestIndex.GetCandidatesForReleasedKeys by
@​thomhurst in thomhurst/TUnit#6069
* perf: collapse OfType chains in JUnitXmlWriter (#​6052) by @​thomhurst
in thomhurst/TUnit#6070
* perf(engine): avoid closure allocation in
AfterHookPairTracker.GetOrCreateAfterAssemblyTask (#​6041) by
@​thomhurst in thomhurst/TUnit#6071
* perf: avoid closure allocation in
BeforeHookTaskCache.GetOrCreateBeforeAssemblyTask (#​6040) by
@​thomhurst in thomhurst/TUnit#6073
* perf: use TryAdd in TestDependencyResolver dependency dedupe by
@​thomhurst in thomhurst/TUnit#6068
* perf: replace LINQ Any with foreach in TestGenericTypeResolver
(#​6044) by @​thomhurst in thomhurst/TUnit#6072
* perf: avoid Cast<object>().FirstOrDefault() iterator alloc in
CastHelper (#​6029) by @​thomhurst in
thomhurst/TUnit#6074
* perf(engine): avoid string round-trip when building nested type names
(#​6049) by @​thomhurst in thomhurst/TUnit#6075
* perf(engine): replace Select+ToArray with manual Type[] build (#​6043)
by @​thomhurst in thomhurst/TUnit#6076
* perf(core): replace OfType().FirstOrDefault()/.Any() with foreach in
ClassConstructorHelper by @​thomhurst in
thomhurst/TUnit#6078
* perf(engine): avoid FirstOrDefault iterator alloc in
TestGenericTypeResolver by @​thomhurst in
thomhurst/TUnit#6079
* perf(engine): use SearchValues<char> for reporter filename
sanitization by @​thomhurst in
thomhurst/TUnit#6090
* perf: dedupe TestDataFormatter.FormatArguments with pooled
StringBuilder by @​thomhurst in
thomhurst/TUnit#6088
* perf(engine): use MemoryExtensions.Split for path parsing in
MetadataFilterMatcher by @​thomhurst in
thomhurst/TUnit#6085
* perf(engine): use CollectionsMarshal.GetValueRefOrAddDefault for
dictionary index builds by @​thomhurst in
thomhurst/TUnit#6086
* perf(engine): replace LINQ Where closure with inline filter in
MetadataDependencyExpander BFS by @​thomhurst in
thomhurst/TUnit#6084
* perf(engine): pool StringBuilder in DisplayNameBuilder.FormatArguments
by @​thomhurst in thomhurst/TUnit#6082
* Preserve specialized chaining after null assertions by @​thomhurst in
thomhurst/TUnit#6008
* perf: use EnumerateLines for line splitting in HtmlReportGenerator by
@​thomhurst in thomhurst/TUnit#6089
* perf: collapse Replace chain in TestNameFormatter.BuildTestId by
@​thomhurst in thomhurst/TUnit#6083
* perf: use OrdinalIgnoreCase Contains in HtmlReportGenerator span
mapping by @​thomhurst in thomhurst/TUnit#6093
* perf(assertions): avoid eager interpolated-string alloc in assertion
source ctors by @​thomhurst in
thomhurst/TUnit#6091
* perf: optimize TestNameFormatter argument and bool formatting by
@​thomhurst in thomhurst/TUnit#6095
* perf: use FrozenSet/FrozenDictionary for read-only static lookups by
@​thomhurst in thomhurst/TUnit#6099
* perf: avoid GetCustomAttributes() + LINQ chain for per-property
attribute scans by @​thomhurst in
thomhurst/TUnit#6098
* perf(engine): replace magic-string RequiredAttribute match with type
check in ConstructorHelper by @​thomhurst in
thomhurst/TUnit#6087
* perf(core): replace Select+Func factory chain in DataSourceHelpers by
@​thomhurst in thomhurst/TUnit#6081
* perf: replace LINQ dependency extraction with manual loop by
@​thomhurst in thomhurst/TUnit#6096
* perf(core): avoid string[] alloc in ArgumentFormatter.FormatArguments
by @​thomhurst in thomhurst/TUnit#6080
* perf: use [GeneratedRegex] in MetadataFilterMatcher by @​thomhurst in
thomhurst/TUnit#6094
* perf: dedupe GetSimpleTypeName into shared TypeNameFormatter by
@​thomhurst in thomhurst/TUnit#6097
* fix: remove GitVersion MSBuild task, pin local builds to 99.99.99
(#​6077) by @​thomhurst in thomhurst/TUnit#6101
* HTML Report: source link + code snippet on Source tab (#​5993) by
@​thomhurst in thomhurst/TUnit#6100
* perf(sourcegen): Single-pass attribute classification by @​thomhurst
in thomhurst/TUnit#6111
* perf(core): eliminate per-test allocations in TestDetails/HookMethod
by @​thomhurst in thomhurst/TUnit#6109
* perf: hoist char[] alloc in FsCheckPropertyTestExecutor to static
SearchValues by @​thomhurst in
thomhurst/TUnit#6108
* perf(core): de-LINQ data-source expansion by @​thomhurst in
thomhurst/TUnit#6110
* perf: avoid LINQ chains in TestDependency equality and
MethodDataSourceAttribute method matching by @​thomhurst in
thomhurst/TUnit#6092
* perf(engine): reduce allocations in reflection-mode
discovery/execution by @​thomhurst in
thomhurst/TUnit#6113
* perf(assertions): allocation-free passing path (TUnit.Assertions) by
@​thomhurst in thomhurst/TUnit#6112
### Dependencies
 ... (truncated)

## 1.46.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.46.0 -->

## What's Changed
### Other Changes
* docs: add Rider VSTest conflict troubleshooting by @​smolchanovsky in
thomhurst/TUnit#5989
* Populate generated test metadata with full source spans by @​Copilot
in thomhurst/TUnit#5991
* Add devcontainer configuration by @​Copilot in
thomhurst/TUnit#5995
* fix: treenode filter pre-filter rejects parenthesised segments
(#​6026) by @​thomhurst in thomhurst/TUnit#6027
* fix(engine): isolate per-session state under MTP server-mode
concurrency (#​6001) by @​thomhurst in
thomhurst/TUnit#6025
### Dependencies
* chore(deps): update dependency stackexchange.redis to 2.13.10 by
@​thomhurst in thomhurst/TUnit#5985
* chore(deps): update tunit to 1.45.29 by @​thomhurst in
thomhurst/TUnit#5986
* chore(deps): update dependency mockolate to 3.2.1 by @​thomhurst in
thomhurst/TUnit#5987
* chore(deps): update dependency microsoft.playwright to 1.60.0 by
@​thomhurst in thomhurst/TUnit#5988
* chore(deps): update dependency messagepack to 3.1.6 by @​thomhurst in
thomhurst/TUnit#5992
* chore(deps): update dependency polyfill to 10.7.0 by @​thomhurst in
thomhurst/TUnit#5998
* chore(deps): update dependency polyfill to 10.7.0 by @​thomhurst in
thomhurst/TUnit#5997
* chore(deps): update verify to 31.17.0 by @​thomhurst in
thomhurst/TUnit#6000
* chore(deps): update verify to 31.18.0 by @​thomhurst in
thomhurst/TUnit#6013
* chore(deps): update dependency microsoft.net.test.sdk to 18.6.0 by
@​thomhurst in thomhurst/TUnit#6016
* chore(deps): update dependency dompurify to v3.4.6 by @​thomhurst in
thomhurst/TUnit#6015
* chore(deps): update dependency dompurify to v3.4.7 by @​thomhurst in
thomhurst/TUnit#6019
* chore(deps): update dependency npgsql to 10.0.3 by @​thomhurst in
thomhurst/TUnit#6020
* chore(deps): update dependency stackexchange.redis to 2.13.17 by
@​thomhurst in thomhurst/TUnit#6021
* chore(deps): update dependency npgsql.entityframeworkcore.postgresql
to 10.0.2 by @​thomhurst in thomhurst/TUnit#6022

## New Contributors
* @​smolchanovsky made their first contribution in
thomhurst/TUnit#5989

**Full Changelog**:
thomhurst/TUnit@v1.45.29...v1.46.0

## 1.45.29

<!-- Release notes generated using configuration in .github/release.yml
at v1.45.29 -->

## What's Changed
### Other Changes
* Fix shared fixture lifetime for reused discovery instances by
@​thomhurst in thomhurst/TUnit#5983
* Preserve override accessibility in generated mocks by @​thomhurst in
thomhurst/TUnit#5984
### Dependencies
* chore(deps): update tunit to 1.45.22 by @​thomhurst in
thomhurst/TUnit#5974
* chore(deps): update dependency messagepack to 3.1.5 by @​thomhurst in
thomhurst/TUnit#5978
* chore(deps): update aspire to 13.3.5 by @​thomhurst in
thomhurst/TUnit#5980


**Full Changelog**:
thomhurst/TUnit@v1.45.22...v1.45.29

## 1.45.22

<!-- Release notes generated using configuration in .github/release.yml
at v1.45.22 -->

## What's Changed
### Other Changes
* Remove ".NET" from Aspire references by @​antmdvs in
thomhurst/TUnit#5968
* Fix chained mock setup behavior by @​thomhurst in
thomhurst/TUnit#5973
### Dependencies
* chore(deps): update tunit to 1.45.8 by @​thomhurst in
thomhurst/TUnit#5958
* chore(deps): update dependency nunit to 4.6.1 by @​thomhurst in
thomhurst/TUnit#5961
* chore(deps): update dependency testcontainers.postgresql to 4.12.0 by
@​thomhurst in thomhurst/TUnit#5963
* chore(deps): update dependency testcontainers.redis to 4.12.0 by
@​thomhurst in thomhurst/TUnit#5965
* chore(deps): update dependency testcontainers.kafka to 4.12.0 by
@​thomhurst in thomhurst/TUnit#5962
* chore(deps): update aspire to 13.3.4 by @​thomhurst in
thomhurst/TUnit#5966
* chore(deps): bump webpack-dev-server from 5.2.2 to 5.2.4 in /docs by
@​dependabot[bot] in thomhurst/TUnit#5964

## New Contributors
* @​antmdvs made their first contribution in
thomhurst/TUnit#5968

**Full Changelog**:
thomhurst/TUnit@v1.45.8...v1.45.22

## 1.45.8

<!-- Release notes generated using configuration in .github/release.yml
at v1.45.8 -->

## What's Changed
### Other Changes
* fix(aspire): route CreateHttpClient through IHttpClientFactory by
@​thomhurst in thomhurst/TUnit#5957
### Dependencies
* chore(deps): update tunit to 1.45.0 by @​thomhurst in
thomhurst/TUnit#5949
* chore(deps): update dependency dompurify to v3.4.5 by @​thomhurst in
thomhurst/TUnit#5951
* chore(deps): update dependency
microsoft.testing.extensions.codecoverage to 18.7.0 by @​thomhurst in
thomhurst/TUnit#5953
* chore(deps): update dependency coverlet.collector to 10.0.1 by
@​thomhurst in thomhurst/TUnit#5952
* chore(deps): update dependency polyfill to 10.6.0 by @​thomhurst in
thomhurst/TUnit#5955
* chore(deps): update dependency polyfill to 10.6.0 by @​thomhurst in
thomhurst/TUnit#5954


**Full Changelog**:
thomhurst/TUnit@v1.45.0...v1.45.8

## 1.45.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.45.0 -->

## What's Changed
### Other Changes
* fix(generator): place CallerArgumentExpression before params in
[GenerateAssertion] emit by @​JohnVerheij in
thomhurst/TUnit#5940
* fix(sourcegen): drop covariant TActual when [GenerateAssertion] method
has its own type parameters by @​JohnVerheij in
thomhurst/TUnit#5935
* feat(assertions): add CancellationToken overload to WaitsFor and
Eventually by @​JohnVerheij in
thomhurst/TUnit#5938
* fix(html-report): extract categories using MTP Key=name convention by
@​thomhurst in thomhurst/TUnit#5946
* feat(html-report): rewrite as split-pane design template by
@​thomhurst in thomhurst/TUnit#5947
### Dependencies
* chore(deps): update microsoft.testing to 2.2.3 by @​thomhurst in
thomhurst/TUnit#5927
* chore(deps): update mstest to 4.2.3 by @​thomhurst in
thomhurst/TUnit#5928
* chore(deps): update tunit to 1.44.39 by @​thomhurst in
thomhurst/TUnit#5929
* chore(deps): update aspire to 13.3.3 by @​thomhurst in
thomhurst/TUnit#5933
* chore(deps): update dependency dompurify to v3.4.4 by @​thomhurst in
thomhurst/TUnit#5944
* chore(deps): update dependency qs to v6.15.2 by @​thomhurst in
thomhurst/TUnit#5941


**Full Changelog**:
thomhurst/TUnit@v1.44.39...v1.45.0

Commits viewable in [compare
view](thomhurst/TUnit@v1.44.39...v1.48.6).
</details>

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
intellitect-bot pushed a commit to IntelliTect/EssentialCSharp.Web that referenced this pull request Jun 2, 2026
Updated [TUnit](https://github.com/thomhurst/TUnit) from 1.45.29 to
1.48.6.

<details>
<summary>Release notes</summary>

_Sourced from [TUnit's
releases](https://github.com/thomhurst/TUnit/releases)._

## 1.48.6

<!-- Release notes generated using configuration in .github/release.yml
at v1.48.6 -->

## What's Changed
### Other Changes
* fix(sourcegen): fully-qualify Linq calls in params array binding
(#​6140) by @​thomhurst in thomhurst/TUnit#6141
### Dependencies
* chore(deps): update tunit to 1.48.0 by @​thomhurst in
thomhurst/TUnit#6135
* chore(deps): update dependency polyfill to 10.7.1 by @​thomhurst in
thomhurst/TUnit#6137
* chore(deps): update dependency polyfill to 10.7.1 by @​thomhurst in
thomhurst/TUnit#6138
* chore(deps): update verify to 31.19.0 by @​thomhurst in
thomhurst/TUnit#6139


**Full Changelog**:
thomhurst/TUnit@v1.48.0...v1.48.6

## 1.48.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.48.0 -->

## What's Changed
### Other Changes
* feat(html-report): baked-in C# syntax highlighting on Source tab by
@​slang25 in thomhurst/TUnit#6132
* feat(analyzers): suppress VSTHRD200 on test and hook methods by
@​thomhurst in thomhurst/TUnit#6123
* fix(source-gen): correct source location for cross-project inherited
tests by @​slang25 in thomhurst/TUnit#6133
* feat(assertions): add WasCalled to tunit mocks assertions by
@​robertcoltheart in thomhurst/TUnit#6126
* feat(arguments): bind array values to a single array test parameter by
@​thomhurst in thomhurst/TUnit#6122
* fix: populate retry/flaky attempt history in HTML report (#​6119) by
@​thomhurst in thomhurst/TUnit#6124
### Dependencies
* chore(deps): update tunit to 1.47.0 by @​thomhurst in
thomhurst/TUnit#6115
* chore(deps): update dependency
microsoft.visualstudio.threading.analyzers to 17.14.15 by @​thomhurst in
thomhurst/TUnit#6134


**Full Changelog**:
thomhurst/TUnit@v1.47.0...v1.48.0

## 1.47.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.47.0 -->

## What's Changed
### Other Changes
* perf(engine): hoist GetParameters and dict-dedup AfterTestDiscovery
hooks by @​thomhurst in thomhurst/TUnit#6062
* perf(engine): hoist GetParameters and drop LINQ in reflection
discovery by @​thomhurst in thomhurst/TUnit#6063
* perf(engine): cache treenode filter path on TestMetadata by
@​thomhurst in thomhurst/TUnit#6064
* perf: use is T pattern in ReflectionExtensions.HasAttribute fallback
(#​6060) by @​thomhurst in thomhurst/TUnit#6066
* perf: replace OrderBy().ToArray() with Array.Sort in
ConstraintKeyScheduler by @​thomhurst in
thomhurst/TUnit#6067
* perf: pool HashSet in WaitingTestIndex.GetCandidatesForReleasedKeys by
@​thomhurst in thomhurst/TUnit#6069
* perf: collapse OfType chains in JUnitXmlWriter (#​6052) by @​thomhurst
in thomhurst/TUnit#6070
* perf(engine): avoid closure allocation in
AfterHookPairTracker.GetOrCreateAfterAssemblyTask (#​6041) by
@​thomhurst in thomhurst/TUnit#6071
* perf: avoid closure allocation in
BeforeHookTaskCache.GetOrCreateBeforeAssemblyTask (#​6040) by
@​thomhurst in thomhurst/TUnit#6073
* perf: use TryAdd in TestDependencyResolver dependency dedupe by
@​thomhurst in thomhurst/TUnit#6068
* perf: replace LINQ Any with foreach in TestGenericTypeResolver
(#​6044) by @​thomhurst in thomhurst/TUnit#6072
* perf: avoid Cast<object>().FirstOrDefault() iterator alloc in
CastHelper (#​6029) by @​thomhurst in
thomhurst/TUnit#6074
* perf(engine): avoid string round-trip when building nested type names
(#​6049) by @​thomhurst in thomhurst/TUnit#6075
* perf(engine): replace Select+ToArray with manual Type[] build (#​6043)
by @​thomhurst in thomhurst/TUnit#6076
* perf(core): replace OfType().FirstOrDefault()/.Any() with foreach in
ClassConstructorHelper by @​thomhurst in
thomhurst/TUnit#6078
* perf(engine): avoid FirstOrDefault iterator alloc in
TestGenericTypeResolver by @​thomhurst in
thomhurst/TUnit#6079
* perf(engine): use SearchValues<char> for reporter filename
sanitization by @​thomhurst in
thomhurst/TUnit#6090
* perf: dedupe TestDataFormatter.FormatArguments with pooled
StringBuilder by @​thomhurst in
thomhurst/TUnit#6088
* perf(engine): use MemoryExtensions.Split for path parsing in
MetadataFilterMatcher by @​thomhurst in
thomhurst/TUnit#6085
* perf(engine): use CollectionsMarshal.GetValueRefOrAddDefault for
dictionary index builds by @​thomhurst in
thomhurst/TUnit#6086
* perf(engine): replace LINQ Where closure with inline filter in
MetadataDependencyExpander BFS by @​thomhurst in
thomhurst/TUnit#6084
* perf(engine): pool StringBuilder in DisplayNameBuilder.FormatArguments
by @​thomhurst in thomhurst/TUnit#6082
* Preserve specialized chaining after null assertions by @​thomhurst in
thomhurst/TUnit#6008
* perf: use EnumerateLines for line splitting in HtmlReportGenerator by
@​thomhurst in thomhurst/TUnit#6089
* perf: collapse Replace chain in TestNameFormatter.BuildTestId by
@​thomhurst in thomhurst/TUnit#6083
* perf: use OrdinalIgnoreCase Contains in HtmlReportGenerator span
mapping by @​thomhurst in thomhurst/TUnit#6093
* perf(assertions): avoid eager interpolated-string alloc in assertion
source ctors by @​thomhurst in
thomhurst/TUnit#6091
* perf: optimize TestNameFormatter argument and bool formatting by
@​thomhurst in thomhurst/TUnit#6095
* perf: use FrozenSet/FrozenDictionary for read-only static lookups by
@​thomhurst in thomhurst/TUnit#6099
* perf: avoid GetCustomAttributes() + LINQ chain for per-property
attribute scans by @​thomhurst in
thomhurst/TUnit#6098
* perf(engine): replace magic-string RequiredAttribute match with type
check in ConstructorHelper by @​thomhurst in
thomhurst/TUnit#6087
* perf(core): replace Select+Func factory chain in DataSourceHelpers by
@​thomhurst in thomhurst/TUnit#6081
* perf: replace LINQ dependency extraction with manual loop by
@​thomhurst in thomhurst/TUnit#6096
* perf(core): avoid string[] alloc in ArgumentFormatter.FormatArguments
by @​thomhurst in thomhurst/TUnit#6080
* perf: use [GeneratedRegex] in MetadataFilterMatcher by @​thomhurst in
thomhurst/TUnit#6094
* perf: dedupe GetSimpleTypeName into shared TypeNameFormatter by
@​thomhurst in thomhurst/TUnit#6097
* fix: remove GitVersion MSBuild task, pin local builds to 99.99.99
(#​6077) by @​thomhurst in thomhurst/TUnit#6101
* HTML Report: source link + code snippet on Source tab (#​5993) by
@​thomhurst in thomhurst/TUnit#6100
* perf(sourcegen): Single-pass attribute classification by @​thomhurst
in thomhurst/TUnit#6111
* perf(core): eliminate per-test allocations in TestDetails/HookMethod
by @​thomhurst in thomhurst/TUnit#6109
* perf: hoist char[] alloc in FsCheckPropertyTestExecutor to static
SearchValues by @​thomhurst in
thomhurst/TUnit#6108
* perf(core): de-LINQ data-source expansion by @​thomhurst in
thomhurst/TUnit#6110
* perf: avoid LINQ chains in TestDependency equality and
MethodDataSourceAttribute method matching by @​thomhurst in
thomhurst/TUnit#6092
* perf(engine): reduce allocations in reflection-mode
discovery/execution by @​thomhurst in
thomhurst/TUnit#6113
* perf(assertions): allocation-free passing path (TUnit.Assertions) by
@​thomhurst in thomhurst/TUnit#6112
### Dependencies
 ... (truncated)

## 1.46.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.46.0 -->

## What's Changed
### Other Changes
* docs: add Rider VSTest conflict troubleshooting by @​smolchanovsky in
thomhurst/TUnit#5989
* Populate generated test metadata with full source spans by @​Copilot
in thomhurst/TUnit#5991
* Add devcontainer configuration by @​Copilot in
thomhurst/TUnit#5995
* fix: treenode filter pre-filter rejects parenthesised segments
(#​6026) by @​thomhurst in thomhurst/TUnit#6027
* fix(engine): isolate per-session state under MTP server-mode
concurrency (#​6001) by @​thomhurst in
thomhurst/TUnit#6025
### Dependencies
* chore(deps): update dependency stackexchange.redis to 2.13.10 by
@​thomhurst in thomhurst/TUnit#5985
* chore(deps): update tunit to 1.45.29 by @​thomhurst in
thomhurst/TUnit#5986
* chore(deps): update dependency mockolate to 3.2.1 by @​thomhurst in
thomhurst/TUnit#5987
* chore(deps): update dependency microsoft.playwright to 1.60.0 by
@​thomhurst in thomhurst/TUnit#5988
* chore(deps): update dependency messagepack to 3.1.6 by @​thomhurst in
thomhurst/TUnit#5992
* chore(deps): update dependency polyfill to 10.7.0 by @​thomhurst in
thomhurst/TUnit#5998
* chore(deps): update dependency polyfill to 10.7.0 by @​thomhurst in
thomhurst/TUnit#5997
* chore(deps): update verify to 31.17.0 by @​thomhurst in
thomhurst/TUnit#6000
* chore(deps): update verify to 31.18.0 by @​thomhurst in
thomhurst/TUnit#6013
* chore(deps): update dependency microsoft.net.test.sdk to 18.6.0 by
@​thomhurst in thomhurst/TUnit#6016
* chore(deps): update dependency dompurify to v3.4.6 by @​thomhurst in
thomhurst/TUnit#6015
* chore(deps): update dependency dompurify to v3.4.7 by @​thomhurst in
thomhurst/TUnit#6019
* chore(deps): update dependency npgsql to 10.0.3 by @​thomhurst in
thomhurst/TUnit#6020
* chore(deps): update dependency stackexchange.redis to 2.13.17 by
@​thomhurst in thomhurst/TUnit#6021
* chore(deps): update dependency npgsql.entityframeworkcore.postgresql
to 10.0.2 by @​thomhurst in thomhurst/TUnit#6022

## New Contributors
* @​smolchanovsky made their first contribution in
thomhurst/TUnit#5989

**Full Changelog**:
thomhurst/TUnit@v1.45.29...v1.46.0

Commits viewable in [compare
view](thomhurst/TUnit@v1.45.29...v1.48.6).
</details>

Updated [TUnit.AspNetCore](https://github.com/thomhurst/TUnit) from
1.45.29 to 1.48.6.

<details>
<summary>Release notes</summary>

_Sourced from [TUnit.AspNetCore's
releases](https://github.com/thomhurst/TUnit/releases)._

## 1.48.6

<!-- Release notes generated using configuration in .github/release.yml
at v1.48.6 -->

## What's Changed
### Other Changes
* fix(sourcegen): fully-qualify Linq calls in params array binding
(#​6140) by @​thomhurst in thomhurst/TUnit#6141
### Dependencies
* chore(deps): update tunit to 1.48.0 by @​thomhurst in
thomhurst/TUnit#6135
* chore(deps): update dependency polyfill to 10.7.1 by @​thomhurst in
thomhurst/TUnit#6137
* chore(deps): update dependency polyfill to 10.7.1 by @​thomhurst in
thomhurst/TUnit#6138
* chore(deps): update verify to 31.19.0 by @​thomhurst in
thomhurst/TUnit#6139


**Full Changelog**:
thomhurst/TUnit@v1.48.0...v1.48.6

## 1.48.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.48.0 -->

## What's Changed
### Other Changes
* feat(html-report): baked-in C# syntax highlighting on Source tab by
@​slang25 in thomhurst/TUnit#6132
* feat(analyzers): suppress VSTHRD200 on test and hook methods by
@​thomhurst in thomhurst/TUnit#6123
* fix(source-gen): correct source location for cross-project inherited
tests by @​slang25 in thomhurst/TUnit#6133
* feat(assertions): add WasCalled to tunit mocks assertions by
@​robertcoltheart in thomhurst/TUnit#6126
* feat(arguments): bind array values to a single array test parameter by
@​thomhurst in thomhurst/TUnit#6122
* fix: populate retry/flaky attempt history in HTML report (#​6119) by
@​thomhurst in thomhurst/TUnit#6124
### Dependencies
* chore(deps): update tunit to 1.47.0 by @​thomhurst in
thomhurst/TUnit#6115
* chore(deps): update dependency
microsoft.visualstudio.threading.analyzers to 17.14.15 by @​thomhurst in
thomhurst/TUnit#6134


**Full Changelog**:
thomhurst/TUnit@v1.47.0...v1.48.0

## 1.47.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.47.0 -->

## What's Changed
### Other Changes
* perf(engine): hoist GetParameters and dict-dedup AfterTestDiscovery
hooks by @​thomhurst in thomhurst/TUnit#6062
* perf(engine): hoist GetParameters and drop LINQ in reflection
discovery by @​thomhurst in thomhurst/TUnit#6063
* perf(engine): cache treenode filter path on TestMetadata by
@​thomhurst in thomhurst/TUnit#6064
* perf: use is T pattern in ReflectionExtensions.HasAttribute fallback
(#​6060) by @​thomhurst in thomhurst/TUnit#6066
* perf: replace OrderBy().ToArray() with Array.Sort in
ConstraintKeyScheduler by @​thomhurst in
thomhurst/TUnit#6067
* perf: pool HashSet in WaitingTestIndex.GetCandidatesForReleasedKeys by
@​thomhurst in thomhurst/TUnit#6069
* perf: collapse OfType chains in JUnitXmlWriter (#​6052) by @​thomhurst
in thomhurst/TUnit#6070
* perf(engine): avoid closure allocation in
AfterHookPairTracker.GetOrCreateAfterAssemblyTask (#​6041) by
@​thomhurst in thomhurst/TUnit#6071
* perf: avoid closure allocation in
BeforeHookTaskCache.GetOrCreateBeforeAssemblyTask (#​6040) by
@​thomhurst in thomhurst/TUnit#6073
* perf: use TryAdd in TestDependencyResolver dependency dedupe by
@​thomhurst in thomhurst/TUnit#6068
* perf: replace LINQ Any with foreach in TestGenericTypeResolver
(#​6044) by @​thomhurst in thomhurst/TUnit#6072
* perf: avoid Cast<object>().FirstOrDefault() iterator alloc in
CastHelper (#​6029) by @​thomhurst in
thomhurst/TUnit#6074
* perf(engine): avoid string round-trip when building nested type names
(#​6049) by @​thomhurst in thomhurst/TUnit#6075
* perf(engine): replace Select+ToArray with manual Type[] build (#​6043)
by @​thomhurst in thomhurst/TUnit#6076
* perf(core): replace OfType().FirstOrDefault()/.Any() with foreach in
ClassConstructorHelper by @​thomhurst in
thomhurst/TUnit#6078
* perf(engine): avoid FirstOrDefault iterator alloc in
TestGenericTypeResolver by @​thomhurst in
thomhurst/TUnit#6079
* perf(engine): use SearchValues<char> for reporter filename
sanitization by @​thomhurst in
thomhurst/TUnit#6090
* perf: dedupe TestDataFormatter.FormatArguments with pooled
StringBuilder by @​thomhurst in
thomhurst/TUnit#6088
* perf(engine): use MemoryExtensions.Split for path parsing in
MetadataFilterMatcher by @​thomhurst in
thomhurst/TUnit#6085
* perf(engine): use CollectionsMarshal.GetValueRefOrAddDefault for
dictionary index builds by @​thomhurst in
thomhurst/TUnit#6086
* perf(engine): replace LINQ Where closure with inline filter in
MetadataDependencyExpander BFS by @​thomhurst in
thomhurst/TUnit#6084
* perf(engine): pool StringBuilder in DisplayNameBuilder.FormatArguments
by @​thomhurst in thomhurst/TUnit#6082
* Preserve specialized chaining after null assertions by @​thomhurst in
thomhurst/TUnit#6008
* perf: use EnumerateLines for line splitting in HtmlReportGenerator by
@​thomhurst in thomhurst/TUnit#6089
* perf: collapse Replace chain in TestNameFormatter.BuildTestId by
@​thomhurst in thomhurst/TUnit#6083
* perf: use OrdinalIgnoreCase Contains in HtmlReportGenerator span
mapping by @​thomhurst in thomhurst/TUnit#6093
* perf(assertions): avoid eager interpolated-string alloc in assertion
source ctors by @​thomhurst in
thomhurst/TUnit#6091
* perf: optimize TestNameFormatter argument and bool formatting by
@​thomhurst in thomhurst/TUnit#6095
* perf: use FrozenSet/FrozenDictionary for read-only static lookups by
@​thomhurst in thomhurst/TUnit#6099
* perf: avoid GetCustomAttributes() + LINQ chain for per-property
attribute scans by @​thomhurst in
thomhurst/TUnit#6098
* perf(engine): replace magic-string RequiredAttribute match with type
check in ConstructorHelper by @​thomhurst in
thomhurst/TUnit#6087
* perf(core): replace Select+Func factory chain in DataSourceHelpers by
@​thomhurst in thomhurst/TUnit#6081
* perf: replace LINQ dependency extraction with manual loop by
@​thomhurst in thomhurst/TUnit#6096
* perf(core): avoid string[] alloc in ArgumentFormatter.FormatArguments
by @​thomhurst in thomhurst/TUnit#6080
* perf: use [GeneratedRegex] in MetadataFilterMatcher by @​thomhurst in
thomhurst/TUnit#6094
* perf: dedupe GetSimpleTypeName into shared TypeNameFormatter by
@​thomhurst in thomhurst/TUnit#6097
* fix: remove GitVersion MSBuild task, pin local builds to 99.99.99
(#​6077) by @​thomhurst in thomhurst/TUnit#6101
* HTML Report: source link + code snippet on Source tab (#​5993) by
@​thomhurst in thomhurst/TUnit#6100
* perf(sourcegen): Single-pass attribute classification by @​thomhurst
in thomhurst/TUnit#6111
* perf(core): eliminate per-test allocations in TestDetails/HookMethod
by @​thomhurst in thomhurst/TUnit#6109
* perf: hoist char[] alloc in FsCheckPropertyTestExecutor to static
SearchValues by @​thomhurst in
thomhurst/TUnit#6108
* perf(core): de-LINQ data-source expansion by @​thomhurst in
thomhurst/TUnit#6110
* perf: avoid LINQ chains in TestDependency equality and
MethodDataSourceAttribute method matching by @​thomhurst in
thomhurst/TUnit#6092
* perf(engine): reduce allocations in reflection-mode
discovery/execution by @​thomhurst in
thomhurst/TUnit#6113
* perf(assertions): allocation-free passing path (TUnit.Assertions) by
@​thomhurst in thomhurst/TUnit#6112
### Dependencies
 ... (truncated)

## 1.46.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.46.0 -->

## What's Changed
### Other Changes
* docs: add Rider VSTest conflict troubleshooting by @​smolchanovsky in
thomhurst/TUnit#5989
* Populate generated test metadata with full source spans by @​Copilot
in thomhurst/TUnit#5991
* Add devcontainer configuration by @​Copilot in
thomhurst/TUnit#5995
* fix: treenode filter pre-filter rejects parenthesised segments
(#​6026) by @​thomhurst in thomhurst/TUnit#6027
* fix(engine): isolate per-session state under MTP server-mode
concurrency (#​6001) by @​thomhurst in
thomhurst/TUnit#6025
### Dependencies
* chore(deps): update dependency stackexchange.redis to 2.13.10 by
@​thomhurst in thomhurst/TUnit#5985
* chore(deps): update tunit to 1.45.29 by @​thomhurst in
thomhurst/TUnit#5986
* chore(deps): update dependency mockolate to 3.2.1 by @​thomhurst in
thomhurst/TUnit#5987
* chore(deps): update dependency microsoft.playwright to 1.60.0 by
@​thomhurst in thomhurst/TUnit#5988
* chore(deps): update dependency messagepack to 3.1.6 by @​thomhurst in
thomhurst/TUnit#5992
* chore(deps): update dependency polyfill to 10.7.0 by @​thomhurst in
thomhurst/TUnit#5998
* chore(deps): update dependency polyfill to 10.7.0 by @​thomhurst in
thomhurst/TUnit#5997
* chore(deps): update verify to 31.17.0 by @​thomhurst in
thomhurst/TUnit#6000
* chore(deps): update verify to 31.18.0 by @​thomhurst in
thomhurst/TUnit#6013
* chore(deps): update dependency microsoft.net.test.sdk to 18.6.0 by
@​thomhurst in thomhurst/TUnit#6016
* chore(deps): update dependency dompurify to v3.4.6 by @​thomhurst in
thomhurst/TUnit#6015
* chore(deps): update dependency dompurify to v3.4.7 by @​thomhurst in
thomhurst/TUnit#6019
* chore(deps): update dependency npgsql to 10.0.3 by @​thomhurst in
thomhurst/TUnit#6020
* chore(deps): update dependency stackexchange.redis to 2.13.17 by
@​thomhurst in thomhurst/TUnit#6021
* chore(deps): update dependency npgsql.entityframeworkcore.postgresql
to 10.0.2 by @​thomhurst in thomhurst/TUnit#6022

## New Contributors
* @​smolchanovsky made their first contribution in
thomhurst/TUnit#5989

**Full Changelog**:
thomhurst/TUnit@v1.45.29...v1.46.0

Commits viewable in [compare
view](thomhurst/TUnit@v1.45.29...v1.48.6).
</details>

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
github-actions Bot pushed a commit to IntelliTect/CodingGuidelines that referenced this pull request Jun 2, 2026
Updated [TUnit.Core](https://github.com/thomhurst/TUnit) from 1.45.29 to
1.48.6.

<details>
<summary>Release notes</summary>

_Sourced from [TUnit.Core's
releases](https://github.com/thomhurst/TUnit/releases)._

## 1.48.6

<!-- Release notes generated using configuration in .github/release.yml
at v1.48.6 -->

## What's Changed
### Other Changes
* fix(sourcegen): fully-qualify Linq calls in params array binding
(#​6140) by @​thomhurst in thomhurst/TUnit#6141
### Dependencies
* chore(deps): update tunit to 1.48.0 by @​thomhurst in
thomhurst/TUnit#6135
* chore(deps): update dependency polyfill to 10.7.1 by @​thomhurst in
thomhurst/TUnit#6137
* chore(deps): update dependency polyfill to 10.7.1 by @​thomhurst in
thomhurst/TUnit#6138
* chore(deps): update verify to 31.19.0 by @​thomhurst in
thomhurst/TUnit#6139


**Full Changelog**:
thomhurst/TUnit@v1.48.0...v1.48.6

## 1.48.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.48.0 -->

## What's Changed
### Other Changes
* feat(html-report): baked-in C# syntax highlighting on Source tab by
@​slang25 in thomhurst/TUnit#6132
* feat(analyzers): suppress VSTHRD200 on test and hook methods by
@​thomhurst in thomhurst/TUnit#6123
* fix(source-gen): correct source location for cross-project inherited
tests by @​slang25 in thomhurst/TUnit#6133
* feat(assertions): add WasCalled to tunit mocks assertions by
@​robertcoltheart in thomhurst/TUnit#6126
* feat(arguments): bind array values to a single array test parameter by
@​thomhurst in thomhurst/TUnit#6122
* fix: populate retry/flaky attempt history in HTML report (#​6119) by
@​thomhurst in thomhurst/TUnit#6124
### Dependencies
* chore(deps): update tunit to 1.47.0 by @​thomhurst in
thomhurst/TUnit#6115
* chore(deps): update dependency
microsoft.visualstudio.threading.analyzers to 17.14.15 by @​thomhurst in
thomhurst/TUnit#6134


**Full Changelog**:
thomhurst/TUnit@v1.47.0...v1.48.0

## 1.47.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.47.0 -->

## What's Changed
### Other Changes
* perf(engine): hoist GetParameters and dict-dedup AfterTestDiscovery
hooks by @​thomhurst in thomhurst/TUnit#6062
* perf(engine): hoist GetParameters and drop LINQ in reflection
discovery by @​thomhurst in thomhurst/TUnit#6063
* perf(engine): cache treenode filter path on TestMetadata by
@​thomhurst in thomhurst/TUnit#6064
* perf: use is T pattern in ReflectionExtensions.HasAttribute fallback
(#​6060) by @​thomhurst in thomhurst/TUnit#6066
* perf: replace OrderBy().ToArray() with Array.Sort in
ConstraintKeyScheduler by @​thomhurst in
thomhurst/TUnit#6067
* perf: pool HashSet in WaitingTestIndex.GetCandidatesForReleasedKeys by
@​thomhurst in thomhurst/TUnit#6069
* perf: collapse OfType chains in JUnitXmlWriter (#​6052) by @​thomhurst
in thomhurst/TUnit#6070
* perf(engine): avoid closure allocation in
AfterHookPairTracker.GetOrCreateAfterAssemblyTask (#​6041) by
@​thomhurst in thomhurst/TUnit#6071
* perf: avoid closure allocation in
BeforeHookTaskCache.GetOrCreateBeforeAssemblyTask (#​6040) by
@​thomhurst in thomhurst/TUnit#6073
* perf: use TryAdd in TestDependencyResolver dependency dedupe by
@​thomhurst in thomhurst/TUnit#6068
* perf: replace LINQ Any with foreach in TestGenericTypeResolver
(#​6044) by @​thomhurst in thomhurst/TUnit#6072
* perf: avoid Cast<object>().FirstOrDefault() iterator alloc in
CastHelper (#​6029) by @​thomhurst in
thomhurst/TUnit#6074
* perf(engine): avoid string round-trip when building nested type names
(#​6049) by @​thomhurst in thomhurst/TUnit#6075
* perf(engine): replace Select+ToArray with manual Type[] build (#​6043)
by @​thomhurst in thomhurst/TUnit#6076
* perf(core): replace OfType().FirstOrDefault()/.Any() with foreach in
ClassConstructorHelper by @​thomhurst in
thomhurst/TUnit#6078
* perf(engine): avoid FirstOrDefault iterator alloc in
TestGenericTypeResolver by @​thomhurst in
thomhurst/TUnit#6079
* perf(engine): use SearchValues<char> for reporter filename
sanitization by @​thomhurst in
thomhurst/TUnit#6090
* perf: dedupe TestDataFormatter.FormatArguments with pooled
StringBuilder by @​thomhurst in
thomhurst/TUnit#6088
* perf(engine): use MemoryExtensions.Split for path parsing in
MetadataFilterMatcher by @​thomhurst in
thomhurst/TUnit#6085
* perf(engine): use CollectionsMarshal.GetValueRefOrAddDefault for
dictionary index builds by @​thomhurst in
thomhurst/TUnit#6086
* perf(engine): replace LINQ Where closure with inline filter in
MetadataDependencyExpander BFS by @​thomhurst in
thomhurst/TUnit#6084
* perf(engine): pool StringBuilder in DisplayNameBuilder.FormatArguments
by @​thomhurst in thomhurst/TUnit#6082
* Preserve specialized chaining after null assertions by @​thomhurst in
thomhurst/TUnit#6008
* perf: use EnumerateLines for line splitting in HtmlReportGenerator by
@​thomhurst in thomhurst/TUnit#6089
* perf: collapse Replace chain in TestNameFormatter.BuildTestId by
@​thomhurst in thomhurst/TUnit#6083
* perf: use OrdinalIgnoreCase Contains in HtmlReportGenerator span
mapping by @​thomhurst in thomhurst/TUnit#6093
* perf(assertions): avoid eager interpolated-string alloc in assertion
source ctors by @​thomhurst in
thomhurst/TUnit#6091
* perf: optimize TestNameFormatter argument and bool formatting by
@​thomhurst in thomhurst/TUnit#6095
* perf: use FrozenSet/FrozenDictionary for read-only static lookups by
@​thomhurst in thomhurst/TUnit#6099
* perf: avoid GetCustomAttributes() + LINQ chain for per-property
attribute scans by @​thomhurst in
thomhurst/TUnit#6098
* perf(engine): replace magic-string RequiredAttribute match with type
check in ConstructorHelper by @​thomhurst in
thomhurst/TUnit#6087
* perf(core): replace Select+Func factory chain in DataSourceHelpers by
@​thomhurst in thomhurst/TUnit#6081
* perf: replace LINQ dependency extraction with manual loop by
@​thomhurst in thomhurst/TUnit#6096
* perf(core): avoid string[] alloc in ArgumentFormatter.FormatArguments
by @​thomhurst in thomhurst/TUnit#6080
* perf: use [GeneratedRegex] in MetadataFilterMatcher by @​thomhurst in
thomhurst/TUnit#6094
* perf: dedupe GetSimpleTypeName into shared TypeNameFormatter by
@​thomhurst in thomhurst/TUnit#6097
* fix: remove GitVersion MSBuild task, pin local builds to 99.99.99
(#​6077) by @​thomhurst in thomhurst/TUnit#6101
* HTML Report: source link + code snippet on Source tab (#​5993) by
@​thomhurst in thomhurst/TUnit#6100
* perf(sourcegen): Single-pass attribute classification by @​thomhurst
in thomhurst/TUnit#6111
* perf(core): eliminate per-test allocations in TestDetails/HookMethod
by @​thomhurst in thomhurst/TUnit#6109
* perf: hoist char[] alloc in FsCheckPropertyTestExecutor to static
SearchValues by @​thomhurst in
thomhurst/TUnit#6108
* perf(core): de-LINQ data-source expansion by @​thomhurst in
thomhurst/TUnit#6110
* perf: avoid LINQ chains in TestDependency equality and
MethodDataSourceAttribute method matching by @​thomhurst in
thomhurst/TUnit#6092
* perf(engine): reduce allocations in reflection-mode
discovery/execution by @​thomhurst in
thomhurst/TUnit#6113
* perf(assertions): allocation-free passing path (TUnit.Assertions) by
@​thomhurst in thomhurst/TUnit#6112
### Dependencies
 ... (truncated)

## 1.46.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.46.0 -->

## What's Changed
### Other Changes
* docs: add Rider VSTest conflict troubleshooting by @​smolchanovsky in
thomhurst/TUnit#5989
* Populate generated test metadata with full source spans by @​Copilot
in thomhurst/TUnit#5991
* Add devcontainer configuration by @​Copilot in
thomhurst/TUnit#5995
* fix: treenode filter pre-filter rejects parenthesised segments
(#​6026) by @​thomhurst in thomhurst/TUnit#6027
* fix(engine): isolate per-session state under MTP server-mode
concurrency (#​6001) by @​thomhurst in
thomhurst/TUnit#6025
### Dependencies
* chore(deps): update dependency stackexchange.redis to 2.13.10 by
@​thomhurst in thomhurst/TUnit#5985
* chore(deps): update tunit to 1.45.29 by @​thomhurst in
thomhurst/TUnit#5986
* chore(deps): update dependency mockolate to 3.2.1 by @​thomhurst in
thomhurst/TUnit#5987
* chore(deps): update dependency microsoft.playwright to 1.60.0 by
@​thomhurst in thomhurst/TUnit#5988
* chore(deps): update dependency messagepack to 3.1.6 by @​thomhurst in
thomhurst/TUnit#5992
* chore(deps): update dependency polyfill to 10.7.0 by @​thomhurst in
thomhurst/TUnit#5998
* chore(deps): update dependency polyfill to 10.7.0 by @​thomhurst in
thomhurst/TUnit#5997
* chore(deps): update verify to 31.17.0 by @​thomhurst in
thomhurst/TUnit#6000
* chore(deps): update verify to 31.18.0 by @​thomhurst in
thomhurst/TUnit#6013
* chore(deps): update dependency microsoft.net.test.sdk to 18.6.0 by
@​thomhurst in thomhurst/TUnit#6016
* chore(deps): update dependency dompurify to v3.4.6 by @​thomhurst in
thomhurst/TUnit#6015
* chore(deps): update dependency dompurify to v3.4.7 by @​thomhurst in
thomhurst/TUnit#6019
* chore(deps): update dependency npgsql to 10.0.3 by @​thomhurst in
thomhurst/TUnit#6020
* chore(deps): update dependency stackexchange.redis to 2.13.17 by
@​thomhurst in thomhurst/TUnit#6021
* chore(deps): update dependency npgsql.entityframeworkcore.postgresql
to 10.0.2 by @​thomhurst in thomhurst/TUnit#6022

## New Contributors
* @​smolchanovsky made their first contribution in
thomhurst/TUnit#5989

**Full Changelog**:
thomhurst/TUnit@v1.45.29...v1.46.0

Commits viewable in [compare
view](thomhurst/TUnit@v1.45.29...v1.48.6).
</details>

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=TUnit.Core&package-manager=nuget&previous-version=1.45.29&new-version=1.48.6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.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.

perf(core): de-LINQ data-source expansion (matrix Cartesian, exclusions, tuple factories)

1 participant