diff --git a/src/NUnitFramework/framework/Constraints/ConstraintBuilder.cs b/src/NUnitFramework/framework/Constraints/ConstraintBuilder.cs index 9b9f8cda1d..4001d8c913 100644 --- a/src/NUnitFramework/framework/Constraints/ConstraintBuilder.cs +++ b/src/NUnitFramework/framework/Constraints/ConstraintBuilder.cs @@ -175,6 +175,17 @@ public void Append(Constraint constraint) constraint.Builder = this; } + /// + /// Replaces the last pushed constraint with the specified constraint. + /// + /// The constraint to replace the lastPushed with. + public void Replace(Constraint constraint) + { + _constraints.Pop(); + _lastPushed = _ops.Top; + Append(constraint); + } + /// /// Sets the top operator right context. /// diff --git a/src/NUnitFramework/framework/Constraints/ConstraintExpression.cs b/src/NUnitFramework/framework/Constraints/ConstraintExpression.cs index 536d89fe5a..4f09363560 100644 --- a/src/NUnitFramework/framework/Constraints/ConstraintExpression.cs +++ b/src/NUnitFramework/framework/Constraints/ConstraintExpression.cs @@ -427,6 +427,49 @@ public EqualConstraint EqualTo(object? expected) return Append(new EqualConstraint(expected)); } + /// + /// Returns a constraint that tests two strings for equality + /// + public EqualStringConstraint EqualTo(string? expected) + { + return Append(new EqualStringConstraint(expected)); + } + + /// + /// Returns a constraint that tests two date time offset instances for equality + /// + public EqualDateTimeOffsetConstraint EqualTo(DateTimeOffset expected) + { + return Append(new EqualDateTimeOffsetConstraint(expected)); + } + + /// + /// Returns a constraint that tests two date time instances for equality + /// + public EqualTimeBaseConstraint EqualTo(DateTime expected) + { + return Append(new EqualTimeBaseConstraint(expected, x => x.Ticks)); + } + + /// + /// Returns a constraint that tests two timespan instances for equality + /// + public EqualTimeBaseConstraint EqualTo(TimeSpan expected) + { + return Append(new EqualTimeBaseConstraint(expected, x => x.Ticks)); + } + + /// + /// Returns a constraint that tests two numbers for equality + /// +#pragma warning disable CS3024 // Constraint type is not CLS-compliant + public EqualNumericConstraint EqualTo(T expected) + where T : unmanaged, IConvertible, IEquatable + { + return Append(new EqualNumericConstraint(expected)); + } +#pragma warning restore CS3024 // Constraint type is not CLS-compliant + #endregion #region SameAs diff --git a/src/NUnitFramework/framework/Constraints/EqualConstraintResult.cs b/src/NUnitFramework/framework/Constraints/EqualConstraintResult.cs index e6d6dd41d8..c946d82028 100644 --- a/src/NUnitFramework/framework/Constraints/EqualConstraintResult.cs +++ b/src/NUnitFramework/framework/Constraints/EqualConstraintResult.cs @@ -57,6 +57,51 @@ public EqualConstraintResult(EqualConstraint constraint, object? actual, bool ha _failurePoints = constraint.HasFailurePoints ? constraint.FailurePoints : Array.Empty(); } + /// + /// Construct an EqualConstraintResult + /// + public EqualConstraintResult(Constraint constraint, object? actual, Tolerance tolerance, bool hasSucceeded) + : base(constraint, actual, hasSucceeded) + { + _expectedValue = constraint.Arguments[0]; + _tolerance = tolerance; + _comparingProperties = false; + _caseInsensitive = false; + _ignoringWhiteSpace = false; + _clipStrings = false; + _failurePoints = Array.Empty(); + } + + /// + /// Construct an EqualConstraintResult + /// + public EqualConstraintResult(Constraint constraint, object? actual, bool hasSucceeded) + : base(constraint, actual, hasSucceeded) + { + _expectedValue = constraint.Arguments[0]; + _tolerance = Tolerance.Default; + _comparingProperties = false; + _caseInsensitive = false; + _ignoringWhiteSpace = false; + _clipStrings = false; + _failurePoints = Array.Empty(); + } + + /// + /// Construct an EqualConstraintResult + /// + public EqualConstraintResult(EqualStringWithoutUsingConstraint constraint, object? actual, bool caseInsensitive, bool ignoringWhiteSpace, bool clipStrings, bool hasSucceeded) + : base(constraint, actual, hasSucceeded) + { + _expectedValue = constraint.Arguments[0]; + _tolerance = Tolerance.Exact; + _comparingProperties = false; + _caseInsensitive = caseInsensitive; + _ignoringWhiteSpace = ignoringWhiteSpace; + _clipStrings = clipStrings; + _failurePoints = Array.Empty(); + } + /// /// Write a failure message. Overridden to provide custom /// failure messages for EqualConstraint. diff --git a/src/NUnitFramework/framework/Constraints/EqualDateTimeOffsetConstraint.cs b/src/NUnitFramework/framework/Constraints/EqualDateTimeOffsetConstraint.cs new file mode 100644 index 0000000000..3fc3800aef --- /dev/null +++ b/src/NUnitFramework/framework/Constraints/EqualDateTimeOffsetConstraint.cs @@ -0,0 +1,50 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; + +namespace NUnit.Framework.Constraints +{ + /// + /// EqualConstraint is able to compare an actual value with the + /// expected value provided in its constructor. Two objects are + /// considered equal if both are null, or if both have the same + /// value. NUnit has special semantics for some object types. + /// + public class EqualDateTimeOffsetConstraint : EqualTimeBaseConstraint + { + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + /// The expected value. + public EqualDateTimeOffsetConstraint(DateTimeOffset expected) + : base(expected, x => x.UtcTicks) + { + } + + #endregion + + #region Constraint Modifiers + + /// + /// Flags the constraint to include + /// property in comparison of two values. + /// + /// + /// Using this modifier does not allow to use the + /// constraint modifier. + /// + public EqualDateTimeOffsetConstraintWithSameOffset WithSameOffset + { + get + { + var constraint = new EqualDateTimeOffsetConstraintWithSameOffset(Expected); + Builder?.Replace(constraint); + return constraint; + } + } + + #endregion + } +} diff --git a/src/NUnitFramework/framework/Constraints/EqualDateTimeOffsetConstraintWithSameOffset.cs b/src/NUnitFramework/framework/Constraints/EqualDateTimeOffsetConstraintWithSameOffset.cs new file mode 100644 index 0000000000..77feefb30c --- /dev/null +++ b/src/NUnitFramework/framework/Constraints/EqualDateTimeOffsetConstraintWithSameOffset.cs @@ -0,0 +1,79 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using System.Text; + +namespace NUnit.Framework.Constraints +{ + /// + /// EqualConstraint is able to compare an actual value with the + /// expected value provided in its constructor. Two objects are + /// considered equal if both are null, or if both have the same + /// value. NUnit has special semantics for some object types. + /// + public class EqualDateTimeOffsetConstraintWithSameOffset : Constraint + { + private readonly DateTimeOffset _expected; + + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + /// The expected value. + public EqualDateTimeOffsetConstraintWithSameOffset(DateTimeOffset expected) + : base(expected) + { + _expected = expected; + } + + #endregion + + #region Public Methods + + /// + /// Test whether the constraint is satisfied by a given value + /// + /// The value to be tested + /// True for success, false for failure + public ConstraintResult ApplyTo(DateTimeOffset actual) + { + bool hasSucceeded = _expected.Equals(actual) && _expected.Offset == actual.Offset; + + return new ConstraintResult(this, actual, hasSucceeded); + } + + /// + /// Test whether the constraint is satisfied by a given value + /// + /// The value to be tested + /// True for success, false for failure + public override ConstraintResult ApplyTo(TActual actual) + { + if (actual is DateTimeOffset dateTimeOffset) + { + return ApplyTo(dateTimeOffset); + } + + return new ConstraintResult(this, actual, false); + } + + /// + /// The Description of what this constraint tests, for + /// use in messages and in the ConstraintResult. + /// + public override string Description + { + get + { + var sb = new StringBuilder(MsgUtils.FormatValue(_expected)); + + sb.Append(" with the same offset"); + + return sb.ToString(); + } + } + + #endregion + } +} diff --git a/src/NUnitFramework/framework/Constraints/EqualNumericConstraint.cs b/src/NUnitFramework/framework/Constraints/EqualNumericConstraint.cs new file mode 100644 index 0000000000..c8584dff58 --- /dev/null +++ b/src/NUnitFramework/framework/Constraints/EqualNumericConstraint.cs @@ -0,0 +1,31 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; + +namespace NUnit.Framework.Constraints +{ + /// + /// EqualNumericConstraint is able to compare an actual value with the + /// expected value provided in its constructor. Two objects are + /// considered equal if both are null, or if both have the same + /// value. NUnit has special semantics for some object types. + /// +#pragma warning disable CS3024 // Constraint type is not CLS-compliant + public class EqualNumericConstraint : EqualNumericWithoutUsingConstraint, IEqualWithUsingConstraint + where T : unmanaged, IConvertible, IEquatable +#pragma warning restore CS3024 // Constraint type is not CLS-compliant + { + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + /// The expected value. + public EqualNumericConstraint(T expected) + : base(expected) + { + } + + #endregion + } +} diff --git a/src/NUnitFramework/framework/Constraints/EqualNumericWithoutUsingConstraint.cs b/src/NUnitFramework/framework/Constraints/EqualNumericWithoutUsingConstraint.cs new file mode 100644 index 0000000000..6c70bd49ee --- /dev/null +++ b/src/NUnitFramework/framework/Constraints/EqualNumericWithoutUsingConstraint.cs @@ -0,0 +1,196 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using System.Text; + +namespace NUnit.Framework.Constraints +{ + /// + /// EqualNumericConstraint is able to compare an actual value with the + /// expected value provided in its constructor. Two objects are + /// considered equal if both are null, or if both have the same + /// value. NUnit has special semantics for some object types. + /// +#pragma warning disable CS3024 // Constraint type is not CLS-compliant + public class EqualNumericWithoutUsingConstraint : Constraint + where T : unmanaged, IConvertible, IEquatable +#pragma warning restore CS3024 // Constraint type is not CLS-compliant + { + #region Static and Instance Fields + + private readonly T _expected; + + private Tolerance _tolerance = Tolerance.Default; + + #endregion + + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + /// The expected value. + public EqualNumericWithoutUsingConstraint(T expected) + : base(expected) + { + _expected = expected; + } + + #endregion + + #region Properties + + /// + public override string DisplayName => "Equal"; + + /// + /// The expected value. + /// + public T Expected => _expected; + + /// + /// Gets the tolerance for this comparison. + /// + /// + /// The tolerance. + /// + public Tolerance Tolerance => _tolerance; + + #endregion + + #region Constraint Modifiers + + /// + /// Flag the constraint to use a tolerance when determining equality. + /// + /// Tolerance value to be used + /// Self. + public EqualNumericWithoutUsingConstraint Within(T amount) + { + if (!_tolerance.IsUnsetOrDefault) + throw new InvalidOperationException("Within modifier may appear only once in a constraint expression"); + + _tolerance = new Tolerance(amount); + return this; + } + + /// + /// Switches the .Within() modifier to interpret its tolerance as + /// a distance in representable values (see remarks). + /// + /// Self. + /// + /// Ulp stands for "unit in the last place" and describes the minimum + /// amount a given value can change. For any integers, an ulp is 1 whole + /// digit. For floating point values, the accuracy of which is better + /// for smaller numbers and worse for larger numbers, an ulp depends + /// on the size of the number. Using ulps for comparison of floating + /// point results instead of fixed tolerances is safer because it will + /// automatically compensate for the added inaccuracy of larger numbers. + /// + public EqualNumericWithoutUsingConstraint Ulps + { + get + { + _tolerance = _tolerance.Ulps; + return this; + } + } + + /// + /// Switches the .Within() modifier to interpret its tolerance as + /// a percentage that the actual values is allowed to deviate from + /// the expected value. + /// + /// Self + public EqualNumericWithoutUsingConstraint Percent + { + get + { + _tolerance = _tolerance.Percent; + return this; + } + } + + #endregion + + #region Public Methods + + /// + /// Test whether the constraint is satisfied by a given value + /// + /// The value to be tested + /// True for success, false for failure + public ConstraintResult ApplyTo(T actual) + { + bool hasSucceeded = Numerics.AreEqual(_expected, actual, ref _tolerance); + + return ConstraintResult(actual, hasSucceeded); + } + + /// + /// Test whether the constraint is satisfied by a given value + /// + /// The value to be tested + /// True for success, false for failure + public override ConstraintResult ApplyTo(TActual actual) + { + bool hasSucceeded; + + if (actual is null) + { + hasSucceeded = false; + } + else if (actual is T t) + { + hasSucceeded = Numerics.AreEqual(_expected, t, ref _tolerance); + } + else if (actual is IEquatable equatable) + { + hasSucceeded = equatable.Equals(_expected); + } + else if (actual is not string and IConvertible) + { + hasSucceeded = Numerics.AreEqual(actual, _expected, ref _tolerance); + } + else + { + hasSucceeded = false; + } + + return ConstraintResult(actual, hasSucceeded); + } + + private ConstraintResult ConstraintResult(TActual actual, bool hasSucceeded) + { + return new EqualConstraintResult(this, actual, _tolerance, hasSucceeded); + } + + /// + /// The Description of what this constraint tests, for + /// use in messages and in the ConstraintResult. + /// + public override string Description + { + get + { + var sb = new StringBuilder(MsgUtils.FormatValue(_expected)); + + if (_tolerance is not null && _tolerance.HasVariance) + { + sb.Append(" +/- "); + sb.Append(MsgUtils.FormatValue(_tolerance.Amount)); + if (_tolerance.Mode != ToleranceMode.Linear) + { + sb.Append(" "); + sb.Append(_tolerance.Mode.ToString()); + } + } + + return sb.ToString(); + } + } + + #endregion + } +} diff --git a/src/NUnitFramework/framework/Constraints/EqualNumericWithoutUsingConstraintExtensions.cs b/src/NUnitFramework/framework/Constraints/EqualNumericWithoutUsingConstraintExtensions.cs new file mode 100644 index 0000000000..e15878d9d2 --- /dev/null +++ b/src/NUnitFramework/framework/Constraints/EqualNumericWithoutUsingConstraintExtensions.cs @@ -0,0 +1,34 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using NUnit.Framework.Constraints; + +namespace NUnit.Framework +{ + /// + /// Extension methods for . + /// + public static class EqualNumericWithoutUsingConstraintExtensions + { + /// + /// Flag the constraint to use a tolerance when determining equality. + /// + /// The original constraint. + /// Tolerance value to be used + /// Original constraint promoted to . + public static EqualNumericWithoutUsingConstraint Within(this EqualNumericWithoutUsingConstraint constraint, double amount) + { + return new EqualNumericConstraint(constraint.Expected).Within(amount); + } + + /// + /// Flag the constraint to use a tolerance when determining equality. + /// + /// The original constraint. + /// Tolerance value to be used + /// Original constraint promoted to . + public static EqualNumericWithoutUsingConstraint Within(this EqualNumericWithoutUsingConstraint constraint, double amount) + { + return new EqualNumericConstraint(constraint.Expected).Within(amount); + } + } +} diff --git a/src/NUnitFramework/framework/Constraints/EqualStringConstraint.cs b/src/NUnitFramework/framework/Constraints/EqualStringConstraint.cs new file mode 100644 index 0000000000..9cb2b33542 --- /dev/null +++ b/src/NUnitFramework/framework/Constraints/EqualStringConstraint.cs @@ -0,0 +1,46 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using System.Collections.Generic; + +namespace NUnit.Framework.Constraints +{ + /// + /// EqualConstraint is able to compare an actual value with the + /// expected value provided in its constructor. Two objects are + /// considered equal if both are null, or if both have the same + /// value. NUnit has special semantics for some object types. + /// + public class EqualStringConstraint : EqualStringWithoutUsingConstraint, IEqualWithUsingConstraint + { + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + /// The expected value. + public EqualStringConstraint(string? expected) + : base(expected) + { + } + + #endregion + + #region Public Methods + + /// + /// Sets the to be used in the comparison. + /// + /// comparer to use for comparing strings. + /// + /// Equal constraint comparing + /// with an actual value using the user supplied comparer. + /// + public EqualUsingConstraint Using(StringComparer comparer) + { + return new EqualUsingConstraint(Expected, (IEqualityComparer)comparer); + } + + #endregion + } +} diff --git a/src/NUnitFramework/framework/Constraints/EqualStringWithoutUsingConstraint.cs b/src/NUnitFramework/framework/Constraints/EqualStringWithoutUsingConstraint.cs new file mode 100644 index 0000000000..210d02add8 --- /dev/null +++ b/src/NUnitFramework/framework/Constraints/EqualStringWithoutUsingConstraint.cs @@ -0,0 +1,165 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System.Text; +using NUnit.Framework.Constraints.Comparers; + +namespace NUnit.Framework.Constraints +{ + /// + /// EqualConstraint is able to compare an actual value with the + /// expected value provided in its constructor. Two objects are + /// considered equal if both are null, or if both have the same + /// value. NUnit has special semantics for some object types. + /// + public class EqualStringWithoutUsingConstraint : Constraint + { + #region Static and Instance Fields + + private readonly string? _expected; + + private bool _caseInsensitive; + private bool _ignoringWhiteSpace; + private bool _clipStrings; + + #endregion + + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + /// The expected value. + public EqualStringWithoutUsingConstraint(string? expected) + : base(expected) + { + _expected = expected; + _clipStrings = true; + } + + #endregion + + /// + /// Gets the expected value. + /// + public string? Expected => _expected; + + #region Constraint Modifiers + + /// + /// Flag the constraint to ignore case and return self. + /// + public EqualStringWithoutUsingConstraint IgnoreCase + { + get + { + _caseInsensitive = true; + return this; + } + } + + /// + /// Flag the constraint to ignore white space and return self. + /// + public EqualStringWithoutUsingConstraint IgnoreWhiteSpace + { + get + { + _ignoringWhiteSpace = true; + return this; + } + } + + /// + /// Flag the constraint to suppress string clipping + /// and return self. + /// + public EqualStringWithoutUsingConstraint NoClip + { + get + { + _clipStrings = false; + return this; + } + } + + #endregion + + #region Public Methods + + /// + /// Test whether the constraint is satisfied by a given value + /// + /// The value to be tested + /// True for success, false for failure + public ConstraintResult ApplyTo(string? actual) + { + bool hasSucceeded; + + if (actual is null) + { + hasSucceeded = _expected is null; + } + else if (_expected is null) + { + hasSucceeded = false; + } + else + { + hasSucceeded = StringsComparer.Equals(_expected, actual, _caseInsensitive, _ignoringWhiteSpace); + } + + return ConstraintResult(actual, hasSucceeded); + } + + /// + /// + /// I wish we could hide this method, but it is public in the base class. + /// + public sealed override ConstraintResult ApplyTo(TActual actual) + { + bool hasSucceeded; + + if (actual is null) + { + hasSucceeded = _expected is null; + } + else if (_expected is null) + { + hasSucceeded = false; + } + else + { + return ApplyTo(actual as string); + } + + return ConstraintResult(actual, hasSucceeded); + } + + private ConstraintResult ConstraintResult(T actual, bool hasSucceeded) + { + return new EqualConstraintResult(this, actual, _caseInsensitive, _ignoringWhiteSpace, _clipStrings, hasSucceeded); + } + + /// + /// The Description of what this constraint tests, for + /// use in messages and in the ConstraintResult. + /// + public override string Description + { + get + { + var sb = new StringBuilder(MsgUtils.FormatValue(_expected)); + + if (_caseInsensitive) + sb.Append(", ignoring case"); + + if (_ignoringWhiteSpace) + sb.Append(", ignoring white-space"); + + return sb.ToString(); + } + } + + #endregion + } +} diff --git a/src/NUnitFramework/framework/Constraints/EqualTimeBasedConstraintWithNumericTolerance{T}.cs b/src/NUnitFramework/framework/Constraints/EqualTimeBasedConstraintWithNumericTolerance{T}.cs new file mode 100644 index 0000000000..7ae866d065 --- /dev/null +++ b/src/NUnitFramework/framework/Constraints/EqualTimeBasedConstraintWithNumericTolerance{T}.cs @@ -0,0 +1,89 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; + +namespace NUnit.Framework.Constraints +{ + /// + /// EqualConstraint is able to compare an actual value with the + /// expected value provided in its constructor. Two objects are + /// considered equal if both are null, or if both have the same + /// value. NUnit has special semantics for some object types. + /// + public class EqualTimeBasedConstraintWithNumericTolerance + where T : notnull, IEquatable, IComparable + { + private readonly T _expected; + private readonly Func _getTicks; + private readonly double _tolerance; + + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + /// The expected value. + /// Method to extract the Ticks from an instance of . + /// The tolerance to apply when qualified with a unit. + public EqualTimeBasedConstraintWithNumericTolerance(T expected, Func getTicks, double tolerance) + { + _expected = expected; + _tolerance = tolerance; + _getTicks = getTicks; + } + + #endregion + + /// + /// The ConstraintBuilder holding this constraint + /// + public ConstraintBuilder? Builder { get; set; } + + #region Constraint Modifiers + + /// + /// Causes the tolerance to be interpreted as a TimeSpan in days. + /// + /// Self + public EqualTimeBasedConstraintWithTimeSpanTolerance Days => Within(TimeSpan.FromDays(_tolerance)); + + /// + /// Causes the tolerance to be interpreted as a TimeSpan in hours. + /// + /// Self + public EqualTimeBasedConstraintWithTimeSpanTolerance Hours => Within(TimeSpan.FromHours(_tolerance)); + + /// + /// Causes the tolerance to be interpreted as a TimeSpan in minutes. + /// + /// Self + public EqualTimeBasedConstraintWithTimeSpanTolerance Minutes => Within(TimeSpan.FromMinutes(_tolerance)); + + /// + /// Causes the tolerance to be interpreted as a TimeSpan in seconds. + /// + /// Self + public EqualTimeBasedConstraintWithTimeSpanTolerance Seconds => Within(TimeSpan.FromSeconds(_tolerance)); + + /// + /// Causes the tolerance to be interpreted as a TimeSpan in milliseconds. + /// + /// Self + public EqualTimeBasedConstraintWithTimeSpanTolerance Milliseconds => Within(TimeSpan.FromMilliseconds(_tolerance)); + + /// + /// Causes the tolerance to be interpreted as a TimeSpan in clock ticks. + /// + /// Self + public EqualTimeBasedConstraintWithTimeSpanTolerance Ticks => Within(TimeSpan.FromTicks((long)_tolerance)); + + private EqualTimeBasedConstraintWithTimeSpanTolerance Within(TimeSpan amount) + { + var constraint = new EqualTimeBasedConstraintWithTimeSpanTolerance(_expected, _getTicks, amount); + Builder?.Replace(constraint); + return constraint; + } + + #endregion + } +} diff --git a/src/NUnitFramework/framework/Constraints/EqualTimeBasedConstraintWithTimeSpanTolerance{T}.cs b/src/NUnitFramework/framework/Constraints/EqualTimeBasedConstraintWithTimeSpanTolerance{T}.cs new file mode 100644 index 0000000000..f7e779706c --- /dev/null +++ b/src/NUnitFramework/framework/Constraints/EqualTimeBasedConstraintWithTimeSpanTolerance{T}.cs @@ -0,0 +1,86 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using System.Text; + +namespace NUnit.Framework.Constraints +{ + /// + /// EqualConstraint is able to compare an actual value with the + /// expected value provided in its constructor. Two objects are + /// considered equal if both are null, or if both have the same + /// value. NUnit has special semantics for some object types. + /// + public class EqualTimeBasedConstraintWithTimeSpanTolerance : Constraint + where T : notnull, IEquatable, IComparable + { + private readonly T _expected; + private readonly Func _getTicks; + private readonly TimeSpan _tolerance; + + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + /// The expected value. + /// The tolerance to apply when comparing for equality. + /// Method to extract the Ticks from an instance of . + public EqualTimeBasedConstraintWithTimeSpanTolerance(T expected, Func getTicks, TimeSpan tolerance) + : base(expected) + { + _expected = expected; + _getTicks = getTicks; + _tolerance = tolerance; + } + + #endregion + + /// + /// Test whether the constraint is satisfied by a given value + /// + /// The value to be tested + /// True for success, false for failure + public ConstraintResult ApplyTo(T actual) + { + long ticksExpected = _getTicks(_expected); + long ticksActual = _getTicks(actual); + + bool hasSucceeded = Math.Abs(ticksExpected - ticksActual) <= _tolerance.Ticks; + + return new ConstraintResult(this, actual, hasSucceeded); + } + + /// + /// Test whether the constraint is satisfied by a given value + /// + /// The value to be tested + /// True for success, false for failure + public override ConstraintResult ApplyTo(TActual actual) + { + if (actual is T t) + { + return ApplyTo(t); + } + + return new ConstraintResult(this, actual, false); + } + + /// + /// The Description of what this constraint tests, for + /// use in messages and in the ConstraintResult. + /// + public override string Description + { + get + { + var sb = new StringBuilder(MsgUtils.FormatValue(_expected)); + + sb.Append(" +/- "); + sb.Append(MsgUtils.FormatValue(_tolerance)); + + return sb.ToString(); + } + } + } +} diff --git a/src/NUnitFramework/framework/Constraints/EqualTimeBasedConstraint{T}.cs b/src/NUnitFramework/framework/Constraints/EqualTimeBasedConstraint{T}.cs new file mode 100644 index 0000000000..f042f6dac2 --- /dev/null +++ b/src/NUnitFramework/framework/Constraints/EqualTimeBasedConstraint{T}.cs @@ -0,0 +1,116 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; + +namespace NUnit.Framework.Constraints +{ + /// + /// EqualConstraint is able to compare an actual value with the + /// expected value provided in its constructor. Two objects are + /// considered equal if both are null, or if both have the same + /// value. NUnit has special semantics for some object types. + /// + public class EqualTimeBaseConstraint : Constraint + where T : struct, IEquatable, IComparable + { + #region Static and Instance Fields + + private readonly T _expected; + private readonly Func _getTicks; + + #endregion + + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + /// The expected value. + /// Method to extract the Ticks from an instance of . + public EqualTimeBaseConstraint(T expected, Func getTicks) + : base(expected) + { + _expected = expected; + _getTicks = getTicks; + } + + #endregion + + /// + /// Gets the expected value. + /// + public T Expected => _expected; + + #region Constraint Modifiers + + /// + /// Flag the constraint to use a tolerance when determining equality. + /// + /// Tolerance value to be used + /// Self. + public EqualTimeBasedConstraintWithTimeSpanTolerance Within(TimeSpan amount) + { + var constraint = new EqualTimeBasedConstraintWithTimeSpanTolerance(_expected, _getTicks, amount); + Builder?.Replace(constraint); + return constraint; + } + + /// + /// Flag the constraint to use a tolerance when determining equality. + /// + /// Tolerance value to be used + /// Self. + public EqualTimeBasedConstraintWithNumericTolerance Within(double amount) + { + return new EqualTimeBasedConstraintWithNumericTolerance(_expected, _getTicks, amount) + { + Builder = Builder, + }; + } + + #endregion + + #region Public Methods + + /// + /// Test whether the constraint is satisfied by a given value + /// + /// The value to be tested + /// True for success, false for failure + public virtual ConstraintResult ApplyTo(T actual) + { + bool hasSucceeded = _expected.Equals(actual); + + return new ConstraintResult(this, actual, hasSucceeded); + } + + /// + /// Test whether the constraint is satisfied by a given value + /// + /// The value to be tested + /// True for success, false for failure + public override ConstraintResult ApplyTo(TActual actual) + { + if (actual is T t) + { + return ApplyTo(t); + } + + return new ConstraintResult(this, actual, false); + } + + /// + /// The Description of what this constraint tests, for + /// use in messages and in the ConstraintResult. + /// + public override string Description + { + get + { + return MsgUtils.FormatValue(_expected); + } + } + + #endregion + } +} diff --git a/src/NUnitFramework/framework/Constraints/EqualUsingConstraint.cs b/src/NUnitFramework/framework/Constraints/EqualUsingConstraint.cs new file mode 100644 index 0000000000..dc7eac86eb --- /dev/null +++ b/src/NUnitFramework/framework/Constraints/EqualUsingConstraint.cs @@ -0,0 +1,196 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace NUnit.Framework.Constraints +{ + /// + /// EqualUsingConstraint where the comparison is done by a user supplied comparer. + /// + public class EqualUsingConstraint : Constraint + { + #region Static and Instance Fields + + private readonly T? _expected; + + private readonly Func? _comparer; + private readonly Func? _nonTypedComparer; + + #endregion + + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + /// The expected value. + /// The comparer to use. + public EqualUsingConstraint(T? expected, Func comparer) + : base(expected) + { + _expected = expected; + _comparer = comparer; + } + + /// + /// Initializes a new instance of the class. + /// + /// The expected value. + /// The comparer to use. + public EqualUsingConstraint(T? expected, Func comparer) + : base(expected) + { + _expected = expected; + _nonTypedComparer = comparer; + } + + /// + /// Initializes a new instance of the class. + /// + /// The expected value. + /// The comparer to use. + public EqualUsingConstraint(T? expected, IEqualityComparer comparer) + : this(expected, comparer.Equals) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The expected value. + /// The comparer to use. + public EqualUsingConstraint(T? expected, IComparer comparer) + : this(expected, (x, y) => comparer.Compare(x, y) == 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The expected value. + /// The comparer to use. + public EqualUsingConstraint(T? expected, Comparison comparer) + : this(expected, (x, y) => comparer.Invoke(x, y) == 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The expected value. + /// The comparer to use. + public EqualUsingConstraint(T? expected, IEqualityComparer comparer) + : this(expected, (object x, object y) => comparer.Equals(x, y)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The expected value. + /// The comparer to use. + public EqualUsingConstraint(T? expected, IComparer comparer) + : this(expected, (object x, object y) => comparer.Compare(x, y) == 0) + { + } + + #endregion + + #region Public Methods + + /// + /// Test whether the constraint is satisfied by a given value + /// + /// The value to be tested + /// True for success, false for failure + public virtual ConstraintResult ApplyTo(T? actual) + { + bool hasSucceeded; + + if (actual is null) + { + hasSucceeded = _expected is null; + } + else if (_expected is null) + { + hasSucceeded = false; + } + else if (_comparer is not null) + { + hasSucceeded = _comparer.Invoke(actual, _expected); + } + else if (_nonTypedComparer is not null) + { + hasSucceeded = _nonTypedComparer.Invoke(actual, _expected); + } + else + { + hasSucceeded = false; + } + + return ConstraintResult(actual, hasSucceeded); + } + + /// + /// + /// I wish we could hide this method, but it is public in the base class. + /// + public sealed override ConstraintResult ApplyTo(TActual actual) + { + bool hasSucceeded; + + if (actual is null) + { + hasSucceeded = _expected is null; + } + else if (_expected is null) + { + hasSucceeded = false; + } + else if (_nonTypedComparer is not null) + { + hasSucceeded = _nonTypedComparer.Invoke(actual, _expected); + } + else if (_comparer is not null && actual is T t) + { + hasSucceeded = _comparer.Invoke(t, _expected); + } + else + { + hasSucceeded = false; + } + + return ConstraintResult(actual, hasSucceeded); + } + + private ConstraintResult ConstraintResult(TActual actual, bool hasSucceeded) + { + return new EqualConstraintResult(this, actual, hasSucceeded); + } + + /// + /// The Description of what this constraint tests, for + /// use in messages and in the ConstraintResult. + /// + public override string Description + { + get + { + var sb = new StringBuilder(MsgUtils.FormatValue(_expected)); + + if (_comparer is not null) + sb.Append(", using strongly typed comparer"); + + if (_nonTypedComparer is not null) + sb.Append(", using untyped comparer"); + + return sb.ToString(); + } + } + + #endregion + } +} diff --git a/src/NUnitFramework/framework/Constraints/IEqualWithUsingConstraint.cs b/src/NUnitFramework/framework/Constraints/IEqualWithUsingConstraint.cs new file mode 100644 index 0000000000..2807ce1c5d --- /dev/null +++ b/src/NUnitFramework/framework/Constraints/IEqualWithUsingConstraint.cs @@ -0,0 +1,15 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +namespace NUnit.Framework +{ + /// + /// Interface for equal constraints which support user comparisons. + /// + public interface IEqualWithUsingConstraint + { + /// + /// The expected value. + /// + public T Expected { get; } + } +} diff --git a/src/NUnitFramework/framework/Constraints/IEqualWithUsingConstraintExtensions.cs b/src/NUnitFramework/framework/Constraints/IEqualWithUsingConstraintExtensions.cs new file mode 100644 index 0000000000..a7a8222cbd --- /dev/null +++ b/src/NUnitFramework/framework/Constraints/IEqualWithUsingConstraintExtensions.cs @@ -0,0 +1,151 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; +using System.Collections; +using System.Collections.Generic; +using NUnit.Framework.Constraints; + +namespace NUnit.Framework +{ + /// + /// Allows specifying a custom comparer for the . + /// + public static class IEqualWithUsingConstraintExtensions + { + #region TExpected Typed Comparers + + /// + /// Flag the constraint to use the supplied boolean-returning delegate. + /// + /// The constraint to add a user comparer to. + /// The boolean-returning delegate to use. + /// The type of the expected value. + /// + /// Equal constraint comparing + /// with an actual value using the user supplied comparer. + /// + public static EqualUsingConstraint Using(this IEqualWithUsingConstraint constraint, Func comparer) + { + return new EqualUsingConstraint(constraint.Expected, comparer); + } + + /// + /// Flag the constraint to use the supplied IEqualityComparer object. + /// + /// The constraint to add a user comparer to. + /// The comparer to use. + /// The type of the expected value. + /// + /// Equal constraint comparing + /// with an actual value using the user supplied comparer. + /// + public static EqualUsingConstraint Using(this IEqualWithUsingConstraint constraint, IEqualityComparer comparer) + { + return new EqualUsingConstraint(constraint.Expected, comparer); + } + + /// + /// Flag the constraint to use the supplied IComparer object. + /// + /// The constraint to add a user comparer to. + /// The comparer to use. + /// The type of the expected value. + /// + /// Equal constraint comparing + /// with an actual value using the user supplied comparer. + /// + public static EqualUsingConstraint Using(this IEqualWithUsingConstraint constraint, IComparer comparer) + { + return new EqualUsingConstraint(constraint.Expected, comparer); + } + + /// + /// Flag the constraint to use the supplied Comparison object. + /// + /// The constraint to add a user comparer to. + /// The comparer to use. + /// The type of the expected value. + /// + /// Equal constraint comparing + /// with an actual value using the user supplied comparer. + /// + public static EqualUsingConstraint Using(this IEqualWithUsingConstraint constraint, Comparison comparer) + { + return new EqualUsingConstraint(constraint.Expected, comparer); + } + + #endregion + + #region TExpected vs TActual Typed Comparers + + /// + /// Flag the constraint to use the supplied predicate function + /// + /// The constraint to add a user comparer to. + /// The comparison function to use. + /// The type of the actual value. + /// The type of the expected value. + /// + /// Equal constraint comparing + /// with an actual value using the user supplied comparer. + /// + public static EqualUsingConstraint Using(this IEqualWithUsingConstraint constraint, Func comparer) + { + return new EqualUsingConstraint(constraint.Expected, + (object x, object y) => comparer.Invoke((TActual)x, (TExpected)y)); + } + + /// + /// Flag the constraint to use the supplied IComparer object. + /// + /// The constraint to add a user comparer to. + /// The comparer to use. + /// The type of the expected value. + /// The type of the actual value. + /// + /// Equal constraint comparing + /// with an actual value using the user supplied comparer. + /// + public static EqualUsingConstraint Using(this IEqualWithUsingConstraint constraint, IComparer comparer) + { + return new EqualUsingConstraint(constraint.Expected, + (object x, object y) => comparer.Compare((TActual)x, (TActual)y) == 0); + } + + #endregion + + #region Non-Generic Comparers + + /// + /// Flag the constraint to use the supplied IEqualityComparer object. + /// + /// The constraint to add a user comparer to. + /// The comparer object to use. + /// The type of the expected value. + /// + /// Equal constraint comparing + /// with an actual value using the user supplied comparer. + /// + public static EqualUsingConstraint Using(this IEqualWithUsingConstraint constraint, IEqualityComparer comparer) + { + return new EqualUsingConstraint(constraint.Expected, comparer); + } + + /// + /// Flag the constraint to use the supplied IComparer object. + /// + /// The constraint to add a user comparer to. + /// The comparer to use. + /// The type of the expected value. + /// + /// Equal constraint comparing + /// with an actual value using the user supplied comparer. + /// + public static EqualUsingConstraint Using(this IEqualWithUsingConstraint constraint, IComparer comparer) + { + return new EqualUsingConstraint(constraint.Expected, comparer); + } + + #endregion + } +} diff --git a/src/NUnitFramework/framework/Constraints/Numerics.cs b/src/NUnitFramework/framework/Constraints/Numerics.cs index 3982e4e2c4..cb3619a6e9 100644 --- a/src/NUnitFramework/framework/Constraints/Numerics.cs +++ b/src/NUnitFramework/framework/Constraints/Numerics.cs @@ -161,6 +161,43 @@ public static bool AreEqual(object expected, object actual, ref Tolerance tolera return AreEqual(Convert.ToInt32(expected), Convert.ToInt32(actual), tolerance); } + /// + /// Test two numeric values for equality, performing the usual numeric + /// conversions and using a provided or default tolerance. If the tolerance + /// provided is Empty, this method may set it to a default tolerance. + /// + /// The expected value + /// The actual value + /// A reference to the tolerance in effect + /// True if the values are equal + public static bool AreEqual(T1 expected, T2 actual, ref Tolerance tolerance) + where T1 : unmanaged, IConvertible + where T2 : unmanaged, IConvertible + { + if (expected is double || actual is double) + return AreEqual(expected.ToDouble(null), actual.ToDouble(null), ref tolerance); + + if (expected is float || actual is float) + return AreEqual(expected.ToSingle(null), actual.ToSingle(null), ref tolerance); + + if (tolerance.Mode == ToleranceMode.Ulps) + throw new InvalidOperationException("Ulps may only be specified for floating point arguments"); + + if (expected is decimal || actual is decimal) + return AreEqual(expected.ToDecimal(null), actual.ToDecimal(null), tolerance); + + if (expected is ulong || actual is ulong) + return AreEqual(expected.ToUInt64(null), actual.ToUInt64(null), tolerance); + + if (expected is long || actual is long) + return AreEqual(expected.ToInt64(null), actual.ToInt64(null), tolerance); + + if (expected is uint || actual is uint) + return AreEqual(expected.ToUInt32(null), actual.ToUInt32(null), tolerance); + + return AreEqual(expected.ToInt32(null), actual.ToInt32(null), tolerance); + } + private static bool AreEqual(double expected, double actual, ref Tolerance tolerance) { if (double.IsNaN(expected) && double.IsNaN(actual)) diff --git a/src/NUnitFramework/framework/Constraints/PrefixConstraint.cs b/src/NUnitFramework/framework/Constraints/PrefixConstraint.cs index 6de627d1c7..2f785c03c0 100644 --- a/src/NUnitFramework/framework/Constraints/PrefixConstraint.cs +++ b/src/NUnitFramework/framework/Constraints/PrefixConstraint.cs @@ -1,5 +1,7 @@ // Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt +using System; + namespace NUnit.Framework.Constraints { /// @@ -42,8 +44,16 @@ protected PrefixConstraint(IResolveConstraint baseConstraint, string description /// internal static string FormatDescription(string descriptionPrefix, IConstraint baseConstraint) { + bool isEqualConstraint = baseConstraint is EqualConstraint or EqualStringConstraint; + + if (!isEqualConstraint) + { + Type constraintType = baseConstraint.GetType(); + isEqualConstraint = constraintType.IsGenericType && constraintType.GetGenericTypeDefinition() == typeof(EqualNumericConstraint<>); + } + return string.Format( - baseConstraint is EqualConstraint ? "{0} equal to {1}" : "{0} {1}", + isEqualConstraint ? "{0} equal to {1}" : "{0} {1}", descriptionPrefix, baseConstraint.Description); } diff --git a/src/NUnitFramework/framework/Is.cs b/src/NUnitFramework/framework/Is.cs index 31360e1ea5..cf1960fca1 100644 --- a/src/NUnitFramework/framework/Is.cs +++ b/src/NUnitFramework/framework/Is.cs @@ -158,6 +158,49 @@ public static EqualConstraint EqualTo(IEnumerable? expected) return new EqualConstraint(expected); } + /// + /// Returns a constraint that tests two strings for equality + /// + public static EqualStringConstraint EqualTo(string? expected) + { + return new EqualStringConstraint(expected); + } + + /// + /// Returns a constraint that tests two date time offset instances for equality. + /// + public static EqualDateTimeOffsetConstraint EqualTo(DateTimeOffset expected) + { + return new EqualDateTimeOffsetConstraint(expected); + } + + /// + /// Returns a constraint that tests two date time instances for equality. + /// + public static EqualTimeBaseConstraint EqualTo(DateTime expected) + { + return new EqualTimeBaseConstraint(expected, x => x.Ticks); + } + + /// + /// Returns a constraint that tests two timespan instances for equality. + /// + public static EqualTimeBaseConstraint EqualTo(TimeSpan expected) + { + return new EqualTimeBaseConstraint(expected, x => x.Ticks); + } + + /// + /// Returns a constraint that tests two numbers for equality + /// +#pragma warning disable CS3024 // Constraint type is not CLS-compliant + public static EqualNumericConstraint EqualTo(T expected) + where T : unmanaged, IConvertible, IEquatable + { + return new EqualNumericConstraint(expected); + } +#pragma warning restore CS3024 // Constraint type is not CLS-compliant + #endregion #region SameAs diff --git a/src/NUnitFramework/tests/Constraints/EqualConstraintTests.cs b/src/NUnitFramework/tests/Constraints/EqualConstraintTests.cs index d278c23ba6..5e1e951304 100644 --- a/src/NUnitFramework/tests/Constraints/EqualConstraintTests.cs +++ b/src/NUnitFramework/tests/Constraints/EqualConstraintTests.cs @@ -49,7 +49,7 @@ public void Complex_PassesEquality() [Test] public void RespectsCultureWhenCaseIgnored() { - var constraint = new EqualConstraint("r\u00E9sum\u00E9").IgnoreCase; + var constraint = new EqualStringConstraint("r\u00E9sum\u00E9").IgnoreCase; var result = constraint.ApplyTo("re\u0301sume\u0301"); @@ -59,7 +59,7 @@ public void RespectsCultureWhenCaseIgnored() [Test] public void DoesntRespectCultureWhenCasingMatters() { - var constraint = new EqualConstraint("r\u00E9sum\u00E9"); + var constraint = new EqualStringConstraint("r\u00E9sum\u00E9"); var result = constraint.ApplyTo("re\u0301sume\u0301"); @@ -69,7 +69,7 @@ public void DoesntRespectCultureWhenCasingMatters() [Test] public void IgnoreWhiteSpace() { - var constraint = new EqualConstraint("Hello World").IgnoreWhiteSpace; + var constraint = new EqualStringConstraint("Hello World").IgnoreWhiteSpace; var result = constraint.ApplyTo("Hello\tWorld"); @@ -102,7 +102,7 @@ public void ExtendedIgnoreWhiteSpaceExample() [Test] public void IgnoreWhiteSpaceFail() { - var constraint = new EqualConstraint("Hello World").IgnoreWhiteSpace; + var constraint = new EqualStringConstraint("Hello World").IgnoreWhiteSpace; var result = constraint.ApplyTo("Hello Universe"); @@ -112,7 +112,7 @@ public void IgnoreWhiteSpaceFail() [Test] public void IgnoreWhiteSpaceAndIgnoreCase() { - var constraint = new EqualConstraint("Hello World").IgnoreWhiteSpace.IgnoreCase; + var constraint = new EqualStringConstraint("Hello World").IgnoreWhiteSpace.IgnoreCase; var result = constraint.ApplyTo("hello\r\nworld\r\n"); @@ -384,6 +384,9 @@ public void CanMatchDatesWithinTicks() Assert.That(actual, new EqualConstraint(expected).Within(TimeSpan.TicksPerMinute * 5).Ticks); } +/* + * This no longer compiles! Preventing illegal code and runtime exceptions. + * [Test] public void ErrorIfDaysPrecedesWithin() { @@ -419,6 +422,7 @@ public void ErrorIfTicksPrecedesWithin() { Assert.Throws(() => Assert.That(DateTime.Now, Is.EqualTo(DateTime.Now).Ticks.Within(5))); } +*/ } #endregion @@ -469,6 +473,9 @@ public void NegativeEqualityTestWithTolerance(DateTimeOffset value1, DateTimeOff Assert.That(value1, Is.Not.EqualTo(value2).Within(1).Minutes); } +/* + * The XML documentation says that WithSameOffset doesn't work together with Within, but the code below would says it is. + * [Theory] public void NegativeEqualityTestWithToleranceAndWithSameOffset(DateTimeOffset value1, DateTimeOffset value2) { @@ -494,6 +501,7 @@ public void NegativeEqualityTestWithinToleranceAndWithSameOffset(DateTimeOffset Assert.That(value1, Is.Not.EqualTo(value2).Within(1).Minutes.WithSameOffset); } +*/ } public class DateTimeOffSetEquality @@ -804,7 +812,9 @@ public class ObjectEquality [Test] public void CompareObjectsWithToleranceAsserts() { - Assert.Throws(() => Assert.That("abc", new EqualConstraint("abcd").Within(1))); + // This now no longer compiles as EqualStringConstraint doesn't support Tolerance. + // Assert.Throws(() => Assert.That("abc", new EqualStringConstraint("abcd").Within(1))); + Assert.Pass("EqualStringConstraint does not support Tolerance, so this test is not applicable."); } } @@ -907,7 +917,7 @@ public void UsesProvidedGenericComparer() var comparer = new GenericComparer(); Assert.Multiple(() => { - Assert.That(2 + 2, Is.EqualTo(4).Using(comparer)); + Assert.That(2 + 2, Is.EqualTo(4).Using(comparer)); Assert.That(comparer.WasCalled, "Comparer was not called"); }); } @@ -937,19 +947,25 @@ public void UsesProvidedGenericEqualityComparison() [Test] public void UsesBooleanReturningDelegate() { - Assert.That(2 + 2, Is.EqualTo(4).Using((x, y) => x.Equals(y))); + Assert.That(2 + 2, Is.EqualTo(4).Using((x, y) => x.Equals(y))); } [Test] public void UsesProvidedLambda_IntArgs() { - Assert.That(2 + 2, Is.EqualTo(4).Using((x, y) => x.CompareTo(y))); + Assert.That(2 + 2, Is.EqualTo(4).Using((x, y) => x.CompareTo(y))); } [Test, SetCulture("en-US")] public void UsesProvidedLambda_StringArgs() { - Assert.That("hello", Is.EqualTo("HELLO").Using((x, y) => string.Compare(x, y, StringComparison.CurrentCultureIgnoreCase))); + Assert.That("hello", Is.EqualTo("HELLO").Using((x, y) => string.Compare(x, y, StringComparison.CurrentCultureIgnoreCase))); + } + + [Test, SetCulture("en-US")] + public void UsesStringComparer() + { + Assert.That("hello", Is.EqualTo("HELLO").Using(StringComparer.OrdinalIgnoreCase)); } [Test] diff --git a/src/NUnitFramework/tests/Constraints/EqualTest.cs b/src/NUnitFramework/tests/Constraints/EqualTest.cs index 88933e93ce..49bc73ec7e 100644 --- a/src/NUnitFramework/tests/Constraints/EqualTest.cs +++ b/src/NUnitFramework/tests/Constraints/EqualTest.cs @@ -15,7 +15,7 @@ public void FailedStringMatchShowsFailurePosition() CheckExceptionMessage( Assert.Throws(() => { - Assert.That("abcdgfe", new EqualConstraint("abcdefg")); + Assert.That("abcdgfe", Is.EqualTo("abcdefg")); })); } @@ -30,7 +30,7 @@ public void LongStringsAreTruncated() CheckExceptionMessage( Assert.Throws(() => { - Assert.That(actual, new EqualConstraint(expected)); + Assert.That(actual, Is.EqualTo(expected)); })); } @@ -43,7 +43,7 @@ public void LongStringsAreTruncatedAtBothEndsIfNecessary() CheckExceptionMessage( Assert.Throws(() => { - Assert.That(actual, new EqualConstraint(expected)); + Assert.That(actual, Is.EqualTo(expected)); })); } @@ -56,7 +56,7 @@ public void LongStringsAreTruncatedAtFrontEndIfNecessary() CheckExceptionMessage( Assert.Throws(() => { - Assert.That(actual, new EqualConstraint(expected)); + Assert.That(actual, Is.EqualTo(expected)); })); } diff --git a/src/NUnitFramework/tests/Constraints/NotConstraintTests.cs b/src/NUnitFramework/tests/Constraints/NotConstraintTests.cs index 7579ec3ba0..2dcd211944 100644 --- a/src/NUnitFramework/tests/Constraints/NotConstraintTests.cs +++ b/src/NUnitFramework/tests/Constraints/NotConstraintTests.cs @@ -24,7 +24,7 @@ public void SetUp() [Test] public void NotHonorsIgnoreCaseUsingConstructors() { - var ex = Assert.Throws(() => Assert.That("abc", new NotConstraint(new EqualConstraint("ABC").IgnoreCase))); + var ex = Assert.Throws(() => Assert.That("abc", new NotConstraint(new EqualStringConstraint("ABC").IgnoreCase))); Assert.That(ex?.Message, Does.Contain("ignoring case")); } diff --git a/src/NUnitFramework/tests/Constraints/ThrowsConstraintTests.cs b/src/NUnitFramework/tests/Constraints/ThrowsConstraintTests.cs index 257ed2b2fc..654ca2d96a 100644 --- a/src/NUnitFramework/tests/Constraints/ThrowsConstraintTests.cs +++ b/src/NUnitFramework/tests/Constraints/ThrowsConstraintTests.cs @@ -67,13 +67,13 @@ public class ThrowsConstraintTest_WithConstraint : ThrowsConstraintTestBase protected override Constraint TheConstraint { get; } = new ThrowsConstraint( new AndConstraint( new ExceptionTypeConstraint(typeof(ArgumentException)), - new PropertyConstraint("ParamName", new EqualConstraint("myParam")))); + new PropertyConstraint("ParamName", new EqualStringConstraint("myParam")))); [SetUp] public void SetUp() { ExpectedDescription = @" and property ParamName equal to ""myParam"""; - StringRepresentation = @" >>>"; + StringRepresentation = @" >>>"; } #pragma warning disable IDE0052 // Remove unread private members diff --git a/src/NUnitFramework/tests/Constraints/ToStringTests.cs b/src/NUnitFramework/tests/Constraints/ToStringTests.cs index ec98fd50e9..dd901bd507 100644 --- a/src/NUnitFramework/tests/Constraints/ToStringTests.cs +++ b/src/NUnitFramework/tests/Constraints/ToStringTests.cs @@ -24,7 +24,7 @@ public void CanDisplaySimpleConstraints_Resolved() Assert.That(constraint.Resolve().ToString(), Is.EqualTo("")); constraint = Has.Attribute(typeof(TestAttribute)).With.Property("Description").EqualTo("smoke"); Assert.That(constraint.Resolve().ToString(), - Is.EqualTo(">>")); + Is.EqualTo(">>")); } [Test] @@ -34,7 +34,7 @@ public void DisplayPrefixConstraints_Unresolved() Assert.That(Is.Not.All.EqualTo(5).ToString(), Is.EqualTo(">")); Assert.That(Has.Property("X").EqualTo(5).ToString(), Is.EqualTo(">")); Assert.That(Has.Attribute(typeof(TestAttribute)).With.Property("Description").EqualTo("smoke").ToString(), - Is.EqualTo(">")); + Is.EqualTo(">")); } [Test] diff --git a/src/NUnitFramework/tests/Constraints/ToleranceTests.cs b/src/NUnitFramework/tests/Constraints/ToleranceTests.cs index 46ff690f75..4a3595e1ab 100644 --- a/src/NUnitFramework/tests/Constraints/ToleranceTests.cs +++ b/src/NUnitFramework/tests/Constraints/ToleranceTests.cs @@ -139,8 +139,6 @@ public void ToStringTests() Assert.That(Is.EqualTo(5).Within(2).Tolerance.ToString(), Is.EqualTo("2")); Assert.That(Is.EqualTo(5).Within(2).Ulps.Tolerance.ToString(), Is.EqualTo("2 Ulps")); Assert.That(Is.EqualTo(5).Within(2).Percent.Tolerance.ToString(), Is.EqualTo("2 Percent")); - Assert.That(Is.EqualTo(5).Within(2).Seconds.Tolerance.ToString(), Is.EqualTo("00:00:02")); - Assert.That(Is.EqualTo(5).Within(2).Minutes.Tolerance.ToString(), Is.EqualTo("00:02:00")); }); } } diff --git a/src/NUnitFramework/tests/Syntax/EqualityTests.cs b/src/NUnitFramework/tests/Syntax/EqualityTests.cs index 6cd100ae8b..f7621c86c2 100644 --- a/src/NUnitFramework/tests/Syntax/EqualityTests.cs +++ b/src/NUnitFramework/tests/Syntax/EqualityTests.cs @@ -20,7 +20,7 @@ public class EqualToTest_IgnoreCase : SyntaxTest [SetUp] public void SetUp() { - ParseTree = @""; + ParseTree = @""; StaticSyntax = Is.EqualTo("X").IgnoreCase; BuilderSyntax = Builder().EqualTo("X").IgnoreCase; } diff --git a/src/NUnitFramework/tests/Syntax/ThrowsTests.cs b/src/NUnitFramework/tests/Syntax/ThrowsTests.cs index 6a1c26ab9e..dae66bbaee 100644 --- a/src/NUnitFramework/tests/Syntax/ThrowsTests.cs +++ b/src/NUnitFramework/tests/Syntax/ThrowsTests.cs @@ -23,7 +23,7 @@ public void ThrowsExceptionWithConstraint() { IResolveConstraint expr = Throws.Exception.With.Property("ParamName").EqualTo("myParam"); Assert.That( - expr.Resolve().ToString(), Is.EqualTo(@">>")); + expr.Resolve().ToString(), Is.EqualTo(@">>")); } [Test] @@ -47,7 +47,7 @@ public void ThrowsTypeOfAndConstraint() { IResolveConstraint expr = Throws.TypeOf(typeof(ArgumentException)).And.Property("ParamName").EqualTo("myParam"); Assert.That( - expr.Resolve().ToString(), Is.EqualTo(@" >>>")); + expr.Resolve().ToString(), Is.EqualTo(@" >>>")); } [Test] @@ -55,7 +55,7 @@ public void ThrowsExceptionTypeOfAndConstraint() { IResolveConstraint expr = Throws.Exception.TypeOf(typeof(ArgumentException)).And.Property("ParamName").EqualTo("myParam"); Assert.That( - expr.Resolve().ToString(), Is.EqualTo(@" >>>")); + expr.Resolve().ToString(), Is.EqualTo(@" >>>")); } [Test] @@ -63,7 +63,7 @@ public void ThrowsTypeOfWithConstraint() { IResolveConstraint expr = Throws.TypeOf(typeof(ArgumentException)).With.Property("ParamName").EqualTo("myParam"); Assert.That( - expr.Resolve().ToString(), Is.EqualTo(@" >>>")); + expr.Resolve().ToString(), Is.EqualTo(@" >>>")); } [Test]