Skip to content

Commit

Permalink
Add support for .IgnoreWhiteSpace modifier
Browse files Browse the repository at this point in the history
to:
- AnyOfConstraint
- CollectionItemsEqualConstaint
- ContainsConstraint
- EqualConstraint
- UniqueItemsConstraint
  • Loading branch information
manfred-brands committed Mar 16, 2024
1 parent 28827fe commit cc452f8
Show file tree
Hide file tree
Showing 24 changed files with 358 additions and 68 deletions.
1 change: 1 addition & 0 deletions nuget/framework/nunit.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<dependencies>
<group targetFramework="net462">
<dependency id="System.Threading.Tasks.Extensions" version="4.5.4" exclude="Build,Analyzers" />
<dependency id="System.ValueTuple" version="4.5.0" exclude="Build,Analyzers" />
</group>
<group targetFramework="net6.0" />
</dependencies>
Expand Down
12 changes: 12 additions & 0 deletions src/NUnitFramework/framework/Constraints/AnyOfConstraint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,18 @@ public AnyOfConstraint IgnoreCase
}
}

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

/// <summary>
/// Flag the constraint to use the supplied IComparer object.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ protected CollectionItemsEqualConstraint(object? arg) : base(arg)
/// </summary>
protected bool IgnoringCase => _comparer.IgnoreCase;

/// <summary>
/// Get a flag indicating whether the user requested us to ignore white space.
/// </summary>
protected bool IgnoringWhiteSpace => _comparer.IgnoreWhiteSpace;

/// <summary>
/// Get a flag indicating whether any external comparers are in use.
/// </summary>
Expand All @@ -61,6 +66,18 @@ public CollectionItemsEqualConstraint IgnoreCase
}
}

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

/// <summary>
/// Flag the constraint to use the supplied IComparer object.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,22 @@ public static EqualMethodResult Equal(object x, object y, ref Tolerance toleranc
if (tolerance.HasVariance)
return EqualMethodResult.ToleranceNotSupported;

var stringComparison = equalityComparer.IgnoreCase ? StringComparison.CurrentCultureIgnoreCase : StringComparison.Ordinal;
return xString.Equals(yString, stringComparison) ?
EqualMethodResult.ComparedEqual : EqualMethodResult.ComparedNotEqual;
return Equals(xString, yString, equalityComparer.IgnoreCase, equalityComparer.IgnoreWhiteSpace) ?
EqualMethodResult.ComparedEqual :
EqualMethodResult.ComparedNotEqual;
}

public static bool Equals(string x, string y, bool ignoreCase, bool ignoreWhiteSpace)
{
if (ignoreWhiteSpace)
{
(int mismatchExpected, int mismatchActual) = MsgUtils.FindMismatchPosition(x, y, ignoreCase, true);
return mismatchExpected == -1 && mismatchActual == -1;
}
else
{
return x.Equals(y, ignoreCase ? StringComparison.CurrentCultureIgnoreCase : StringComparison.Ordinal);
}
}
}
}
17 changes: 17 additions & 0 deletions src/NUnitFramework/framework/Constraints/ContainsConstraint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class ContainsConstraint : Constraint
private readonly object? _expected;
private Constraint? _realConstraint;
private bool _ignoreCase;
private bool _ignoreWhiteSpace;

/// <summary>
/// Initializes a new instance of the <see cref="ContainsConstraint"/> class.
Expand Down Expand Up @@ -61,6 +62,18 @@ public ContainsConstraint IgnoreCase
}
}

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

/// <summary>
/// Test whether the constraint is satisfied by a given value
/// </summary>
Expand All @@ -78,13 +91,17 @@ public override ConstraintResult ApplyTo<TActual>(TActual actual)
StringConstraint constraint = new SubstringConstraint(substring);
if (_ignoreCase)
constraint = constraint.IgnoreCase;
if (_ignoreWhiteSpace)
throw new InvalidOperationException("IgnoreWhiteSpace not supported on SubStringConstraint");
_realConstraint = constraint;
}
else
{
var itemConstraint = new EqualConstraint(_expected);
if (_ignoreCase)
itemConstraint = itemConstraint.IgnoreCase;
if (_ignoreWhiteSpace)
itemConstraint = itemConstraint.IgnoreWhiteSpace;
_realConstraint = new SomeItemsConstraint(itemConstraint);
}

Expand Down
23 changes: 23 additions & 0 deletions src/NUnitFramework/framework/Constraints/EqualConstraint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ public EqualConstraint(object? expected)
/// </value>
public bool CaseInsensitive => _comparer.IgnoreCase;

/// <summary>
/// Gets a value indicating whether to compare ignoring white space.
/// </summary>
/// <value>
/// <see langword="true"/> if comparing ignoreing white space; otherwise, <see langword="false"/>.
/// </value>
public bool IgnoringWhiteSpace => _comparer.IgnoreWhiteSpace;

/// <summary>
/// Gets a value indicating whether or not to clip strings.
/// </summary>
Expand Down Expand Up @@ -96,6 +104,18 @@ public EqualConstraint IgnoreCase
}
}

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

/// <summary>
/// Flag the constraint to suppress string clipping
/// and return self.
Expand Down Expand Up @@ -397,6 +417,9 @@ public override string Description
if (_comparer.IgnoreCase)
sb.Append(", ignoring case");

if (_comparer.IgnoreWhiteSpace)
sb.Append(", ignoring white-space");

return sb.ToString();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class EqualConstraintResult : ConstraintResult
private readonly object? _expectedValue;
private readonly Tolerance _tolerance;
private readonly bool _caseInsensitive;
private readonly bool _ignoringWhiteSpace;
private readonly bool _clipStrings;
private readonly IList<NUnitEqualityComparer.FailurePoint> _failurePoints;

Expand Down Expand Up @@ -49,6 +50,7 @@ public EqualConstraintResult(EqualConstraint constraint, object? actual, bool ha
_expectedValue = constraint.Arguments[0];
_tolerance = constraint.Tolerance;
_caseInsensitive = constraint.CaseInsensitive;
_ignoringWhiteSpace = constraint.IgnoringWhiteSpace;
_clipStrings = constraint.ClipStrings;
_failurePoints = constraint.FailurePoints;
}
Expand Down Expand Up @@ -82,14 +84,14 @@ private void DisplayDifferences(MessageWriter writer, object? expected, object?
#region DisplayStringDifferences
private void DisplayStringDifferences(MessageWriter writer, string expected, string actual)
{
int mismatch = MsgUtils.FindMismatchPosition(expected, actual, 0, _caseInsensitive);
(int mismatchExpected, int mismatchActual) = MsgUtils.FindMismatchPosition(expected, actual, _caseInsensitive, _ignoringWhiteSpace);

if (expected.Length == actual.Length)
writer.WriteMessageLine(StringsDiffer_1, expected.Length, mismatch);
writer.WriteMessageLine(StringsDiffer_1, expected.Length, mismatchExpected);
else
writer.WriteMessageLine(StringsDiffer_2, expected.Length, actual.Length, mismatch);
writer.WriteMessageLine(StringsDiffer_2, expected.Length, actual.Length, mismatchExpected);

writer.DisplayStringDifferences(expected, actual, mismatch, _caseInsensitive, _clipStrings);
writer.DisplayStringDifferences(expected, actual, mismatchExpected, mismatchActual, _caseInsensitive, _ignoringWhiteSpace, _clipStrings);
}
#endregion

Expand Down Expand Up @@ -162,12 +164,12 @@ private void DisplayCollectionDifferenceWithFailurePoint(MessageWriter writer, I
{
if (failurePoint.ExpectedValue is string expectedString && failurePoint.ActualValue is string actualString)
{
int mismatch = MsgUtils.FindMismatchPosition(expectedString, actualString, 0, _caseInsensitive);
(int mismatchExpected, int _) = MsgUtils.FindMismatchPosition(expectedString, actualString, _caseInsensitive, _ignoringWhiteSpace);

if (expectedString.Length == actualString.Length)
writer.WriteMessageLine(StringsDiffer_1, expectedString.Length, mismatch);
writer.WriteMessageLine(StringsDiffer_1, expectedString.Length, mismatchExpected);
else
writer.WriteMessageLine(StringsDiffer_2, expectedString.Length, actualString.Length, mismatch);
writer.WriteMessageLine(StringsDiffer_2, expectedString.Length, actualString.Length, mismatchExpected);
writer.WriteLine($" Expected: {MsgUtils.FormatCollection(expected)}");
writer.WriteLine($" But was: {MsgUtils.FormatCollection(actual)}");
writer.WriteLine($" First non-matching item at index [{failurePoint.Position}]: \"{failurePoint.ExpectedValue}\"");
Expand Down
6 changes: 4 additions & 2 deletions src/NUnitFramework/framework/Constraints/MessageWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,12 @@ public void WriteMessageLine(string message, params object?[]? args)
/// </summary>
/// <param name="expected">The expected string value</param>
/// <param name="actual">The actual string value</param>
/// <param name="mismatch">The point at which the strings don't match or -1</param>
/// <param name="mismatchExpected">The point in <paramref name="expected"/> at which the strings don't match or -1</param>
/// <param name="mismatchActual">The point in <paramref name="actual"/> at which the strings don't match or -1</param>
/// <param name="ignoreCase">If true, case is ignored in locating the point where the strings differ</param>
/// <param name="ignoreWhiteSpace">If true, white space is ignored in locating the point where the strings differ</param>
/// <param name="clipping">If true, the strings should be clipped to fit the line</param>
public abstract void DisplayStringDifferences(string expected, string actual, int mismatch, bool ignoreCase, bool clipping);
public abstract void DisplayStringDifferences(string expected, string actual, int mismatchExpected, int mismatchActual, bool ignoreCase, bool ignoreWhiteSpace, bool clipping);

/// <summary>
/// Writes the text for an actual value.
Expand Down
60 changes: 41 additions & 19 deletions src/NUnitFramework/framework/Constraints/MsgUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -575,8 +575,9 @@ public static string ClipString(string s, int maxStringLength, int clipStart)
/// <param name="expected"></param>
/// <param name="actual"></param>
/// <param name="maxDisplayLength"></param>
/// <param name="mismatch"></param>
public static void ClipExpectedAndActual(ref string expected, ref string actual, int maxDisplayLength, int mismatch)
/// <param name="mismatchExpected"></param>
/// <param name="mismatchActual"></param>
public static void ClipExpectedAndActual(ref string expected, ref string actual, int maxDisplayLength, int mismatchExpected, int mismatchActual)
{
// Case 1: Both strings fit on line
int maxStringLength = Math.Max(expected.Length, actual.Length);
Expand All @@ -585,14 +586,17 @@ public static void ClipExpectedAndActual(ref string expected, ref string actual,

// Case 2: Assume that the tail of each string fits on line
int clipLength = maxDisplayLength - ELLIPSIS.Length;
int clipStart = maxStringLength - clipLength;

// Case 3: If it doesn't, center the mismatch position
if (clipStart > mismatch)
clipStart = Math.Max(0, mismatch - clipLength / 2);

expected = ClipString(expected, maxDisplayLength, clipStart);
actual = ClipString(actual, maxDisplayLength, clipStart);
int clipStartExpected = Math.Max(0, expected.Length - clipLength);
if (clipStartExpected > mismatchExpected)
clipStartExpected = Math.Max(0, mismatchExpected - clipLength / 2);
expected = ClipString(expected, maxDisplayLength, clipStartExpected);

int clipStartActual = Math.Max(0, actual.Length - clipLength);
if (clipStartActual > mismatchActual)
clipStartActual = Math.Max(0, mismatchActual - clipLength / 2);
actual = ClipString(actual, maxDisplayLength, clipStartActual);
}

/// <summary>
Expand All @@ -601,34 +605,52 @@ public static void ClipExpectedAndActual(ref string expected, ref string actual,
/// </summary>
/// <param name="expected">The expected string</param>
/// <param name="actual">The actual string</param>
/// <param name="istart">The index in the strings at which comparison should start</param>
/// <param name="ignoreCase">Boolean indicating whether case should be ignored</param>
/// <returns>-1 if no mismatch found, or the index where mismatch found</returns>
public static int FindMismatchPosition(string expected, string actual, int istart, bool ignoreCase)
/// <param name="ignoreWhiteSpace">Boolean indicating whether white space should be ignored</param>
/// <returns>(-1,-1) if no mismatch found, or the indices (expected, actual) where mismatches found.</returns>
public static (int, int) FindMismatchPosition(string expected, string actual, bool ignoreCase, bool ignoreWhiteSpace)
{
int length = Math.Min(expected.Length, actual.Length);

string s1 = ignoreCase ? expected.ToLower() : expected;
string s2 = ignoreCase ? actual.ToLower() : actual;
int i1 = 0;
int i2 = 0;

for (int i = istart; i < length; i++)
while (true)
{
if (s1[i] != s2[i])
return i;
if (ignoreWhiteSpace)
{
// Find next non-white space character in both s1 and s2.
for (; i1 < s1.Length && char.IsWhiteSpace(s1[i1]); i1++)
;
for (; i2 < s2.Length && char.IsWhiteSpace(s2[i2]); i2++)
;
}

if (i1 < s1.Length && i2 < s2.Length)
{
if (s1[i1] != s2[i2])
return (i1, i2);
i1++;
i2++;
}
else
{
break;
}
}

//
// Strings have same content up to the length of the shorter string.
// Mismatch occurs because string lengths are different, so show
// that they start differing where the shortest string ends
//
if (expected.Length != actual.Length)
return length;
if (i1 < s1.Length || i2 < s2.Length)
return (i1, i2);

//
// Same strings : We shouldn't get here
//
return -1;
return (-1, -1);
}
}
}
15 changes: 15 additions & 0 deletions src/NUnitFramework/framework/Constraints/NUnitEqualityComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ public sealed class NUnitEqualityComparer
/// </summary>
private bool _caseInsensitive;

/// <summary>
/// If true, all string comparisons will ignore white space differences
/// </summary>
private bool _ignoreWhiteSpace;

/// <summary>
/// If true, arrays will be treated as collections, allowing
/// those of different dimensions to be compared
Expand Down Expand Up @@ -101,6 +106,16 @@ public bool IgnoreCase
set => _caseInsensitive = value;
}

/// <summary>
/// Gets and sets a flag indicating whether white space should
/// be ignored in determining equality.
/// </summary>
public bool IgnoreWhiteSpace
{
get => _ignoreWhiteSpace;
set => _ignoreWhiteSpace = value;
}

/// <summary>
/// Gets and sets a flag indicating whether an instance properties
/// should be compared when determining equality.
Expand Down
Loading

0 comments on commit cc452f8

Please sign in to comment.