Skip to content

Commit

Permalink
Idiomatic assertions for IEqualityComparer<T> (#1194)
Browse files Browse the repository at this point in the history
Co-authored-by: Andrei Ivascu <7030530+aivascu@users.noreply.github.com>
  • Loading branch information
Kralizek and aivascu committed Sep 10, 2020
1 parent d2fa483 commit 3092ff1
Show file tree
Hide file tree
Showing 17 changed files with 1,501 additions and 0 deletions.
38 changes: 38 additions & 0 deletions Src/Idioms/EqualityComparerAssertion.cs
@@ -0,0 +1,38 @@
using System.Collections.Generic;
using AutoFixture.Kernel;

namespace AutoFixture.Idioms
{
/// <summary>
/// Encapsulates a unit test that verifies that a type implementing <see cref="IEqualityComparer{T}"/> implements it correctly.
/// </summary>
public class EqualityComparerAssertion : CompositeIdiomaticAssertion
{
/// <summary>
/// Initializes a new instance of the <see cref="EqualityComparerAssertion"/> class.
/// </summary>
/// <param name="builder">
/// A composer which can create instances required to implement the idiomatic unit test,
/// such as the owner of the property, as well as the value to be assigned and read from
/// the member.
/// </param>
/// <remarks>
/// <para>
/// <paramref name="builder" /> will typically be a <see cref="Fixture" /> instance.
/// </para>
/// </remarks>
public EqualityComparerAssertion(ISpecimenBuilder builder)
: base(CreateChildAssertions(builder))
{
}

private static IEnumerable<IIdiomaticAssertion> CreateChildAssertions(ISpecimenBuilder builder)
{
yield return new EqualityComparerEqualsSelfAssertion(builder);
yield return new EqualityComparerEqualsSymmetricAssertion(builder);
yield return new EqualityComparerEqualsTransitiveAssertion(builder);
yield return new EqualityComparerGetHashCodeAssertion(builder);
yield return new EqualityComparerEqualsNullAssertion(builder);
}
}
}
66 changes: 66 additions & 0 deletions Src/Idioms/EqualityComparerEqualsAssertion.cs
@@ -0,0 +1,66 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using AutoFixture.Kernel;

namespace AutoFixture.Idioms
{
/// <summary>
/// A base class for building classes that encapsulate a unit test that verifies that a type implementing <see cref="IEqualityComparer{T}"/> implements it correctly.
/// </summary>
public abstract class EqualityComparerEqualsAssertion : IdiomaticAssertion
{
/// <summary>
/// Gets the builder supplied by the constructor.
/// </summary>
public ISpecimenBuilder Builder { get; }

/// <summary>
/// Initializes a new instance of the <see cref="EqualityComparerEqualsAssertion"/> class.
/// </summary>
/// <param name="builder">
/// A composer which can create instances required to implement the idiomatic unit test,
/// such as the owner of the property, as well as the value to be assigned and read from
/// the member.
/// </param>
/// <remarks>
/// <para>
/// <paramref name="builder" /> will typically be a <see cref="Fixture" /> instance.
/// </para>
/// </remarks>
protected EqualityComparerEqualsAssertion(ISpecimenBuilder builder)
{
this.Builder = builder ?? throw new ArgumentNullException(nameof(builder));
}

/// <summary>
/// Forwards the call to <see cref="VerifyEquals"/> if the supplied method is an implementation of <see cref="IEqualityComparer{T}.Equals(T,T)"/>.
/// </summary>
/// <param name="methodInfo">The method to verify.</param>
public override void Verify(MethodInfo methodInfo)
{
if (methodInfo == null) throw new ArgumentNullException(nameof(methodInfo));
if (!IsEqualityComparerEqualsMethod(methodInfo)) return;

this.VerifyEquals(methodInfo, methodInfo.GetParameters()[0].ParameterType);
}

/// <summary>
/// Abstract method used to verify if the method implements correctly <see cref="IEqualityComparer{T}.Equals(T,T)"/>.
/// </summary>
/// <param name="methodInfo">The method to verify.</param>
/// <param name="argumentType">The argument type of <see cref="IEqualityComparer{T}.Equals(T,T)"/>.</param>
protected abstract void VerifyEquals(MethodInfo methodInfo, Type argumentType);

private static bool IsEqualityComparerEqualsMethod(MethodInfo methodInfo)
{
return methodInfo is { Name: nameof(IEqualityComparer.Equals), ReflectedType: { } type }
&& methodInfo.GetParameters().Length == 2
&& type.ImplementsGenericInterfaceDefinition(typeof(IEqualityComparer<>))
&& type.ImplementsGenericInterface(
typeof(IEqualityComparer<>),
methodInfo.GetParameters()[0].ParameterType);
}
}
}
59 changes: 59 additions & 0 deletions Src/Idioms/EqualityComparerEqualsNullAssertion.cs
@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using AutoFixture.Kernel;

namespace AutoFixture.Idioms
{
/// <summary>
/// Encapsulates a unit test that verifies that a type implementing <see cref="IEqualityComparer{T}"/> implements it correctly
/// with respect of the rule: calling Equals(x, null) should return false.
/// </summary>
public class EqualityComparerEqualsNullAssertion : EqualityComparerEqualsAssertion
{
/// <summary>
/// Initializes a new instance of the <see cref="EqualityComparerEqualsNullAssertion"/> class.
/// </summary>
/// <param name="builder">
/// A composer which can create instances required to implement the idiomatic unit test,
/// such as the owner of the property, as well as the value to be assigned and read from
/// the member.
/// </param>
/// <remarks>
/// <para>
/// <paramref name="builder" /> will typically be a <see cref="Fixture" /> instance.
/// </para>
/// </remarks>
public EqualityComparerEqualsNullAssertion(ISpecimenBuilder builder)
: base(builder)
{
}

/// <summary>
/// Verifies that `calling Equals(x, null) should return false`
/// if the supplied method is an implementation of <see cref="IEqualityComparer{T}.Equals(T,T)"/>.
/// </summary>
/// <param name="methodInfo">The method to verify.</param>
/// <param name="argumentType">The argument type of <see cref="IEqualityComparer{T}.Equals(T,T)"/>.</param>
protected override void VerifyEquals(MethodInfo methodInfo, Type argumentType)
{
if (methodInfo == null) throw new ArgumentNullException(nameof(methodInfo));
if (argumentType == null) throw new ArgumentNullException(nameof(argumentType));
if (methodInfo.ReflectedType!.IsValueType) return;

var comparer = this.Builder.CreateAnonymous(methodInfo.ReflectedType);
var testSubject = this.Builder.CreateAnonymous(argumentType);

var result = (bool)methodInfo.Invoke(comparer, new[] { testSubject, null });

if (result)
{
throw new EqualityComparerImplementationException(string.Format(CultureInfo.CurrentCulture,
"The type '{0}' implements the `IEqualityComparer<T>` interface incorrectly: " +
"calling Equals(x, null) should return false.",
methodInfo.ReflectedType!.FullName));
}
}
}
}
58 changes: 58 additions & 0 deletions Src/Idioms/EqualityComparerEqualsNullNullAssertion.cs
@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using AutoFixture.Kernel;

namespace AutoFixture.Idioms
{
/// <summary>
/// Encapsulates a unit test that verifies that a type implementing <see cref="IEqualityComparer{T}"/> implements it correctly
/// with respect of the rule: calling Equals(null, null) should return true.
/// </summary>
public class EqualityComparerEqualsNullNullAssertion : EqualityComparerEqualsAssertion
{
/// <summary>
/// Initializes a new instance of the <see cref="EqualityComparerEqualsNullNullAssertion"/> class.
/// </summary>
/// <param name="builder">
/// A composer which can create instances required to implement the idiomatic unit test,
/// such as the owner of the property, as well as the value to be assigned and read from
/// the member.
/// </param>
/// <remarks>
/// <para>
/// <paramref name="builder" /> will typically be a <see cref="Fixture" /> instance.
/// </para>
/// </remarks>
public EqualityComparerEqualsNullNullAssertion(ISpecimenBuilder builder)
: base(builder)
{
}

/// <summary>
/// Verifies that `calling Equals(null, null) should return true`
/// if the supplied method is an implementation of <see cref="IEqualityComparer{T}.Equals(T,T)"/>.
/// </summary>
/// <param name="methodInfo">The method to verify.</param>
/// <param name="argumentType">The argument type of <see cref="IEqualityComparer{T}.Equals(T,T)"/>.</param>
protected override void VerifyEquals(MethodInfo methodInfo, Type argumentType)
{
if (methodInfo == null) throw new ArgumentNullException(nameof(methodInfo));
if (argumentType == null) throw new ArgumentNullException(nameof(argumentType));
if (methodInfo.ReflectedType!.IsValueType) return;

var comparer = this.Builder.CreateAnonymous(methodInfo.ReflectedType);

var result = (bool)methodInfo.Invoke(comparer, new object[] { null, null });

if (!result)
{
throw new EqualityComparerImplementationException(string.Format(CultureInfo.CurrentCulture,
"The type '{0}' implements the `IEqualityComparer<T>` interface incorrectly: " +
"calling Equals(null, null) should return true.",
methodInfo.ReflectedType!.FullName));
}
}
}
}
58 changes: 58 additions & 0 deletions Src/Idioms/EqualityComparerEqualsSelfAssertion.cs
@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using AutoFixture.Kernel;

namespace AutoFixture.Idioms
{
/// <summary>
/// Encapsulates a unit test that verifies that a type implementing <see cref="IEqualityComparer{T}"/> implements it correctly
/// with respect of the rule: calling Equals(x, x) should return true.
/// </summary>
public class EqualityComparerEqualsSelfAssertion : EqualityComparerEqualsAssertion
{
/// <summary>
/// Initializes a new instance of the <see cref="EqualityComparerEqualsSelfAssertion"/> class.
/// </summary>
/// <param name="builder">
/// A composer which can create instances required to implement the idiomatic unit test,
/// such as the owner of the property, as well as the value to be assigned and read from
/// the member.
/// </param>
/// <remarks>
/// <para>
/// <paramref name="builder" /> will typically be a <see cref="Fixture" /> instance.
/// </para>
/// </remarks>
public EqualityComparerEqualsSelfAssertion(ISpecimenBuilder builder)
: base(builder)
{
}

/// <summary>
/// Verifies that `calling Equals(x, x) should return true`
/// if the supplied method is an implementation of <see cref="IEqualityComparer{T}.Equals(T,T)"/>.
/// </summary>
/// <param name="methodInfo">The method to verify.</param>
/// <param name="argumentType">The argument type of <see cref="IEqualityComparer{T}.Equals(T,T)"/>.</param>
protected override void VerifyEquals(MethodInfo methodInfo, Type argumentType)
{
if (methodInfo == null) throw new ArgumentNullException(nameof(methodInfo));
if (argumentType == null) throw new ArgumentNullException(nameof(argumentType));

var comparer = this.Builder.CreateAnonymous(methodInfo.ReflectedType);
var testSubject = this.Builder.CreateAnonymous(argumentType);

var result = (bool)methodInfo.Invoke(comparer, new[] { testSubject, testSubject });

if (!result)
{
throw new EqualityComparerImplementationException(string.Format(CultureInfo.CurrentCulture,
"The type '{0}' implements the `IEqualityComparer<T>` interface incorrectly: " +
"calling Equals(x, x) should return true.",
methodInfo.ReflectedType!.FullName));
}
}
}
}
60 changes: 60 additions & 0 deletions Src/Idioms/EqualityComparerEqualsSymmetricAssertion.cs
@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using AutoFixture.Kernel;

namespace AutoFixture.Idioms
{
/// <summary>
/// Encapsulates a unit test that verifies that a type implementing <see cref="IEqualityComparer{T}"/> implements it correctly
/// with respect of the rule: calling Equals(x, y) should return same as Equals(y, x).
/// </summary>
public class EqualityComparerEqualsSymmetricAssertion : EqualityComparerEqualsAssertion
{
/// <summary>
/// Initializes a new instance of the <see cref="EqualityComparerEqualsSymmetricAssertion"/> class.
/// </summary>
/// <param name="builder">
/// A composer which can create instances required to implement the idiomatic unit test,
/// such as the owner of the property, as well as the value to be assigned and read from
/// the member.
/// </param>
/// <remarks>
/// <para>
/// <paramref name="builder" /> will typically be a <see cref="Fixture" /> instance.
/// </para>
/// </remarks>
public EqualityComparerEqualsSymmetricAssertion(ISpecimenBuilder builder)
: base(builder)
{
}

/// <summary>
/// Verifies that `calling Equals(x, y) should return same as Equals(y, x)`
/// if the supplied method is an implementation of <see cref="IEqualityComparer{T}.Equals(T,T)"/>.
/// </summary>
/// <param name="methodInfo">The method to verify.</param>
/// <param name="argumentType">The argument type of <see cref="IEqualityComparer{T}.Equals(T,T)"/>.</param>
protected override void VerifyEquals(MethodInfo methodInfo, Type argumentType)
{
if (methodInfo == null) throw new ArgumentNullException(nameof(methodInfo));
if (argumentType == null) throw new ArgumentNullException(nameof(argumentType));

var comparer = this.Builder.CreateAnonymous(methodInfo.ReflectedType);
var firstTestSubject = this.Builder.CreateAnonymous(argumentType);
var secondTestSubject = this.Builder.CreateAnonymous(argumentType);

var directResult = (bool)methodInfo.Invoke(comparer, new[] { firstTestSubject, secondTestSubject });
var invertedResult = (bool)methodInfo.Invoke(comparer, new[] { secondTestSubject, firstTestSubject });

if (directResult != invertedResult)
{
throw new EqualityComparerImplementationException(string.Format(CultureInfo.CurrentCulture,
"The type '{0}' implements the `IEqualityComparer<T>` interface incorrectly: " +
"calling Equals(x, y) should return same as Equals(y, x).",
methodInfo.ReflectedType!.FullName));
}
}
}
}

0 comments on commit 3092ff1

Please sign in to comment.