diff --git a/nuget/framework/nunit.nuspec b/nuget/framework/nunit.nuspec
index ef8aae14bf..eabb7639a3 100644
--- a/nuget/framework/nunit.nuspec
+++ b/nuget/framework/nunit.nuspec
@@ -33,6 +33,7 @@
+
diff --git a/src/NUnitFramework/framework/Constraints/AnyOfConstraint.cs b/src/NUnitFramework/framework/Constraints/AnyOfConstraint.cs
index bed68c18fb..a870da6466 100644
--- a/src/NUnitFramework/framework/Constraints/AnyOfConstraint.cs
+++ b/src/NUnitFramework/framework/Constraints/AnyOfConstraint.cs
@@ -70,6 +70,18 @@ public AnyOfConstraint IgnoreCase
}
}
+ ///
+ /// Flag the constraint to ignore white space and return self.
+ ///
+ public AnyOfConstraint IgnoreWhiteSpace
+ {
+ get
+ {
+ _comparer.IgnoreWhiteSpace = true;
+ return this;
+ }
+ }
+
///
/// Flag the constraint to use the supplied IComparer object.
///
diff --git a/src/NUnitFramework/framework/Constraints/CollectionItemsEqualConstraint.cs b/src/NUnitFramework/framework/Constraints/CollectionItemsEqualConstraint.cs
index e28afb29be..2c5987289a 100644
--- a/src/NUnitFramework/framework/Constraints/CollectionItemsEqualConstraint.cs
+++ b/src/NUnitFramework/framework/Constraints/CollectionItemsEqualConstraint.cs
@@ -40,6 +40,11 @@ protected CollectionItemsEqualConstraint(object? arg) : base(arg)
///
protected bool IgnoringCase => _comparer.IgnoreCase;
+ ///
+ /// Get a flag indicating whether the user requested us to ignore white space.
+ ///
+ protected bool IgnoringWhiteSpace => _comparer.IgnoreWhiteSpace;
+
///
/// Get a flag indicating whether any external comparers are in use.
///
@@ -61,6 +66,18 @@ public CollectionItemsEqualConstraint IgnoreCase
}
}
+ ///
+ /// Flag the constraint to ignore white space and return self.
+ ///
+ public CollectionItemsEqualConstraint IgnoreWhiteSpace
+ {
+ get
+ {
+ _comparer.IgnoreWhiteSpace = true;
+ return this;
+ }
+ }
+
///
/// Flag the constraint to use the supplied IComparer object.
///
diff --git a/src/NUnitFramework/framework/Constraints/Comparers/StringsComparer.cs b/src/NUnitFramework/framework/Constraints/Comparers/StringsComparer.cs
index cdd517b298..482db57e27 100644
--- a/src/NUnitFramework/framework/Constraints/Comparers/StringsComparer.cs
+++ b/src/NUnitFramework/framework/Constraints/Comparers/StringsComparer.cs
@@ -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);
+ }
}
}
}
diff --git a/src/NUnitFramework/framework/Constraints/ConstraintExpression.cs b/src/NUnitFramework/framework/Constraints/ConstraintExpression.cs
index 0608027142..634693e944 100644
--- a/src/NUnitFramework/framework/Constraints/ConstraintExpression.cs
+++ b/src/NUnitFramework/framework/Constraints/ConstraintExpression.cs
@@ -104,6 +104,23 @@ public Constraint Append(Constraint constraint)
return constraint;
}
+ ///
+ /// Appends a constraint to the expression and returns that
+ /// constraint, which is associated with the current state
+ /// of the expression being built. Note that the constraint
+ /// is not reduced at this time. For example, if there
+ /// is a NotOperator on the stack we don't reduce and
+ /// return a NotConstraint. The original constraint must
+ /// be returned because it may support modifiers that
+ /// are yet to be applied.
+ ///
+ public T Append(T constraint)
+ where T : Constraint
+ {
+ builder.Append(constraint);
+ return constraint;
+ }
+
#endregion
#region Not
@@ -300,7 +317,7 @@ public Constraint Matches(Predicate predicate)
///
/// Returns a constraint that tests for null
///
- public NullConstraint Null => (NullConstraint)Append(new NullConstraint());
+ public NullConstraint Null => Append(new NullConstraint());
#endregion
@@ -309,7 +326,7 @@ public Constraint Matches(Predicate predicate)
///
/// Returns a constraint that tests for default value
///
- public DefaultConstraint Default => (DefaultConstraint)Append(new DefaultConstraint());
+ public DefaultConstraint Default => Append(new DefaultConstraint());
#endregion
@@ -318,7 +335,7 @@ public Constraint Matches(Predicate predicate)
///
/// Returns a constraint that tests for True
///
- public TrueConstraint True => (TrueConstraint)Append(new TrueConstraint());
+ public TrueConstraint True => Append(new TrueConstraint());
#endregion
@@ -327,7 +344,7 @@ public Constraint Matches(Predicate predicate)
///
/// Returns a constraint that tests for False
///
- public FalseConstraint False => (FalseConstraint)Append(new FalseConstraint());
+ public FalseConstraint False => Append(new FalseConstraint());
#endregion
@@ -336,7 +353,7 @@ public Constraint Matches(Predicate predicate)
///
/// Returns a constraint that tests for a positive value
///
- public GreaterThanConstraint Positive => (GreaterThanConstraint)Append(new GreaterThanConstraint(0));
+ public GreaterThanConstraint Positive => Append(new GreaterThanConstraint(0));
#endregion
@@ -345,7 +362,7 @@ public Constraint Matches(Predicate predicate)
///
/// Returns a constraint that tests for a negative value
///
- public LessThanConstraint Negative => (LessThanConstraint)Append(new LessThanConstraint(0));
+ public LessThanConstraint Negative => Append(new LessThanConstraint(0));
#endregion
@@ -354,7 +371,7 @@ public Constraint Matches(Predicate predicate)
///
/// Returns a constraint that tests if item is equal to zero
///
- public EqualConstraint Zero => (EqualConstraint)Append(new EqualConstraint(0));
+ public EqualConstraint Zero => Append(new EqualConstraint(0));
#endregion
@@ -363,7 +380,7 @@ public Constraint Matches(Predicate predicate)
///
/// Returns a constraint that tests for NaN
///
- public NaNConstraint NaN => (NaNConstraint)Append(new NaNConstraint());
+ public NaNConstraint NaN => Append(new NaNConstraint());
#endregion
@@ -372,7 +389,16 @@ public Constraint Matches(Predicate predicate)
///
/// Returns a constraint that tests for empty
///
- public EmptyConstraint Empty => (EmptyConstraint)Append(new EmptyConstraint());
+ public EmptyConstraint Empty => Append(new EmptyConstraint());
+
+ #endregion
+
+ #region WhiteSpace
+
+ ///
+ /// Returns a constraint that tests for white-space
+ ///
+ public WhiteSpaceConstraint WhiteSpace => Append(new WhiteSpaceConstraint());
#endregion
@@ -382,14 +408,14 @@ public Constraint Matches(Predicate predicate)
/// Returns a constraint that tests whether a collection
/// contains all unique items.
///
- public UniqueItemsConstraint Unique => (UniqueItemsConstraint)Append(new UniqueItemsConstraint());
+ public UniqueItemsConstraint Unique => Append(new UniqueItemsConstraint());
#endregion
///
/// Returns a constraint that tests whether an object graph is serializable in XML format.
///
- public XmlSerializableConstraint XmlSerializable => (XmlSerializableConstraint)Append(new XmlSerializableConstraint());
+ public XmlSerializableConstraint XmlSerializable => Append(new XmlSerializableConstraint());
#region EqualTo
@@ -398,7 +424,7 @@ public Constraint Matches(Predicate predicate)
///
public EqualConstraint EqualTo(object? expected)
{
- return (EqualConstraint)Append(new EqualConstraint(expected));
+ return Append(new EqualConstraint(expected));
}
#endregion
@@ -410,7 +436,7 @@ public EqualConstraint EqualTo(object? expected)
///
public SameAsConstraint SameAs(object? expected)
{
- return (SameAsConstraint)Append(new SameAsConstraint(expected));
+ return Append(new SameAsConstraint(expected));
}
#endregion
@@ -423,7 +449,7 @@ public SameAsConstraint SameAs(object? expected)
///
public GreaterThanConstraint GreaterThan(object expected)
{
- return (GreaterThanConstraint)Append(new GreaterThanConstraint(expected));
+ return Append(new GreaterThanConstraint(expected));
}
#endregion
@@ -436,7 +462,7 @@ public GreaterThanConstraint GreaterThan(object expected)
///
public GreaterThanOrEqualConstraint GreaterThanOrEqualTo(object expected)
{
- return (GreaterThanOrEqualConstraint)Append(new GreaterThanOrEqualConstraint(expected));
+ return Append(new GreaterThanOrEqualConstraint(expected));
}
///
@@ -445,7 +471,7 @@ public GreaterThanOrEqualConstraint GreaterThanOrEqualTo(object expected)
///
public GreaterThanOrEqualConstraint AtLeast(object expected)
{
- return (GreaterThanOrEqualConstraint)Append(new GreaterThanOrEqualConstraint(expected));
+ return Append(new GreaterThanOrEqualConstraint(expected));
}
#endregion
@@ -458,7 +484,7 @@ public GreaterThanOrEqualConstraint AtLeast(object expected)
///
public LessThanConstraint LessThan(object expected)
{
- return (LessThanConstraint)Append(new LessThanConstraint(expected));
+ return Append(new LessThanConstraint(expected));
}
#endregion
@@ -471,7 +497,7 @@ public LessThanConstraint LessThan(object expected)
///
public LessThanOrEqualConstraint LessThanOrEqualTo(object expected)
{
- return (LessThanOrEqualConstraint)Append(new LessThanOrEqualConstraint(expected));
+ return Append(new LessThanOrEqualConstraint(expected));
}
///
@@ -480,7 +506,7 @@ public LessThanOrEqualConstraint LessThanOrEqualTo(object expected)
///
public LessThanOrEqualConstraint AtMost(object expected)
{
- return (LessThanOrEqualConstraint)Append(new LessThanOrEqualConstraint(expected));
+ return Append(new LessThanOrEqualConstraint(expected));
}
#endregion
@@ -493,7 +519,7 @@ public LessThanOrEqualConstraint AtMost(object expected)
///
public ExactTypeConstraint TypeOf(Type expectedType)
{
- return (ExactTypeConstraint)Append(new ExactTypeConstraint(expectedType));
+ return Append(new ExactTypeConstraint(expectedType));
}
///
@@ -502,7 +528,7 @@ public ExactTypeConstraint TypeOf(Type expectedType)
///
public ExactTypeConstraint TypeOf()
{
- return (ExactTypeConstraint)Append(new ExactTypeConstraint(typeof(TExpected)));
+ return Append(new ExactTypeConstraint(typeof(TExpected)));
}
#endregion
@@ -515,7 +541,7 @@ public ExactTypeConstraint TypeOf()
///
public InstanceOfTypeConstraint InstanceOf(Type expectedType)
{
- return (InstanceOfTypeConstraint)Append(new InstanceOfTypeConstraint(expectedType));
+ return Append(new InstanceOfTypeConstraint(expectedType));
}
///
@@ -524,7 +550,7 @@ public InstanceOfTypeConstraint InstanceOf(Type expectedType)
///
public InstanceOfTypeConstraint InstanceOf()
{
- return (InstanceOfTypeConstraint)Append(new InstanceOfTypeConstraint(typeof(TExpected)));
+ return Append(new InstanceOfTypeConstraint(typeof(TExpected)));
}
#endregion
@@ -537,7 +563,7 @@ public InstanceOfTypeConstraint InstanceOf()
///
public AssignableFromConstraint AssignableFrom(Type expectedType)
{
- return (AssignableFromConstraint)Append(new AssignableFromConstraint(expectedType));
+ return Append(new AssignableFromConstraint(expectedType));
}
///
@@ -546,7 +572,7 @@ public AssignableFromConstraint AssignableFrom(Type expectedType)
///
public AssignableFromConstraint AssignableFrom()
{
- return (AssignableFromConstraint)Append(new AssignableFromConstraint(typeof(TExpected)));
+ return Append(new AssignableFromConstraint(typeof(TExpected)));
}
#endregion
@@ -559,7 +585,7 @@ public AssignableFromConstraint AssignableFrom()
///
public AssignableToConstraint AssignableTo(Type expectedType)
{
- return (AssignableToConstraint)Append(new AssignableToConstraint(expectedType));
+ return Append(new AssignableToConstraint(expectedType));
}
///
@@ -568,7 +594,7 @@ public AssignableToConstraint AssignableTo(Type expectedType)
///
public AssignableToConstraint AssignableTo()
{
- return (AssignableToConstraint)Append(new AssignableToConstraint(typeof(TExpected)));
+ return Append(new AssignableToConstraint(typeof(TExpected)));
}
#endregion
@@ -582,7 +608,7 @@ public AssignableToConstraint AssignableTo()
///
public CollectionEquivalentConstraint EquivalentTo(IEnumerable expected)
{
- return (CollectionEquivalentConstraint)Append(new CollectionEquivalentConstraint(expected));
+ return Append(new CollectionEquivalentConstraint(expected));
}
#endregion
@@ -595,7 +621,7 @@ public CollectionEquivalentConstraint EquivalentTo(IEnumerable expected)
///
public CollectionSubsetConstraint SubsetOf(IEnumerable expected)
{
- return (CollectionSubsetConstraint)Append(new CollectionSubsetConstraint(expected));
+ return Append(new CollectionSubsetConstraint(expected));
}
#endregion
@@ -608,7 +634,7 @@ public CollectionSubsetConstraint SubsetOf(IEnumerable expected)
///
public CollectionSupersetConstraint SupersetOf(IEnumerable expected)
{
- return (CollectionSupersetConstraint)Append(new CollectionSupersetConstraint(expected));
+ return Append(new CollectionSupersetConstraint(expected));
}
#endregion
@@ -618,7 +644,7 @@ public CollectionSupersetConstraint SupersetOf(IEnumerable expected)
///
/// Returns a constraint that tests whether a collection is ordered
///
- public CollectionOrderedConstraint Ordered => (CollectionOrderedConstraint)Append(new CollectionOrderedConstraint());
+ public CollectionOrderedConstraint Ordered => Append(new CollectionOrderedConstraint());
#endregion
@@ -630,7 +656,7 @@ public CollectionSupersetConstraint SupersetOf(IEnumerable expected)
///
public SomeItemsConstraint Member(object? expected)
{
- return (SomeItemsConstraint)Append(new SomeItemsConstraint(new EqualConstraint(expected)));
+ return Append(new SomeItemsConstraint(new EqualConstraint(expected)));
}
#endregion
@@ -649,7 +675,7 @@ public SomeItemsConstraint Member(object? expected)
///
public SomeItemsConstraint Contains(object? expected)
{
- return (SomeItemsConstraint)Append(new SomeItemsConstraint(new EqualConstraint(expected)));
+ return Append(new SomeItemsConstraint(new EqualConstraint(expected)));
}
///
@@ -665,7 +691,7 @@ public SomeItemsConstraint Contains(object? expected)
///
public ContainsConstraint Contains(string? expected)
{
- return (ContainsConstraint)Append(new ContainsConstraint(expected));
+ return Append(new ContainsConstraint(expected));
}
///
@@ -700,7 +726,7 @@ public ContainsConstraint Contain(string? expected)
/// The key to be matched in the Dictionary key collection
public DictionaryContainsKeyConstraint ContainKey(object expected)
{
- return (DictionaryContainsKeyConstraint)Append(new DictionaryContainsKeyConstraint(expected));
+ return Append(new DictionaryContainsKeyConstraint(expected));
}
///
@@ -710,7 +736,7 @@ public DictionaryContainsKeyConstraint ContainKey(object expected)
/// The value to be matched in the Dictionary value collection
public DictionaryContainsValueConstraint ContainValue(object expected)
{
- return (DictionaryContainsValueConstraint)Append(new DictionaryContainsValueConstraint(expected));
+ return Append(new DictionaryContainsValueConstraint(expected));
}
#endregion
@@ -722,7 +748,7 @@ public DictionaryContainsValueConstraint ContainValue(object expected)
///
public StartsWithConstraint StartWith(string expected)
{
- return (StartsWithConstraint)Append(new StartsWithConstraint(expected));
+ return Append(new StartsWithConstraint(expected));
}
///
@@ -731,7 +757,7 @@ public StartsWithConstraint StartWith(string expected)
///
public StartsWithConstraint StartsWith(string expected)
{
- return (StartsWithConstraint)Append(new StartsWithConstraint(expected));
+ return Append(new StartsWithConstraint(expected));
}
#endregion
@@ -744,7 +770,7 @@ public StartsWithConstraint StartsWith(string expected)
///
public EndsWithConstraint EndWith(string expected)
{
- return (EndsWithConstraint)Append(new EndsWithConstraint(expected));
+ return Append(new EndsWithConstraint(expected));
}
///
@@ -753,7 +779,7 @@ public EndsWithConstraint EndWith(string expected)
///
public EndsWithConstraint EndsWith(string expected)
{
- return (EndsWithConstraint)Append(new EndsWithConstraint(expected));
+ return Append(new EndsWithConstraint(expected));
}
#endregion
@@ -766,7 +792,7 @@ public EndsWithConstraint EndsWith(string expected)
///
public RegexConstraint Match([StringSyntax(StringSyntaxAttribute.Regex)] string pattern)
{
- return (RegexConstraint)Append(new RegexConstraint(pattern));
+ return Append(new RegexConstraint(pattern));
}
///
@@ -775,7 +801,7 @@ public RegexConstraint Match([StringSyntax(StringSyntaxAttribute.Regex)] string
///
public RegexConstraint Match(Regex regex)
{
- return (RegexConstraint)Append(new RegexConstraint(regex));
+ return Append(new RegexConstraint(regex));
}
///
@@ -784,7 +810,7 @@ public RegexConstraint Match(Regex regex)
///
public RegexConstraint Matches([StringSyntax(StringSyntaxAttribute.Regex)] string pattern)
{
- return (RegexConstraint)Append(new RegexConstraint(pattern));
+ return Append(new RegexConstraint(pattern));
}
///
@@ -793,7 +819,7 @@ public RegexConstraint Matches([StringSyntax(StringSyntaxAttribute.Regex)] strin
///
public RegexConstraint Matches(Regex regex)
{
- return (RegexConstraint)Append(new RegexConstraint(regex));
+ return Append(new RegexConstraint(regex));
}
#endregion
@@ -806,7 +832,7 @@ public RegexConstraint Matches(Regex regex)
///
public SamePathConstraint SamePath(string expected)
{
- return (SamePathConstraint)Append(new SamePathConstraint(expected));
+ return Append(new SamePathConstraint(expected));
}
#endregion
@@ -819,7 +845,7 @@ public SamePathConstraint SamePath(string expected)
///
public SubPathConstraint SubPathOf(string expected)
{
- return (SubPathConstraint)Append(new SubPathConstraint(expected));
+ return Append(new SubPathConstraint(expected));
}
#endregion
@@ -832,7 +858,7 @@ public SubPathConstraint SubPathOf(string expected)
///
public SamePathOrUnderConstraint SamePathOrUnder(string expected)
{
- return (SamePathOrUnderConstraint)Append(new SamePathOrUnderConstraint(expected));
+ return Append(new SamePathOrUnderConstraint(expected));
}
#endregion
@@ -846,7 +872,7 @@ public SamePathOrUnderConstraint SamePathOrUnder(string expected)
/// Inclusive end of the range.
public RangeConstraint InRange(object from, object to)
{
- return (RangeConstraint)Append(new RangeConstraint(from, to));
+ return Append(new RangeConstraint(from, to));
}
#endregion
@@ -874,7 +900,7 @@ public AnyOfConstraint AnyOf(params object?[]? expected)
expected = new object?[] { null };
}
- return (AnyOfConstraint)Append(new AnyOfConstraint(expected));
+ return Append(new AnyOfConstraint(expected));
}
///
@@ -883,7 +909,7 @@ public AnyOfConstraint AnyOf(params object?[]? expected)
/// Expected values
public AnyOfConstraint AnyOf(ICollection expected)
{
- return (AnyOfConstraint)Append(new AnyOfConstraint(expected));
+ return Append(new AnyOfConstraint(expected));
}
#endregion
diff --git a/src/NUnitFramework/framework/Constraints/ContainsConstraint.cs b/src/NUnitFramework/framework/Constraints/ContainsConstraint.cs
index 592b546dbe..9e3f714fba 100644
--- a/src/NUnitFramework/framework/Constraints/ContainsConstraint.cs
+++ b/src/NUnitFramework/framework/Constraints/ContainsConstraint.cs
@@ -17,6 +17,7 @@ public class ContainsConstraint : Constraint
private readonly object? _expected;
private Constraint? _realConstraint;
private bool _ignoreCase;
+ private bool _ignoreWhiteSpace;
///
/// Initializes a new instance of the class.
@@ -61,6 +62,18 @@ public ContainsConstraint IgnoreCase
}
}
+ ///
+ /// Flag the constraint to ignore white-space and return self.
+ ///
+ public ContainsConstraint IgnoreWhiteSpace
+ {
+ get
+ {
+ _ignoreWhiteSpace = true;
+ return this;
+ }
+ }
+
///
/// Test whether the constraint is satisfied by a given value
///
@@ -78,6 +91,8 @@ public override ConstraintResult ApplyTo(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
@@ -85,6 +100,8 @@ public override ConstraintResult ApplyTo(TActual actual)
var itemConstraint = new EqualConstraint(_expected);
if (_ignoreCase)
itemConstraint = itemConstraint.IgnoreCase;
+ if (_ignoreWhiteSpace)
+ itemConstraint = itemConstraint.IgnoreWhiteSpace;
_realConstraint = new SomeItemsConstraint(itemConstraint);
}
diff --git a/src/NUnitFramework/framework/Constraints/EmptyStringConstraint.cs b/src/NUnitFramework/framework/Constraints/EmptyStringConstraint.cs
index fddadd6cad..656f876eba 100644
--- a/src/NUnitFramework/framework/Constraints/EmptyStringConstraint.cs
+++ b/src/NUnitFramework/framework/Constraints/EmptyStringConstraint.cs
@@ -18,7 +18,7 @@ public class EmptyStringConstraint : StringConstraint
///
/// The value to be tested
/// True for success, false for failure
- protected override bool Matches(string actual)
+ protected override bool Matches(string? actual)
{
return actual == string.Empty;
}
diff --git a/src/NUnitFramework/framework/Constraints/EndsWithConstraint.cs b/src/NUnitFramework/framework/Constraints/EndsWithConstraint.cs
index c3e3c216e2..e99e826319 100644
--- a/src/NUnitFramework/framework/Constraints/EndsWithConstraint.cs
+++ b/src/NUnitFramework/framework/Constraints/EndsWithConstraint.cs
@@ -26,7 +26,7 @@ public EndsWithConstraint(string expected) : base(expected)
///
///
///
- protected override bool Matches(string actual)
+ protected override bool Matches(string? actual)
{
var stringComparison = caseInsensitive ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture;
return actual is not null && actual.EndsWith(expected, stringComparison);
diff --git a/src/NUnitFramework/framework/Constraints/EqualConstraint.cs b/src/NUnitFramework/framework/Constraints/EqualConstraint.cs
index 31eee03212..7bc9a6a07d 100644
--- a/src/NUnitFramework/framework/Constraints/EqualConstraint.cs
+++ b/src/NUnitFramework/framework/Constraints/EqualConstraint.cs
@@ -65,6 +65,14 @@ public EqualConstraint(object? expected)
///
public bool CaseInsensitive => _comparer.IgnoreCase;
+ ///
+ /// Gets a value indicating whether to compare ignoring white space.
+ ///
+ ///
+ /// if comparing ignoreing white space; otherwise, .
+ ///
+ public bool IgnoringWhiteSpace => _comparer.IgnoreWhiteSpace;
+
///
/// Gets a value indicating whether or not to clip strings.
///
@@ -96,6 +104,18 @@ public EqualConstraint IgnoreCase
}
}
+ ///
+ /// Flag the constraint to ignore white space and return self.
+ ///
+ public EqualConstraint IgnoreWhiteSpace
+ {
+ get
+ {
+ _comparer.IgnoreWhiteSpace = true;
+ return this;
+ }
+ }
+
///
/// Flag the constraint to suppress string clipping
/// and return self.
@@ -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();
}
}
diff --git a/src/NUnitFramework/framework/Constraints/EqualConstraintResult.cs b/src/NUnitFramework/framework/Constraints/EqualConstraintResult.cs
index ecdce827e7..4b45e9fa41 100644
--- a/src/NUnitFramework/framework/Constraints/EqualConstraintResult.cs
+++ b/src/NUnitFramework/framework/Constraints/EqualConstraintResult.cs
@@ -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 _failurePoints;
@@ -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;
}
@@ -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
@@ -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}\"");
diff --git a/src/NUnitFramework/framework/Constraints/MessageWriter.cs b/src/NUnitFramework/framework/Constraints/MessageWriter.cs
index 07d60a8c25..c040c8c00d 100644
--- a/src/NUnitFramework/framework/Constraints/MessageWriter.cs
+++ b/src/NUnitFramework/framework/Constraints/MessageWriter.cs
@@ -2,6 +2,7 @@
using System.IO;
using System.Collections;
+using System;
namespace NUnit.Framework.Constraints
{
@@ -81,11 +82,33 @@ public void WriteMessageLine(string message, params object?[]? args)
///
/// The expected string value
/// The actual string value
- /// The point at which the strings don't match or -1
+ /// The point in at which the strings don't match or -1
/// If true, case is ignored in locating the point where the strings differ
/// If true, the strings should be clipped to fit the line
public abstract void DisplayStringDifferences(string expected, string actual, int mismatch, bool ignoreCase, bool clipping);
+ ///
+ /// Display the expected and actual string values on separate lines.
+ /// If the mismatch parameter is >=0, an additional line is displayed
+ /// line containing a caret that points to the mismatch point.
+ ///
+ /// The expected string value
+ /// The actual string value
+ /// The point in at which the strings don't match or -1
+ /// The point in at which the strings don't match or -1
+ /// If true, case is ignored in locating the point where the strings differ
+ /// If true, white space is ignored in locating the point where the strings differ
+ /// If true, the strings should be clipped to fit the line
+ public virtual void DisplayStringDifferences(string expected, string actual, int mismatchExpected, int mismatchActual, bool ignoreCase, bool ignoreWhiteSpace, bool clipping)
+ {
+ if (ignoreWhiteSpace && mismatchExpected != mismatchActual)
+ {
+ throw new NotImplementedException("Please override to show difference with 'ignoreWhiteSpace'");
+ }
+
+ DisplayStringDifferences(expected, actual, mismatchExpected, ignoreCase, clipping);
+ }
+
///
/// Writes the text for an actual value.
///
diff --git a/src/NUnitFramework/framework/Constraints/MsgUtils.cs b/src/NUnitFramework/framework/Constraints/MsgUtils.cs
index ba72fc3930..5d1a98e4f0 100644
--- a/src/NUnitFramework/framework/Constraints/MsgUtils.cs
+++ b/src/NUnitFramework/framework/Constraints/MsgUtils.cs
@@ -395,64 +395,84 @@ public static string GetTypeRepresentation(object obj)
[return: NotNullIfNotNull("s")]
public static string? EscapeControlChars(string? s)
{
- if (s is not null)
- {
- StringBuilder sb = new StringBuilder();
+ int index = 0;
+ return EscapeControlChars(s, ref index);
+ }
- foreach (char c in s)
- {
- switch (c)
- {
- //case '\'':
- // sb.Append("\\\'");
- // break;
- //case '\"':
- // sb.Append("\\\"");
- // break;
- case '\\':
- sb.Append("\\\\");
- break;
- case '\0':
- sb.Append("\\0");
- break;
- case '\a':
- sb.Append("\\a");
- break;
- case '\b':
- sb.Append("\\b");
- break;
- case '\f':
- sb.Append("\\f");
- break;
- case '\n':
- sb.Append("\\n");
- break;
- case '\r':
- sb.Append("\\r");
- break;
- case '\t':
- sb.Append("\\t");
- break;
- case '\v':
- sb.Append("\\v");
- break;
+ ///
+ /// Converts any control characters in a string
+ /// to their escaped representation.
+ ///
+ /// The string to be converted
+ /// The index in the array of a specific spot, which needs to be updated when expanding.
+ /// The converted string
+ [return: NotNullIfNotNull("s")]
+ public static string? EscapeControlChars(string? s, ref int index)
+ {
+ if (s is null)
+ return null;
- case '\x0085':
- case '\x2028':
- case '\x2029':
- sb.Append($"\\x{(int)c:X4}");
- break;
+ int originalIndex = index;
+ const int headRoom = 42;
+ StringBuilder sb = new(s.Length + headRoom);
- default:
- sb.Append(c);
- break;
+ for (int i = 0; i < s.Length; i++)
+ {
+ char c = s[i];
+ string? escaped = EscapeControlChars(c);
+ if (escaped is null)
+ {
+ sb.Append(c);
+ }
+ else
+ {
+ sb.Append(escaped);
+ if (originalIndex > i)
+ {
+ index += escaped.Length - 1;
}
}
-
- s = sb.ToString();
}
- return s;
+ return sb.ToString();
+ }
+
+ private static string? EscapeControlChars(char c)
+ {
+ switch (c)
+ {
+ //case '\'':
+ // return "\\\'";
+ //case '\"':
+ // return ("\\\"");
+ // break;
+ case '\\':
+ return "\\\\";
+ case '\0':
+ return "\\0";
+ case '\a':
+ return "\\a";
+ case '\b':
+ return "\\b";
+ case '\f':
+ return "\\f";
+ case '\n':
+ return "\\n";
+ case '\r':
+ return "\\r";
+ case '\t':
+ return "\\t";
+ case '\v':
+ return "\\v";
+
+ case '\x0085':
+ case '\x2028':
+ case '\x2029':
+ return $"\\x{(int)c:X4}";
+
+ default:
+ return null;
+ }
}
///
@@ -466,7 +486,8 @@ public static string GetTypeRepresentation(object obj)
{
if (s is not null)
{
- StringBuilder sb = new StringBuilder();
+ const int headRoom = 42;
+ StringBuilder sb = new(s.Length + headRoom);
foreach (char c in s)
{
@@ -536,85 +557,147 @@ public static int[] GetArrayIndicesFromCollectionIndex(IEnumerable collection, l
/// string with ellipses representing the removed parts
///
/// The string to be clipped
- /// The maximum permitted length of the result string
+ /// The length of the clipped string
/// The point at which to start clipping
/// The clipped string
- public static string ClipString(string s, int maxStringLength, int clipStart)
+ public static string ClipString(string s, int clipLength, int clipStart)
{
- int clipLength = maxStringLength;
- StringBuilder sb = new StringBuilder();
+ StringBuilder sb = new StringBuilder(s.Length + 2 * ELLIPSIS.Length);
if (clipStart > 0)
- {
- clipLength -= ELLIPSIS.Length;
sb.Append(ELLIPSIS);
+
+ int remainingLength = s.Length - clipStart;
+ int count = Math.Min(remainingLength, clipLength);
+ sb.Append(s, clipStart, count);
+
+ if (remainingLength > clipLength)
+ sb.Append(ELLIPSIS);
+
+ return sb.ToString();
+ }
+
+ ///
+ /// Clips the string if it exceeds .
+ ///
+ ///
+ /// The string ensures that the content around stays visible
+ /// by either clipping from the front or the back or both. The clipped part is replaced with "...".
+ ///
+ /// The string to clip
+ /// The assumed length of the string (needed if called for a pair)
+ /// The maximum length of the display message.
+ /// The location in that needs to stay visible.
+ /// Clip string with a maximum length of .
+ public static string ClipWhenNeeded(string s, int length, int maxDisplayLength, ref int mismatchLocation)
+ {
+ if (length <= maxDisplayLength)
+ {
+ // No need to clip
+ return s;
}
- if (s.Length - clipStart > clipLength)
+ // We need to clip at least one side.
+ maxDisplayLength -= ELLIPSIS.Length;
+
+ const int minimumJoiningMatchingCharacters = 5;
+
+ int clipStart;
+
+ if (mismatchLocation + minimumJoiningMatchingCharacters < maxDisplayLength)
{
- clipLength -= ELLIPSIS.Length;
- sb.Append(s.Substring(clipStart, clipLength));
- sb.Append(ELLIPSIS);
+ // Clip the tail
+ clipStart = 0;
}
- else if (clipStart > 0)
+ else if (length - mismatchLocation + minimumJoiningMatchingCharacters < maxDisplayLength)
{
- sb.Append(s.Substring(clipStart));
+ // Show the tail
+ clipStart = length - maxDisplayLength;
}
else
{
- sb.Append(s);
+ // We need to clip both sides.
+ maxDisplayLength -= ELLIPSIS.Length;
+
+ // Centre the clip around the mismatchLocation
+ clipStart = mismatchLocation - maxDisplayLength / 2;
}
- return sb.ToString();
+ if (clipStart > 0)
+ {
+ // If clipping off the front, adjust the location
+ // and correct for the ... added to the front.
+ mismatchLocation -= clipStart - ELLIPSIS.Length;
+ }
+
+ return ClipString(s, maxDisplayLength, clipStart);
}
///
/// Clip the expected and actual strings in a coordinated fashion,
/// so that they may be displayed together.
///
- ///
- ///
- ///
- ///
- public static void ClipExpectedAndActual(ref string expected, ref string actual, int maxDisplayLength, int mismatch)
+ ///
+ /// The values of and
+ /// are assumed to be the same. If and
+ /// are not linked, then call individually.
+ ///
+ /// The expected string to clip
+ /// The actual string to clip
+ /// The maximum length of the display message.
+ /// The location in that needs to stay visible.
+ /// The location in that needs to stay visible.
+ public static void ClipExpectedAndActual(ref string expected, ref string actual, int maxDisplayLength, ref int mismatchExpected, ref int mismatchActual)
{
- // Case 1: Both strings fit on line
- int maxStringLength = Math.Max(expected.Length, actual.Length);
- if (maxStringLength <= maxDisplayLength)
- return;
-
- // Case 2: Assume that the tail of each string fits on line
- int clipLength = maxDisplayLength - ELLIPSIS.Length;
- int clipStart = maxStringLength - clipLength;
+ if (mismatchExpected != mismatchActual)
+ {
+ throw new ArgumentException($"The values for {nameof(mismatchExpected)} and {nameof(mismatchActual)} should be the same.");
+ }
- // Case 3: If it doesn't, center the mismatch position
- if (clipStart > mismatch)
- clipStart = Math.Max(0, mismatch - clipLength / 2);
+ // Clip based upon longest length
+ int longestLength = Math.Max(expected.Length, actual.Length);
+ if (longestLength <= maxDisplayLength)
+ return;
- expected = ClipString(expected, maxDisplayLength, clipStart);
- actual = ClipString(actual, maxDisplayLength, clipStart);
+ expected = ClipWhenNeeded(expected, longestLength, maxDisplayLength, ref mismatchExpected);
+ actual = ClipWhenNeeded(actual, longestLength, maxDisplayLength, ref mismatchActual);
}
///
- /// Shows the position two strings start to differ. Comparison
- /// starts at the start index.
+ /// Finds the position two strings start to differ.
///
/// The expected string
/// The actual string
- /// The index in the strings at which comparison should start
/// Boolean indicating whether case should be ignored
- /// -1 if no mismatch found, or the index where mismatch found
- public static int FindMismatchPosition(string expected, string actual, int istart, bool ignoreCase)
+ /// Boolean indicating whether white space should be ignored
+ /// (-1,-1) if no mismatch found, or the indices (expected, actual) where mismatches found.
+ 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.
+ i1 = FindNonWhiteSpace(s1, i1);
+ i2 = FindNonWhiteSpace(s2, i2);
+ }
+
+ if (i1 < s1.Length && i2 < s2.Length)
+ {
+ if (s1[i1] != s2[i2])
+ return (i1, i2);
+ i1++;
+ i2++;
+ }
+ else
+ {
+ break;
+ }
}
//
@@ -622,13 +705,21 @@ public static int FindMismatchPosition(string expected, string actual, int istar
// 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);
+ }
+
+ private static int FindNonWhiteSpace(string s, int i)
+ {
+ while (i < s.Length && char.IsWhiteSpace(s[i]))
+ i++;
+
+ return i;
}
}
}
diff --git a/src/NUnitFramework/framework/Constraints/NUnitEqualityComparer.cs b/src/NUnitFramework/framework/Constraints/NUnitEqualityComparer.cs
index 4d05f2262a..4777868a0a 100644
--- a/src/NUnitFramework/framework/Constraints/NUnitEqualityComparer.cs
+++ b/src/NUnitFramework/framework/Constraints/NUnitEqualityComparer.cs
@@ -58,6 +58,11 @@ public sealed class NUnitEqualityComparer
///
private bool _caseInsensitive;
+ ///
+ /// If true, all string comparisons will ignore white space differences
+ ///
+ private bool _ignoreWhiteSpace;
+
///
/// If true, arrays will be treated as collections, allowing
/// those of different dimensions to be compared
@@ -101,6 +106,16 @@ public bool IgnoreCase
set => _caseInsensitive = value;
}
+ ///
+ /// Gets and sets a flag indicating whether white space should
+ /// be ignored in determining equality.
+ ///
+ public bool IgnoreWhiteSpace
+ {
+ get => _ignoreWhiteSpace;
+ set => _ignoreWhiteSpace = value;
+ }
+
///
/// Gets and sets a flag indicating whether an instance properties
/// should be compared when determining equality.
diff --git a/src/NUnitFramework/framework/Constraints/SamePathConstraint.cs b/src/NUnitFramework/framework/Constraints/SamePathConstraint.cs
index 6f09d49a88..9bac6757cb 100644
--- a/src/NUnitFramework/framework/Constraints/SamePathConstraint.cs
+++ b/src/NUnitFramework/framework/Constraints/SamePathConstraint.cs
@@ -28,7 +28,7 @@ public SamePathConstraint(string expected) : base(expected)
///
/// The value to be tested
/// True for success, false for failure
- protected override bool Matches(string actual)
+ protected override bool Matches(string? actual)
{
return actual is not null && StringUtil.StringsEqual(Canonicalize(expected), Canonicalize(actual), caseInsensitive);
}
diff --git a/src/NUnitFramework/framework/Constraints/SamePathOrUnderConstraint.cs b/src/NUnitFramework/framework/Constraints/SamePathOrUnderConstraint.cs
index 98d9bc9c1b..c23c981c88 100644
--- a/src/NUnitFramework/framework/Constraints/SamePathOrUnderConstraint.cs
+++ b/src/NUnitFramework/framework/Constraints/SamePathOrUnderConstraint.cs
@@ -28,7 +28,7 @@ public SamePathOrUnderConstraint(string expected) : base(expected)
///
/// The value to be tested
/// True for success, false for failure
- protected override bool Matches(string actual)
+ protected override bool Matches(string? actual)
{
if (actual is null)
return false;
diff --git a/src/NUnitFramework/framework/Constraints/StartsWithConstraint.cs b/src/NUnitFramework/framework/Constraints/StartsWithConstraint.cs
index 3397186d3a..c40eb78a61 100644
--- a/src/NUnitFramework/framework/Constraints/StartsWithConstraint.cs
+++ b/src/NUnitFramework/framework/Constraints/StartsWithConstraint.cs
@@ -26,7 +26,7 @@ public StartsWithConstraint(string expected) : base(expected)
///
///
///
- protected override bool Matches(string actual)
+ protected override bool Matches(string? actual)
{
var stringComparison = caseInsensitive ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture;
return actual is not null && actual.StartsWith(expected, stringComparison);
diff --git a/src/NUnitFramework/framework/Constraints/StringConstraint.cs b/src/NUnitFramework/framework/Constraints/StringConstraint.cs
index b4ce5a2841..b2787711b1 100644
--- a/src/NUnitFramework/framework/Constraints/StringConstraint.cs
+++ b/src/NUnitFramework/framework/Constraints/StringConstraint.cs
@@ -100,6 +100,6 @@ public override ConstraintResult ApplyTo(TActual actual)
///
/// The string to be tested
/// True for success, false for failure
- protected abstract bool Matches(string actual);
+ protected abstract bool Matches(string? actual);
}
}
diff --git a/src/NUnitFramework/framework/Constraints/SubPathConstraint.cs b/src/NUnitFramework/framework/Constraints/SubPathConstraint.cs
index b72b63bd35..6f4d7a8b93 100644
--- a/src/NUnitFramework/framework/Constraints/SubPathConstraint.cs
+++ b/src/NUnitFramework/framework/Constraints/SubPathConstraint.cs
@@ -26,7 +26,7 @@ public SubPathConstraint(string expected) : base(expected)
///
/// The value to be tested
/// True for success, false for failure
- protected override bool Matches(string actual)
+ protected override bool Matches(string? actual)
{
return actual is not null && IsSubPath(Canonicalize(expected), Canonicalize(actual));
}
diff --git a/src/NUnitFramework/framework/Constraints/SubstringConstraint.cs b/src/NUnitFramework/framework/Constraints/SubstringConstraint.cs
index f4e46aa503..71e45f7125 100644
--- a/src/NUnitFramework/framework/Constraints/SubstringConstraint.cs
+++ b/src/NUnitFramework/framework/Constraints/SubstringConstraint.cs
@@ -41,7 +41,7 @@ public override StringConstraint IgnoreCase
///
/// The value to be tested
/// True for success, false for failure
- protected override bool Matches(string actual)
+ protected override bool Matches(string? actual)
{
if (actual is null)
return false;
diff --git a/src/NUnitFramework/framework/Constraints/UniqueItemsConstraint.cs b/src/NUnitFramework/framework/Constraints/UniqueItemsConstraint.cs
index dd7ba6f96e..a6e32080d8 100644
--- a/src/NUnitFramework/framework/Constraints/UniqueItemsConstraint.cs
+++ b/src/NUnitFramework/framework/Constraints/UniqueItemsConstraint.cs
@@ -5,6 +5,8 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
+using System.Text.RegularExpressions;
+using NUnit.Framework.Constraints.Comparers;
using NUnit.Framework.Internal;
namespace NUnit.Framework.Constraints
@@ -100,10 +102,10 @@ private ICollection OriginalAlgorithm(IEnumerable actual)
{
var itemsOfT = ItemsCastMethod.MakeGenericMethod(itemsType).Invoke(null, new[] { actual })!;
- if (IgnoringCase)
+ if (IgnoringCase || IgnoringWhiteSpace)
{
if (itemsType == typeof(string))
- return (ICollection)StringsUniqueIgnoringCase((IEnumerable)itemsOfT);
+ return (ICollection)StringsUniqueIgnoringCaseOrWhiteSpace((IEnumerable)itemsOfT);
else if (itemsType == typeof(char))
return (ICollection)CharsUniqueIgnoringCase((IEnumerable)itemsOfT);
}
@@ -156,11 +158,11 @@ private ICollection GetNonUniqueItems(IEnumerable actual)
else if (!IsTypeSafeForFastPath(memberType))
return OriginalAlgorithm(actual);
- // Special handling for ignore case with strings and chars
- if (IgnoringCase)
+ // Special handling for ignore case/white-space with strings and chars
+ if (IgnoringCase || IgnoringWhiteSpace)
{
if (memberType == typeof(string))
- return (ICollection)StringsUniqueIgnoringCase((IEnumerable)actual);
+ return (ICollection)StringsUniqueIgnoringCaseOrWhiteSpace((IEnumerable)actual);
else if (memberType == typeof(char))
return (ICollection)CharsUniqueIgnoringCase((IEnumerable)actual);
}
@@ -182,14 +184,14 @@ private static bool IsTypeSafeForFastPath(Type? type)
private static ICollection ItemsUnique(IEnumerable actual)
=> NonUniqueItemsInternal(actual, EqualityComparer.Default);
- private ICollection StringsUniqueIgnoringCase(IEnumerable actual)
- => NonUniqueItemsInternal(actual, new NUnitStringEqualityComparer(IgnoringCase));
+ private ICollection StringsUniqueIgnoringCaseOrWhiteSpace(IEnumerable actual)
+ => NonUniqueItemsInternal(actual, new NUnitStringEqualityComparer(IgnoringCase, IgnoringWhiteSpace));
private ICollection CharsUniqueIgnoringCase(IEnumerable actual)
{
var result = NonUniqueItemsInternal(
actual.Select(x => x.ToString()),
- new NUnitStringEqualityComparer(IgnoringCase));
+ new NUnitStringEqualityComparer(IgnoringCase, false));
return result.Select(x => x[0]).ToList();
}
@@ -247,27 +249,40 @@ private static bool IsHandledSpeciallyByNUnit(Type type)
private sealed class NUnitStringEqualityComparer : IEqualityComparer
{
+ private static readonly Regex WhiteSpace = new(@"\s+", RegexOptions.Compiled);
+
private readonly bool _ignoreCase;
+ private readonly bool _ignoreWhiteSpace;
- public NUnitStringEqualityComparer(bool ignoreCase)
+ public NUnitStringEqualityComparer(bool ignoreCase, bool ignoreWhiteSpace)
{
_ignoreCase = ignoreCase;
+ _ignoreWhiteSpace = ignoreWhiteSpace;
}
public bool Equals(string? x, string? y)
{
- var stringComparison = _ignoreCase ? StringComparison.CurrentCultureIgnoreCase : StringComparison.Ordinal;
- return string.Equals(x, y, stringComparison);
+ return x is not null && y is not null ?
+ StringsComparer.Equals(x, y, _ignoreCase, _ignoreWhiteSpace) :
+ ReferenceEquals(x, y);
}
public int GetHashCode(string obj)
{
- if (obj is null)
+ if (obj is not string s)
+ {
return 0;
- else if (_ignoreCase)
- return StringComparer.CurrentCultureIgnoreCase.GetHashCode(obj);
+ }
+
+ if (_ignoreWhiteSpace)
+ {
+ s = WhiteSpace.Replace(s, string.Empty);
+ }
+
+ if (_ignoreCase)
+ return StringComparer.CurrentCultureIgnoreCase.GetHashCode(s);
else
- return obj.GetHashCode();
+ return s.GetHashCode();
}
}
diff --git a/src/NUnitFramework/framework/Constraints/WhiteSpaceConstraint.cs b/src/NUnitFramework/framework/Constraints/WhiteSpaceConstraint.cs
new file mode 100644
index 0000000000..dc9d80b69b
--- /dev/null
+++ b/src/NUnitFramework/framework/Constraints/WhiteSpaceConstraint.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt
+
+namespace NUnit.Framework.Constraints
+{
+ ///
+ /// WhiteSpaceConstraint tests whether a string contains white space.
+ ///
+ public class WhiteSpaceConstraint : StringConstraint
+ {
+ private const string WhiteSpace = "white-space";
+
+ ///
+ public override string Description => WhiteSpace;
+
+ ///
+ public override string DisplayName => WhiteSpace;
+
+ ///
+ /// Test whether the constraint is satisfied by a given value
+ ///
+ /// The value to be tested
+ /// True for success, false for failure
+ protected override bool Matches(string? actual)
+ {
+ return string.IsNullOrWhiteSpace(actual);
+ }
+ }
+}
diff --git a/src/NUnitFramework/framework/Internal/Execution/TextMessageWriter.cs b/src/NUnitFramework/framework/Internal/Execution/TextMessageWriter.cs
index f7547e5e7a..972cb4bb4e 100644
--- a/src/NUnitFramework/framework/Internal/Execution/TextMessageWriter.cs
+++ b/src/NUnitFramework/framework/Internal/Execution/TextMessageWriter.cs
@@ -164,17 +164,14 @@ public override void DisplayDifferences(object? expected, object? actual, Tolera
}
}
- ///
- /// Display the expected and actual string values on separate lines.
- /// If the mismatch parameter is >=0, an additional line is displayed
- /// line containing a caret that points to the mismatch point.
- ///
- /// The expected string value
- /// The actual string value
- /// The point at which the strings don't match or -1
- /// If true, case is ignored in string comparisons
- /// If true, clip the strings to fit the max line length
+ ///
public override void DisplayStringDifferences(string expected, string actual, int mismatch, bool ignoreCase, bool clipping)
+ {
+ DisplayStringDifferences(expected, actual, mismatch, mismatch, ignoreCase, false, clipping);
+ }
+
+ ///
+ public override void DisplayStringDifferences(string expected, string actual, int mismatchExpected, int mismatchActual, bool ignoreCase, bool ignoreWhiteSpace, bool clipping)
{
// Maximum string we can display without truncating
int maxDisplayLength = MaxLineLength
@@ -182,23 +179,33 @@ public override void DisplayStringDifferences(string expected, string actual, in
- 2; // 2 quotation marks
if (clipping)
- MsgUtils.ClipExpectedAndActual(ref expected, ref actual, maxDisplayLength, mismatch);
-
- expected = MsgUtils.EscapeControlChars(expected);
- actual = MsgUtils.EscapeControlChars(actual);
+ {
+ if (ignoreWhiteSpace)
+ {
+ expected = MsgUtils.ClipWhenNeeded(expected, expected.Length, maxDisplayLength, ref mismatchExpected);
+ actual = MsgUtils.ClipWhenNeeded(actual, actual.Length, maxDisplayLength, ref mismatchActual);
+ }
+ else
+ {
+ MsgUtils.ClipExpectedAndActual(ref expected, ref actual, maxDisplayLength, ref mismatchExpected, ref mismatchActual);
+ }
+ }
- // The mismatch position may have changed due to clipping or white space conversion
- mismatch = MsgUtils.FindMismatchPosition(expected, actual, 0, ignoreCase);
+ expected = MsgUtils.EscapeControlChars(expected, ref mismatchExpected);
+ actual = MsgUtils.EscapeControlChars(actual, ref mismatchActual);
Write(Pfx_Expected);
Write(MsgUtils.FormatValue(expected));
if (ignoreCase)
Write(", ignoring case");
+ if (ignoreWhiteSpace)
+ Write(", ignoring white-space");
WriteLine();
+ if (mismatchExpected >= 0 && mismatchExpected != mismatchActual)
+ WriteCaretLine(mismatchExpected);
WriteActualLine(actual);
- //DisplayDifferences(expected, actual);
- if (mismatch >= 0)
- WriteCaretLine(mismatch);
+ if (mismatchActual >= 0)
+ WriteCaretLine(mismatchActual);
}
#endregion
diff --git a/src/NUnitFramework/framework/Is.cs b/src/NUnitFramework/framework/Is.cs
index aa0c42a6cb..8dc06cbea5 100644
--- a/src/NUnitFramework/framework/Is.cs
+++ b/src/NUnitFramework/framework/Is.cs
@@ -115,6 +115,15 @@ public abstract class Is
#endregion
+ #region WhiteSpace
+
+ ///
+ /// Returns a constraint that tests for white-space
+ ///
+ public static WhiteSpaceConstraint WhiteSpace => new();
+
+ #endregion
+
#region Unique
///
diff --git a/src/NUnitFramework/framework/nunit.framework.csproj b/src/NUnitFramework/framework/nunit.framework.csproj
index 32f1a3581b..36818728cf 100644
--- a/src/NUnitFramework/framework/nunit.framework.csproj
+++ b/src/NUnitFramework/framework/nunit.framework.csproj
@@ -9,6 +9,7 @@
+
diff --git a/src/NUnitFramework/tests/Constraints/AnyOfConstraintTests.cs b/src/NUnitFramework/tests/Constraints/AnyOfConstraintTests.cs
index 7fed988e6a..d31cabb9d4 100644
--- a/src/NUnitFramework/tests/Constraints/AnyOfConstraintTests.cs
+++ b/src/NUnitFramework/tests/Constraints/AnyOfConstraintTests.cs
@@ -30,6 +30,20 @@ public void ItemIsPresent_IgnoreCase()
Assert.That(anyOf.ApplyTo("AB").Status, Is.EqualTo(ConstraintStatus.Success));
}
+ [Test]
+ public void ItemIsPresent_IgnoreWhiteSpace()
+ {
+ var anyOf = new AnyOfConstraint(new[] { "a", "B", "a b" }).IgnoreWhiteSpace;
+ Assert.That(anyOf.ApplyTo("ab").Status, Is.EqualTo(ConstraintStatus.Success));
+ }
+
+ [Test]
+ public void ItemIsPresent_IgnoreCaseWhiteSpace()
+ {
+ var anyOf = new AnyOfConstraint(new[] { "a", "B", "ab" }).IgnoreCase.IgnoreWhiteSpace;
+ Assert.That(anyOf.ApplyTo("A B").Status, Is.EqualTo(ConstraintStatus.Success));
+ }
+
[Test]
public void ItemIsPresent_WithEqualityComparer()
{
diff --git a/src/NUnitFramework/tests/Constraints/CollectionEqualsTests.cs b/src/NUnitFramework/tests/Constraints/CollectionEqualsTests.cs
index 6147d04019..2d015d7c8d 100644
--- a/src/NUnitFramework/tests/Constraints/CollectionEqualsTests.cs
+++ b/src/NUnitFramework/tests/Constraints/CollectionEqualsTests.cs
@@ -95,6 +95,17 @@ public void HonorsIgnoreCase(IEnumerable expected, IEnumerable actual)
new object[] { new List { "a", "b", "c" }, new List { "A", "B", "C" } },
};
+ [TestCaseSource(nameof(IgnoreWhiteSpaceData))]
+ public void HonorsIgnoreWhiteSpace(IEnumerable expected, IEnumerable actual)
+ {
+ Assert.That(expected, Is.EqualTo(actual).IgnoreWhiteSpace);
+ }
+
+ private static readonly object[] IgnoreWhiteSpaceData =
+ {
+ new object[] { new SimpleObjectCollection(" x", "y ", " z "), new SimpleObjectCollection("x ", " y", "z") },
+ };
+
[Test]
[DefaultFloatingPointTolerance(0.5)]
public void StructuralComparerOnSameCollection_RespectsAndSetsToleranceByRef()
diff --git a/src/NUnitFramework/tests/Constraints/CollectionEquivalentConstraintTests.cs b/src/NUnitFramework/tests/Constraints/CollectionEquivalentConstraintTests.cs
index 3d2e4b0e6c..2bc87f10fd 100644
--- a/src/NUnitFramework/tests/Constraints/CollectionEquivalentConstraintTests.cs
+++ b/src/NUnitFramework/tests/Constraints/CollectionEquivalentConstraintTests.cs
@@ -101,6 +101,15 @@ public void EquivalentHonorsIgnoreCase()
Assert.That(new CollectionEquivalentConstraint(set1).IgnoreCase.ApplyTo(set2).IsSuccess);
}
+ [Test]
+ public void EquivalentHonorsIgnoreWhiteSpace()
+ {
+ ICollection set1 = new SimpleObjectCollection("abc", "def", "ghi");
+ ICollection set2 = new SimpleObjectCollection("g h i", "d e f", "a b c");
+
+ Assert.That(new CollectionEquivalentConstraint(set1).IgnoreWhiteSpace.ApplyTo(set2).IsSuccess);
+ }
+
[Test]
[TestCaseSource(typeof(IgnoreCaseDataProvider), nameof(IgnoreCaseDataProvider.TestCases))]
public void HonorsIgnoreCase(IEnumerable expected, IEnumerable actual)
diff --git a/src/NUnitFramework/tests/Constraints/ConstraintExpressionTests.cs b/src/NUnitFramework/tests/Constraints/ConstraintExpressionTests.cs
index 0af9b1d135..b81c42c78c 100644
--- a/src/NUnitFramework/tests/Constraints/ConstraintExpressionTests.cs
+++ b/src/NUnitFramework/tests/Constraints/ConstraintExpressionTests.cs
@@ -88,6 +88,14 @@ public void ConstraintExpressionAnyOfType()
Assert.That("red", constraint);
}
+ [Test]
+ public void ConstraintExpressionAnyOfTypeIgnoreWhiteSpace()
+ {
+ var constraintExpression = new ConstraintExpression();
+ var constraint = constraintExpression.AnyOf(new string[] { "RED", "GREEN" }).IgnoreWhiteSpace;
+ Assert.That(" R E D ", constraint);
+ }
+
[Test]
public void ConstraintExpressionAnyOfList()
{
diff --git a/src/NUnitFramework/tests/Constraints/ContainsConstraintTests.cs b/src/NUnitFramework/tests/Constraints/ContainsConstraintTests.cs
index f7328ed344..0a6fd2b7cd 100644
--- a/src/NUnitFramework/tests/Constraints/ContainsConstraintTests.cs
+++ b/src/NUnitFramework/tests/Constraints/ContainsConstraintTests.cs
@@ -21,6 +21,26 @@ public void HonorsIgnoreCaseForStringCollection()
Assert.That(result.IsSuccess);
}
+ [Test]
+ public void HonorsIgnoreWhiteSpaceForStringCollection()
+ {
+ var actualItems = new[] { "ABC", "d e f" };
+ var constraint = new ContainsConstraint("def").IgnoreWhiteSpace;
+
+ var result = constraint.ApplyTo(actualItems);
+ Assert.That(result.IsSuccess);
+ }
+
+ [Test]
+ public void HonorsIgnoreWhiteSpaceForStringCollectionSearchItem()
+ {
+ var actualItems = new[] { "ABC", "d e f" };
+ var constraint = new ContainsConstraint("A B C").IgnoreWhiteSpace;
+
+ var result = constraint.ApplyTo(actualItems);
+ Assert.That(result.IsSuccess);
+ }
+
[Test, SetCulture("en-US")]
public void HonorsIgnoreCaseForString()
{
diff --git a/src/NUnitFramework/tests/Constraints/DictionaryContainsKeyValueConstraintTests.cs b/src/NUnitFramework/tests/Constraints/DictionaryContainsKeyValueConstraintTests.cs
index 03a30be62d..30426387b6 100644
--- a/src/NUnitFramework/tests/Constraints/DictionaryContainsKeyValueConstraintTests.cs
+++ b/src/NUnitFramework/tests/Constraints/DictionaryContainsKeyValueConstraintTests.cs
@@ -181,6 +181,14 @@ public void IgnoreCaseIsHonored()
Assert.That(dictionary, new DictionaryContainsKeyValuePairConstraint("HI", "UNIVERSE").IgnoreCase);
}
+ [Test]
+ public void IgnoreWhiteSpaceIsHonored()
+ {
+ var dictionary = new Dictionary { { "Hello", "World" }, { "Hi ", "Universe" }, { "Hola", "Mundo" } };
+
+ Assert.That(dictionary, new DictionaryContainsKeyValuePairConstraint("Hi", " U n i v e r s e").IgnoreWhiteSpace);
+ }
+
[Test, SetCulture("en-US")]
public void UsingIsHonored()
{
diff --git a/src/NUnitFramework/tests/Constraints/DictionaryContainsValueConstraintTests.cs b/src/NUnitFramework/tests/Constraints/DictionaryContainsValueConstraintTests.cs
index 78c66f2777..8a7d0ddd52 100644
--- a/src/NUnitFramework/tests/Constraints/DictionaryContainsValueConstraintTests.cs
+++ b/src/NUnitFramework/tests/Constraints/DictionaryContainsValueConstraintTests.cs
@@ -69,6 +69,14 @@ public void IgnoreCaseIsHonored()
Assert.That(dictionary, new DictionaryContainsValueConstraint("UNIVERSE").IgnoreCase);
}
+ [Test]
+ public void IgnoreWhiteSpaceIsHonored()
+ {
+ var dictionary = new Dictionary { { "Hello", "World" }, { "Hi", "Universe" }, { "Hola", "Mundo" } };
+
+ Assert.That(dictionary, new DictionaryContainsValueConstraint("U n i v e r s e").IgnoreWhiteSpace);
+ }
+
[Test, SetCulture("en-US")]
public void UsingIsHonored()
{
diff --git a/src/NUnitFramework/tests/Constraints/EqualConstraintTests.cs b/src/NUnitFramework/tests/Constraints/EqualConstraintTests.cs
index 38d161cb30..371457cb80 100644
--- a/src/NUnitFramework/tests/Constraints/EqualConstraintTests.cs
+++ b/src/NUnitFramework/tests/Constraints/EqualConstraintTests.cs
@@ -66,6 +66,59 @@ public void DoesntRespectCultureWhenCasingMatters()
Assert.That(result.IsSuccess, Is.False);
}
+ [Test]
+ public void IgnoreWhiteSpace()
+ {
+ var constraint = new EqualConstraint("Hello World").IgnoreWhiteSpace;
+
+ var result = constraint.ApplyTo("Hello\tWorld");
+
+ Assert.That(result.IsSuccess, Is.True);
+ }
+
+ [Test]
+ public void ExtendedIgnoreWhiteSpaceExample()
+ {
+ const string prettyJson = """
+ "persons":[
+ {
+ "name": "John",
+ "surname": "Smith"
+ },
+ {
+ "name": "Jane",
+ "surname": "Doe"
+ }
+ ]
+ """;
+ const string condensedJson = """
+ "persons":[{"name":"John","surname":"Smith"},{"name": "Jane","surname": "Doe"}]
+ """;
+
+ Assert.That(condensedJson, Is.Not.EqualTo(prettyJson));
+ Assert.That(condensedJson, Is.EqualTo(prettyJson).IgnoreWhiteSpace);
+ }
+
+ [Test]
+ public void IgnoreWhiteSpaceFail()
+ {
+ var constraint = new EqualConstraint("Hello World").IgnoreWhiteSpace;
+
+ var result = constraint.ApplyTo("Hello Universe");
+
+ Assert.That(result.IsSuccess, Is.False);
+ }
+
+ [Test]
+ public void IgnoreWhiteSpaceAndIgnoreCase()
+ {
+ var constraint = new EqualConstraint("Hello World").IgnoreWhiteSpace.IgnoreCase;
+
+ var result = constraint.ApplyTo("hello\r\nworld\r\n");
+
+ Assert.That(result.IsSuccess, Is.True);
+ }
+
[Test]
public void Bug524CharIntWithoutOverload()
{
diff --git a/src/NUnitFramework/tests/Constraints/MsgUtilTests.cs b/src/NUnitFramework/tests/Constraints/MsgUtilTests.cs
index 52c123c324..305265c777 100644
--- a/src/NUnitFramework/tests/Constraints/MsgUtilTests.cs
+++ b/src/NUnitFramework/tests/Constraints/MsgUtilTests.cs
@@ -277,6 +277,14 @@ public static void EscapeControlCharsTest(string? input, string? expected)
Assert.That(MsgUtils.EscapeControlChars(input), Is.EqualTo(expected));
}
+ [TestCase("Hello\r\nWorld", 4, "Hello\\r\\nWorld", 4)]
+ [TestCase("Hello\r\nWorld", 7, "Hello\\r\\nWorld", 9)]
+ public static void EscapeControlCharsWithIndexTest(string? input, int index, string? expected, int expectedIndex)
+ {
+ Assert.That(MsgUtils.EscapeControlChars(input, ref index), Is.EqualTo(expected));
+ Assert.That(index, Is.EqualTo(expectedIndex));
+ }
+
[Test]
public static void EscapeNullCharInString()
{
@@ -307,9 +315,9 @@ public static void EscapesNullControlChars()
private const string S52 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
[TestCase(S52, 52, 0, S52, TestName = "NoClippingNeeded")]
- [TestCase(S52, 29, 0, "abcdefghijklmnopqrstuvwxyz...", TestName = "ClipAtEnd")]
- [TestCase(S52, 29, 26, "...ABCDEFGHIJKLMNOPQRSTUVWXYZ", TestName = "ClipAtStart")]
- [TestCase(S52, 28, 26, "...ABCDEFGHIJKLMNOPQRSTUV...", TestName = "ClipAtStartAndEnd")]
+ [TestCase(S52, 26, 0, "abcdefghijklmnopqrstuvwxyz...", TestName = "ClipAtEnd")]
+ [TestCase(S52, 26, 26, "...ABCDEFGHIJKLMNOPQRSTUVWXYZ", TestName = "ClipAtStart")]
+ [TestCase(S52, 22, 26, "...ABCDEFGHIJKLMNOPQRSTUV...", TestName = "ClipAtStartAndEnd")]
public static void TestClipString(string input, int max, int start, string result)
{
System.Console.WriteLine("input= \"{0}\"", input);
@@ -318,7 +326,45 @@ public static void TestClipString(string input, int max, int start, string resul
}
#endregion
+ #region ClipWhenNeeded
+
+ [Test]
+ public static void ClipWhenNeeded_StringFitsInLine()
+ {
+ int mismatchedLocation = 5;
+ string clipped = MsgUtils.ClipWhenNeeded(S52, S52.Length, 52, ref mismatchedLocation);
+ Assert.That(clipped, Is.EqualTo(S52));
+ Assert.That(mismatchedLocation, Is.EqualTo(5));
+ }
+
+ [Test]
+ public static void ClipWhenNeeded_StringDoesNotFitInLineMismatchLocationEarly()
+ {
+ int mismatchedLocation = 10;
+ string clipped = MsgUtils.ClipWhenNeeded(S52, S52.Length, 29, ref mismatchedLocation);
+ Assert.That(clipped, Is.EqualTo("abcdefghijklmnopqrstuvwxyz..."));
+ Assert.That(mismatchedLocation, Is.EqualTo(10));
+ }
+
+ [Test]
+ public static void ClipWhenNeeded_StringDoesNotFitInLineMismatchLocationInTheMiddle()
+ {
+ int mismatchedLocation = 26;
+ string clipped = MsgUtils.ClipWhenNeeded(S52, S52.Length, 29, ref mismatchedLocation);
+ Assert.That(clipped, Is.EqualTo("...pqrstuvwxyzABCDEFGHIJKL..."));
+ Assert.That(mismatchedLocation, Is.EqualTo(26 - (26 - 23 / 2) + 3));
+ }
+ [Test]
+ public static void ClipWhenNeeded_StringDoesNotFitInLineMismatchLocationAlmostAtEnd()
+ {
+ int mismatchedLocation = 50;
+ string clipped = MsgUtils.ClipWhenNeeded(S52, S52.Length, 29, ref mismatchedLocation);
+ Assert.That(clipped, Is.EqualTo("...ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
+ Assert.That(mismatchedLocation, Is.EqualTo(50 - (29 - 3) + 3));
+ }
+
+ #endregion
#region ClipExpectedAndActual
[Test]
@@ -326,15 +372,22 @@ public static void ClipExpectedAndActual_StringsFitInLine()
{
string eClip = S52;
string aClip = "abcde";
- MsgUtils.ClipExpectedAndActual(ref eClip, ref aClip, 52, 5);
+ int mismatchExpected = 5;
+ int mismatchActual = 5;
+ MsgUtils.ClipExpectedAndActual(ref eClip, ref aClip, 52, ref mismatchExpected, ref mismatchActual);
Assert.That(eClip, Is.EqualTo(S52));
Assert.That(aClip, Is.EqualTo("abcde"));
+ Assert.That(mismatchExpected, Is.EqualTo(5));
+ Assert.That(mismatchActual, Is.EqualTo(5));
eClip = S52;
aClip = "abcdefghijklmno?qrstuvwxyz";
- MsgUtils.ClipExpectedAndActual(ref eClip, ref aClip, 52, 15);
+ mismatchExpected = mismatchActual = 15;
+ MsgUtils.ClipExpectedAndActual(ref eClip, ref aClip, 52, ref mismatchExpected, ref mismatchActual);
Assert.That(eClip, Is.EqualTo(S52));
Assert.That(aClip, Is.EqualTo("abcdefghijklmno?qrstuvwxyz"));
+ Assert.That(mismatchExpected, Is.EqualTo(15));
+ Assert.That(mismatchActual, Is.EqualTo(15));
}
[Test]
@@ -342,24 +395,40 @@ public static void ClipExpectedAndActual_StringTailsFitInLine()
{
string s1 = S52;
string s2 = S52.Replace('Z', '?');
- MsgUtils.ClipExpectedAndActual(ref s1, ref s2, 29, 51);
+ int mismatchExpected = 51;
+ int mismatchActual = 51;
+ MsgUtils.ClipExpectedAndActual(ref s1, ref s2, 29, ref mismatchExpected, ref mismatchActual);
Assert.That(s1, Is.EqualTo("...ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
+ Assert.That(mismatchExpected, Is.EqualTo(51 - 26 + 3));
+ Assert.That(mismatchActual, Is.EqualTo(51 - 26 + 3));
}
[Test]
- public static void ClipExpectedAndActual_StringsDoNotFitInLine()
+ public static void ClipExpectedAndActual_StringsHeadFitsInLine()
{
string s1 = S52;
string s2 = "abcdefghij";
- MsgUtils.ClipExpectedAndActual(ref s1, ref s2, 29, 10);
+ int mismatchExpected = 10;
+ int mismatchActual = 10;
+ MsgUtils.ClipExpectedAndActual(ref s1, ref s2, 29, ref mismatchExpected, ref mismatchActual);
Assert.That(s1, Is.EqualTo("abcdefghijklmnopqrstuvwxyz..."));
Assert.That(s2, Is.EqualTo("abcdefghij"));
+ Assert.That(mismatchExpected, Is.EqualTo(10));
+ Assert.That(mismatchActual, Is.EqualTo(10));
+ }
- s1 = S52;
- s2 = "abcdefghijklmno?qrstuvwxyz";
- MsgUtils.ClipExpectedAndActual(ref s1, ref s2, 25, 15);
- Assert.That(s1, Is.EqualTo("...efghijklmnopqrstuvw..."));
- Assert.That(s2, Is.EqualTo("...efghijklmno?qrstuvwxyz"));
+ [Test]
+ public static void ClipExpectedAndActual_StringsDoNotFitInLine()
+ {
+ string s1 = S52;
+ string s2 = "abcdefghijklmno?qrstuvwxyz";
+ int mismatchExpected = 15;
+ int mismatchActual = 15;
+ MsgUtils.ClipExpectedAndActual(ref s1, ref s2, 17, ref mismatchExpected, ref mismatchActual);
+ Assert.That(s1, Is.EqualTo("...klmnopqrstu..."));
+ Assert.That(s2, Is.EqualTo("...klmno?qrstu..."));
+ Assert.That(mismatchExpected, Is.EqualTo(8));
+ Assert.That(mismatchActual, Is.EqualTo(8));
}
#endregion
diff --git a/src/NUnitFramework/tests/Constraints/UniqueItemsConstraintTests.cs b/src/NUnitFramework/tests/Constraints/UniqueItemsConstraintTests.cs
index b8d9540e11..0dfd50fcbb 100644
--- a/src/NUnitFramework/tests/Constraints/UniqueItemsConstraintTests.cs
+++ b/src/NUnitFramework/tests/Constraints/UniqueItemsConstraintTests.cs
@@ -45,6 +45,15 @@ public void HonorsIgnoreCase(IEnumerable actual)
Assert.That(result.IsSuccess, Is.False, $"{actual} should not be unique ignoring case");
}
+ [TestCaseSource(nameof(IgnoreWhiteSpaceData))]
+ public void HonorsIgnoreWhiteSpace(IEnumerable actual)
+ {
+ var constraint = new UniqueItemsConstraint().IgnoreWhiteSpace;
+ var result = constraint.ApplyTo(actual);
+
+ Assert.That(result.IsSuccess, Is.False, $"{actual} should not be unique ignoring white-space");
+ }
+
private static readonly object[] IgnoreCaseData =
{
new object[] { new SimpleObjectCollection("x", "y", "z", "Z") },
@@ -52,6 +61,12 @@ public void HonorsIgnoreCase(IEnumerable actual)
new object[] { new[] { "a", "b", "c", "C" } }
};
+ private static readonly object[] IgnoreWhiteSpaceData =
+ {
+ new object[] { new SimpleObjectCollection("x", "y", "z", " z ") },
+ new object[] { new[] { "a", "b", "c", " c " } }
+ };
+
private static readonly object[] DuplicateItemsData =
{
new object[] { new[] { 1, 2, 3, 2 }, new[] { 2 } },
diff --git a/src/NUnitFramework/tests/Constraints/WhiteSpaceContraintTests.cs b/src/NUnitFramework/tests/Constraints/WhiteSpaceContraintTests.cs
new file mode 100644
index 0000000000..ec5765651b
--- /dev/null
+++ b/src/NUnitFramework/tests/Constraints/WhiteSpaceContraintTests.cs
@@ -0,0 +1,47 @@
+// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt
+
+using NUnit.Framework.Constraints;
+
+namespace NUnit.Framework.Tests.Constraints
+{
+ [TestFixture]
+ public class WhiteSpaceContraintTests : StringConstraintTests
+ {
+ protected override Constraint TheConstraint { get; } = new WhiteSpaceConstraint();
+
+ [SetUp]
+ public void SetUp()
+ {
+ ExpectedDescription = "white-space";
+ StringRepresentation = "";
+ }
+
+ private static readonly object[] SuccessData = new object[]
+ {
+ string.Empty,
+ " ",
+ "\f",
+ "\n",
+ "\r",
+ "\t",
+ "\v",
+ };
+ private static readonly object[] FailureData = new object[]
+ {
+ new TestCaseData("Hello", "\"Hello\""),
+ new TestCaseData("Hello World", "\"Hello World\""),
+ };
+
+ [TestCaseSource(nameof(SuccessData))]
+ public void TestIsWhiteSpace(string text)
+ {
+ Assert.That(text, Is.WhiteSpace);
+ }
+
+ [TestCaseSource(nameof(FailureData))]
+ public void TestIsNotWhiteSpace(string text, string message)
+ {
+ Assert.That(text, Is.Not.WhiteSpace, message);
+ }
+ }
+}
diff --git a/src/NUnitFramework/tests/Internal/TextMessageWriterTests.cs b/src/NUnitFramework/tests/Internal/TextMessageWriterTests.cs
index 0f65d39c29..97cb3c64c9 100644
--- a/src/NUnitFramework/tests/Internal/TextMessageWriterTests.cs
+++ b/src/NUnitFramework/tests/Internal/TextMessageWriterTests.cs
@@ -31,7 +31,7 @@ public void DisplayStringDifferences()
string s72 = "abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
string exp = "abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXY...";
- _writer.DisplayStringDifferences(s72, "abcde", 5, false, true);
+ _writer.DisplayStringDifferences(s72, "abcde", 5, 5, false, false, true);
string message = _writer.ToString();
Assert.That(message, Is.EqualTo(
TextMessageWriter.Pfx_Expected + Q(exp) + NL +
@@ -44,7 +44,7 @@ public void DisplayStringDifferences_NoClipping()
{
string s72 = "abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
- _writer.DisplayStringDifferences(s72, "abcde", 5, false, false);
+ _writer.DisplayStringDifferences(s72, "abcde", 5, 5, false, false, false);
string message = _writer.ToString();
Assert.That(message, Is.EqualTo(
TextMessageWriter.Pfx_Expected + Q(s72) + NL +
@@ -52,6 +52,22 @@ public void DisplayStringDifferences_NoClipping()
" ----------------^" + NL));
}
+ [Test]
+ public void DisplayStringDifferences_IgnoreWhiteSpace()
+ {
+ string expected = "abc def";
+ string actual = "a b c d e g";
+
+ _writer.DisplayStringDifferences(expected, actual, 6, 10, false, true, false);
+ string message = _writer.ToString();
+ string expectedMessage =
+ TextMessageWriter.Pfx_Expected + Q(expected) + ", ignoring white-space" + NL +
+ " -----------------^" + NL +
+ TextMessageWriter.Pfx_Actual + Q(actual) + NL +
+ " ---------------------^" + NL;
+ Assert.That(message, Is.EqualTo(expectedMessage));
+ }
+
[Test]
public void WriteMessageLine_EmbeddedZeroes()
{