diff --git a/TUnit.Assertions.Tests/NumericEqualityToleranceAssertionTests.cs b/TUnit.Assertions.Tests/NumericEqualityToleranceAssertionTests.cs new file mode 100644 index 0000000000..ee93457948 --- /dev/null +++ b/TUnit.Assertions.Tests/NumericEqualityToleranceAssertionTests.cs @@ -0,0 +1,227 @@ +namespace TUnit.Assertions.Tests; + +public class NumericEqualityToleranceAssertionTests +{ + [Test] + public async Task Double_RelativeTolerance_Passes_When_Within_Percentage() + { + const double actual = 104.9; + const double expected = 100.0; + + await Assert.That(actual) + .IsEqualTo(expected) + .WithinRelativeTolerance(5); + } + + [Test] + public async Task Double_RelativeTolerance_Fails_When_Outside_Percentage() + { + const double actual = 105.1; + const double expected = 100.0; + + var sut = async () => await Assert.That(actual) + .IsEqualTo(expected) + .WithinRelativeTolerance(5); + + await Assert.That(sut).ThrowsException(); + } + + [Test] + public async Task Double_RelativeTolerance_Includes_Exact_Boundary() + { + const double actual = 105.0; + const double expected = 100.0; + + await Assert.That(actual) + .IsEqualTo(expected) + .WithinRelativeTolerance(5); + } + + [Test] + public async Task Double_RelativeTolerance_Works_With_Negative_Expected_Value() + { + const double actual = -104.9; + const double expected = -100.0; + + await Assert.That(actual) + .IsEqualTo(expected) + .WithinRelativeTolerance(5); + } + + [Test] + public async Task Double_RelativeTolerance_Fails_With_Negative_Expected_Value_When_Outside_Boundary() + { + const double actual = -106.0; + const double expected = -100.0; + + var sut = async () => await Assert.That(actual) + .IsEqualTo(expected) + .WithinRelativeTolerance(5); + + await Assert.That(sut).ThrowsException(); + } + + [Test] + public async Task Double_RelativeTolerance_ZeroExpected_Passes_Only_For_Exact_Zero() + { + const double actual = 0.0; + const double expected = 0.0; + + await Assert.That(actual) + .IsEqualTo(expected) + .WithinRelativeTolerance(5); + } + + [Test] + public async Task Double_RelativeTolerance_ZeroExpected_Fails_For_NonZero_Actual() + { + const double actual = 0.0001; + const double expected = 0.0; + + var sut = async () => await Assert.That(actual) + .IsEqualTo(expected) + .WithinRelativeTolerance(5); + + await Assert.That(sut).ThrowsException(); + } + + [Test] + public async Task Double_RelativeTolerance_Treats_NaN_As_Equal_To_NaN() + { + const double actual = double.NaN; + const double expected = double.NaN; + + await Assert.That(actual) + .IsEqualTo(expected) + .WithinRelativeTolerance(5); + } + + [Test] + public async Task Double_RelativeTolerance_Fails_When_Only_One_Side_Is_NaN() + { + const double actual = double.NaN; + const double expected = 100.0; + + var sut = async () => await Assert.That(actual) + .IsEqualTo(expected) + .WithinRelativeTolerance(5); + + await Assert.That(sut).ThrowsException(); + } + + [Test] + public async Task Double_RelativeTolerance_Treats_Matching_Positive_Infinity_As_Equal() + { + const double actual = double.PositiveInfinity; + const double expected = double.PositiveInfinity; + + await Assert.That(actual) + .IsEqualTo(expected) + .WithinRelativeTolerance(5); + } + + [Test] + public async Task Double_RelativeTolerance_Fails_For_Mismatched_Infinities() + { + const double actual = double.PositiveInfinity; + const double expected = double.NegativeInfinity; + + var sut = async () => await Assert.That(actual) + .IsEqualTo(expected) + .WithinRelativeTolerance(5); + + await Assert.That(sut).ThrowsException(); + } + + [Test] + public async Task Float_RelativeTolerance_Passes_When_Within_Percentage() + { + const float actual = 104.9f; + const float expected = 100f; + + await Assert.That(actual) + .IsEqualTo(expected) + .WithinRelativeTolerance(5); + } + + [Test] + public async Task Float_RelativeTolerance_Treats_NaN_As_Equal_To_NaN() + { + const float actual = float.NaN; + const float expected = float.NaN; + + await Assert.That(actual) + .IsEqualTo(expected) + .WithinRelativeTolerance(5); + } + + [Test] + public async Task Float_RelativeTolerance_Treats_Matching_Infinity_As_Equal() + { + const float actual = float.PositiveInfinity; + const float expected = float.PositiveInfinity; + + await Assert.That(actual) + .IsEqualTo(expected) + .WithinRelativeTolerance(5); + } + + [Test] + public async Task Decimal_RelativeTolerance_Passes_When_Within_Percentage() + { + const decimal actual = 104.9m; + const decimal expected = 100m; + + await Assert.That(actual) + .IsEqualTo(expected) + .WithinRelativeTolerance(5); + } + + [Test] + public async Task Decimal_RelativeTolerance_Fails_When_Outside_Percentage() + { + const decimal actual = 105.1m; + const decimal expected = 100m; + + var sut = async () => await Assert.That(actual) + .IsEqualTo(expected) + .WithinRelativeTolerance(5); + + await Assert.That(sut).ThrowsException(); + } + + [Test] + public async Task RelativeTolerance_Exception_Message_Contains_Expectation_Text() + { + const double actual = 106.0; + const double expected = 100.0; + + var sut = async () => await Assert.That(actual) + .IsEqualTo(expected) + .WithinRelativeTolerance(5); + + var exception = await Assert.That(sut).ThrowsException(); + + await Assert.That(exception.Message.NormalizeLineEndings()) + .Contains("Expected to be within 5% of 100") + .And.Contains("but found 106") + .And.Contains("Assert.That(actual).IsEqualTo(expected).WithinRelativeTolerance(5)"); + } + + [Test] + public async Task RelativeTolerance_Exception_Message_Contains_Difference_Details() + { + const double actual = 106.0; + const double expected = 100.0; + + var sut = async () => await Assert.That(actual) + .IsEqualTo(expected) + .WithinRelativeTolerance(5); + + var exception = await Assert.That(sut).ThrowsException(); + + await Assert.That(exception.Message.NormalizeLineEndings()) + .Contains("differs by 6") + .And.Contains("relative tolerance of 5%"); + } +} diff --git a/TUnit.Assertions.Tests/NumericToleranceAssertionTests.cs b/TUnit.Assertions.Tests/NumericToleranceAssertionTests.cs new file mode 100644 index 0000000000..fdac31cc5d --- /dev/null +++ b/TUnit.Assertions.Tests/NumericToleranceAssertionTests.cs @@ -0,0 +1,291 @@ +using TUnit.Assertions.Extensions; + +namespace TUnit.Assertions.Tests; + +public class NumericToleranceAssertionTests +{ + // ========================================== + // IsCloseTo tests - double + // ========================================== + + [Test] + public async Task Double_IsCloseTo_Within_Tolerance_Passes() + { + double value = 10.5; + await Assert.That(value).IsCloseTo(10.0, 0.5); + } + + [Test] + public async Task Double_IsCloseTo_Exact_Match_Passes() + { + double value = 3.14; + await Assert.That(value).IsCloseTo(3.14, 0.0); + } + + [Test] + public async Task Double_IsCloseTo_Outside_Tolerance_Fails() + { + double value = 10.0; + await Assert.ThrowsAsync( + async () => await Assert.That(value).IsCloseTo(12.0, 1.0)); + } + + [Test] + public async Task Double_IsCloseTo_NaN_Both_Passes() + { + await Assert.That(double.NaN).IsCloseTo(double.NaN, 0.1); + } + + [Test] + public async Task Double_IsCloseTo_NaN_Actual_Fails() + { + await Assert.ThrowsAsync( + async () => await Assert.That(double.NaN).IsCloseTo(1.0, 0.1)); + } + + [Test] + public async Task Double_IsCloseTo_Negative_Values_Passes() + { + double value = -5.1; + await Assert.That(value).IsCloseTo(-5.0, 0.2); + } + + // ========================================== + // IsCloseTo tests - float + // ========================================== + + [Test] + public async Task Float_IsCloseTo_Within_Tolerance_Passes() + { + float value = 10.5f; + await Assert.That(value).IsCloseTo(10.0f, 0.5f); + } + + [Test] + public async Task Float_IsCloseTo_Outside_Tolerance_Fails() + { + float value = 10.0f; + await Assert.ThrowsAsync( + async () => await Assert.That(value).IsCloseTo(12.0f, 1.0f)); + } + + [Test] + public async Task Float_IsCloseTo_NaN_Both_Passes() + { + await Assert.That(float.NaN).IsCloseTo(float.NaN, 0.1f); + } + + // ========================================== + // IsCloseTo tests - int + // ========================================== + + [Test] + public async Task Int_IsCloseTo_Within_Tolerance_Passes() + { + int value = 105; + await Assert.That(value).IsCloseTo(100, 5); + } + + [Test] + public async Task Int_IsCloseTo_Exact_Match_Passes() + { + int value = 42; + await Assert.That(value).IsCloseTo(42, 0); + } + + [Test] + public async Task Int_IsCloseTo_Outside_Tolerance_Fails() + { + int value = 100; + await Assert.ThrowsAsync( + async () => await Assert.That(value).IsCloseTo(110, 5)); + } + + // ========================================== + // IsCloseTo tests - long + // ========================================== + + [Test] + public async Task Long_IsCloseTo_Within_Tolerance_Passes() + { + long value = 1000000005L; + await Assert.That(value).IsCloseTo(1000000000L, 10L); + } + + [Test] + public async Task Long_IsCloseTo_Outside_Tolerance_Fails() + { + long value = 100L; + await Assert.ThrowsAsync( + async () => await Assert.That(value).IsCloseTo(200L, 50L)); + } + + // ========================================== + // IsCloseTo tests - decimal + // ========================================== + + [Test] + public async Task Decimal_IsCloseTo_Within_Tolerance_Passes() + { + decimal value = 10.05m; + await Assert.That(value).IsCloseTo(10.0m, 0.1m); + } + + [Test] + public async Task Decimal_IsCloseTo_Outside_Tolerance_Fails() + { + decimal value = 10.0m; + await Assert.ThrowsAsync( + async () => await Assert.That(value).IsCloseTo(12.0m, 1.0m)); + } + + // ========================================== + // IsWithinPercentOf tests - double + // ========================================== + + [Test] + public async Task Double_IsWithinPercentOf_Passes() + { + double value = 105.0; + await Assert.That(value).IsWithinPercentOf(100.0, 10.0); + } + + [Test] + public async Task Double_IsWithinPercentOf_Exact_Match_Passes() + { + double value = 100.0; + await Assert.That(value).IsWithinPercentOf(100.0, 0.0); + } + + [Test] + public async Task Double_IsWithinPercentOf_At_Boundary_Passes() + { + double value = 110.0; + await Assert.That(value).IsWithinPercentOf(100.0, 10.0); + } + + [Test] + public async Task Double_IsWithinPercentOf_Outside_Fails() + { + double value = 120.0; + await Assert.ThrowsAsync( + async () => await Assert.That(value).IsWithinPercentOf(100.0, 10.0)); + } + + [Test] + public async Task Double_IsWithinPercentOf_Negative_Expected_Passes() + { + double value = -95.0; + await Assert.That(value).IsWithinPercentOf(-100.0, 10.0); + } + + [Test] + public async Task Double_IsWithinPercentOf_NaN_Both_Passes() + { + await Assert.That(double.NaN).IsWithinPercentOf(double.NaN, 10.0); + } + + // ========================================== + // IsWithinPercentOf tests - float + // ========================================== + + [Test] + public async Task Float_IsWithinPercentOf_Passes() + { + float value = 105.0f; + await Assert.That(value).IsWithinPercentOf(100.0f, 10.0f); + } + + [Test] + public async Task Float_IsWithinPercentOf_Outside_Fails() + { + float value = 120.0f; + await Assert.ThrowsAsync( + async () => await Assert.That(value).IsWithinPercentOf(100.0f, 10.0f)); + } + + // ========================================== + // IsWithinPercentOf tests - int + // ========================================== + + [Test] + public async Task Int_IsWithinPercentOf_Passes() + { + int value = 105; + await Assert.That(value).IsWithinPercentOf(100, 10.0); + } + + [Test] + public async Task Int_IsWithinPercentOf_Outside_Fails() + { + int value = 120; + await Assert.ThrowsAsync( + async () => await Assert.That(value).IsWithinPercentOf(100, 10.0)); + } + + // ========================================== + // IsWithinPercentOf tests - long + // ========================================== + + [Test] + public async Task Long_IsWithinPercentOf_Passes() + { + long value = 1050L; + await Assert.That(value).IsWithinPercentOf(1000L, 10.0); + } + + [Test] + public async Task Long_IsWithinPercentOf_Outside_Fails() + { + long value = 1200L; + await Assert.ThrowsAsync( + async () => await Assert.That(value).IsWithinPercentOf(1000L, 10.0)); + } + + // ========================================== + // IsWithinPercentOf tests - decimal + // ========================================== + + [Test] + public async Task Decimal_IsWithinPercentOf_Passes() + { + decimal value = 105.0m; + await Assert.That(value).IsWithinPercentOf(100.0m, 10.0m); + } + + [Test] + public async Task Decimal_IsWithinPercentOf_Outside_Fails() + { + decimal value = 120.0m; + await Assert.ThrowsAsync( + async () => await Assert.That(value).IsWithinPercentOf(100.0m, 10.0m)); + } + + // ========================================== + // Edge cases + // ========================================== + + [Test] + public async Task Double_IsCloseTo_Zero_Expected_Passes() + { + double value = 0.001; + await Assert.That(value).IsCloseTo(0.0, 0.01); + } + + [Test] + public async Task Int_IsWithinPercentOf_Zero_Expected_Fails() + { + // 10% of 0 is 0, so only exact match passes + int value = 1; + await Assert.ThrowsAsync( + async () => await Assert.That(value).IsWithinPercentOf(0, 10.0)); + } + + [Test] + public async Task Int_IsWithinPercentOf_Zero_Expected_Zero_Actual_Passes() + { + // 0 is within any percent of 0 + int value = 0; + await Assert.That(value).IsWithinPercentOf(0, 10.0); + } +} diff --git a/TUnit.Assertions/Conditions/NumericIsCloseToAssertion.cs b/TUnit.Assertions/Conditions/NumericIsCloseToAssertion.cs new file mode 100644 index 0000000000..d6b0dafb8c --- /dev/null +++ b/TUnit.Assertions/Conditions/NumericIsCloseToAssertion.cs @@ -0,0 +1,290 @@ +using TUnit.Assertions.Attributes; +using TUnit.Assertions.Chaining; +using TUnit.Assertions.Core; + +namespace TUnit.Assertions.Conditions; + +/// +/// Asserts that a double value is close to an expected value within an absolute tolerance. +/// |actual - expected| <= tolerance +/// +[AssertionExtension("IsCloseTo", OverloadResolutionPriority = 2)] +public class DoubleIsCloseToAssertion : Assertion +{ + private readonly double _expected; + private readonly double _tolerance; + + public DoubleIsCloseToAssertion( + AssertionContext context, + double expected, + double tolerance) + : base(context) + { + if (double.IsNaN(tolerance)) + { + throw new ArgumentOutOfRangeException(nameof(tolerance), "Tolerance cannot be NaN."); + } + + if (tolerance < 0) + { + throw new ArgumentOutOfRangeException(nameof(tolerance), "Tolerance cannot be negative."); + } + + _expected = expected; + _tolerance = tolerance; + } + + protected override Task CheckAsync(EvaluationMetadata metadata) + { + var value = metadata.Value; + var exception = metadata.Exception; + + if (exception != null) + { + return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}")); + } + + // Handle NaN comparisons + if (double.IsNaN(value) && double.IsNaN(_expected)) + { + return AssertionResult._passedTask; + } + + if (double.IsNaN(value) || double.IsNaN(_expected)) + { + return Task.FromResult(AssertionResult.Failed($"found {value}")); + } + + // Handle infinity + if (double.IsPositiveInfinity(value) && double.IsPositiveInfinity(_expected)) + { + return AssertionResult._passedTask; + } + + if (double.IsNegativeInfinity(value) && double.IsNegativeInfinity(_expected)) + { + return AssertionResult._passedTask; + } + + var diff = Math.Abs(value - _expected); + + return diff <= _tolerance + ? AssertionResult._passedTask + : Task.FromResult(AssertionResult.Failed($"found {value}, which differs by {diff}")); + } + + protected override string GetExpectation() => + $"to be close to {_expected} within tolerance {_tolerance}"; +} + +/// +/// Asserts that a float value is close to an expected value within an absolute tolerance. +/// |actual - expected| <= tolerance +/// +[AssertionExtension("IsCloseTo", OverloadResolutionPriority = 2)] +public class FloatIsCloseToAssertion : Assertion +{ + private readonly float _expected; + private readonly float _tolerance; + + public FloatIsCloseToAssertion( + AssertionContext context, + float expected, + float tolerance) + : base(context) + { + if (float.IsNaN(tolerance)) + { + throw new ArgumentOutOfRangeException(nameof(tolerance), "Tolerance cannot be NaN."); + } + + if (tolerance < 0) + { + throw new ArgumentOutOfRangeException(nameof(tolerance), "Tolerance cannot be negative."); + } + + _expected = expected; + _tolerance = tolerance; + } + + protected override Task CheckAsync(EvaluationMetadata metadata) + { + var value = metadata.Value; + var exception = metadata.Exception; + + if (exception != null) + { + return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}")); + } + + // Handle NaN comparisons + if (float.IsNaN(value) && float.IsNaN(_expected)) + { + return AssertionResult._passedTask; + } + + if (float.IsNaN(value) || float.IsNaN(_expected)) + { + return Task.FromResult(AssertionResult.Failed($"found {value}")); + } + + // Handle infinity + if (float.IsPositiveInfinity(value) && float.IsPositiveInfinity(_expected)) + { + return AssertionResult._passedTask; + } + + if (float.IsNegativeInfinity(value) && float.IsNegativeInfinity(_expected)) + { + return AssertionResult._passedTask; + } + + var diff = Math.Abs(value - _expected); + + if (diff <= _tolerance) + { + return AssertionResult._passedTask; + } + + return Task.FromResult(AssertionResult.Failed($"found {value}, which differs by {diff}")); + } + + protected override string GetExpectation() => + $"to be close to {_expected} within tolerance {_tolerance}"; +} + +/// +/// Asserts that an int value is close to an expected value within an absolute tolerance. +/// |actual - expected| <= tolerance +/// +[AssertionExtension("IsCloseTo", OverloadResolutionPriority = 2)] +public class IntIsCloseToAssertion : Assertion +{ + private readonly int _expected; + private readonly int _tolerance; + + public IntIsCloseToAssertion( + AssertionContext context, + int expected, + int tolerance) + : base(context) + { + if (tolerance < 0) + { + throw new ArgumentOutOfRangeException(nameof(tolerance), "Tolerance cannot be negative."); + } + + _expected = expected; + _tolerance = tolerance; + } + + protected override Task CheckAsync(EvaluationMetadata metadata) + { + var value = metadata.Value; + var exception = metadata.Exception; + + if (exception != null) + { + return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}")); + } + + var diff = Math.Abs((long)value - _expected); + + return diff <= _tolerance + ? AssertionResult._passedTask + : Task.FromResult(AssertionResult.Failed($"found {value}, which differs by {diff}")); + } + + protected override string GetExpectation() => + $"to be close to {_expected} within tolerance {_tolerance}"; +} + +/// +/// Asserts that a long value is close to an expected value within an absolute tolerance. +/// |actual - expected| <= tolerance +/// +[AssertionExtension("IsCloseTo", OverloadResolutionPriority = 2)] +public class LongIsCloseToAssertion : Assertion +{ + private readonly long _expected; + private readonly long _tolerance; + + public LongIsCloseToAssertion( + AssertionContext context, + long expected, + long tolerance) + : base(context) + { + if (tolerance < 0) + { + throw new ArgumentOutOfRangeException(nameof(tolerance), "Tolerance cannot be negative."); + } + + _expected = expected; + _tolerance = tolerance; + } + + protected override Task CheckAsync(EvaluationMetadata metadata) + { + var value = metadata.Value; + var exception = metadata.Exception; + + if (exception != null) + { + return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}")); + } + + var diff = Math.Abs((decimal) value - _expected); + return diff <= _tolerance + ? AssertionResult._passedTask + : Task.FromResult(AssertionResult.Failed($"found {value}, which differs by {diff}")); + } + + protected override string GetExpectation() => $"to be close to {_expected} within tolerance {_tolerance}"; +} + +/// +/// Asserts that a decimal value is close to an expected value within an absolute tolerance. +/// |actual - expected| <= tolerance +/// +[AssertionExtension("IsCloseTo", OverloadResolutionPriority = 2)] +public class DecimalIsCloseToAssertion : Assertion +{ + private readonly decimal _expected; + private readonly decimal _tolerance; + + public DecimalIsCloseToAssertion( + AssertionContext context, + decimal expected, + decimal tolerance) + : base(context) + { + if (tolerance < 0) + { + throw new ArgumentOutOfRangeException(nameof(tolerance), "Tolerance cannot be negative."); + } + + _expected = expected; + _tolerance = tolerance; + } + + protected override Task CheckAsync(EvaluationMetadata metadata) + { + var value = metadata.Value; + var exception = metadata.Exception; + + if (exception != null) + { + return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}")); + } + + var diff = Math.Abs(value - _expected); + + return diff <= _tolerance + ? AssertionResult._passedTask + : Task.FromResult(AssertionResult.Failed($"found {value}, which differs by {diff}")); + } + + protected override string GetExpectation() => + $"to be close to {_expected} within tolerance {_tolerance}"; +} diff --git a/TUnit.Assertions/Conditions/NumericIsWithinPercentOfAssertion.cs b/TUnit.Assertions/Conditions/NumericIsWithinPercentOfAssertion.cs new file mode 100644 index 0000000000..2457669be7 --- /dev/null +++ b/TUnit.Assertions/Conditions/NumericIsWithinPercentOfAssertion.cs @@ -0,0 +1,328 @@ +using TUnit.Assertions.Attributes; +using TUnit.Assertions.Core; + +namespace TUnit.Assertions.Conditions; + +/// +/// Asserts that a double value is within a given percentage of an expected value. +/// |actual - expected| <= |expected * percent / 100| +/// +[AssertionExtension("IsWithinPercentOf", OverloadResolutionPriority = 2)] +public class DoubleIsWithinPercentOfAssertion : Assertion +{ + private readonly double _expected; + private readonly double _percent; + + public DoubleIsWithinPercentOfAssertion( + AssertionContext context, + double expected, + double percent) + : base(context) + { + if (double.IsNaN(percent)) + { + throw new ArgumentOutOfRangeException(nameof(percent), "Percent cannot be NaN."); + } + + if (percent < 0) + { + throw new ArgumentOutOfRangeException(nameof(percent), "Percent cannot be negative."); + } + + _expected = expected; + _percent = percent; + } + + protected override Task CheckAsync(EvaluationMetadata metadata) + { + var value = metadata.Value; + var exception = metadata.Exception; + + if (exception != null) + { + return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}")); + } + + // Handle NaN comparisons + if (double.IsNaN(value) && double.IsNaN(_expected)) + { + return AssertionResult._passedTask; + } + + if (double.IsNaN(value) || double.IsNaN(_expected)) + { + return Task.FromResult(AssertionResult.Failed($"found {value}")); + } + + // Handle infinity + if (double.IsPositiveInfinity(value) && double.IsPositiveInfinity(_expected)) + { + return AssertionResult._passedTask; + } + + if (double.IsNegativeInfinity(value) && double.IsNegativeInfinity(_expected)) + { + return AssertionResult._passedTask; + } + + var diff = Math.Abs(value - _expected); + var allowedDelta = Math.Abs(_expected * _percent / 100.0); + + if (diff <= allowedDelta) + { + return AssertionResult._passedTask; + } + + var actualPercent = _expected != 0 ? (diff / Math.Abs(_expected)) * 100 : double.PositiveInfinity; + + return Task.FromResult(AssertionResult.Failed( + $"found {value}, which differs by {diff} ({actualPercent:F2}% of expected)")); + + } + + protected override string GetExpectation() => + $"to be within {_percent}% of {_expected}"; +} + +/// +/// Asserts that a float value is within a given percentage of an expected value. +/// |actual - expected| <= |expected * percent / 100| +/// +[AssertionExtension("IsWithinPercentOf", OverloadResolutionPriority = 2)] +public class FloatIsWithinPercentOfAssertion : Assertion +{ + private readonly float _expected; + private readonly float _percent; + + public FloatIsWithinPercentOfAssertion( + AssertionContext context, + float expected, + float percent) + : base(context) + { + if (float.IsNaN(percent)) + { + throw new ArgumentOutOfRangeException(nameof(percent), "Percent cannot be NaN."); + } + + if (percent < 0) + { + throw new ArgumentOutOfRangeException(nameof(percent), "Percent cannot be negative."); + } + + _expected = expected; + _percent = percent; + } + + protected override Task CheckAsync(EvaluationMetadata metadata) + { + var value = metadata.Value; + var exception = metadata.Exception; + + if (exception != null) + { + return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}")); + } + + // Handle NaN comparisons + if (float.IsNaN(value) && float.IsNaN(_expected)) + { + return AssertionResult._passedTask; + } + + if (float.IsNaN(value) || float.IsNaN(_expected)) + { + return Task.FromResult(AssertionResult.Failed($"found {value}")); + } + + // Handle infinity + if (float.IsPositiveInfinity(value) && float.IsPositiveInfinity(_expected)) + { + return AssertionResult._passedTask; + } + + if (float.IsNegativeInfinity(value) && float.IsNegativeInfinity(_expected)) + { + return AssertionResult._passedTask; + } + + var diff = Math.Abs(value - _expected); + var allowedDelta = Math.Abs(_expected * _percent / 100.0f); + + if (diff <= allowedDelta) + { + return AssertionResult._passedTask; + } + + var actualPercent = _expected != 0f + ? (diff / Math.Abs(_expected)) * 100f + : float.PositiveInfinity; + + return Task.FromResult(AssertionResult.Failed( + $"found {value}, which differs by {diff} ({actualPercent:F2}% of expected)")); + } + + protected override string GetExpectation() => + $"to be within {_percent}% of {_expected}"; +} + +/// +/// Asserts that an int value is within a given percentage of an expected value. +/// |actual - expected| <= |expected * percent / 100| +/// +[AssertionExtension("IsWithinPercentOf", OverloadResolutionPriority = 2)] +public class IntIsWithinPercentOfAssertion : Assertion +{ + private readonly int _expected; + private readonly double _percent; + + public IntIsWithinPercentOfAssertion( + AssertionContext context, + int expected, + double percent) + : base(context) + { + if (percent < 0) + { + throw new ArgumentOutOfRangeException(nameof(percent), "Percent cannot be negative."); + } + + _expected = expected; + _percent = percent; + } + + protected override Task CheckAsync(EvaluationMetadata metadata) + { + var value = metadata.Value; + var exception = metadata.Exception; + + if (exception != null) + { + return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}")); + } + + var diff = Math.Abs((double)value - _expected); + var allowedDelta = Math.Abs(_expected * _percent / 100.0); + + if (diff <= allowedDelta) + { + return AssertionResult._passedTask; + } + + var actualPercent = _expected != 0 ? (diff / Math.Abs(_expected)) * 100 : double.PositiveInfinity; + + return Task.FromResult(AssertionResult.Failed( + $"found {value}, which differs by {(long)diff} ({actualPercent:F2}% of expected)")); + } + + protected override string GetExpectation() => + $"to be within {_percent}% of {_expected}"; +} + +/// +/// Asserts that a long value is within a given percentage of an expected value. +/// |actual - expected| <= |expected * percent / 100| +/// +[AssertionExtension("IsWithinPercentOf", OverloadResolutionPriority = 2)] +public class LongIsWithinPercentOfAssertion : Assertion +{ + private readonly long _expected; + private readonly double _percent; + + public LongIsWithinPercentOfAssertion( + AssertionContext context, + long expected, + double percent) + : base(context) + { + if (percent < 0) + { + throw new ArgumentOutOfRangeException(nameof(percent), "Percent cannot be negative."); + } + + _expected = expected; + _percent = percent; + } + + protected override Task CheckAsync(EvaluationMetadata metadata) + { + var value = metadata.Value; + var exception = metadata.Exception; + + if (exception != null) + { + return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}")); + } + + var diff = Math.Abs((double)value - _expected); + var allowedDelta = Math.Abs(_expected * _percent / 100.0); + + if (diff <= allowedDelta) + { + return AssertionResult._passedTask; + } + + var actualPercent = _expected != 0 ? (diff / Math.Abs(_expected)) * 100 : double.PositiveInfinity; + + return Task.FromResult(AssertionResult.Failed( + $"found {value}, which differs by {(long)diff} ({actualPercent:F2}% of expected)")); + } + + protected override string GetExpectation() => + $"to be within {_percent}% of {_expected}"; +} + +/// +/// Asserts that a decimal value is within a given percentage of an expected value. +/// |actual - expected| <= |expected * percent / 100| +/// +[AssertionExtension("IsWithinPercentOf", OverloadResolutionPriority = 2)] +public class DecimalIsWithinPercentOfAssertion : Assertion +{ + private readonly decimal _expected; + private readonly decimal _percent; + + public DecimalIsWithinPercentOfAssertion( + AssertionContext context, + decimal expected, + decimal percent) + : base(context) + { + if (percent < 0) + { + throw new ArgumentOutOfRangeException(nameof(percent), "Percent cannot be negative."); + } + + _expected = expected; + _percent = percent; + } + + protected override Task CheckAsync(EvaluationMetadata metadata) + { + var value = metadata.Value; + var exception = metadata.Exception; + + if (exception != null) + { + return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}")); + } + + var diff = Math.Abs(value - _expected); + var allowedDelta = Math.Abs(_expected * _percent / 100m); + + if (diff <= allowedDelta) + { + return AssertionResult._passedTask; + } + + var percentDisplay = _expected == 0m + ? "Infinity%" + : $"{(diff / Math.Abs(_expected)) * 100m:F2}%"; + + return Task.FromResult(AssertionResult.Failed( + $"found {value}, which differs by {diff} ({percentDisplay} of expected)")); + } + + protected override string GetExpectation() => + $"to be within {_percent}% of {_expected}"; +} diff --git a/TUnit.Assertions/Conditions/SpecializedEqualityAssertions.cs b/TUnit.Assertions/Conditions/SpecializedEqualityAssertions.cs index e3883889e6..87ca9a9574 100644 --- a/TUnit.Assertions/Conditions/SpecializedEqualityAssertions.cs +++ b/TUnit.Assertions/Conditions/SpecializedEqualityAssertions.cs @@ -1,4 +1,3 @@ -using System.Text; using TUnit.Assertions.Attributes; using TUnit.Assertions.Core; @@ -24,10 +23,7 @@ public DateOnlyEqualsAssertion WithinDays(int days) return this; } - protected override bool HasToleranceValue() - { - return true; // int? always has meaningful value when not null - } + protected override bool HasToleranceValue() => true; // int? always has meaningful value when not null protected override bool IsWithinTolerance(DateOnly actual, DateOnly expected, int toleranceDays) { @@ -36,19 +32,13 @@ protected override bool IsWithinTolerance(DateOnly actual, DateOnly expected, in } protected override object CalculateDifference(DateOnly actual, DateOnly expected) - { - return Math.Abs(actual.DayNumber - expected.DayNumber); - } + => Math.Abs(actual.DayNumber - expected.DayNumber); protected override bool AreExactlyEqual(DateOnly actual, DateOnly expected) - { - return actual == expected; - } + => actual == expected; protected override string FormatDifferenceMessage(DateOnly actual, object difference) - { - return $"found {actual}, which is {difference} days from expected"; - } + => $"found {actual}, which is {difference} days from expected"; protected override string GetExpectation() { @@ -73,31 +63,26 @@ public TimeOnlyEqualsAssertion( { } - protected override bool HasToleranceValue() - { - return true; // TimeSpan? always has meaningful value when not null - } + protected override bool HasToleranceValue() => true; // TimeSpan? always has meaningful value when not null protected override bool IsWithinTolerance(TimeOnly actual, TimeOnly expected, TimeSpan tolerance) { - var diff = actual > expected ? actual.ToTimeSpan() - expected.ToTimeSpan() : expected.ToTimeSpan() - actual.ToTimeSpan(); + var diff = actual > expected + ? actual.ToTimeSpan() - expected.ToTimeSpan() + : expected.ToTimeSpan() - actual.ToTimeSpan(); return diff <= tolerance; } protected override object CalculateDifference(TimeOnly actual, TimeOnly expected) - { - return actual > expected ? actual.ToTimeSpan() - expected.ToTimeSpan() : expected.ToTimeSpan() - actual.ToTimeSpan(); - } + => actual > expected + ? actual.ToTimeSpan() - expected.ToTimeSpan() + : expected.ToTimeSpan() - actual.ToTimeSpan(); protected override bool AreExactlyEqual(TimeOnly actual, TimeOnly expected) - { - return actual == expected; - } + => actual == expected; protected override string FormatDifferenceMessage(TimeOnly actual, object difference) - { - return $"found {actual}, which is {difference} from expected"; - } + => $"found {actual}, which is {difference} from expected"; } #endif @@ -109,19 +94,14 @@ public class DoubleEqualsAssertion : ToleranceBasedEqualsAssertion context, - double expected) - : base(context, expected) + double expected) : base(context, expected) { } - protected override bool HasToleranceValue() - { - return true; // double? always has meaningful value when not null - } + protected override bool HasToleranceValue() => true; // double? always has meaningful value when not null protected override bool IsWithinTolerance(double actual, double expected, double tolerance) { - // Handle NaN comparisons: NaN is only equal to NaN if (double.IsNaN(actual) && double.IsNaN(expected)) { return true; @@ -132,7 +112,6 @@ protected override bool IsWithinTolerance(double actual, double expected, double return false; } - // Handle infinity: infinity equals infinity if (double.IsPositiveInfinity(actual) && double.IsPositiveInfinity(expected)) { return true; @@ -147,15 +126,41 @@ protected override bool IsWithinTolerance(double actual, double expected, double return diff <= tolerance; } - protected override object CalculateDifference(double actual, double expected) + protected override bool IsWithinRelativeTolerance(double actual, double expected, double percentTolerance) { - return Math.Abs(actual - expected); + if (double.IsNaN(actual) && double.IsNaN(expected)) + { + return true; + } + + if (double.IsNaN(actual) || double.IsNaN(expected)) + { + return false; + } + + if (double.IsInfinity(actual) || double.IsInfinity(expected)) + { + return double.Equals(actual, expected); + } + + var diff = Math.Abs(actual - expected); + + // Relative tolerance around zero is not meaningful. + // Require exact equality in that case. + if (expected == 0d) + { + return diff == 0d; + } + + var allowedDifference = Math.Abs(expected) * (percentTolerance / 100d); + return diff <= allowedDifference; } + protected override object CalculateDifference(double actual, double expected) + => Math.Abs(actual - expected); + protected override bool AreExactlyEqual(double actual, double expected) - { - return double.Equals(actual, expected); - } + => double.Equals(actual, expected); } /// @@ -166,19 +171,14 @@ public class FloatEqualsAssertion : ToleranceBasedEqualsAssertion { public FloatEqualsAssertion( AssertionContext context, - float expected) - : base(context, expected) + float expected) : base(context, expected) { } - protected override bool HasToleranceValue() - { - return true; // float? always has meaningful value when not null - } + protected override bool HasToleranceValue() => true; protected override bool IsWithinTolerance(float actual, float expected, float tolerance) { - // Handle NaN comparisons: NaN is only equal to NaN if (float.IsNaN(actual) && float.IsNaN(expected)) { return true; @@ -189,7 +189,6 @@ protected override bool IsWithinTolerance(float actual, float expected, float to return false; } - // Handle infinity: infinity equals infinity if (float.IsPositiveInfinity(actual) && float.IsPositiveInfinity(expected)) { return true; @@ -204,74 +203,80 @@ protected override bool IsWithinTolerance(float actual, float expected, float to return diff <= tolerance; } - protected override object CalculateDifference(float actual, float expected) + protected override bool IsWithinRelativeTolerance(float actual, float expected, double percentTolerance) { - return Math.Abs(actual - expected); + if (float.IsNaN(actual) && float.IsNaN(expected)) + { + return true; + } + + if (float.IsNaN(actual) || float.IsNaN(expected)) + { + return false; + } + + if (float.IsInfinity(actual) || float.IsInfinity(expected)) + { + return float.Equals(actual, expected); + } + + var diff = Math.Abs(actual - expected); + + if (expected == 0f) + { + return diff == 0f; + } + + var allowedDifference = Math.Abs(expected) * ((float) percentTolerance / 100f); + return diff <= allowedDifference; } + protected override object CalculateDifference(float actual, float expected) + => Math.Abs(actual - expected); + protected override bool AreExactlyEqual(float actual, float expected) - { - return float.Equals(actual, expected); - } + => float.Equals(actual, expected); } /// /// Asserts that an int value is equal to another, with optional tolerance. /// [AssertionExtension("IsEqualTo", OverloadResolutionPriority = 2)] -public class IntEqualsAssertion : Assertion +public class IntEqualsAssertion : ToleranceBasedEqualsAssertion { - private readonly int _expected; - private int? _tolerance; - public IntEqualsAssertion( AssertionContext context, int expected) - : base(context) + : base(context, expected) { - _expected = expected; } - public IntEqualsAssertion Within(int tolerance) + protected override bool HasToleranceValue() => true; + + protected override bool IsWithinTolerance(int actual, int expected, int tolerance) { - _tolerance = tolerance; - Context.ExpressionBuilder.Append($".Within({tolerance})"); - return this; + var diff = Math.Abs(actual - expected); + return diff <= tolerance; } - protected override Task CheckAsync(EvaluationMetadata metadata) + protected override bool IsWithinRelativeTolerance(int actual, int expected, double percentTolerance) { - var value = metadata.Value; - var exception = metadata.Exception; - - if (exception != null) - { - return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}")); - } - - if (_tolerance.HasValue) - { - var diff = Math.Abs(value - _expected); - if (diff <= _tolerance.Value) - { - return AssertionResult._passedTask; - } - - return Task.FromResult(AssertionResult.Failed($"found {value}, which differs by {diff}")); - } + var diff = Math.Abs((double) actual - expected); - if (value == _expected) + if (expected == 0) { - return AssertionResult._passedTask; + return diff == 0d; } - return Task.FromResult(AssertionResult.Failed($"found {value}")); + var allowedDifference = Math.Abs(expected) * (percentTolerance / 100d); + return diff <= allowedDifference; } - protected override string GetExpectation() => - _tolerance.HasValue - ? $"to be within {_tolerance} of {_expected}" - : $"to be {_expected}"; + protected override object CalculateDifference(int actual, int expected) + => Math.Abs(actual - expected); + + protected override bool AreExactlyEqual(int actual, int expected) + => actual == expected; } /// @@ -287,10 +292,7 @@ public LongEqualsAssertion( { } - protected override bool HasToleranceValue() - { - return true; // long? always has meaningful value when not null - } + protected override bool HasToleranceValue() => true; // long? always has meaningful value when not null protected override bool IsWithinTolerance(long actual, long expected, long tolerance) { @@ -298,15 +300,24 @@ protected override bool IsWithinTolerance(long actual, long expected, long toler return diff <= tolerance; } - protected override object CalculateDifference(long actual, long expected) + protected override bool IsWithinRelativeTolerance(long actual, long expected, double percentTolerance) { - return Math.Abs(actual - expected); + var diff = Math.Abs((decimal) actual - expected); + + if (expected == 0L) + { + return diff == 0m; + } + + var allowedDifference = Math.Abs(expected) * ((decimal) percentTolerance / 100m); + return diff <= allowedDifference; } + protected override object CalculateDifference(long actual, long expected) + => Math.Abs(actual - expected); + protected override bool AreExactlyEqual(long actual, long expected) - { - return actual == expected; - } + => actual == expected; } /// @@ -317,15 +328,11 @@ public class DecimalEqualsAssertion : ToleranceBasedEqualsAssertion context, - decimal expected) - : base(context, expected) + decimal expected) : base(context, expected) { } - protected override bool HasToleranceValue() - { - return true; // decimal? always has meaningful value when not null - } + protected override bool HasToleranceValue() => true; protected override bool IsWithinTolerance(decimal actual, decimal expected, decimal tolerance) { @@ -333,15 +340,24 @@ protected override bool IsWithinTolerance(decimal actual, decimal expected, deci return diff <= tolerance; } - protected override object CalculateDifference(decimal actual, decimal expected) + protected override bool IsWithinRelativeTolerance(decimal actual, decimal expected, double percentTolerance) { - return Math.Abs(actual - expected); + var diff = Math.Abs(actual - expected); + + if (expected == 0m) + { + return diff == 0m; + } + + var allowedDifference = Math.Abs(expected) * ((decimal) percentTolerance / 100m); + return diff <= allowedDifference; } + protected override object CalculateDifference(decimal actual, decimal expected) + => Math.Abs(actual - expected); + protected override bool AreExactlyEqual(decimal actual, decimal expected) - { - return actual == expected; - } + => actual == expected; } /// @@ -357,26 +373,23 @@ public DateTimeOffsetEqualsAssertion( { } - protected override bool HasToleranceValue() - { - return true; // TimeSpan? always has meaningful value when not null - } + protected override bool HasToleranceValue() => true; // TimeSpan? always has meaningful value when not null protected override bool IsWithinTolerance(DateTimeOffset actual, DateTimeOffset expected, TimeSpan tolerance) { - var diff = actual > expected ? actual - expected : expected - actual; + var diff = actual > expected + ? actual - expected + : expected - actual; return diff <= tolerance; } protected override object CalculateDifference(DateTimeOffset actual, DateTimeOffset expected) - { - return actual > expected ? actual - expected : expected - actual; - } + => actual > expected + ? actual - expected + : expected - actual; protected override bool AreExactlyEqual(DateTimeOffset actual, DateTimeOffset expected) - { - return actual == expected; - } + => actual == expected; } /// @@ -415,21 +428,17 @@ protected override Task CheckAsync(EvaluationMetadata if (_tolerance.HasValue) { - var diff = value > _expected ? value - _expected : _expected - value; - if (diff <= _tolerance.Value) - { - return AssertionResult._passedTask; - } - - return Task.FromResult(AssertionResult.Failed($"found {value}, which differs by {diff}")); - } - - if (value == _expected) - { - return AssertionResult._passedTask; + var diff = value > _expected + ? value - _expected + : _expected - value; + return diff <= _tolerance.Value + ? AssertionResult._passedTask + : Task.FromResult(AssertionResult.Failed($"found {value}, which differs by {diff}")); } - return Task.FromResult(AssertionResult.Failed($"found {value}")); + return value == _expected + ? AssertionResult._passedTask + : Task.FromResult(AssertionResult.Failed($"found {value}")); } protected override string GetExpectation() => diff --git a/TUnit.Assertions/Conditions/ToleranceBasedEqualsAssertion.cs b/TUnit.Assertions/Conditions/ToleranceBasedEqualsAssertion.cs index c5a5f57692..ca9942a99d 100644 --- a/TUnit.Assertions/Conditions/ToleranceBasedEqualsAssertion.cs +++ b/TUnit.Assertions/Conditions/ToleranceBasedEqualsAssertion.cs @@ -4,14 +4,15 @@ namespace TUnit.Assertions.Conditions; /// /// Base class for equality assertions that support tolerance-based comparison. -/// Provides common pattern for Double, Float, Long, DateTime, DateTimeOffset, TimeOnly, and DateOnly equality assertions. /// /// The type of value being compared -/// The type used to express tolerance (e.g., double, TimeSpan, int) +/// The type used to express absolute tolerance public abstract class ToleranceBasedEqualsAssertion : Assertion { private readonly TValue _expected; private TTolerance? _tolerance; + private double? _relativeTolerancePercent; + private ToleranceMode _toleranceMode = ToleranceMode.None; protected ToleranceBasedEqualsAssertion( AssertionContext context, @@ -21,16 +22,47 @@ protected ToleranceBasedEqualsAssertion( _expected = expected; } + private enum ToleranceMode + { + None, + Absolute, + Relative + } + /// - /// Specifies the acceptable tolerance for the comparison. + /// Specifies the acceptable absolute tolerance for the comparison. /// public ToleranceBasedEqualsAssertion Within(TTolerance tolerance) { _tolerance = tolerance; + _relativeTolerancePercent = null; + _toleranceMode = ToleranceMode.Absolute; + Context.ExpressionBuilder.Append($".Within({tolerance})"); return this; } + /// + /// Specifies the acceptable relative tolerance percentage for the comparison. + /// For example, 5 means within 5% of the expected value. + /// + public ToleranceBasedEqualsAssertion WithinRelativeTolerance(double percentTolerance) + { + if (double.IsNaN(percentTolerance) || double.IsInfinity(percentTolerance) || percentTolerance < 0) + { + throw new ArgumentOutOfRangeException( + nameof(percentTolerance), + "Relative tolerance must be a finite, non-negative percentage."); + } + + _relativeTolerancePercent = percentTolerance; + _tolerance = default; + _toleranceMode = ToleranceMode.Relative; + + Context.ExpressionBuilder.Append($".WithinRelativeTolerance({percentTolerance})"); + return this; + } + protected override Task CheckAsync(EvaluationMetadata metadata) { var value = metadata.Value; @@ -41,64 +73,98 @@ protected override Task CheckAsync(EvaluationMetadata m return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}")); } - // If tolerance is specified, use tolerance-based comparison - if (_tolerance != null && HasToleranceValue()) - { - if (IsWithinTolerance(value!, _expected, _tolerance)) - { - return AssertionResult._passedTask; - } - - var difference = CalculateDifference(value!, _expected); - return Task.FromResult(AssertionResult.Failed(FormatDifferenceMessage(value!, difference))); - } + var actual = value!; - // No tolerance - exact equality check - if (AreExactlyEqual(value!, _expected)) + switch (_toleranceMode) { - return AssertionResult._passedTask; + case ToleranceMode.Absolute: + if (_tolerance != null && HasToleranceValue()) + { + if (IsWithinTolerance(actual, _expected, _tolerance)) + { + return AssertionResult._passedTask; + } + + var absoluteDifference = CalculateDifference(actual, _expected); + return Task.FromResult( + AssertionResult.Failed( + FormatDifferenceMessage(actual, absoluteDifference))); + } + + break; + + case ToleranceMode.Relative: + if (_relativeTolerancePercent.HasValue) + { + if (IsWithinRelativeTolerance(actual, _expected, _relativeTolerancePercent.Value)) + { + return AssertionResult._passedTask; + } + + var relativeDifference = CalculateDifference(actual, _expected); + return Task.FromResult( + AssertionResult.Failed( + FormatRelativeDifferenceMessage(actual, relativeDifference, _relativeTolerancePercent.Value))); + } + + break; } - return Task.FromResult(AssertionResult.Failed($"found {value}")); + return AreExactlyEqual(actual, _expected) + ? AssertionResult._passedTask + : Task.FromResult(AssertionResult.Failed($"found {actual}")); } /// - /// Checks if tolerance has been set and has a meaningful value (not null/default). + /// Checks if absolute tolerance has been set and has a meaningful value. /// protected abstract bool HasToleranceValue(); /// - /// Determines if the actual value is within the specified tolerance of the expected value. + /// Determines if the actual value is within the specified absolute tolerance of the expected value. /// protected abstract bool IsWithinTolerance(TValue actual, TValue expected, TTolerance tolerance); + /// + /// Determines if the actual value is within the specified relative tolerance percentage of the expected value. + /// Default implementation is unsupported for non-numeric types. + /// + protected virtual bool IsWithinRelativeTolerance(TValue actual, TValue expected, double percentTolerance) + => throw new NotSupportedException($"{GetType().Name} does not support relative tolerance."); + /// /// Calculates the difference between actual and expected values. - /// Used for error message formatting. /// protected abstract object CalculateDifference(TValue actual, TValue expected); /// - /// Checks if two values are exactly equal (without tolerance). + /// Checks if two values are exactly equal. /// protected abstract bool AreExactlyEqual(TValue actual, TValue expected); /// - /// Formats the error message when values are not within tolerance. - /// Default implementation can be overridden for custom formatting. + /// Formats the failure message for absolute tolerance comparisons. /// protected virtual string FormatDifferenceMessage(TValue actual, object difference) - { - return $"found {actual}, which differs by {difference}"; - } + => $"found {actual}, which differs by {difference}"; + + /// + /// Formats the failure message for relative tolerance comparisons. + /// + protected virtual string FormatRelativeDifferenceMessage(TValue actual, object difference, double percentTolerance) + => $"found {actual}, which differs by {difference} and is outside the allowed relative tolerance of {percentTolerance}%"; protected override string GetExpectation() { - if (_tolerance != null && HasToleranceValue()) + return _toleranceMode switch { - return $"to be within {_tolerance} of {_expected}"; - } + ToleranceMode.Absolute when _tolerance != null && HasToleranceValue() + => $"to be within {_tolerance} of {_expected}", + + ToleranceMode.Relative when _relativeTolerancePercent.HasValue + => $"to be within {_relativeTolerancePercent}% of {_expected}", - return $"to be {_expected}"; + _ => $"to be {_expected}" + }; } } diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt index adff69eaad..1f7f9729c5 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt @@ -1005,8 +1005,23 @@ namespace .Conditions protected override bool AreExactlyEqual(decimal actual, decimal expected) { } protected override object CalculateDifference(decimal actual, decimal expected) { } protected override bool HasToleranceValue() { } + protected override bool IsWithinRelativeTolerance(decimal actual, decimal expected, double percentTolerance) { } protected override bool IsWithinTolerance(decimal actual, decimal expected, decimal tolerance) { } } + [.("IsCloseTo", OverloadResolutionPriority=2)] + public class DecimalIsCloseToAssertion : . + { + public DecimalIsCloseToAssertion(. context, decimal expected, decimal tolerance) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } + [.("IsWithinPercentOf", OverloadResolutionPriority=2)] + public class DecimalIsWithinPercentOfAssertion : . + { + public DecimalIsWithinPercentOfAssertion(. context, decimal expected, decimal percent) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } public class DictionaryAllKeysAssertion : . where TDictionary : . where TKey : notnull @@ -1120,8 +1135,23 @@ namespace .Conditions protected override bool AreExactlyEqual(double actual, double expected) { } protected override object CalculateDifference(double actual, double expected) { } protected override bool HasToleranceValue() { } + protected override bool IsWithinRelativeTolerance(double actual, double expected, double percentTolerance) { } protected override bool IsWithinTolerance(double actual, double expected, double tolerance) { } } + [.("IsCloseTo", OverloadResolutionPriority=2)] + public class DoubleIsCloseToAssertion : . + { + public DoubleIsCloseToAssertion(. context, double expected, double tolerance) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } + [.("IsWithinPercentOf", OverloadResolutionPriority=2)] + public class DoubleIsWithinPercentOfAssertion : . + { + public DoubleIsWithinPercentOfAssertion(. context, double expected, double percent) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } [.("IsEqualTo")] public class EqualsAssertion : . { @@ -1232,8 +1262,23 @@ namespace .Conditions protected override bool AreExactlyEqual(float actual, float expected) { } protected override object CalculateDifference(float actual, float expected) { } protected override bool HasToleranceValue() { } + protected override bool IsWithinRelativeTolerance(float actual, float expected, double percentTolerance) { } protected override bool IsWithinTolerance(float actual, float expected, float tolerance) { } } + [.("IsCloseTo", OverloadResolutionPriority=2)] + public class FloatIsCloseToAssertion : . + { + public FloatIsCloseToAssertion(. context, float expected, float tolerance) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } + [.("IsWithinPercentOf", OverloadResolutionPriority=2)] + public class FloatIsWithinPercentOfAssertion : . + { + public FloatIsWithinPercentOfAssertion(. context, float expected, float percent) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } public class HasDistinctItemsAssertion : . where TCollection : . { @@ -1328,12 +1373,28 @@ namespace .Conditions [.<>("IsFromEnd", ExpectationMessage="be from the end")] public static class IndexAssertionExtensions { } [.("IsEqualTo", OverloadResolutionPriority=2)] - public class IntEqualsAssertion : . + public class IntEqualsAssertion : . { public IntEqualsAssertion(. context, int expected) { } + protected override bool AreExactlyEqual(int actual, int expected) { } + protected override object CalculateDifference(int actual, int expected) { } + protected override bool HasToleranceValue() { } + protected override bool IsWithinRelativeTolerance(int actual, int expected, double percentTolerance) { } + protected override bool IsWithinTolerance(int actual, int expected, int tolerance) { } + } + [.("IsCloseTo", OverloadResolutionPriority=2)] + public class IntIsCloseToAssertion : . + { + public IntIsCloseToAssertion(. context, int expected, int tolerance) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } + [.("IsWithinPercentOf", OverloadResolutionPriority=2)] + public class IntIsWithinPercentOfAssertion : . + { + public IntIsWithinPercentOfAssertion(. context, int expected, double percent) { } protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } - public . Within(int tolerance) { } } public class IsAssignableFromAssertion : . { @@ -1531,8 +1592,23 @@ namespace .Conditions protected override bool AreExactlyEqual(long actual, long expected) { } protected override object CalculateDifference(long actual, long expected) { } protected override bool HasToleranceValue() { } + protected override bool IsWithinRelativeTolerance(long actual, long expected, double percentTolerance) { } protected override bool IsWithinTolerance(long actual, long expected, long tolerance) { } } + [.("IsCloseTo", OverloadResolutionPriority=2)] + public class LongIsCloseToAssertion : . + { + public LongIsCloseToAssertion(. context, long expected, long tolerance) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } + [.("IsWithinPercentOf", OverloadResolutionPriority=2)] + public class LongIsWithinPercentOfAssertion : . + { + public LongIsWithinPercentOfAssertion(. context, long expected, double percent) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } public class MappedSatisfiesAssertion : . { public MappedSatisfiesAssertion(. context, selector, <., .?> assertions, string selectorDescription) { } @@ -2092,10 +2168,13 @@ namespace .Conditions protected abstract object CalculateDifference(TValue actual, TValue expected); protected override .<.> CheckAsync(. metadata) { } protected virtual string FormatDifferenceMessage(TValue actual, object difference) { } + protected virtual string FormatRelativeDifferenceMessage(TValue actual, object difference, double percentTolerance) { } protected override string GetExpectation() { } protected abstract bool HasToleranceValue(); + protected virtual bool IsWithinRelativeTolerance(TValue actual, TValue expected, double percentTolerance) { } protected abstract bool IsWithinTolerance(TValue actual, TValue expected, TTolerance tolerance); public . Within(TTolerance tolerance) { } + public . WithinRelativeTolerance(double percentTolerance) { } } [.<>("ContainsGenericParameters", CustomName="DoesNotContainGenericParameters", ExpectationMessage="contain generic parameters", NegateLogic=true)] [.<>("ContainsGenericParameters", ExpectationMessage="contain generic parameters")] @@ -3351,6 +3430,16 @@ namespace .Extensions [.(2)] public static . IsEqualTo(this . source, decimal expected, [.("expected")] string? expectedExpression = null) { } } + public static class DecimalIsCloseToAssertionExtensions + { + [.(2)] + public static . IsCloseTo(this . source, decimal expected, decimal tolerance, [.("expected")] string? expectedExpression = null, [.("tolerance")] string? toleranceExpression = null) { } + } + public static class DecimalIsWithinPercentOfAssertionExtensions + { + [.(2)] + public static . IsWithinPercentOf(this . source, decimal expected, decimal percent, [.("expected")] string? expectedExpression = null, [.("percent")] string? percentExpression = null) { } + } public sealed class Decimal_IsNotZero_Assertion : . { public Decimal_IsNotZero_Assertion(. context) { } @@ -3476,6 +3565,11 @@ namespace .Extensions [.(2)] public static . IsEqualTo(this . source, double expected, [.("expected")] string? expectedExpression = null) { } } + public static class DoubleIsCloseToAssertionExtensions + { + [.(2)] + public static . IsCloseTo(this . source, double expected, double tolerance, [.("expected")] string? expectedExpression = null, [.("tolerance")] string? toleranceExpression = null) { } + } public class DoubleIsFiniteWithDoubleAssertion : . { public DoubleIsFiniteWithDoubleAssertion(. context, bool negated = false) { } @@ -3518,6 +3612,11 @@ namespace .Extensions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public static class DoubleIsWithinPercentOfAssertionExtensions + { + [.(2)] + public static . IsWithinPercentOf(this . source, double expected, double percent, [.("expected")] string? expectedExpression = null, [.("percent")] string? percentExpression = null) { } + } public sealed class Double_IsNotZero_Assertion : . { public Double_IsNotZero_Assertion(. context) { } @@ -3804,6 +3903,11 @@ namespace .Extensions [.(2)] public static . IsEqualTo(this . source, float expected, [.("expected")] string? expectedExpression = null) { } } + public static class FloatIsCloseToAssertionExtensions + { + [.(2)] + public static . IsCloseTo(this . source, float expected, float tolerance, [.("expected")] string? expectedExpression = null, [.("tolerance")] string? toleranceExpression = null) { } + } public class FloatIsFiniteWithFloatAssertion : . { public FloatIsFiniteWithFloatAssertion(. context, bool negated = false) { } @@ -3846,6 +3950,11 @@ namespace .Extensions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public static class FloatIsWithinPercentOfAssertionExtensions + { + [.(2)] + public static . IsWithinPercentOf(this . source, float expected, float percent, [.("expected")] string? expectedExpression = null, [.("percent")] string? percentExpression = null) { } + } public sealed class Float_IsNotZero_Assertion : . { public Float_IsNotZero_Assertion(. context) { } @@ -4168,6 +4277,16 @@ namespace .Extensions [.(2)] public static . IsEqualTo(this . source, int expected, [.("expected")] string? expectedExpression = null) { } } + public static class IntIsCloseToAssertionExtensions + { + [.(2)] + public static . IsCloseTo(this . source, int expected, int tolerance, [.("expected")] string? expectedExpression = null, [.("tolerance")] string? toleranceExpression = null) { } + } + public static class IntIsWithinPercentOfAssertionExtensions + { + [.(2)] + public static . IsWithinPercentOf(this . source, int expected, double percent, [.("expected")] string? expectedExpression = null, [.("percent")] string? percentExpression = null) { } + } public sealed class Int_IsEven_Assertion : . { public Int_IsEven_Assertion(. context) { } @@ -4430,6 +4549,16 @@ namespace .Extensions [.(2)] public static . IsEqualTo(this . source, long expected, [.("expected")] string? expectedExpression = null) { } } + public static class LongIsCloseToAssertionExtensions + { + [.(2)] + public static . IsCloseTo(this . source, long expected, long tolerance, [.("expected")] string? expectedExpression = null, [.("tolerance")] string? toleranceExpression = null) { } + } + public static class LongIsWithinPercentOfAssertionExtensions + { + [.(2)] + public static . IsWithinPercentOf(this . source, long expected, double percent, [.("expected")] string? expectedExpression = null, [.("percent")] string? percentExpression = null) { } + } public sealed class Long_IsEven_Assertion : . { public Long_IsEven_Assertion(. context) { } diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt index f02b43e3d4..e97fbcac3b 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt @@ -988,8 +988,23 @@ namespace .Conditions protected override bool AreExactlyEqual(decimal actual, decimal expected) { } protected override object CalculateDifference(decimal actual, decimal expected) { } protected override bool HasToleranceValue() { } + protected override bool IsWithinRelativeTolerance(decimal actual, decimal expected, double percentTolerance) { } protected override bool IsWithinTolerance(decimal actual, decimal expected, decimal tolerance) { } } + [.("IsCloseTo", OverloadResolutionPriority=2)] + public class DecimalIsCloseToAssertion : . + { + public DecimalIsCloseToAssertion(. context, decimal expected, decimal tolerance) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } + [.("IsWithinPercentOf", OverloadResolutionPriority=2)] + public class DecimalIsWithinPercentOfAssertion : . + { + public DecimalIsWithinPercentOfAssertion(. context, decimal expected, decimal percent) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } public class DictionaryAllKeysAssertion : . where TDictionary : . where TKey : notnull @@ -1103,8 +1118,23 @@ namespace .Conditions protected override bool AreExactlyEqual(double actual, double expected) { } protected override object CalculateDifference(double actual, double expected) { } protected override bool HasToleranceValue() { } + protected override bool IsWithinRelativeTolerance(double actual, double expected, double percentTolerance) { } protected override bool IsWithinTolerance(double actual, double expected, double tolerance) { } } + [.("IsCloseTo", OverloadResolutionPriority=2)] + public class DoubleIsCloseToAssertion : . + { + public DoubleIsCloseToAssertion(. context, double expected, double tolerance) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } + [.("IsWithinPercentOf", OverloadResolutionPriority=2)] + public class DoubleIsWithinPercentOfAssertion : . + { + public DoubleIsWithinPercentOfAssertion(. context, double expected, double percent) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } [.("IsEqualTo")] public class EqualsAssertion : . { @@ -1215,8 +1245,23 @@ namespace .Conditions protected override bool AreExactlyEqual(float actual, float expected) { } protected override object CalculateDifference(float actual, float expected) { } protected override bool HasToleranceValue() { } + protected override bool IsWithinRelativeTolerance(float actual, float expected, double percentTolerance) { } protected override bool IsWithinTolerance(float actual, float expected, float tolerance) { } } + [.("IsCloseTo", OverloadResolutionPriority=2)] + public class FloatIsCloseToAssertion : . + { + public FloatIsCloseToAssertion(. context, float expected, float tolerance) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } + [.("IsWithinPercentOf", OverloadResolutionPriority=2)] + public class FloatIsWithinPercentOfAssertion : . + { + public FloatIsWithinPercentOfAssertion(. context, float expected, float percent) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } public class HasDistinctItemsAssertion : . where TCollection : . { @@ -1311,12 +1356,28 @@ namespace .Conditions [.<>("IsFromEnd", ExpectationMessage="be from the end")] public static class IndexAssertionExtensions { } [.("IsEqualTo", OverloadResolutionPriority=2)] - public class IntEqualsAssertion : . + public class IntEqualsAssertion : . { public IntEqualsAssertion(. context, int expected) { } + protected override bool AreExactlyEqual(int actual, int expected) { } + protected override object CalculateDifference(int actual, int expected) { } + protected override bool HasToleranceValue() { } + protected override bool IsWithinRelativeTolerance(int actual, int expected, double percentTolerance) { } + protected override bool IsWithinTolerance(int actual, int expected, int tolerance) { } + } + [.("IsCloseTo", OverloadResolutionPriority=2)] + public class IntIsCloseToAssertion : . + { + public IntIsCloseToAssertion(. context, int expected, int tolerance) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } + [.("IsWithinPercentOf", OverloadResolutionPriority=2)] + public class IntIsWithinPercentOfAssertion : . + { + public IntIsWithinPercentOfAssertion(. context, int expected, double percent) { } protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } - public . Within(int tolerance) { } } public class IsAssignableFromAssertion : . { @@ -1514,8 +1575,23 @@ namespace .Conditions protected override bool AreExactlyEqual(long actual, long expected) { } protected override object CalculateDifference(long actual, long expected) { } protected override bool HasToleranceValue() { } + protected override bool IsWithinRelativeTolerance(long actual, long expected, double percentTolerance) { } protected override bool IsWithinTolerance(long actual, long expected, long tolerance) { } } + [.("IsCloseTo", OverloadResolutionPriority=2)] + public class LongIsCloseToAssertion : . + { + public LongIsCloseToAssertion(. context, long expected, long tolerance) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } + [.("IsWithinPercentOf", OverloadResolutionPriority=2)] + public class LongIsWithinPercentOfAssertion : . + { + public LongIsWithinPercentOfAssertion(. context, long expected, double percent) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } public class MappedSatisfiesAssertion : . { public MappedSatisfiesAssertion(. context, selector, <., .?> assertions, string selectorDescription) { } @@ -2075,10 +2151,13 @@ namespace .Conditions protected abstract object CalculateDifference(TValue actual, TValue expected); protected override .<.> CheckAsync(. metadata) { } protected virtual string FormatDifferenceMessage(TValue actual, object difference) { } + protected virtual string FormatRelativeDifferenceMessage(TValue actual, object difference, double percentTolerance) { } protected override string GetExpectation() { } protected abstract bool HasToleranceValue(); + protected virtual bool IsWithinRelativeTolerance(TValue actual, TValue expected, double percentTolerance) { } protected abstract bool IsWithinTolerance(TValue actual, TValue expected, TTolerance tolerance); public . Within(TTolerance tolerance) { } + public . WithinRelativeTolerance(double percentTolerance) { } } [.<>("ContainsGenericParameters", CustomName="DoesNotContainGenericParameters", ExpectationMessage="contain generic parameters", NegateLogic=true)] [.<>("ContainsGenericParameters", ExpectationMessage="contain generic parameters")] @@ -3319,6 +3398,14 @@ namespace .Extensions { public static . IsEqualTo(this . source, decimal expected, [.("expected")] string? expectedExpression = null) { } } + public static class DecimalIsCloseToAssertionExtensions + { + public static . IsCloseTo(this . source, decimal expected, decimal tolerance, [.("expected")] string? expectedExpression = null, [.("tolerance")] string? toleranceExpression = null) { } + } + public static class DecimalIsWithinPercentOfAssertionExtensions + { + public static . IsWithinPercentOf(this . source, decimal expected, decimal percent, [.("expected")] string? expectedExpression = null, [.("percent")] string? percentExpression = null) { } + } public sealed class Decimal_IsNotZero_Assertion : . { public Decimal_IsNotZero_Assertion(. context) { } @@ -3443,6 +3530,10 @@ namespace .Extensions { public static . IsEqualTo(this . source, double expected, [.("expected")] string? expectedExpression = null) { } } + public static class DoubleIsCloseToAssertionExtensions + { + public static . IsCloseTo(this . source, double expected, double tolerance, [.("expected")] string? expectedExpression = null, [.("tolerance")] string? toleranceExpression = null) { } + } public class DoubleIsFiniteWithDoubleAssertion : . { public DoubleIsFiniteWithDoubleAssertion(. context, bool negated = false) { } @@ -3485,6 +3576,10 @@ namespace .Extensions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public static class DoubleIsWithinPercentOfAssertionExtensions + { + public static . IsWithinPercentOf(this . source, double expected, double percent, [.("expected")] string? expectedExpression = null, [.("percent")] string? percentExpression = null) { } + } public sealed class Double_IsNotZero_Assertion : . { public Double_IsNotZero_Assertion(. context) { } @@ -3770,6 +3865,10 @@ namespace .Extensions { public static . IsEqualTo(this . source, float expected, [.("expected")] string? expectedExpression = null) { } } + public static class FloatIsCloseToAssertionExtensions + { + public static . IsCloseTo(this . source, float expected, float tolerance, [.("expected")] string? expectedExpression = null, [.("tolerance")] string? toleranceExpression = null) { } + } public class FloatIsFiniteWithFloatAssertion : . { public FloatIsFiniteWithFloatAssertion(. context, bool negated = false) { } @@ -3812,6 +3911,10 @@ namespace .Extensions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public static class FloatIsWithinPercentOfAssertionExtensions + { + public static . IsWithinPercentOf(this . source, float expected, float percent, [.("expected")] string? expectedExpression = null, [.("percent")] string? percentExpression = null) { } + } public sealed class Float_IsNotZero_Assertion : . { public Float_IsNotZero_Assertion(. context) { } @@ -4133,6 +4236,14 @@ namespace .Extensions { public static . IsEqualTo(this . source, int expected, [.("expected")] string? expectedExpression = null) { } } + public static class IntIsCloseToAssertionExtensions + { + public static . IsCloseTo(this . source, int expected, int tolerance, [.("expected")] string? expectedExpression = null, [.("tolerance")] string? toleranceExpression = null) { } + } + public static class IntIsWithinPercentOfAssertionExtensions + { + public static . IsWithinPercentOf(this . source, int expected, double percent, [.("expected")] string? expectedExpression = null, [.("percent")] string? percentExpression = null) { } + } public sealed class Int_IsEven_Assertion : . { public Int_IsEven_Assertion(. context) { } @@ -4380,6 +4491,14 @@ namespace .Extensions { public static . IsEqualTo(this . source, long expected, [.("expected")] string? expectedExpression = null) { } } + public static class LongIsCloseToAssertionExtensions + { + public static . IsCloseTo(this . source, long expected, long tolerance, [.("expected")] string? expectedExpression = null, [.("tolerance")] string? toleranceExpression = null) { } + } + public static class LongIsWithinPercentOfAssertionExtensions + { + public static . IsWithinPercentOf(this . source, long expected, double percent, [.("expected")] string? expectedExpression = null, [.("percent")] string? percentExpression = null) { } + } public sealed class Long_IsEven_Assertion : . { public Long_IsEven_Assertion(. context) { } diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt index 3cb7308e6f..478b6783f9 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt @@ -1005,8 +1005,23 @@ namespace .Conditions protected override bool AreExactlyEqual(decimal actual, decimal expected) { } protected override object CalculateDifference(decimal actual, decimal expected) { } protected override bool HasToleranceValue() { } + protected override bool IsWithinRelativeTolerance(decimal actual, decimal expected, double percentTolerance) { } protected override bool IsWithinTolerance(decimal actual, decimal expected, decimal tolerance) { } } + [.("IsCloseTo", OverloadResolutionPriority=2)] + public class DecimalIsCloseToAssertion : . + { + public DecimalIsCloseToAssertion(. context, decimal expected, decimal tolerance) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } + [.("IsWithinPercentOf", OverloadResolutionPriority=2)] + public class DecimalIsWithinPercentOfAssertion : . + { + public DecimalIsWithinPercentOfAssertion(. context, decimal expected, decimal percent) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } public class DictionaryAllKeysAssertion : . where TDictionary : . where TKey : notnull @@ -1120,8 +1135,23 @@ namespace .Conditions protected override bool AreExactlyEqual(double actual, double expected) { } protected override object CalculateDifference(double actual, double expected) { } protected override bool HasToleranceValue() { } + protected override bool IsWithinRelativeTolerance(double actual, double expected, double percentTolerance) { } protected override bool IsWithinTolerance(double actual, double expected, double tolerance) { } } + [.("IsCloseTo", OverloadResolutionPriority=2)] + public class DoubleIsCloseToAssertion : . + { + public DoubleIsCloseToAssertion(. context, double expected, double tolerance) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } + [.("IsWithinPercentOf", OverloadResolutionPriority=2)] + public class DoubleIsWithinPercentOfAssertion : . + { + public DoubleIsWithinPercentOfAssertion(. context, double expected, double percent) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } [.("IsEqualTo")] public class EqualsAssertion : . { @@ -1232,8 +1262,23 @@ namespace .Conditions protected override bool AreExactlyEqual(float actual, float expected) { } protected override object CalculateDifference(float actual, float expected) { } protected override bool HasToleranceValue() { } + protected override bool IsWithinRelativeTolerance(float actual, float expected, double percentTolerance) { } protected override bool IsWithinTolerance(float actual, float expected, float tolerance) { } } + [.("IsCloseTo", OverloadResolutionPriority=2)] + public class FloatIsCloseToAssertion : . + { + public FloatIsCloseToAssertion(. context, float expected, float tolerance) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } + [.("IsWithinPercentOf", OverloadResolutionPriority=2)] + public class FloatIsWithinPercentOfAssertion : . + { + public FloatIsWithinPercentOfAssertion(. context, float expected, float percent) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } public class HasDistinctItemsAssertion : . where TCollection : . { @@ -1328,12 +1373,28 @@ namespace .Conditions [.<>("IsFromEnd", ExpectationMessage="be from the end")] public static class IndexAssertionExtensions { } [.("IsEqualTo", OverloadResolutionPriority=2)] - public class IntEqualsAssertion : . + public class IntEqualsAssertion : . { public IntEqualsAssertion(. context, int expected) { } + protected override bool AreExactlyEqual(int actual, int expected) { } + protected override object CalculateDifference(int actual, int expected) { } + protected override bool HasToleranceValue() { } + protected override bool IsWithinRelativeTolerance(int actual, int expected, double percentTolerance) { } + protected override bool IsWithinTolerance(int actual, int expected, int tolerance) { } + } + [.("IsCloseTo", OverloadResolutionPriority=2)] + public class IntIsCloseToAssertion : . + { + public IntIsCloseToAssertion(. context, int expected, int tolerance) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } + [.("IsWithinPercentOf", OverloadResolutionPriority=2)] + public class IntIsWithinPercentOfAssertion : . + { + public IntIsWithinPercentOfAssertion(. context, int expected, double percent) { } protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } - public . Within(int tolerance) { } } public class IsAssignableFromAssertion : . { @@ -1531,8 +1592,23 @@ namespace .Conditions protected override bool AreExactlyEqual(long actual, long expected) { } protected override object CalculateDifference(long actual, long expected) { } protected override bool HasToleranceValue() { } + protected override bool IsWithinRelativeTolerance(long actual, long expected, double percentTolerance) { } protected override bool IsWithinTolerance(long actual, long expected, long tolerance) { } } + [.("IsCloseTo", OverloadResolutionPriority=2)] + public class LongIsCloseToAssertion : . + { + public LongIsCloseToAssertion(. context, long expected, long tolerance) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } + [.("IsWithinPercentOf", OverloadResolutionPriority=2)] + public class LongIsWithinPercentOfAssertion : . + { + public LongIsWithinPercentOfAssertion(. context, long expected, double percent) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } public class MappedSatisfiesAssertion : . { public MappedSatisfiesAssertion(. context, selector, <., .?> assertions, string selectorDescription) { } @@ -2092,10 +2168,13 @@ namespace .Conditions protected abstract object CalculateDifference(TValue actual, TValue expected); protected override .<.> CheckAsync(. metadata) { } protected virtual string FormatDifferenceMessage(TValue actual, object difference) { } + protected virtual string FormatRelativeDifferenceMessage(TValue actual, object difference, double percentTolerance) { } protected override string GetExpectation() { } protected abstract bool HasToleranceValue(); + protected virtual bool IsWithinRelativeTolerance(TValue actual, TValue expected, double percentTolerance) { } protected abstract bool IsWithinTolerance(TValue actual, TValue expected, TTolerance tolerance); public . Within(TTolerance tolerance) { } + public . WithinRelativeTolerance(double percentTolerance) { } } [.<>("ContainsGenericParameters", CustomName="DoesNotContainGenericParameters", ExpectationMessage="contain generic parameters", NegateLogic=true)] [.<>("ContainsGenericParameters", ExpectationMessage="contain generic parameters")] @@ -3351,6 +3430,16 @@ namespace .Extensions [.(2)] public static . IsEqualTo(this . source, decimal expected, [.("expected")] string? expectedExpression = null) { } } + public static class DecimalIsCloseToAssertionExtensions + { + [.(2)] + public static . IsCloseTo(this . source, decimal expected, decimal tolerance, [.("expected")] string? expectedExpression = null, [.("tolerance")] string? toleranceExpression = null) { } + } + public static class DecimalIsWithinPercentOfAssertionExtensions + { + [.(2)] + public static . IsWithinPercentOf(this . source, decimal expected, decimal percent, [.("expected")] string? expectedExpression = null, [.("percent")] string? percentExpression = null) { } + } public sealed class Decimal_IsNotZero_Assertion : . { public Decimal_IsNotZero_Assertion(. context) { } @@ -3476,6 +3565,11 @@ namespace .Extensions [.(2)] public static . IsEqualTo(this . source, double expected, [.("expected")] string? expectedExpression = null) { } } + public static class DoubleIsCloseToAssertionExtensions + { + [.(2)] + public static . IsCloseTo(this . source, double expected, double tolerance, [.("expected")] string? expectedExpression = null, [.("tolerance")] string? toleranceExpression = null) { } + } public class DoubleIsFiniteWithDoubleAssertion : . { public DoubleIsFiniteWithDoubleAssertion(. context, bool negated = false) { } @@ -3518,6 +3612,11 @@ namespace .Extensions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public static class DoubleIsWithinPercentOfAssertionExtensions + { + [.(2)] + public static . IsWithinPercentOf(this . source, double expected, double percent, [.("expected")] string? expectedExpression = null, [.("percent")] string? percentExpression = null) { } + } public sealed class Double_IsNotZero_Assertion : . { public Double_IsNotZero_Assertion(. context) { } @@ -3804,6 +3903,11 @@ namespace .Extensions [.(2)] public static . IsEqualTo(this . source, float expected, [.("expected")] string? expectedExpression = null) { } } + public static class FloatIsCloseToAssertionExtensions + { + [.(2)] + public static . IsCloseTo(this . source, float expected, float tolerance, [.("expected")] string? expectedExpression = null, [.("tolerance")] string? toleranceExpression = null) { } + } public class FloatIsFiniteWithFloatAssertion : . { public FloatIsFiniteWithFloatAssertion(. context, bool negated = false) { } @@ -3846,6 +3950,11 @@ namespace .Extensions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public static class FloatIsWithinPercentOfAssertionExtensions + { + [.(2)] + public static . IsWithinPercentOf(this . source, float expected, float percent, [.("expected")] string? expectedExpression = null, [.("percent")] string? percentExpression = null) { } + } public sealed class Float_IsNotZero_Assertion : . { public Float_IsNotZero_Assertion(. context) { } @@ -4168,6 +4277,16 @@ namespace .Extensions [.(2)] public static . IsEqualTo(this . source, int expected, [.("expected")] string? expectedExpression = null) { } } + public static class IntIsCloseToAssertionExtensions + { + [.(2)] + public static . IsCloseTo(this . source, int expected, int tolerance, [.("expected")] string? expectedExpression = null, [.("tolerance")] string? toleranceExpression = null) { } + } + public static class IntIsWithinPercentOfAssertionExtensions + { + [.(2)] + public static . IsWithinPercentOf(this . source, int expected, double percent, [.("expected")] string? expectedExpression = null, [.("percent")] string? percentExpression = null) { } + } public sealed class Int_IsEven_Assertion : . { public Int_IsEven_Assertion(. context) { } @@ -4430,6 +4549,16 @@ namespace .Extensions [.(2)] public static . IsEqualTo(this . source, long expected, [.("expected")] string? expectedExpression = null) { } } + public static class LongIsCloseToAssertionExtensions + { + [.(2)] + public static . IsCloseTo(this . source, long expected, long tolerance, [.("expected")] string? expectedExpression = null, [.("tolerance")] string? toleranceExpression = null) { } + } + public static class LongIsWithinPercentOfAssertionExtensions + { + [.(2)] + public static . IsWithinPercentOf(this . source, long expected, double percent, [.("expected")] string? expectedExpression = null, [.("percent")] string? percentExpression = null) { } + } public sealed class Long_IsEven_Assertion : . { public Long_IsEven_Assertion(. context) { } diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt index 32f417e8ac..db43114adc 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt @@ -833,8 +833,23 @@ namespace .Conditions protected override bool AreExactlyEqual(decimal actual, decimal expected) { } protected override object CalculateDifference(decimal actual, decimal expected) { } protected override bool HasToleranceValue() { } + protected override bool IsWithinRelativeTolerance(decimal actual, decimal expected, double percentTolerance) { } protected override bool IsWithinTolerance(decimal actual, decimal expected, decimal tolerance) { } } + [.("IsCloseTo", OverloadResolutionPriority=2)] + public class DecimalIsCloseToAssertion : . + { + public DecimalIsCloseToAssertion(. context, decimal expected, decimal tolerance) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } + [.("IsWithinPercentOf", OverloadResolutionPriority=2)] + public class DecimalIsWithinPercentOfAssertion : . + { + public DecimalIsWithinPercentOfAssertion(. context, decimal expected, decimal percent) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } public class DictionaryAllKeysAssertion : . where TDictionary : . where TKey : notnull @@ -942,8 +957,23 @@ namespace .Conditions protected override bool AreExactlyEqual(double actual, double expected) { } protected override object CalculateDifference(double actual, double expected) { } protected override bool HasToleranceValue() { } + protected override bool IsWithinRelativeTolerance(double actual, double expected, double percentTolerance) { } protected override bool IsWithinTolerance(double actual, double expected, double tolerance) { } } + [.("IsCloseTo", OverloadResolutionPriority=2)] + public class DoubleIsCloseToAssertion : . + { + public DoubleIsCloseToAssertion(. context, double expected, double tolerance) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } + [.("IsWithinPercentOf", OverloadResolutionPriority=2)] + public class DoubleIsWithinPercentOfAssertion : . + { + public DoubleIsWithinPercentOfAssertion(. context, double expected, double percent) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } [.("IsEqualTo")] public class EqualsAssertion : . { @@ -1054,8 +1084,23 @@ namespace .Conditions protected override bool AreExactlyEqual(float actual, float expected) { } protected override object CalculateDifference(float actual, float expected) { } protected override bool HasToleranceValue() { } + protected override bool IsWithinRelativeTolerance(float actual, float expected, double percentTolerance) { } protected override bool IsWithinTolerance(float actual, float expected, float tolerance) { } } + [.("IsCloseTo", OverloadResolutionPriority=2)] + public class FloatIsCloseToAssertion : . + { + public FloatIsCloseToAssertion(. context, float expected, float tolerance) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } + [.("IsWithinPercentOf", OverloadResolutionPriority=2)] + public class FloatIsWithinPercentOfAssertion : . + { + public FloatIsWithinPercentOfAssertion(. context, float expected, float percent) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } public class HasDistinctItemsAssertion : . where TCollection : . { @@ -1147,12 +1192,28 @@ namespace .Conditions [.<.IPAddress>("IsIPv6Teredo", ExpectationMessage="be an IPv6 Teredo address")] public static class IPAddressAssertionExtensions { } [.("IsEqualTo", OverloadResolutionPriority=2)] - public class IntEqualsAssertion : . + public class IntEqualsAssertion : . { public IntEqualsAssertion(. context, int expected) { } + protected override bool AreExactlyEqual(int actual, int expected) { } + protected override object CalculateDifference(int actual, int expected) { } + protected override bool HasToleranceValue() { } + protected override bool IsWithinRelativeTolerance(int actual, int expected, double percentTolerance) { } + protected override bool IsWithinTolerance(int actual, int expected, int tolerance) { } + } + [.("IsCloseTo", OverloadResolutionPriority=2)] + public class IntIsCloseToAssertion : . + { + public IntIsCloseToAssertion(. context, int expected, int tolerance) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } + [.("IsWithinPercentOf", OverloadResolutionPriority=2)] + public class IntIsWithinPercentOfAssertion : . + { + public IntIsWithinPercentOfAssertion(. context, int expected, double percent) { } protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } - public . Within(int tolerance) { } } public class IsAssignableFromAssertion : . { @@ -1348,8 +1409,23 @@ namespace .Conditions protected override bool AreExactlyEqual(long actual, long expected) { } protected override object CalculateDifference(long actual, long expected) { } protected override bool HasToleranceValue() { } + protected override bool IsWithinRelativeTolerance(long actual, long expected, double percentTolerance) { } protected override bool IsWithinTolerance(long actual, long expected, long tolerance) { } } + [.("IsCloseTo", OverloadResolutionPriority=2)] + public class LongIsCloseToAssertion : . + { + public LongIsCloseToAssertion(. context, long expected, long tolerance) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } + [.("IsWithinPercentOf", OverloadResolutionPriority=2)] + public class LongIsWithinPercentOfAssertion : . + { + public LongIsWithinPercentOfAssertion(. context, long expected, double percent) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } public class MappedSatisfiesAssertion : . { public MappedSatisfiesAssertion(. context, selector, <., .?> assertions, string selectorDescription) { } @@ -1853,10 +1929,13 @@ namespace .Conditions protected abstract object CalculateDifference(TValue actual, TValue expected); protected override .<.> CheckAsync(. metadata) { } protected virtual string FormatDifferenceMessage(TValue actual, object difference) { } + protected virtual string FormatRelativeDifferenceMessage(TValue actual, object difference, double percentTolerance) { } protected override string GetExpectation() { } protected abstract bool HasToleranceValue(); + protected virtual bool IsWithinRelativeTolerance(TValue actual, TValue expected, double percentTolerance) { } protected abstract bool IsWithinTolerance(TValue actual, TValue expected, TTolerance tolerance); public . Within(TTolerance tolerance) { } + public . WithinRelativeTolerance(double percentTolerance) { } } [.<>("ContainsGenericParameters", CustomName="DoesNotContainGenericParameters", ExpectationMessage="contain generic parameters", NegateLogic=true)] [.<>("ContainsGenericParameters", ExpectationMessage="contain generic parameters")] @@ -2311,14 +2390,14 @@ namespace .Extensions public static . Length(this . source) { } public static . Member(this . source, .<>> memberSelector, <.<., TItem>, .<.>> assertions) { } public static . Member(this . source, .<>> memberSelector, <.<., TItem>, object> assertions) { } - public static . Member(this . source, .<> memberSelector, <., .> assertions) { } - public static . Member(this . source, .<> memberSelector, <., object> assertions) { } + public static . Member(this . source, .<> memberSelector, <., .> assertions) { } + public static . Member(this . source, .<> memberSelector, <., object> assertions) { } public static . Member(this . source, .<>> memberSelector, <.<., TItem>, .> assertions) { } public static . Member(this . source, .<>> memberSelector, <.<., TKey, TValue>, .<.>> assertions) where TKey : notnull { } public static . Member(this . source, .<>> memberSelector, <.<., TKey, TValue>, object> assertions) where TKey : notnull { } - public static . Member(this . source, .<> memberSelector, <., .> assertions) { } + public static . Member(this . source, .<> memberSelector, <., .> assertions) { } public static . Member(this . source, .<>> memberSelector, <.<., TKey, TValue>, .> assertions) where TKey : notnull { } public static . Satisfies(this . source, predicate, [.("predicate")] string? expression = null) { } @@ -2964,6 +3043,14 @@ namespace .Extensions { public static . IsEqualTo(this . source, decimal expected, [.("expected")] string? expectedExpression = null) { } } + public static class DecimalIsCloseToAssertionExtensions + { + public static . IsCloseTo(this . source, decimal expected, decimal tolerance, [.("expected")] string? expectedExpression = null, [.("tolerance")] string? toleranceExpression = null) { } + } + public static class DecimalIsWithinPercentOfAssertionExtensions + { + public static . IsWithinPercentOf(this . source, decimal expected, decimal percent, [.("expected")] string? expectedExpression = null, [.("percent")] string? percentExpression = null) { } + } public sealed class Decimal_IsNotZero_Assertion : . { public Decimal_IsNotZero_Assertion(. context) { } @@ -3082,6 +3169,10 @@ namespace .Extensions { public static . IsEqualTo(this . source, double expected, [.("expected")] string? expectedExpression = null) { } } + public static class DoubleIsCloseToAssertionExtensions + { + public static . IsCloseTo(this . source, double expected, double tolerance, [.("expected")] string? expectedExpression = null, [.("tolerance")] string? toleranceExpression = null) { } + } public class DoubleIsInfinityWithDoubleAssertion : . { public DoubleIsInfinityWithDoubleAssertion(. context, bool negated = false) { } @@ -3106,6 +3197,10 @@ namespace .Extensions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public static class DoubleIsWithinPercentOfAssertionExtensions + { + public static . IsWithinPercentOf(this . source, double expected, double percent, [.("expected")] string? expectedExpression = null, [.("percent")] string? percentExpression = null) { } + } public sealed class Double_IsNotZero_Assertion : . { public Double_IsNotZero_Assertion(. context) { } @@ -3381,6 +3476,10 @@ namespace .Extensions { public static . IsEqualTo(this . source, float expected, [.("expected")] string? expectedExpression = null) { } } + public static class FloatIsCloseToAssertionExtensions + { + public static . IsCloseTo(this . source, float expected, float tolerance, [.("expected")] string? expectedExpression = null, [.("tolerance")] string? toleranceExpression = null) { } + } public class FloatIsInfinityWithFloatAssertion : . { public FloatIsInfinityWithFloatAssertion(. context, bool negated = false) { } @@ -3405,6 +3504,10 @@ namespace .Extensions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public static class FloatIsWithinPercentOfAssertionExtensions + { + public static . IsWithinPercentOf(this . source, float expected, float percent, [.("expected")] string? expectedExpression = null, [.("percent")] string? percentExpression = null) { } + } public sealed class Float_IsNotZero_Assertion : . { public Float_IsNotZero_Assertion(. context) { } @@ -3709,6 +3812,14 @@ namespace .Extensions { public static . IsEqualTo(this . source, int expected, [.("expected")] string? expectedExpression = null) { } } + public static class IntIsCloseToAssertionExtensions + { + public static . IsCloseTo(this . source, int expected, int tolerance, [.("expected")] string? expectedExpression = null, [.("tolerance")] string? toleranceExpression = null) { } + } + public static class IntIsWithinPercentOfAssertionExtensions + { + public static . IsWithinPercentOf(this . source, int expected, double percent, [.("expected")] string? expectedExpression = null, [.("percent")] string? percentExpression = null) { } + } public sealed class Int_IsEven_Assertion : . { public Int_IsEven_Assertion(. context) { } @@ -3934,6 +4045,14 @@ namespace .Extensions { public static . IsEqualTo(this . source, long expected, [.("expected")] string? expectedExpression = null) { } } + public static class LongIsCloseToAssertionExtensions + { + public static . IsCloseTo(this . source, long expected, long tolerance, [.("expected")] string? expectedExpression = null, [.("tolerance")] string? toleranceExpression = null) { } + } + public static class LongIsWithinPercentOfAssertionExtensions + { + public static . IsWithinPercentOf(this . source, long expected, double percent, [.("expected")] string? expectedExpression = null, [.("percent")] string? percentExpression = null) { } + } public sealed class Long_IsEven_Assertion : . { public Long_IsEven_Assertion(. context) { } @@ -4030,8 +4149,8 @@ namespace .Extensions } public static class PropertyAssertionExtensions { - public static . HasProperty(this . source, .<> propertySelector) { } - public static . HasProperty(this . source, .<> propertySelector, TProperty expectedValue, [.("expectedValue")] string? expression = null) { } + public static . HasProperty(this . source, .<> propertySelector) { } + public static . HasProperty(this . source, .<> propertySelector, TProperty expectedValue, [.("expectedValue")] string? expression = null) { } } public static class ReferenceAssertionExtensions { diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt index 56031f16d2..4290a16f46 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt @@ -2010,6 +2010,7 @@ namespace .Helpers public static class ArgumentFormatter { public static string Format(object? o, .<> formatters) { } + public static string Format(object? o, ? parameterType, .<> formatters) { } public static string FormatArguments(. arguments) { } public static string GetConstantValue(.TestContext testContext, object? o) { } } diff --git a/TUnit.PublicAPI/Tests.cs b/TUnit.PublicAPI/Tests.cs index 67830f80f0..1b3a19736c 100644 --- a/TUnit.PublicAPI/Tests.cs +++ b/TUnit.PublicAPI/Tests.cs @@ -9,21 +9,15 @@ public partial class Tests { [Test] public Task Core_Library_Has_No_API_Changes() - { - return VerifyPublicApi(typeof(TestAttribute).Assembly); - } + => VerifyPublicApi(typeof(TestAttribute).Assembly); [Test] public Task Assertions_Library_Has_No_API_Changes() - { - return VerifyPublicApi(typeof(Assertions.Assert).Assembly); - } + => VerifyPublicApi(typeof(Assertions.Assert).Assembly); [Test] public Task Playwright_Library_Has_No_API_Changes() - { - return VerifyPublicApi(typeof(Playwright.PageTest).Assembly); - } + => VerifyPublicApi(typeof(Playwright.PageTest).Assembly); private async Task VerifyPublicApi(Assembly assembly) { @@ -36,17 +30,14 @@ private async Task VerifyPublicApi(Assembly assembly) }); await VerifyTUnit.Verify(publicApi) - .AddScrubber(sb => Scrub(sb)) + .AddScrubber(Scrub) .AddScrubber(sb => new StringBuilder(sb.ToString().Replace("\r\n", "\n"))) .ScrubLinesWithReplace(x => x.Replace("\r\n", "\n")) .ScrubLinesWithReplace(line => { - if (line.Contains("public static class AssemblyLoader")) - { - return "public static class AssemblyLoader_Guid"; - } - - return line; + return line.Contains("public static class AssemblyLoader") + ? "public static class AssemblyLoader_Guid" + : line; }) .ScrubFilePaths() .OnVerifyMismatch(async (pair, message, verify) =>