Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pull] master from nunit:master #218

Merged
merged 5 commits into from
Jan 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/NUnitFramework/framework/Constraints/AnyOfConstraint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,19 @@ public AnyOfConstraint Using<T>(Func<T, T, bool> comparer)
return this;
}

/// <summary>
/// Enables comparing of instance properties.
/// </summary>
/// <remarks>
/// This allows comparing classes that don't implement <see cref="IEquatable{T}"/>
/// without having to compare each property separately in own code.
/// </remarks>
public AnyOfConstraint UsingPropertiesComparer()
{
_comparer.CompareProperties = true;
return this;
}

#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,19 @@ internal CollectionItemsEqualConstraint Using(EqualityAdapter adapter)
return this;
}

/// <summary>
/// Enables comparing of instance properties.
/// </summary>
/// <remarks>
/// This allows comparing classes that don't implement <see cref="IEquatable{T}"/>
/// without having to compare each property separately in own code.
/// </remarks>
public CollectionItemsEqualConstraint UsingPropertiesComparer()
{
_comparer.CompareProperties = true;
return this;
}

#endregion

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,13 @@ internal enum EqualMethodResult
ComparedEqual,

/// <summary>
/// Method is appropriate and the items are consisdered different.
/// Method is appropriate and the items are considered different.
/// </summary>
ComparedNotEqual
ComparedNotEqual,

/// <summary>
/// Method is appropriate but the class has cyclic references.
/// </summary>
ComparisonPending
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt

using System;
using System.Linq;
using System.Reflection;

namespace NUnit.Framework.Constraints.Comparers
Expand Down Expand Up @@ -29,9 +30,10 @@ public static EqualMethodResult Equal(object x, object y, ref Tolerance toleranc
}

PropertyInfo[] properties = xType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
if (properties.Length == 0 || properties.Length >= 32)
if (properties.Length == 0 || properties.Length >= 32 || properties.Any(p => p.GetIndexParameters().Length > 0))
{
// We can't compare if there are no (or too many) properties.
// We also can't deal with indexer properties as we don't know the range of valid values.
return EqualMethodResult.TypesNotSupported;
}

Expand Down
14 changes: 13 additions & 1 deletion src/NUnitFramework/framework/Constraints/EqualConstraint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,6 @@ public EqualConstraint Ticks
return this;
}
}

/// <summary>
/// Flag the constraint to use the supplied IComparer object.
/// </summary>
Expand Down Expand Up @@ -346,6 +345,19 @@ public EqualConstraint Using<TCollectionType, TMemberType>(Func<TCollectionType,
return this;
}

/// <summary>
/// Enables comparing of instance properties.
/// </summary>
/// <remarks>
/// This allows comparing classes that don't implement <see cref="IEquatable{T}"/>
/// without having to compare each property separately in own code.
/// </remarks>
public EqualConstraint UsingPropertiesComparer()
{
_comparer.CompareProperties = true;
return this;
}

#endregion

#region Public Methods
Expand Down
26 changes: 24 additions & 2 deletions src/NUnitFramework/framework/Constraints/NUnitEqualityComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ public sealed class NUnitEqualityComparer
EquatablesComparer.Equal,
EnumerablesComparer.Equal,
EqualsComparer.Equal,
PropertiesComparer.Equal,
};

/// <summary>
Expand All @@ -65,6 +64,12 @@ public sealed class NUnitEqualityComparer
/// </summary>
private bool _compareAsCollection;

/// <summary>
/// If true, when a class does not implement <see cref="IEquatable{T}"/>
/// it will be compared property by property.
/// </summary>
private bool _compareProperties;

/// <summary>
/// Comparison objects used in comparisons for some constraints.
/// </summary>
Expand Down Expand Up @@ -96,6 +101,16 @@ public bool IgnoreCase
set => _caseInsensitive = value;
}

/// <summary>
/// Gets and sets a flag indicating whether an instance properties
/// should be compared when determining equality.
/// </summary>
public bool CompareProperties
{
get => _compareProperties;
set => _compareProperties = value;
}

/// <summary>
/// Gets and sets a flag indicating that arrays should be
/// compared as collections, without regard to their shape.
Expand Down Expand Up @@ -133,6 +148,7 @@ public bool CompareAsCollection
#endregion

#region Public Methods

/// <summary>
/// Compares two objects for equality within a tolerance.
/// </summary>
Expand All @@ -148,6 +164,7 @@ public bool AreEqual(object? x, object? y, ref Tolerance tolerance)
throw new NotSupportedException($"Specified Tolerance not supported for instances of type '{GetType(x)}' and '{GetType(y)}'");
case EqualMethodResult.ComparedEqual:
return true;
case EqualMethodResult.ComparisonPending:
case EqualMethodResult.ComparedNotEqual:
default:
return false;
Expand All @@ -170,7 +187,7 @@ internal EqualMethodResult AreEqual(object? x, object? y, ref Tolerance toleranc
return EqualMethodResult.ComparedEqual;

if (state.DidCompare(x, y))
return EqualMethodResult.ComparedNotEqual;
return EqualMethodResult.ComparisonPending;

EqualityAdapter? externalComparer = GetExternalComparer(x, y);

Expand All @@ -194,6 +211,11 @@ internal EqualMethodResult AreEqual(object? x, object? y, ref Tolerance toleranc
return result;
}

if (_compareProperties)
{
return PropertiesComparer.Equal(x, y, ref tolerance, state, this);
}

if (tolerance.HasVariance)
return EqualMethodResult.ToleranceNotSupported;

Expand Down
14 changes: 14 additions & 0 deletions src/NUnitFramework/framework/Constraints/SomeItemsConstraint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,20 @@ public SomeItemsConstraint Using<T>(IEqualityComparer<T> comparer)
return this;
}

/// <summary>
/// Enables comparing of instance properties.
/// </summary>
/// <remarks>
/// This allows comparing classes that don't implement <see cref="IEquatable{T}"/>
/// without having to compare each property separately in own code.
/// </remarks>
public SomeItemsConstraint UsingPropertiesComparer()
{
CheckPrecondition(nameof(UsingPropertiesComparer));
_equalConstraint.UsingPropertiesComparer();
return this;
}

[MemberNotNull(nameof(_equalConstraint))]
private void CheckPrecondition(string argument)
{
Expand Down
85 changes: 72 additions & 13 deletions src/NUnitFramework/tests/Assertions/AssertThatTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -377,13 +377,13 @@ public void AssertThatEqualsWithClassWithSomeToleranceAwareMembers()

Assert.Multiple(() =>
{
Assert.That(new ClassWithSomeToleranceAwareMembers(1, 1.1, "1.1", zero), Is.EqualTo(instance));
Assert.That(new ClassWithSomeToleranceAwareMembers(1, 1.2, "1.1", zero), Is.Not.EqualTo(instance));
Assert.That(new ClassWithSomeToleranceAwareMembers(1, 1.2, "1.1", zero), Is.EqualTo(instance).Within(0.1));
Assert.That(new ClassWithSomeToleranceAwareMembers(1, 1.1, "1.1", null), Is.Not.EqualTo(instance));
Assert.That(new ClassWithSomeToleranceAwareMembers(1, 1.1, "2.2", zero), Is.Not.EqualTo(instance));
Assert.That(new ClassWithSomeToleranceAwareMembers(1, 2.2, "1.1", zero), Is.Not.EqualTo(instance));
Assert.That(new ClassWithSomeToleranceAwareMembers(2, 1.1, "1.1", zero), Is.Not.EqualTo(instance));
Assert.That(new ClassWithSomeToleranceAwareMembers(1, 1.1, "1.1", zero), Is.EqualTo(instance).UsingPropertiesComparer());
Assert.That(new ClassWithSomeToleranceAwareMembers(1, 1.2, "1.1", zero), Is.Not.EqualTo(instance).UsingPropertiesComparer());
Assert.That(new ClassWithSomeToleranceAwareMembers(1, 1.2, "1.1", zero), Is.EqualTo(instance).Within(0.1).UsingPropertiesComparer());
Assert.That(new ClassWithSomeToleranceAwareMembers(1, 1.1, "1.1", null), Is.Not.EqualTo(instance).UsingPropertiesComparer());
Assert.That(new ClassWithSomeToleranceAwareMembers(1, 1.1, "2.2", zero), Is.Not.EqualTo(instance).UsingPropertiesComparer());
Assert.That(new ClassWithSomeToleranceAwareMembers(1, 2.2, "1.1", zero), Is.Not.EqualTo(instance).UsingPropertiesComparer());
Assert.That(new ClassWithSomeToleranceAwareMembers(2, 1.1, "1.1", zero), Is.Not.EqualTo(instance).UsingPropertiesComparer());
});
}

Expand Down Expand Up @@ -415,12 +415,12 @@ public void AssertThatEqualsWithStructWithSomeToleranceAwareMembers()

Assert.Multiple(() =>
{
Assert.That(new StructWithSomeToleranceAwareMembers(1, 1.1, "1.1", SomeEnum.One), Is.EqualTo(instance));
Assert.That(new StructWithSomeToleranceAwareMembers(1, 1.2, "1.1", SomeEnum.One), Is.Not.EqualTo(instance));
Assert.That(new StructWithSomeToleranceAwareMembers(1, 1.2, "1.1", SomeEnum.One), Is.EqualTo(instance).Within(0.1));
Assert.That(new StructWithSomeToleranceAwareMembers(1, 1.1, "1.1", SomeEnum.Two), Is.Not.EqualTo(instance).Within(0.1));
Assert.That(new StructWithSomeToleranceAwareMembers(1, 2.2, "1.1", SomeEnum.One), Is.Not.EqualTo(instance));
Assert.That(new StructWithSomeToleranceAwareMembers(2, 1.1, "1.1", SomeEnum.One), Is.Not.EqualTo(instance));
Assert.That(new StructWithSomeToleranceAwareMembers(1, 1.1, "1.1", SomeEnum.One), Is.EqualTo(instance).UsingPropertiesComparer());
Assert.That(new StructWithSomeToleranceAwareMembers(1, 1.2, "1.1", SomeEnum.One), Is.Not.EqualTo(instance).UsingPropertiesComparer());
Assert.That(new StructWithSomeToleranceAwareMembers(1, 1.2, "1.1", SomeEnum.One), Is.EqualTo(instance).Within(0.1).UsingPropertiesComparer());
Assert.That(new StructWithSomeToleranceAwareMembers(1, 1.1, "1.1", SomeEnum.Two), Is.Not.EqualTo(instance).Within(0.1).UsingPropertiesComparer());
Assert.That(new StructWithSomeToleranceAwareMembers(1, 2.2, "1.1", SomeEnum.One), Is.Not.EqualTo(instance).UsingPropertiesComparer());
Assert.That(new StructWithSomeToleranceAwareMembers(2, 1.1, "1.1", SomeEnum.One), Is.Not.EqualTo(instance).UsingPropertiesComparer());
});
}

Expand Down Expand Up @@ -526,5 +526,64 @@ public override string ToString()
return $"{ValueA} {ValueB} '{ValueC}' [{Chained}]";
}
}

[Test]
public void AssertWithRecursiveClass()
{
LinkedList list1 = new(1, new(2, new(3)));
LinkedList list2 = new(1, new(2, new(3)));

Assert.That(list1, Is.Not.EqualTo(list2));
Assert.That(list1, Is.EqualTo(list2).UsingPropertiesComparer());
}

[Test]
public void AssertWithCyclicRecursiveClass()
{
LinkedList list1 = new(1);
LinkedList list2 = new(1);

list1.Next = list1;
list2.Next = list2;

Assert.That(list1, Is.Not.EqualTo(list2)); // Reference comparison
Assert.That(list1, Is.EqualTo(list2).UsingPropertiesComparer());
}

private sealed class LinkedList
{
public LinkedList(int value, LinkedList? next = null)
{
Value = value;
Next = next;
}

public int Value { get; }

public LinkedList? Next { get; set; }
}

[Test]
public void EqualMemberWithIndexer()
{
var members = new Members("Hello", "World", "NUnit");
var copy = new Members("Hello", "World", "NUnit");

Assert.That(members[1], Is.EqualTo("World"));
Assert.That(copy, Is.Not.EqualTo(members));
Assert.That(() => Assert.That(copy, Is.EqualTo(members).UsingPropertiesComparer()), Throws.InstanceOf<NotSupportedException>());
}

private sealed class Members
{
private readonly string[] _members;

public Members(params string[] members)
{
_members = members;
}

public string this[int index] => _members[index];
}
}
}
18 changes: 18 additions & 0 deletions src/NUnitFramework/tests/Constraints/AnyOfConstraintTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,23 @@ public void MissingMember()
{
Assert.That(42, Is.Not.AnyOf(0, -1, 100));
}

[Test]
public void ValidMemberUsingPropertiesComparer()
{
Assert.That(new XY(5, 12), Is.AnyOf(new XY(3, 4), new XY(5, 12)).UsingPropertiesComparer());
}

private sealed class XY
{
public XY(int x, int y)
{
X = x;
Y = y;
}

public int X { get; }
public int Y { get; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ public void FailsWhenValueIsMissing()
}

[Test]
public void SucceedsWhenValueIsPresentUsingContainKey()
public void SucceedsWhenValueIsPresentUsingContainValue()
{
var dictionary = new Dictionary<string, string> { { "Hello", "World" }, { "Hola", "Mundo" } };
Assert.That(dictionary, Does.ContainValue("Mundo"));
}

[Test]
public void SucceedsWhenValueIsNotPresentUsingContainKey()
public void SucceedsWhenValueIsNotPresentUsingContainValue()
{
var dictionary = new Dictionary<string, string> { { "Hello", "World" }, { "Hola", "Mundo" } };
Assert.That(dictionary, Does.Not.ContainValue("NotValue"));
Expand Down Expand Up @@ -77,5 +77,26 @@ public void UsingIsHonored()
Assert.That(dictionary,
new DictionaryContainsValueConstraint("UNIVERSE").Using<string>((x, y) => StringUtil.Compare(x, y, true)));
}

[Test]
public void UsingPropertiesComparerIsHonored()
{
var dictionary = new Dictionary<string, XY> { { "5", new(3, 4) }, { "13", new(5, 12) } };
var value = new XY(5, 12);
Assert.That(dictionary, Does.Not.ContainValue(value));
Assert.That(dictionary, Does.ContainValue(value).UsingPropertiesComparer());
}

private sealed class XY
{
public XY(double x, double y)
{
X = x;
Y = y;
}

public double X { get; }
public double Y { get; }
}
}
}
Loading