Skip to content

Commit

Permalink
Better handling of NaN in numeric assertions. (#1822)
Browse files Browse the repository at this point in the history
  • Loading branch information
dennisdoomen committed Mar 12, 2022
1 parent 16d6312 commit 1cd3a08
Show file tree
Hide file tree
Showing 8 changed files with 1,614 additions and 899 deletions.
4 changes: 2 additions & 2 deletions Src/FluentAssertions/AssertionExtensions.cs
Expand Up @@ -631,7 +631,7 @@ public static NullableNumericAssertions<ulong> Should(this ulong? actualValue)
[Pure]
public static NumericAssertions<float> Should(this float actualValue)
{
return new NumericAssertions<float>(actualValue);
return new FloatAssertions(actualValue);
}

/// <summary>
Expand All @@ -651,7 +651,7 @@ public static NullableNumericAssertions<float> Should(this float? actualValue)
[Pure]
public static NumericAssertions<double> Should(this double actualValue)
{
return new NumericAssertions<double>(actualValue);
return new DoubleAssertions(actualValue);
}

/// <summary>
Expand Down
12 changes: 12 additions & 0 deletions Src/FluentAssertions/Numeric/DoubleAssertions.cs
@@ -0,0 +1,12 @@
namespace FluentAssertions.Numeric
{
internal class DoubleAssertions : NumericAssertions<double>
{
public DoubleAssertions(double value)
: base(value)
{
}

private protected override bool IsNaN(double value) => double.IsNaN(value);
}
}
12 changes: 12 additions & 0 deletions Src/FluentAssertions/Numeric/FloatAssertions.cs
@@ -0,0 +1,12 @@
namespace FluentAssertions.Numeric
{
internal class FloatAssertions : NumericAssertions<float>
{
public FloatAssertions(float value)
: base(value)
{
}

private protected override bool IsNaN(float value) => float.IsNaN(value);
}
}
40 changes: 36 additions & 4 deletions Src/FluentAssertions/Numeric/NumericAssertions.cs
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
Expand Down Expand Up @@ -165,7 +165,7 @@ public AndConstraint<TAssertions> BePositive(string because = "", params object[
public AndConstraint<TAssertions> BeNegative(string because = "", params object[] becauseArgs)
{
Execute.Assertion
.ForCondition(Subject.HasValue && Subject.Value.CompareTo(default(T)) < 0)
.ForCondition(Subject.HasValue && !IsNaN(Subject.Value) && Subject.Value.CompareTo(default(T)) < 0)
.BecauseOf(because, becauseArgs)
.FailWith("Expected {context:value} to be negative{reason}, but found {0}.", Subject);

Expand All @@ -185,8 +185,13 @@ public AndConstraint<TAssertions> BeNegative(string because = "", params object[
/// </param>
public AndConstraint<TAssertions> BeLessThan(T expected, string because = "", params object[] becauseArgs)
{
if (IsNaN(expected))
{
throw new ArgumentException("A value can never be less than NaN", nameof(expected));
}

Execute.Assertion
.ForCondition(Subject.HasValue && Subject.Value.CompareTo(expected) < 0)
.ForCondition(Subject.HasValue && !IsNaN(Subject.Value) && Subject.Value.CompareTo(expected) < 0)
.BecauseOf(because, becauseArgs)
.FailWith("Expected {context:value} to be less than {0}{reason}, but found {1}.", expected, Subject);

Expand All @@ -207,8 +212,13 @@ public AndConstraint<TAssertions> BeLessThan(T expected, string because = "", pa
public AndConstraint<TAssertions> BeLessThanOrEqualTo(T expected, string because = "",
params object[] becauseArgs)
{
if (IsNaN(expected))
{
throw new ArgumentException("A value can never be less than or equal to NaN", nameof(expected));
}

Execute.Assertion
.ForCondition(Subject.HasValue && Subject.Value.CompareTo(expected) <= 0)
.ForCondition(Subject.HasValue && !IsNaN(Subject.Value) && Subject.Value.CompareTo(expected) <= 0)
.BecauseOf(because, becauseArgs)
.FailWith("Expected {context:value} to be less than or equal to {0}{reason}, but found {1}.", expected, Subject);

Expand All @@ -232,6 +242,11 @@ public AndConstraint<TAssertions> BeLessThan(T expected, string because = "", pa
public AndConstraint<TAssertions> BeGreaterThan(T expected, string because = "",
params object[] becauseArgs)
{
if (IsNaN(expected))
{
throw new ArgumentException("A value can never be greater than NaN", nameof(expected));
}

Execute.Assertion
.ForCondition(Subject.HasValue && Subject.Value.CompareTo(expected) > 0)
.BecauseOf(because, becauseArgs)
Expand All @@ -254,6 +269,11 @@ public AndConstraint<TAssertions> BeLessThan(T expected, string because = "", pa
public AndConstraint<TAssertions> BeGreaterThanOrEqualTo(T expected, string because = "",
params object[] becauseArgs)
{
if (IsNaN(expected))
{
throw new ArgumentException("A value can never be greater than or equal to a NaN", nameof(expected));
}

Execute.Assertion
.ForCondition(Subject.HasValue && Subject.Value.CompareTo(expected) >= 0)
.BecauseOf(because, becauseArgs)
Expand Down Expand Up @@ -287,6 +307,11 @@ public AndConstraint<TAssertions> BeLessThan(T expected, string because = "", pa
public AndConstraint<TAssertions> BeInRange(T minimumValue, T maximumValue, string because = "",
params object[] becauseArgs)
{
if (IsNaN(minimumValue) || IsNaN(maximumValue))
{
throw new ArgumentException("A range cannot begin or end with NaN");
}

Execute.Assertion
.ForCondition(Subject.HasValue && (Subject.Value.CompareTo(minimumValue) >= 0) && (Subject.Value.CompareTo(maximumValue) <= 0))
.BecauseOf(because, becauseArgs)
Expand Down Expand Up @@ -318,6 +343,11 @@ public AndConstraint<TAssertions> BeLessThan(T expected, string because = "", pa
public AndConstraint<TAssertions> NotBeInRange(T minimumValue, T maximumValue, string because = "",
params object[] becauseArgs)
{
if (IsNaN(minimumValue) || IsNaN(maximumValue))
{
throw new ArgumentException("A range cannot begin or end with NaN");
}

Execute.Assertion
.ForCondition(Subject.HasValue && !((Subject.Value.CompareTo(minimumValue) >= 0) && (Subject.Value.CompareTo(maximumValue) <= 0)))
.BecauseOf(because, becauseArgs)
Expand Down Expand Up @@ -449,5 +479,7 @@ public AndConstraint<TAssertions> NotBeOfType(Type unexpectedType, string becaus
/// <inheritdoc/>
public override bool Equals(object obj) =>
throw new NotSupportedException("Calling Equals on Assertion classes is not supported.");

private protected virtual bool IsNaN(T value) => false;
}
}
20 changes: 20 additions & 0 deletions Src/FluentAssertions/NumericAssertionsExtensions.cs
Expand Up @@ -752,6 +752,11 @@ public static class NumericAssertionsExtensions
float expectedValue, float precision, string because = "",
params object[] becauseArgs)
{
if (float.IsNaN(expectedValue))
{
throw new ArgumentException("Cannot determine approximation of a float to NaN", nameof(expectedValue));
}

if (precision < 0)
{
throw new ArgumentOutOfRangeException(nameof(precision), $"The value of {nameof(precision)} must be non-negative.");
Expand Down Expand Up @@ -879,6 +884,11 @@ public static class NumericAssertionsExtensions
double expectedValue, double precision, string because = "",
params object[] becauseArgs)
{
if (double.IsNaN(expectedValue))
{
throw new ArgumentException("Cannot determine approximation of a double to NaN", nameof(expectedValue));
}

if (precision < 0)
{
throw new ArgumentOutOfRangeException(nameof(precision), $"The value of {nameof(precision)} must be non-negative.");
Expand Down Expand Up @@ -1137,6 +1147,11 @@ public static class NumericAssertionsExtensions
float unexpectedValue, float precision, string because = "",
params object[] becauseArgs)
{
if (float.IsNaN(unexpectedValue))
{
throw new ArgumentException("Cannot determine approximation of a float to NaN", nameof(unexpectedValue));
}

if (precision < 0)
{
throw new ArgumentOutOfRangeException(nameof(precision), $"The value of {nameof(precision)} must be non-negative.");
Expand Down Expand Up @@ -1262,6 +1277,11 @@ public static class NumericAssertionsExtensions
double unexpectedValue, double precision, string because = "",
params object[] becauseArgs)
{
if (double.IsNaN(unexpectedValue))
{
throw new ArgumentException("Cannot determine approximation of a double to NaN", nameof(unexpectedValue));
}

if (precision < 0)
{
throw new ArgumentOutOfRangeException(nameof(precision), $"The value of {nameof(precision)} must be non-negative.");
Expand Down

0 comments on commit 1cd3a08

Please sign in to comment.