Skip to content

Commit

Permalink
Added an EqualStringConstraint
Browse files Browse the repository at this point in the history
  • Loading branch information
manfred-brands committed Sep 29, 2024
1 parent 3169ea1 commit 17c52f6
Show file tree
Hide file tree
Showing 12 changed files with 313 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,14 @@ public EqualConstraint EqualTo(object? expected)
return Append(new EqualConstraint(expected));
}

/// <summary>
/// Returns a constraint that tests two strings for equality
/// </summary>
public EqualStringConstraint EqualTo(string? expected)
{
return Append(new EqualStringConstraint(expected));
}

#endregion

#region SameAs
Expand Down
15 changes: 15 additions & 0 deletions src/NUnitFramework/framework/Constraints/EqualConstraintResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,21 @@ public EqualConstraintResult(EqualConstraint constraint, object? actual, bool ha
_failurePoints = constraint.HasFailurePoints ? constraint.FailurePoints : Array.Empty<NUnitEqualityComparer.FailurePoint>();
}

/// <summary>
/// Construct an EqualConstraintResult
/// </summary>
public EqualConstraintResult(EqualStringConstraint 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<NUnitEqualityComparer.FailurePoint>();
}

/// <summary>
/// Write a failure message. Overridden to provide custom
/// failure messages for EqualConstraint.
Expand Down
258 changes: 258 additions & 0 deletions src/NUnitFramework/framework/Constraints/EqualStringConstraint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
// 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;
using NUnit.Framework.Constraints.Comparers;

namespace NUnit.Framework.Constraints
{
/// <summary>
/// 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.
/// </summary>
public class EqualStringConstraint : Constraint
{
#region Static and Instance Fields

private readonly string? _expected;

private Func<string, string, bool> _comparer;
private Func<object, object, bool>? _nonTypedComparer;

private bool _caseInsensitive;
private bool _ignoringWhiteSpace;
private bool _clipStrings;

#endregion

#region Constructor

/// <summary>
/// Initializes a new instance of the <see cref="EqualConstraint"/> class.
/// </summary>
/// <param name="expected">The expected value.</param>
public EqualStringConstraint(string? expected)
: base(expected)
{
_expected = expected;
_clipStrings = true;

_comparer = (x, y) => StringsComparer.Equals(x, y, _caseInsensitive, _ignoringWhiteSpace);
}

#endregion

/// <summary>
/// Gets the expected value.
/// </summary>
public string? Expected => _expected;

#region Constraint Modifiers

/// <summary>
/// Flag the constraint to ignore case and return self.
/// </summary>
public EqualStringConstraint IgnoreCase
{
get
{
_caseInsensitive = true;
return this;
}
}

/// <summary>
/// Flag the constraint to ignore white space and return self.
/// </summary>
public EqualStringConstraint IgnoreWhiteSpace
{
get
{
_ignoringWhiteSpace = true;
return this;
}
}

/// <summary>
/// Flag the constraint to suppress string clipping
/// and return self.
/// </summary>
public EqualStringConstraint NoClip
{
get
{
_clipStrings = false;
return this;
}
}

/// <summary>
/// Flag the constraint to use the supplied IEqualityComparer object.
/// </summary>
/// <param name="comparer">The IComparer object to use.</param>
/// <returns>Self.</returns>
public EqualStringConstraint Using(IEqualityComparer<string> comparer)
{
_comparer = (x, y) => comparer.Equals(x, y);
return this;
}

/// <summary>
/// Flag the constraint to use the supplied IEqualityComparer object.
/// </summary>
/// <param name="comparer">The IComparer object to use.</param>
/// <returns>Self.</returns>
public EqualStringConstraint Using(Func<string, string, bool> comparer)
{
_comparer = comparer;
return this;
}

/// <summary>
/// Flag the constraint to use the supplied IComparer object.
/// </summary>
/// <param name="comparer">The IComparer object to use.</param>
/// <returns>Self.</returns>
public EqualStringConstraint Using(IComparer<string> comparer)
{
_comparer = (x, y) => comparer.Compare(x, y) == 0;
return this;
}

/// <summary>
/// Flag the constraint to use the supplied Comparison object.
/// </summary>
/// <param name="comparer">The IComparer object to use.</param>
/// <returns>Self.</returns>
public EqualStringConstraint Using(Comparison<string> comparer)
{
_comparer = (x, y) => comparer.Invoke(x, y) == 0;
return this;
}

/// <summary>
/// Flag the constraint to use the supplied IEqualityComparer object.
/// </summary>
/// <param name="comparer">The IComparer object to use.</param>
/// <returns>Self.</returns>
public EqualStringConstraint Using(IEqualityComparer comparer)
{
_nonTypedComparer = (x, y) => comparer.Equals(x, y);
return this;
}

/// <summary>
/// Flag the constraint to use the supplied IEqualityComparer object.
/// </summary>
/// <param name="comparer">The IComparer object to use.</param>
/// <returns>Self.</returns>
public EqualStringConstraint Using(IComparer comparer)
{
_nonTypedComparer = (x, y) => comparer.Compare(x, y) == 0;
return this;
}

/// <summary>
/// Flag the constraint to use the supplied IComparer object.
/// </summary>
/// <param name="comparer">The IComparer object to use.</param>
/// <returns>Self.</returns>
public EqualStringConstraint Using<TOther>(IComparer<TOther> comparer)
{
_nonTypedComparer = (x, y) => comparer.Compare((TOther)x, (TOther)y) == 0;
return this;
}

#endregion

#region Public Methods

/// <summary>
/// Test whether the constraint is satisfied by a given value
/// </summary>
/// <param name="actual">The value to be tested</param>
/// <returns>True for success, false for failure</returns>
public ConstraintResult ApplyTo(string? 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(_expected, actual);
}
else
{
hasSucceeded = _comparer.Invoke(_expected, actual);
}

return ConstraintResult(actual, hasSucceeded);
}

/// <inheritdoc/>
/// <remarks>
/// I wish we could hide this method, but it is public in the base class.
/// </remarks>
public sealed override ConstraintResult ApplyTo<TActual>(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(_expected, actual);
}
else
{
return ApplyTo(actual as string);
}

return ConstraintResult(actual, hasSucceeded);
}

private ConstraintResult ConstraintResult<T>(T actual, bool hasSucceeded)
{
return new EqualConstraintResult(this, actual, _caseInsensitive, _ignoringWhiteSpace, _clipStrings, hasSucceeded);
}

/// <summary>
/// The Description of what this constraint tests, for
/// use in messages and in the ConstraintResult.
/// </summary>
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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ protected PrefixConstraint(IResolveConstraint baseConstraint, string description
internal static string FormatDescription(string descriptionPrefix, IConstraint baseConstraint)
{
return string.Format(
baseConstraint is EqualConstraint ? "{0} equal to {1}" : "{0} {1}",
baseConstraint is EqualConstraint or EqualStringConstraint ? "{0} equal to {1}" : "{0} {1}",
descriptionPrefix,
baseConstraint.Description);
}
Expand Down
8 changes: 8 additions & 0 deletions src/NUnitFramework/framework/Is.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,14 @@ public static EqualConstraint EqualTo<T>(IEnumerable<T>? expected)
return new EqualConstraint(expected);
}

/// <summary>
/// Returns a constraint that tests two strings for equality
/// </summary>
public static EqualStringConstraint EqualTo(string? expected)
{
return new EqualStringConstraint(expected);
}

#endregion

#region SameAs
Expand Down
16 changes: 9 additions & 7 deletions src/NUnitFramework/tests/Constraints/EqualConstraintTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand All @@ -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");

Expand All @@ -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");

Expand Down Expand Up @@ -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");

Expand All @@ -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");

Expand Down Expand Up @@ -804,7 +804,9 @@ public class ObjectEquality
[Test]
public void CompareObjectsWithToleranceAsserts()
{
Assert.Throws<NotSupportedException>(() => Assert.That("abc", new EqualConstraint("abcd").Within(1)));
// This now no longer compiled as EqualStringConstraint doesn't support Tolerance.
// Assert.Throws<NotSupportedException>(() => Assert.That("abc", new EqualStringConstraint("abcd").Within(1)));
Assert.Pass("EqualStringConstraint does not support Tolerance, so this test is not applicable.");
}
}

Expand Down Expand Up @@ -949,7 +951,7 @@ public void UsesProvidedLambda_IntArgs()
[Test, SetCulture("en-US")]
public void UsesProvidedLambda_StringArgs()
{
Assert.That("hello", Is.EqualTo("HELLO").Using<string>((x, y) => string.Compare(x, y, StringComparison.CurrentCultureIgnoreCase)));
Assert.That("hello", Is.EqualTo("HELLO").Using((x, y) => string.Compare(x, y, StringComparison.CurrentCultureIgnoreCase)));
}

[Test]
Expand Down
Loading

0 comments on commit 17c52f6

Please sign in to comment.