NUnitEqualityComparer
NUnitEqualityComparer encapsulates NUnit's handling of
equality tests between objects.
using NUnit.Framework.Compatibility;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
namespace NUnit.Framework.Constraints
{
public class NUnitEqualityComparer
{
public class FailurePoint
{
public long Position;
public object ExpectedValue;
public object ActualValue;
public bool ExpectedHasData;
public bool ActualHasData;
}
private bool caseInsensitive;
private bool compareAsCollection;
private List<EqualityAdapter> externalComparers = new List<EqualityAdapter>();
private List<FailurePoint> failurePoints;
private static readonly int BUFFER_SIZE = 4096;
public static NUnitEqualityComparer Default => new NUnitEqualityComparer();
public bool IgnoreCase {
get {
return caseInsensitive;
}
set {
caseInsensitive = value;
}
}
public bool CompareAsCollection {
get {
return compareAsCollection;
}
set {
compareAsCollection = value;
}
}
public IList<EqualityAdapter> ExternalComparers => externalComparers;
public IList<FailurePoint> FailurePoints => failurePoints;
public bool WithSameOffset { get; set; }
public bool AreEqual(object x, object y, ref Tolerance tolerance)
{
failurePoints = new List<FailurePoint>();
if (x == null && y == null)
return true;
if (x == null || y == null)
return false;
if (object.ReferenceEquals(x, y))
return true;
Type type = x.GetType();
Type type2 = y.GetType();
Type left = TypeExtensions.GetTypeInfo(type).IsGenericType ? type.GetGenericTypeDefinition() : null;
Type left2 = TypeExtensions.GetTypeInfo(type2).IsGenericType ? type2.GetGenericTypeDefinition() : null;
EqualityAdapter externalComparer = GetExternalComparer(x, y);
if (externalComparer != null)
return externalComparer.AreEqual(x, y);
if (type.IsArray && type2.IsArray && !compareAsCollection)
return ArraysEqual((Array)x, (Array)y, ref tolerance);
if (x is IDictionary && y is IDictionary)
return DictionariesEqual((IDictionary)x, (IDictionary)y, ref tolerance);
if (x is DictionaryEntry && y is DictionaryEntry)
return DictionaryEntriesEqual((DictionaryEntry)x, (DictionaryEntry)y, ref tolerance);
if (left == typeof(KeyValuePair<, >) && left2 == typeof(KeyValuePair<, >)) {
Tolerance tolerance2 = Tolerance.Exact;
object value = type.GetProperty("Key").GetValue(x, null);
object value2 = type2.GetProperty("Key").GetValue(y, null);
object value3 = type.GetProperty("Value").GetValue(x, null);
object value4 = type2.GetProperty("Value").GetValue(y, null);
if (AreEqual(value, value2, ref tolerance2))
return AreEqual(value3, value4, ref tolerance);
return false;
}
if (x is string && y is string)
return StringsEqual((string)x, (string)y);
if (x is IEnumerable && y is IEnumerable)
return EnumerablesEqual((IEnumerable)x, (IEnumerable)y, ref tolerance);
if (x is Stream && y is Stream)
return StreamsEqual((Stream)x, (Stream)y);
if (x is char && y is char)
return CharsEqual((char)x, (char)y);
if (x is DirectoryInfo && y is DirectoryInfo)
return DirectoriesEqual((DirectoryInfo)x, (DirectoryInfo)y);
if (Numerics.IsNumericType(x) && Numerics.IsNumericType(y))
return Numerics.AreEqual(x, y, ref tolerance);
if (x is DateTimeOffset && y is DateTimeOffset) {
DateTimeOffset left3 = (DateTimeOffset)x;
DateTimeOffset right = (DateTimeOffset)y;
bool flag;
if (tolerance != null && tolerance.Value is TimeSpan) {
TimeSpan t = (TimeSpan)tolerance.Value;
flag = ((left3 - right).Duration() <= t);
} else
flag = (left3 == right);
if (flag && WithSameOffset)
flag = (left3.Offset == right.Offset);
return flag;
}
if (tolerance != null && tolerance.Value is TimeSpan) {
TimeSpan t2 = (TimeSpan)tolerance.Value;
if (x is DateTime && y is DateTime)
return ((DateTime)x - (DateTime)y).Duration() <= t2;
if (x is TimeSpan && y is TimeSpan)
return ((TimeSpan)x - (TimeSpan)y).Duration() <= t2;
}
if (FirstImplementsIEquatableOfSecond(type, type2))
return InvokeFirstIEquatableEqualsSecond(x, y);
if (type != type2 && FirstImplementsIEquatableOfSecond(type2, type))
return InvokeFirstIEquatableEqualsSecond(y, x);
return x.Equals(y);
}
private static bool FirstImplementsIEquatableOfSecond(Type first, Type second)
{
foreach (Type equatableGenericArgument in GetEquatableGenericArguments(first)) {
if (equatableGenericArgument.IsAssignableFrom(second))
return true;
}
return false;
}
private static IList<Type> GetEquatableGenericArguments(Type type)
{
List<Type> list = new List<Type>();
Type[] interfaces = type.GetInterfaces();
foreach (Type type2 in interfaces) {
if (TypeExtensions.GetTypeInfo(type2).IsGenericType && type2.GetGenericTypeDefinition().Equals(typeof(IEquatable<>)))
list.Add(type2.GetGenericArguments()[0]);
}
return list;
}
private static bool InvokeFirstIEquatableEqualsSecond(object first, object second)
{
MethodInfo correctGenericEqualsMethod = GetCorrectGenericEqualsMethod(first.GetType(), second.GetType());
if (!(correctGenericEqualsMethod != (MethodInfo)null))
return false;
return (bool)correctGenericEqualsMethod.Invoke(first, new object[1] {
second
});
}
private static MethodInfo GetCorrectGenericEqualsMethod(Type first, Type second)
{
MethodInfo[] methods = first.GetMethods();
MethodInfo[] array = methods;
foreach (MethodInfo methodInfo in array) {
if (methodInfo.Name == "Equals") {
ParameterInfo[] parameters = methodInfo.GetParameters();
if (parameters.Length == 1 && parameters[0].ParameterType != typeof(object) && parameters[0].ParameterType.IsAssignableFrom(second))
return methodInfo;
}
}
return null;
}
private EqualityAdapter GetExternalComparer(object x, object y)
{
foreach (EqualityAdapter externalComparer in externalComparers) {
if (externalComparer.CanCompare(x, y))
return externalComparer;
}
return null;
}
private bool ArraysEqual(Array x, Array y, ref Tolerance tolerance)
{
int rank = x.Rank;
if (rank != y.Rank)
return false;
for (int i = 1; i < rank; i++) {
if (x.GetLength(i) != y.GetLength(i))
return false;
}
return EnumerablesEqual(x, y, ref tolerance);
}
private bool DictionariesEqual(IDictionary x, IDictionary y, ref Tolerance tolerance)
{
if (x.Count != y.Count)
return false;
CollectionTally collectionTally = new CollectionTally(this, x.Keys);
if (!collectionTally.TryRemove(y.Keys) || collectionTally.Count > 0)
return false;
foreach (object key in x.Keys) {
if (!AreEqual(x[key], y[key], ref tolerance))
return false;
}
return true;
}
private bool DictionaryEntriesEqual(DictionaryEntry x, DictionaryEntry y, ref Tolerance tolerance)
{
Tolerance tolerance2 = Tolerance.Exact;
if (AreEqual(x.Key, y.Key, ref tolerance2))
return AreEqual(x.Value, y.Value, ref tolerance);
return false;
}
private bool CollectionsEqual(ICollection x, ICollection y, ref Tolerance tolerance)
{
IEnumerator enumerator = null;
IEnumerator enumerator2 = null;
try {
enumerator = x.GetEnumerator();
enumerator2 = y.GetEnumerator();
int num = 0;
bool flag;
bool flag2;
while (true) {
flag = enumerator.MoveNext();
flag2 = enumerator2.MoveNext();
if (!flag && !flag2)
return true;
if (flag != flag2 || !AreEqual(enumerator.Current, enumerator2.Current, ref tolerance))
break;
num++;
}
FailurePoint failurePoint = new FailurePoint();
failurePoint.Position = num;
failurePoint.ExpectedHasData = flag;
if (flag)
failurePoint.ExpectedValue = enumerator.Current;
failurePoint.ActualHasData = flag2;
if (flag2)
failurePoint.ActualValue = enumerator2.Current;
failurePoints.Insert(0, failurePoint);
return false;
} finally {
(enumerator as IDisposable)?.Dispose();
(enumerator2 as IDisposable)?.Dispose();
}
}
private bool StringsEqual(string x, string y)
{
string text = caseInsensitive ? x.ToLower() : x;
string value = caseInsensitive ? y.ToLower() : y;
return text.Equals(value);
}
private bool CharsEqual(char x, char y)
{
char c = caseInsensitive ? char.ToLower(x) : x;
char c2 = caseInsensitive ? char.ToLower(y) : y;
return c == c2;
}
private bool EnumerablesEqual(IEnumerable x, IEnumerable y, ref Tolerance tolerance)
{
IEnumerator enumerator = null;
IEnumerator enumerator2 = null;
try {
enumerator = x.GetEnumerator();
enumerator2 = y.GetEnumerator();
int num = 0;
bool flag;
bool flag2;
while (true) {
flag = enumerator.MoveNext();
flag2 = enumerator2.MoveNext();
if (!flag && !flag2)
return true;
if (flag != flag2 || !AreEqual(enumerator.Current, enumerator2.Current, ref tolerance))
break;
num++;
}
FailurePoint failurePoint = new FailurePoint();
failurePoint.Position = num;
failurePoint.ExpectedHasData = flag;
if (flag)
failurePoint.ExpectedValue = enumerator.Current;
failurePoint.ActualHasData = flag2;
if (flag2)
failurePoint.ActualValue = enumerator2.Current;
failurePoints.Insert(0, failurePoint);
return false;
} finally {
(enumerator as IDisposable)?.Dispose();
(enumerator2 as IDisposable)?.Dispose();
}
}
private static bool DirectoriesEqual(DirectoryInfo x, DirectoryInfo y)
{
if (x.Attributes != y.Attributes || x.CreationTime != y.CreationTime || x.LastAccessTime != y.LastAccessTime)
return false;
return new SamePathConstraint(x.FullName).ApplyTo(y.FullName).IsSuccess;
}
private bool StreamsEqual(Stream x, Stream y)
{
if (x == y)
return true;
if (!x.CanRead)
throw new ArgumentException("Stream is not readable", "expected");
if (!y.CanRead)
throw new ArgumentException("Stream is not readable", "actual");
if (!x.CanSeek)
throw new ArgumentException("Stream is not seekable", "expected");
if (!y.CanSeek)
throw new ArgumentException("Stream is not seekable", "actual");
if (x.Length != y.Length)
return false;
byte[] array = new byte[BUFFER_SIZE];
byte[] array2 = new byte[BUFFER_SIZE];
BinaryReader binaryReader = new BinaryReader(x);
BinaryReader binaryReader2 = new BinaryReader(y);
long position = x.Position;
long position2 = y.Position;
try {
binaryReader.BaseStream.Seek(0, SeekOrigin.Begin);
binaryReader2.BaseStream.Seek(0, SeekOrigin.Begin);
for (long num = 0; num < x.Length; num += BUFFER_SIZE) {
binaryReader.Read(array, 0, BUFFER_SIZE);
binaryReader2.Read(array2, 0, BUFFER_SIZE);
for (int i = 0; i < BUFFER_SIZE; i++) {
if (array[i] != array2[i]) {
FailurePoint failurePoint = new FailurePoint();
failurePoint.Position = num + i;
failurePoint.ExpectedHasData = true;
failurePoint.ExpectedValue = array[i];
failurePoint.ActualHasData = true;
failurePoint.ActualValue = array2[i];
failurePoints.Insert(0, failurePoint);
return false;
}
}
}
} finally {
x.Position = position;
y.Position = position2;
}
return true;
}
}
}