Skip to content

Commit

Permalink
✨ deep equality tester will compare nullable properties of the same type
Browse files Browse the repository at this point in the history
  • Loading branch information
fluffynuts committed Jun 3, 2024
1 parent 38359ad commit efe7da8
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,37 @@ public void ShouldNotIncludeStaticPropsInComparison()
.To.Be.False();
}

[Test]
public void ShouldBeAbleToCompareNullableAndNonNullablePropsOfSameNameAndUnderlyingType()
{
// Arrange
var date = GetRandom<DateTime>();
var left = new HasDate() { Date = date };
var right = new HasNullableDate() { Date = date };
var sut = Create(left, right);
// Act
var result = sut.AreDeepEqual();
// Assert
Expect(result)
.To.Be.True();
}

public class HasDate
{
public DateTime Date { get; set; }
}

public class HasNullableDate
{
public DateTime? Date { get; set; }
}

public class HasAStaticProp
{
public static int Id { get; set; }
public string Name { get; set; }
}



public class Node
{
public Node Child { get; set; }
Expand Down
83 changes: 60 additions & 23 deletions source/Utils/PeanutButter.Utils/DeepEqualityTester.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ private bool CompareEnums(object objSource, object objCompare)
case EnumComparisonStrategies.ByObjectEquals:
return objSource.Equals(objCompare);
case EnumComparisonStrategies.ByIntegerValue:
return (int) objSource == (int) objCompare;
return (int)objSource == (int)objCompare;
case EnumComparisonStrategies.ByName:
return objSource.ToString() == objCompare.ToString();
default:
Expand All @@ -288,7 +288,8 @@ private bool AreBothEnumTypes(Type sourceType, Type compareType)
Type sourceType,
object objSource,
Type compareType,
object objCompare)
object objCompare
)
{
// naive simple equality tester:
// if the types match, use .Equals, otherwise attempt upcasting to decimal
Expand All @@ -310,15 +311,17 @@ private bool AreBothEnumTypes(Type sourceType, Type compareType)

private bool PerformDecimalEquals(
decimal left,
decimal right)
decimal right
)
{
var customResult = TryCompareWithCustomComparer(left, right);
return customResult ?? left.Equals(right);
}

private bool PerformSameTypeEquals(
object left,
object right)
object right
)
{
var customResult = TryCompareWithCustomComparer(left, right);
if (customResult.HasValue)
Expand Down Expand Up @@ -354,7 +357,14 @@ object right
var method = TryCompareWithCustomComparerGenericMethod.MakeGenericMethod(left.GetType());
try
{
return (bool?) method.Invoke(this, new[] { left, right });
return (bool?)method.Invoke(
this,
new[]
{
left,
right
}
);
}
catch
{
Expand Down Expand Up @@ -416,15 +426,23 @@ private bool IgnoreDateTimeKind()
Type sourceType,
object objSource,
Type compareType,
object objCompare)
object objCompare
)
{
var sourceItemType = GetItemTypeFor(sourceType);
var compareItemType = GetItemTypeFor(compareType);
var method = DeepCollectionCompareGenericMethod.MakeGenericMethod(
sourceItemType,
compareItemType
);
return (bool) method.Invoke(this, new[] { objSource, objCompare });
return (bool)method.Invoke(
this,
new[]
{
objSource,
objCompare
}
);
}

private static Type GetItemTypeFor(Type collectionType)
Expand Down Expand Up @@ -467,7 +485,8 @@ IEnumerable<T2> compare
index++,
cur.Item1,
cur.Item2
));
)
);
}

private bool DeepCompareAtIndex(
Expand Down Expand Up @@ -496,7 +515,8 @@ private void ClearPendingOperations()
private static bool CanBeComparedWithEquals(Type t)
{
var equatableInterface = EquatableInterfaces.FindOrAdd(
t, () => EquatableGenericType.MakeGenericType(t)
t,
() => EquatableGenericType.MakeGenericType(t)
);
return t.Implements(equatableInterface)
|| IsSimpleTypeOrNullableOfSimpleType(t);
Expand Down Expand Up @@ -531,7 +551,8 @@ private static bool CanPerformSimpleTypeMatchFor(Type srcPropType)

private PropertyOrField FindMatchingPropertyInfoFor(
PropertyOrField srcPropInfo,
IEnumerable<PropertyOrField> compareProperties)
IEnumerable<PropertyOrField> compareProperties
)
{
var comparePropInfo = compareProperties.FirstOrDefault(
pi => pi.Name == srcPropInfo.Name
Expand Down Expand Up @@ -586,9 +607,11 @@ bool eitherAreEnumerable

private bool TypesAreComparable(Type srcType, Type compareType)
{
var actualSrcType = srcType.ResolveNullableUnderlyingType();
var actualCompareType = compareType.ResolveNullableUnderlyingType();
return _comparableStrategies.Aggregate(
false,
(acc, cur) => acc || cur(this, srcType, compareType)
(acc, cur) => acc || cur(this, actualSrcType, actualCompareType)
);
}

Expand Down Expand Up @@ -616,7 +639,8 @@ Type arg3
private static bool TypesAreBothEnums(
DeepEqualityTester arg1,
Type arg2,
Type arg3)
Type arg3
)
{
return arg2.IsEnum &&
arg3.IsEnum;
Expand Down Expand Up @@ -657,10 +681,8 @@ Type compareType
Tuple.Create(typeof(int), typeof(float)),
Tuple.Create(typeof(int), typeof(short)),
Tuple.Create(typeof(int), typeof(double)),

Tuple.Create(typeof(long), typeof(short)),
Tuple.Create(typeof(long), typeof(float)),

Tuple.Create(typeof(float), typeof(double)),
Tuple.Create(typeof(float), typeof(decimal)),
Tuple.Create(typeof(double), typeof(decimal))
Expand Down Expand Up @@ -753,7 +775,8 @@ object objCompare
$"{DumpPropertyInfo(srcPropInfos)}",
$"\nComparison has {compareProps.Length} properties:",
$"{DumpPropertyInfo(compareProps)}"
));
)
);
return false;
}

Expand Down Expand Up @@ -819,7 +842,8 @@ IEnumerable<PropertyOrField> right
object objSource,
object objCompare,
PropertyOrField[] srcPropInfos,
PropertyOrField[] comparePropInfos)
PropertyOrField[] comparePropInfos
)
{
var didAnyComparison = false;
var finalResult = srcPropInfos.Aggregate(
Expand All @@ -837,7 +861,8 @@ IEnumerable<PropertyOrField> right
!FailOnMissingProperties) ||
(compareProp != null &&
PropertyValuesMatchFor(objSource, objCompare, srcProp, compareProp));
});
}
);
return (srcPropInfos.IsEmpty() || didAnyComparison) && finalResult;
}

Expand All @@ -861,7 +886,8 @@ private bool IsPending(object objSource, object objCompare)
object objSource,
object objCompare,
PropertyOrField srcProp,
PropertyOrField compareProp)
PropertyOrField compareProp
)
{
var canReadSource = srcProp.TryGetValue(objSource, out var srcValue, out var srcReadException);
var canReadTarget = compareProp.TryGetValue(objCompare, out var compareValue, out var compareReadException);
Expand Down Expand Up @@ -914,7 +940,8 @@ object targetType
private bool TryWrapEnumerable(
object value,
out object wrapped,
out Type wrappedType)
out Type wrappedType
)
{
wrapped = null;
wrappedType = null;
Expand Down Expand Up @@ -943,11 +970,13 @@ PropertyOrField compareProp
TryResolveEnumerable(
ref srcValue,
srcProp,
out var srcEnumerableInterface);
out var srcEnumerableInterface
);
TryResolveEnumerable(
ref compareValue,
compareProp,
out var compareEnumerableInterface);
out var compareEnumerableInterface
);

if (srcEnumerableInterface == null &&
compareEnumerableInterface == null)
Expand All @@ -973,7 +1002,8 @@ PropertyOrField compareProp
private void TryResolveEnumerable(
ref object value,
PropertyOrField prop,
out Type resolvedType)
out Type resolvedType
)
{
var enumerableInterface = TryGetEnumerableInterfaceFor(prop);
if (enumerableInterface == null &&
Expand Down Expand Up @@ -1012,7 +1042,14 @@ Type compareEnumerableInterface
}

var typedMethod = genericMethod.MakeGenericMethod(t1, t2);
return (bool) typedMethod.Invoke(this, new[] { srcValue, compareValue });
return (bool)typedMethod.Invoke(
this,
new[]
{
srcValue,
compareValue
}
);
}


Expand Down

0 comments on commit efe7da8

Please sign in to comment.