Skip to content

Feat/numeric tolerance#5110

Merged
thomhurst merged 5 commits intothomhurst:mainfrom
agray:feat/numeric-tolerance
Mar 22, 2026
Merged

Feat/numeric tolerance#5110
thomhurst merged 5 commits intothomhurst:mainfrom
agray:feat/numeric-tolerance

Conversation

@agray
Copy link
Contributor

@agray agray commented Mar 9, 2026

Description

Add relative tolerance support for numeric equality assertions

This PR adds support for relative tolerance comparisons to numeric equality assertions.

New API:

await Assert.That(actual)
    .IsEqualTo(expected)
    .WithinRelativeTolerance(5);

This asserts that actual is within 5% of expected.

The comparison uses:

|actual - expected| <= |expected| * (percentTolerance / 100)

This enables comparisons where an absolute tolerance is not appropriate (for example when values vary greatly in magnitude).

Scope

Relative tolerance is implemented for numeric equality assertions:

  • DoubleEqualsAssertion
  • FloatEqualsAssertion
  • DecimalEqualsAssertion

Other ToleranceBasedEqualsAssertion types (e.g. DateTime, DateOnly, TimeOnly) remain unchanged.

Edge cases handled

  • Floating-point special cases are handled consistently with existing equality assertions:
  • NaN equals NaN
  • infinities must match sign
  • expected == 0 falls back to exact equality

Tests

Added tests covering:

  • passing/failing relative tolerance
  • boundary inclusion
  • negative expected values
  • zero expected values
  • NaN / infinity handling
  • assertion message and expression rendering
  • All existing tests continue to pass.
image image image

Related

Closes / addresses: #4870

Fixes #
#4870

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Refactoring (no functional changes)

Checklist

Required

  • I have read the Contributing Guidelines
  • My code follows the project's code style (modern C# syntax, proper naming conventions)
  • I have written tests that prove my fix is effective or my feature works

TUnit-Specific Requirements

  • Dual-Mode Implementation: If this change affects test discovery/execution, I have implemented it in BOTH:
    • Source Generator path (TUnit.Core.SourceGenerator)
    • Reflection path (TUnit.Engine)
  • Snapshot Tests: If I changed source generator output or public APIs:
    • I ran TUnit.Core.SourceGenerator.Tests and/or TUnit.PublicAPI tests
    • I reviewed the .received.txt files and accepted them as .verified.txt
    • I committed the updated .verified.txt files

Testing

  • All existing tests pass (dotnet test)
  • I have added tests that cover my changes

Additional Notes

@agray agray force-pushed the feat/numeric-tolerance branch 2 times, most recently from 51eef1d to 185505c Compare March 9, 2026 07:33
Copy link
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

This PR adds relative tolerance support for numeric equality assertions, which addresses a genuine gap in the assertion library. The feature itself is well-motivated and the test coverage is solid. However, I have several architectural and correctness concerns.


Major Issues

1. API Surface Proliferation — Three Overlapping Ways to Do the Same Thing

The PR introduces significant redundancy:

  • IsEqualTo(expected).Within(tolerance) — existing, absolute tolerance
  • NEW: IsEqualTo(expected).WithinRelativeTolerance(5) — relative tolerance on equality chain
  • NEW: IsCloseTo(expected, tolerance) — standalone absolute tolerance assertion
  • NEW: IsWithinPercentOf(expected, percent) — standalone relative tolerance assertion

IsCloseTo and IsEqualTo().Within() are conceptually identical, as are IsWithinPercentOf and IsEqualTo().WithinRelativeTolerance(). The original issue (#4870) asked for relative tolerance — the WithinRelativeTolerance extension on the existing chain is the right solution. The standalone IsCloseTo and IsWithinPercentOf appear unnecessary and significantly bloat the public API surface (10 new public classes + extension methods).

Recommendation: Remove the standalone IsCloseTo and IsWithinPercentOf assertions entirely. They duplicate functionality already available via the IsEqualTo().Within() pattern that users already know.


2. Massive Code Duplication

NumericIsCloseToAssertion.cs and NumericIsWithinPercentOfAssertion.cs each contain 5 nearly-identical concrete classes with repeated NaN/infinity handling, arithmetic, and error message logic. The existing ToleranceBasedEqualsAssertion<TValue, TTolerance> pattern was designed to avoid exactly this — yet it's not used here. This is a significant maintenance liability.

If standalone assertions are kept, they should at minimum share a generic base class or use a helper method for the repeated special-value handling.


3. Bug: Division by Zero in Error Message

In NumericIsWithinPercentOfAssertion.cs, the failure message for FloatIsWithinPercentOfAssertion does not guard against _expected == 0f:

// FloatIsWithinPercentOfAssertion — no zero-guard:
Task.FromResult(AssertionResult.Failed(
    $"found {value}, which differs by {diff} ({(diff / Math.Abs(_expected)) * 100:F2}% of expected)"));

When _expected == 0f and the assertion fails, this produces NaN% or Infinity% in the message. Compare with DoubleIsWithinPercentOfAssertion which correctly guards:

var actualPercent = _expected != 0 ? (diff / Math.Abs(_expected)) * 100 : double.PositiveInfinity;

FloatIsWithinPercentOfAssertion and DecimalIsWithinPercentOfAssertion need the same guard for their failure messages.


4. Missing for / — Silent Runtime Failure

The PR adds IsWithinRelativeTolerance overrides for double, float, and decimal, but not for int or long. The base class default throws NotSupportedException:

protected virtual bool IsWithinRelativeTolerance(TValue actual, TValue expected, double percentTolerance)
    => throw new NotSupportedException($"{GetType().Name} does not support relative tolerance.");

This means a user who writes:

await Assert.That(myInt).IsEqualTo(100).WithinRelativeTolerance(5);

...will get a NotSupportedException at runtime, with no compile-time indication. This is a poor developer experience, especially since WithinRelativeTolerance is a public method on the base class and appears callable. Either implement it for int/long (straightforward — convert to double, apply the formula) or prevent it from being callable via the type system.


5. Precision Loss in

// LongIsCloseToAssertion
var diff = Math.Abs((double) value - (double) _expected);
return diff <= (double) _tolerance

Converting long values to double for subtraction loses precision for large values (e.g., long.MaxValue and long.MaxValue - 1 both round to the same double). The existing LongEqualsAssertion.IsWithinTolerance avoids this by working in long arithmetic:

protected override bool IsWithinTolerance(long actual, long expected, long tolerance)
{
    var diff = Math.Abs(actual - expected);
    return diff <= tolerance;
}

LongIsCloseToAssertion should do the same (though Math.Abs on long.MinValue needs a guard for overflow).


Minor Issues

6. Inconsistent Validation

WithinRelativeTolerance validates the percentage is non-negative, but the new standalone assertions (IsCloseTo, IsWithinPercentOf) accept negative tolerances silently. If these are kept, they should validate too.

7. Zero-Expected Error Formatting Inconsistency

var actualPercent = _expected != 0 ? (diff / Math.Abs(_expected)) * 100m : -1m;
var percentDisplay = actualPercent >= 0 ? $"{actualPercent:F2}%" : "Infinity%";

Using -1m as a sentinel value for "infinity" is fragile and unusual. Prefer double.PositiveInfinity (as the double version does) or just special-case the message string.


What's Good

  • The WithinRelativeTolerance feature itself is well-designed. The formula |actual - expected| <= |expected| * (percent / 100) is correct, and the zero/NaN/infinity edge case handling in DoubleEqualsAssertion and FloatEqualsAssertion is thorough.
  • The ToleranceMode enum with a switch in the base class is a clean way to handle the two tolerance modes.
  • Test coverage is excellent — boundary inclusion, negative values, zero, NaN, and infinity are all tested.
  • Public API snapshots are updated correctly.
  • The validation in WithinRelativeTolerance throwing ArgumentOutOfRangeException for NaN/Infinity/negative inputs is the right approach.

Summary

The core feature (WithinRelativeTolerance on numeric equality assertions) is the right approach and well-implemented for floating-point types. The main concerns are: (1) the standalone IsCloseTo/IsWithinPercentOf assertions appear redundant given the existing API patterns; (2) there's a real bug in FloatIsWithinPercentOfAssertion's error message when expected is zero; and (3) the silent NotSupportedException for int/long relative tolerance is a usability trap.

Copy link
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

This PR adds relative tolerance support for numeric equality assertions, which addresses a genuine gap in the assertion library. The feature itself is well-motivated and test coverage is solid. However, I have several architectural and correctness concerns.


Major Issues

1. API Surface Proliferation — Three Overlapping Ways to Do the Same Thing

The PR introduces significant redundancy:

  • IsEqualTo(expected).Within(tolerance) — existing, absolute tolerance
  • NEW: IsEqualTo(expected).WithinRelativeTolerance(5) — relative tolerance on equality chain
  • NEW: IsCloseTo(expected, tolerance) — standalone absolute tolerance assertion
  • NEW: IsWithinPercentOf(expected, percent) — standalone relative tolerance assertion

IsCloseTo and IsEqualTo().Within() are conceptually identical, as are IsWithinPercentOf and IsEqualTo().WithinRelativeTolerance(). The original issue (#4870) asked for relative tolerance — the WithinRelativeTolerance extension on the existing chain is the right solution. The standalone IsCloseTo and IsWithinPercentOf appear unnecessary and significantly bloat the public API surface (10 new public classes + extension methods).

Recommendation: Consider removing the standalone IsCloseTo and IsWithinPercentOf assertions. They duplicate functionality already available via the IsEqualTo().Within() pattern.


2. Massive Code Duplication

NumericIsCloseToAssertion.cs and NumericIsWithinPercentOfAssertion.cs each contain 5 nearly-identical concrete classes with repeated NaN/infinity handling and error message logic. The existing ToleranceBasedEqualsAssertion<TValue, TTolerance> pattern was designed to avoid exactly this. If standalone assertions are kept, they should at minimum share a generic base class.


3. Bug: Division by Zero in FloatIsWithinPercentOfAssertion Error Message

In NumericIsWithinPercentOfAssertion.cs, the failure message for FloatIsWithinPercentOfAssertion does not guard against _expected == 0f:

// No zero-guard — produces NaN% or Infinity% in the message:
$"found {value}, which differs by {diff} ({(diff / Math.Abs(_expected)) * 100:F2}% of expected)"

Compare with DoubleIsWithinPercentOfAssertion which correctly guards:

var actualPercent = _expected != 0 ? (diff / Math.Abs(_expected)) * 100 : double.PositiveInfinity;

FloatIsWithinPercentOfAssertion needs the same guard.


4. Missing IsWithinRelativeTolerance for int/long — Silent Runtime Failure

The PR adds IsWithinRelativeTolerance overrides for double, float, and decimal, but not for int or long. The base class default throws NotSupportedException. This means:

await Assert.That(myInt).IsEqualTo(100).WithinRelativeTolerance(5);

...compiles fine but throws NotSupportedException at runtime. Since WithinRelativeTolerance is public on the base class and appears callable, users won't know until their test runs. Implement it for int/long (convert to double, apply the formula) or prevent it from being callable via the type system.


5. Precision Loss in LongIsCloseToAssertion

var diff = Math.Abs((double) value - (double) _expected);
return diff <= (double) _tolerance

Converting long to double loses precision for large values — long.MaxValue and long.MaxValue - 1 both round to the same double. The existing LongEqualsAssertion.IsWithinTolerance correctly avoids this by staying in long arithmetic. LongIsCloseToAssertion should do the same.


Minor Issues

6. Inconsistent Validation

WithinRelativeTolerance validates the percentage is non-negative, but the standalone IsCloseTo and IsWithinPercentOf constructors accept negative tolerances silently. If these assertions are kept, they should validate at construction time too.

7. DecimalIsWithinPercentOfAssertion Sentinel Value

var actualPercent = _expected != 0 ? ... : -1m;
var percentDisplay = actualPercent >= 0 ? $"{actualPercent:F2}%" : "Infinity%";

Using -1m as a sentinel for "infinity" is fragile. Prefer a direct if/else on _expected == 0m for clarity.


What's Good

  • The WithinRelativeTolerance feature itself is well-designed. The formula |actual - expected| <= |expected| * (percent / 100) is correct, and zero/NaN/infinity edge case handling in DoubleEqualsAssertion and FloatEqualsAssertion is thorough.
  • The ToleranceMode enum with a switch in the base class is a clean pattern — avoids bool flag proliferation.
  • Test coverage is excellent — boundary inclusion, negative values, zero, NaN, and infinity are all covered.
  • Public API snapshots are updated correctly.
  • Validating WithinRelativeTolerance inputs at construction time (ArgumentOutOfRangeException for NaN/Infinity/negative) is the right approach.

Summary

The core WithinRelativeTolerance feature on numeric equality assertions is the right approach and well-implemented for floating-point types. The main concerns before merging are: (1) the standalone IsCloseTo/IsWithinPercentOf assertions appear redundant and add significant code duplication; (2) there is a real bug in FloatIsWithinPercentOfAssertion's error message when expected is zero; and (3) the silent NotSupportedException for int/long relative tolerance is a usability trap.

@agray
Copy link
Contributor Author

agray commented Mar 11, 2026

@thomhurst : Please give modular a kick

Copy link
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 review identified several issues. Based on the current diff, none of the major issues appear to have been addressed. Restating them for visibility:


Still Outstanding: Major Issues

1. API Surface Proliferation (unresolved)

The standalone and assertions are still present. These are functionally equivalent to the existing and the new patterns, but add 10 new public classes plus extension methods to the API surface. Once public, these are a permanent maintenance burden.

Recommendation: Remove NumericIsCloseToAssertion.cs and NumericIsWithinPercentOfAssertion.cs entirely. The WithinRelativeTolerance chained API is the right approach — it composes cleanly with the existing pattern users already know.

2. Code Duplication (unresolved)

The two new files each contain 5 near-identical concrete classes duplicating NaN/infinity handling, arithmetic, and error formatting. The existing ToleranceBasedEqualsAssertion<TValue, TTolerance> template was designed to prevent exactly this. This is a significant maintenance liability going forward.

3. Bug: Division by Zero in Error Message (unresolved)

When and the assertion fails (diff > 0), this produces or . The Double and Int variants correctly guard against this; Float does not.

4. Missing for / — Silent Runtime Failure (unresolved)

IntEqualsAssertion and LongEqualsAssertion do not override this. Writing Assert.That(myInt).IsEqualTo(100).WithinRelativeTolerance(5) compiles fine but throws NotSupportedException at runtime. Either implement it for int/long (the math is straightforward: cast to double, apply the formula) or use the type system to prevent it from being called.

5. Precision Loss in (unresolved)

For values near long.MaxValue, the cast to double loses precision. The existing LongEqualsAssertion correctly stays in long arithmetic. LongIsCloseToAssertion should do the same.


Still Outstanding: Minor Issues

6. No Input Validation in Standalone Assertions (unresolved)

IsCloseTo and IsWithinPercentOf accept negative tolerances/percents without any validation, while WithinRelativeTolerance does validate. If kept, add guards.

7. Sentinel Value in (unresolved)

Using -1m as a sentinel for infinity is fragile. A simple if/else on _expected == 0m is clearer.


What Remains Good

The core WithinRelativeTolerance feature on the ToleranceBasedEqualsAssertion base class (the ToleranceMode enum, the switch dispatch, the IsWithinRelativeTolerance override for double/float/decimal) is well-structured and the implementations are correct. The test coverage is thorough. These are solid foundations — the PR just needs the duplicate standalone assertions removed and the int/long gap closed.

Copy link
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 review identified several issues. Based on the current diff, none of the major issues appear to have been addressed. Restating them for visibility.


Still Outstanding: Major Issues

1. API Surface Proliferation (unresolved)

The standalone IsCloseTo and IsWithinPercentOf assertions are still present. These are functionally equivalent to the existing IsEqualTo().Within() and the new IsEqualTo().WithinRelativeTolerance() patterns, but add 10 new public classes plus extension methods to the public API. Once merged, these are a permanent maintenance burden.

Recommendation: Remove NumericIsCloseToAssertion.cs and NumericIsWithinPercentOfAssertion.cs entirely. The WithinRelativeTolerance chained API is the right approach — it composes cleanly with the existing pattern users already know.

2. Code Duplication (unresolved)

The two new files each contain 5 near-identical concrete classes duplicating NaN/infinity handling, arithmetic, and error formatting. The existing ToleranceBasedEqualsAssertion<TValue, TTolerance> template was designed to prevent exactly this. This is a significant maintenance liability.

3. Bug: Division by Zero in FloatIsWithinPercentOfAssertion Error Message (unresolved)

// NumericIsWithinPercentOfAssertion.cs — FloatIsWithinPercentOfAssertion
$"found {value}, which differs by {diff} ({(diff / Math.Abs(_expected)) * 100:F2}% of expected)"

When _expected == 0f and the assertion fails, this produces NaN% or Infinity% in the message. The Double and Int variants correctly guard against this with a zero check; Float does not.

4. Missing IsWithinRelativeTolerance for int/long — Silent Runtime Failure (unresolved)

The base class default throws:

protected virtual bool IsWithinRelativeTolerance(TValue actual, TValue expected, double percentTolerance)
    => throw new NotSupportedException(...);

IntEqualsAssertion and LongEqualsAssertion do not override this. Writing Assert.That(myInt).IsEqualTo(100).WithinRelativeTolerance(5) compiles fine but throws NotSupportedException at runtime — with no compile-time indication. Either implement it for int/long (cast to double, apply the formula) or use the type system to prevent the call.

5. Precision Loss in LongIsCloseToAssertion (unresolved)

var diff = Math.Abs((double) value - (double) _expected); // precision loss for large longs

For values near long.MaxValue, the cast to double loses precision. The existing LongEqualsAssertion correctly stays in long arithmetic. LongIsCloseToAssertion should do the same.


Still Outstanding: Minor Issues

6. No Input Validation in Standalone Assertions (unresolved)

IsCloseTo and IsWithinPercentOf accept negative tolerances without any validation, while WithinRelativeTolerance does validate. If these are kept, add guards at construction time.

7. Sentinel Value in DecimalIsWithinPercentOfAssertion (unresolved)

var actualPercent = _expected != 0 ? ... : -1m;
var percentDisplay = actualPercent >= 0 ? $"{actualPercent:F2}%" : "Infinity%";

Using -1m as a sentinel for infinity is fragile. A simple if/else on _expected == 0m is clearer.


What Remains Good

The core WithinRelativeTolerance feature on the ToleranceBasedEqualsAssertion base class — the ToleranceMode enum, the switch dispatch, and the IsWithinRelativeTolerance override for double/float/decimal — is well-structured and the implementations are correct. Test coverage is thorough. These are solid foundations. The PR primarily needs the duplicate standalone assertions removed and the int/long gap closed.

@thomhurst
Copy link
Owner

I think ignore points 1 & 2 from Claude above. But 3 - 7 seem valid if you're okay to address those?

@agray
Copy link
Contributor Author

agray commented Mar 14, 2026

Value in DecimalIsWithinPercentOfAssertion (unresolved)

I'll progress this in a couple of hours.

@agray agray force-pushed the feat/numeric-tolerance branch from b706d48 to d214b9a Compare March 21, 2026 00:40
@agray
Copy link
Contributor Author

agray commented Mar 21, 2026

@thomhurst I've just pushed changes to attempt to address issues 3 through 7.
Please kick the modular mule

Copy link
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 (Commit 09f600b)

Good progress on the latest commit. Issues 3–7 from the previous reviews have all been addressed. Here's the updated status.


Resolved Since Last Review ✓

  • Issue 3 (FloatIsWithinPercentOfAssertion division by zero) — Fixed. float.PositiveInfinity guard is now present.
  • Issue 4 (Missing int/long relative tolerance — silent NotSupportedException) — Fixed. Both IntEqualsAssertion and LongEqualsAssertion now override IsWithinRelativeTolerance. The LongEqualsAssertion implementation is particularly nice: it uses decimal arithmetic to avoid the precision loss that was called out in issue 5.
  • Issue 5 (Precision loss in LongIsCloseToAssertion) — Fixed. Now uses (decimal) value instead of (double) value.
  • Issue 6 (Missing input validation in standalone assertions) — Fixed. IsCloseTo and IsWithinPercentOf now validate at construction time.
  • Issue 7 (DecimalIsWithinPercentOfAssertion sentinel value) — Fixed. Now uses a clean if/else on _expected == 0m.

Still Outstanding

1. API Surface Proliferation (unresolved — deliberate?)

The standalone IsCloseTo and IsWithinPercentOf assertions are still present. To summarise the concern: these are functionally equivalent to the existing IsEqualTo().Within() and the new IsEqualTo().WithinRelativeTolerance() APIs, which adds 10 new public classes plus extension methods to a surface area that becomes permanent once merged. If the author has considered this and disagrees, that's a valid call — but it's worth an explicit decision.

2. Code Duplication (unresolved)

The two new files still each contain 5 near-identical concrete classes with repeated NaN/infinity handling, arithmetic, and error formatting. The ToleranceBasedEqualsAssertion<TValue, TTolerance> pattern was designed to prevent exactly this pattern. If IsCloseTo and IsWithinPercentOf are kept, consider whether a shared generic base can be extracted.


New Minor Observations

Float percentage precision

In FloatEqualsAssertion.IsWithinRelativeTolerance:

var allowedDifference = Math.Abs(expected) * ((float) percentTolerance / 100f);

Casting percentTolerance to float before dividing loses precision in the percentage itself. Consider (float)(percentTolerance / 100.0) to keep the division in double before the final cast, which matches how DoubleEqualsAssertion works.

LongIsWithinPercentOfAssertion uses double, not decimal

In NumericIsWithinPercentOfAssertion.cs, LongIsWithinPercentOfAssertion still computes:

var diff = Math.Abs((double)value - _expected);

Meanwhile LongEqualsAssertion.IsWithinRelativeTolerance correctly uses decimal to avoid precision loss. This creates an inconsistency between the two long-percentage code paths in the same PR. If IsWithinPercentOf is kept, it should match the precision approach used in LongEqualsAssertion.


Summary

The core WithinRelativeTolerance feature on the ToleranceBasedEqualsAssertion base class is now solid — all five numeric types are correctly implemented, the int/long runtime trap is closed, and the edge-case bugs are fixed. The outstanding concerns are the design question around the standalone assertions (issues 1 & 2) and two minor precision inconsistencies in float and long paths of the new code.

thomhurst and others added 4 commits March 22, 2026 10:15
Add relative and absolute tolerance assertions for numeric types
(double, float, int, long, decimal):

- IsCloseTo(expected, tolerance): asserts |actual - expected| <= tolerance
- IsWithinPercentOf(expected, percent): asserts |actual - expected| <= |expected * percent / 100|

Both assertions follow the existing pattern using [AssertionExtension]
for source-generated extension methods. Includes proper handling of
NaN, infinity, and edge cases for floating-point types.

Closes thomhurst#4870
…o double

Cast `value` to `double` before subtraction and compare against
`(double)_tolerance` so that extreme long values (e.g., MaxValue vs MinValue)
no longer silently wrap around.
github-actions bot pushed a commit to IntelliTect/CodingGuidelines that referenced this pull request Mar 23, 2026
[//]: # (dependabot-start)
⚠️  **Dependabot is rebasing this PR** ⚠️ 

Rebasing might not happen immediately, so don't worry if this takes some
time.

Note: if you make any changes to this PR yourself, they will take
precedence over the rebase.

---

[//]: # (dependabot-end)

Updated [TUnit.Core](https://github.com/thomhurst/TUnit) from 1.19.57 to
1.21.6.

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

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

## 1.21.6

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

## What's Changed
### Other Changes
* perf: replace object locks with Lock type for efficient
synchronization by @​thomhurst in
thomhurst/TUnit#5219
* perf: parallelize test metadata collection for source-generated tests
by @​thomhurst in thomhurst/TUnit#5221
* perf: use GetOrAdd args overload to eliminate closure allocations in
event receivers by @​thomhurst in
thomhurst/TUnit#5222
* perf: self-contained TestEntry<T> with consolidated switch invokers
eliminates per-test JIT by @​thomhurst in
thomhurst/TUnit#5223
### Dependencies
* chore(deps): update tunit to 1.21.0 by @​thomhurst in
thomhurst/TUnit#5220


**Full Changelog**:
thomhurst/TUnit@v1.21.0...v1.21.6

## 1.21.0

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

## What's Changed
### Other Changes
* perf: reduce ConcurrentDictionary closure allocations in hot paths by
@​thomhurst in thomhurst/TUnit#5210
* perf: reduce async state machine overhead in test execution pipeline
by @​thomhurst in thomhurst/TUnit#5214
* perf: reduce allocations in EventReceiverOrchestrator and
TestContextExtensions by @​thomhurst in
thomhurst/TUnit#5212
* perf: skip timeout machinery when no timeout configured by @​thomhurst
in thomhurst/TUnit#5211
* perf: reduce allocations and lock contention in ObjectTracker by
@​thomhurst in thomhurst/TUnit#5213
* Feat/numeric tolerance by @​agray in
thomhurst/TUnit#5110
* perf: remove unnecessary lock in ObjectTracker.TrackObjects by
@​thomhurst in thomhurst/TUnit#5217
* perf: eliminate async state machine in
TestCoordinator.ExecuteTestAsync by @​thomhurst in
thomhurst/TUnit#5216
* perf: eliminate LINQ allocation in ObjectTracker.UntrackObjectsAsync
by @​thomhurst in thomhurst/TUnit#5215
* perf: consolidate module initializers into single .cctor via partial
class by @​thomhurst in thomhurst/TUnit#5218
### Dependencies
* chore(deps): update tunit to 1.20.0 by @​thomhurst in
thomhurst/TUnit#5205
* chore(deps): update dependency nunit3testadapter to 6.2.0 by
@​thomhurst in thomhurst/TUnit#5206
* chore(deps): update dependency cliwrap to 3.10.1 by @​thomhurst in
thomhurst/TUnit#5207


**Full Changelog**:
thomhurst/TUnit@v1.20.0...v1.21.0

## 1.20.0

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

## What's Changed
### Other Changes
* Fix inverted colors in HTML report ring chart due to locale-dependent
decimal formatting by @​Copilot in
thomhurst/TUnit#5185
* Fix nullable warnings when using Member() on nullable properties by
@​Copilot in thomhurst/TUnit#5191
* Add CS8629 suppression and member access expression matching to
IsNotNullAssertionSuppressor by @​Copilot in
thomhurst/TUnit#5201
* feat: add ConfigureAppHost hook to AspireFixture by @​thomhurst in
thomhurst/TUnit#5202
* Fix ConfigureTestConfiguration being invoked twice by @​thomhurst in
thomhurst/TUnit#5203
* Add IsEquivalentTo assertion for Memory<T> and ReadOnlyMemory<T> by
@​thomhurst in thomhurst/TUnit#5204
### Dependencies
* chore(deps): update dependency gitversion.tool to v6.6.2 by
@​thomhurst in thomhurst/TUnit#5181
* chore(deps): update dependency gitversion.msbuild to 6.6.2 by
@​thomhurst in thomhurst/TUnit#5180
* chore(deps): update tunit to 1.19.74 by @​thomhurst in
thomhurst/TUnit#5179
* chore(deps): update verify to 31.13.3 by @​thomhurst in
thomhurst/TUnit#5182
* chore(deps): update verify to 31.13.5 by @​thomhurst in
thomhurst/TUnit#5183
* chore(deps): update aspire to 13.1.3 by @​thomhurst in
thomhurst/TUnit#5189
* chore(deps): update dependency stackexchange.redis to 2.12.4 by
@​thomhurst in thomhurst/TUnit#5193
* chore(deps): update microsoft/setup-msbuild action to v3 by
@​thomhurst in thomhurst/TUnit#5197


**Full Changelog**:
thomhurst/TUnit@v1.19.74...v1.20.0

## 1.19.74

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

## What's Changed
### Other Changes
* feat: per-hook activity spans with method names by @​thomhurst in
thomhurst/TUnit#5159
* fix: add tooltip to truncated span names in HTML report by @​thomhurst
in thomhurst/TUnit#5164
* Use enum names instead of numeric values in test display names by
@​Copilot in thomhurst/TUnit#5178
* fix: resolve CS8920 when mocking interfaces whose members return
static-abstract interfaces by @​lucaxchaves in
thomhurst/TUnit#5154
### Dependencies
* chore(deps): update tunit to 1.19.57 by @​thomhurst in
thomhurst/TUnit#5157
* chore(deps): update dependency gitversion.msbuild to 6.6.1 by
@​thomhurst in thomhurst/TUnit#5160
* chore(deps): update dependency gitversion.tool to v6.6.1 by
@​thomhurst in thomhurst/TUnit#5161
* chore(deps): update dependency polyfill to 9.20.0 by @​thomhurst in
thomhurst/TUnit#5163
* chore(deps): update dependency polyfill to 9.20.0 by @​thomhurst in
thomhurst/TUnit#5162
* chore(deps): update dependency polyfill to 9.21.0 by @​thomhurst in
thomhurst/TUnit#5166
* chore(deps): update dependency polyfill to 9.21.0 by @​thomhurst in
thomhurst/TUnit#5167
* chore(deps): update dependency polyfill to 9.22.0 by @​thomhurst in
thomhurst/TUnit#5168
* chore(deps): update dependency polyfill to 9.22.0 by @​thomhurst in
thomhurst/TUnit#5169
* chore(deps): update dependency coverlet.collector to 8.0.1 by
@​thomhurst in thomhurst/TUnit#5177

## New Contributors
* @​lucaxchaves made their first contribution in
thomhurst/TUnit#5154

**Full Changelog**:
thomhurst/TUnit@v1.19.57...v1.19.74

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

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=TUnit.Core&package-manager=nuget&previous-version=1.19.57&new-version=1.21.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>
intellitect-bot pushed a commit to IntelliTect/EssentialCSharp.Web that referenced this pull request Mar 24, 2026
Updated [TUnit](https://github.com/thomhurst/TUnit) from 1.19.57 to
1.21.6.

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

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

## 1.21.6

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

## What's Changed
### Other Changes
* perf: replace object locks with Lock type for efficient
synchronization by @​thomhurst in
thomhurst/TUnit#5219
* perf: parallelize test metadata collection for source-generated tests
by @​thomhurst in thomhurst/TUnit#5221
* perf: use GetOrAdd args overload to eliminate closure allocations in
event receivers by @​thomhurst in
thomhurst/TUnit#5222
* perf: self-contained TestEntry<T> with consolidated switch invokers
eliminates per-test JIT by @​thomhurst in
thomhurst/TUnit#5223
### Dependencies
* chore(deps): update tunit to 1.21.0 by @​thomhurst in
thomhurst/TUnit#5220


**Full Changelog**:
thomhurst/TUnit@v1.21.0...v1.21.6

## 1.21.0

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

## What's Changed
### Other Changes
* perf: reduce ConcurrentDictionary closure allocations in hot paths by
@​thomhurst in thomhurst/TUnit#5210
* perf: reduce async state machine overhead in test execution pipeline
by @​thomhurst in thomhurst/TUnit#5214
* perf: reduce allocations in EventReceiverOrchestrator and
TestContextExtensions by @​thomhurst in
thomhurst/TUnit#5212
* perf: skip timeout machinery when no timeout configured by @​thomhurst
in thomhurst/TUnit#5211
* perf: reduce allocations and lock contention in ObjectTracker by
@​thomhurst in thomhurst/TUnit#5213
* Feat/numeric tolerance by @​agray in
thomhurst/TUnit#5110
* perf: remove unnecessary lock in ObjectTracker.TrackObjects by
@​thomhurst in thomhurst/TUnit#5217
* perf: eliminate async state machine in
TestCoordinator.ExecuteTestAsync by @​thomhurst in
thomhurst/TUnit#5216
* perf: eliminate LINQ allocation in ObjectTracker.UntrackObjectsAsync
by @​thomhurst in thomhurst/TUnit#5215
* perf: consolidate module initializers into single .cctor via partial
class by @​thomhurst in thomhurst/TUnit#5218
### Dependencies
* chore(deps): update tunit to 1.20.0 by @​thomhurst in
thomhurst/TUnit#5205
* chore(deps): update dependency nunit3testadapter to 6.2.0 by
@​thomhurst in thomhurst/TUnit#5206
* chore(deps): update dependency cliwrap to 3.10.1 by @​thomhurst in
thomhurst/TUnit#5207


**Full Changelog**:
thomhurst/TUnit@v1.20.0...v1.21.0

## 1.20.0

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

## What's Changed
### Other Changes
* Fix inverted colors in HTML report ring chart due to locale-dependent
decimal formatting by @​Copilot in
thomhurst/TUnit#5185
* Fix nullable warnings when using Member() on nullable properties by
@​Copilot in thomhurst/TUnit#5191
* Add CS8629 suppression and member access expression matching to
IsNotNullAssertionSuppressor by @​Copilot in
thomhurst/TUnit#5201
* feat: add ConfigureAppHost hook to AspireFixture by @​thomhurst in
thomhurst/TUnit#5202
* Fix ConfigureTestConfiguration being invoked twice by @​thomhurst in
thomhurst/TUnit#5203
* Add IsEquivalentTo assertion for Memory<T> and ReadOnlyMemory<T> by
@​thomhurst in thomhurst/TUnit#5204
### Dependencies
* chore(deps): update dependency gitversion.tool to v6.6.2 by
@​thomhurst in thomhurst/TUnit#5181
* chore(deps): update dependency gitversion.msbuild to 6.6.2 by
@​thomhurst in thomhurst/TUnit#5180
* chore(deps): update tunit to 1.19.74 by @​thomhurst in
thomhurst/TUnit#5179
* chore(deps): update verify to 31.13.3 by @​thomhurst in
thomhurst/TUnit#5182
* chore(deps): update verify to 31.13.5 by @​thomhurst in
thomhurst/TUnit#5183
* chore(deps): update aspire to 13.1.3 by @​thomhurst in
thomhurst/TUnit#5189
* chore(deps): update dependency stackexchange.redis to 2.12.4 by
@​thomhurst in thomhurst/TUnit#5193
* chore(deps): update microsoft/setup-msbuild action to v3 by
@​thomhurst in thomhurst/TUnit#5197


**Full Changelog**:
thomhurst/TUnit@v1.19.74...v1.20.0

## 1.19.74

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

## What's Changed
### Other Changes
* feat: per-hook activity spans with method names by @​thomhurst in
thomhurst/TUnit#5159
* fix: add tooltip to truncated span names in HTML report by @​thomhurst
in thomhurst/TUnit#5164
* Use enum names instead of numeric values in test display names by
@​Copilot in thomhurst/TUnit#5178
* fix: resolve CS8920 when mocking interfaces whose members return
static-abstract interfaces by @​lucaxchaves in
thomhurst/TUnit#5154
### Dependencies
* chore(deps): update tunit to 1.19.57 by @​thomhurst in
thomhurst/TUnit#5157
* chore(deps): update dependency gitversion.msbuild to 6.6.1 by
@​thomhurst in thomhurst/TUnit#5160
* chore(deps): update dependency gitversion.tool to v6.6.1 by
@​thomhurst in thomhurst/TUnit#5161
* chore(deps): update dependency polyfill to 9.20.0 by @​thomhurst in
thomhurst/TUnit#5163
* chore(deps): update dependency polyfill to 9.20.0 by @​thomhurst in
thomhurst/TUnit#5162
* chore(deps): update dependency polyfill to 9.21.0 by @​thomhurst in
thomhurst/TUnit#5166
* chore(deps): update dependency polyfill to 9.21.0 by @​thomhurst in
thomhurst/TUnit#5167
* chore(deps): update dependency polyfill to 9.22.0 by @​thomhurst in
thomhurst/TUnit#5168
* chore(deps): update dependency polyfill to 9.22.0 by @​thomhurst in
thomhurst/TUnit#5169
* chore(deps): update dependency coverlet.collector to 8.0.1 by
@​thomhurst in thomhurst/TUnit#5177

## New Contributors
* @​lucaxchaves made their first contribution in
thomhurst/TUnit#5154

**Full Changelog**:
thomhurst/TUnit@v1.19.57...v1.19.74

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

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=TUnit&package-manager=nuget&previous-version=1.19.57&new-version=1.21.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.

2 participants