private static AggregatedDifference ValueDifference <TA, TE>(TA actual, string firstName, TE expected, int refIndex, ICollection <object> firstSeen) { var result = new AggregatedDifference(); if (expected == null) { if (actual != null) { result.Add(DifferenceDetails.DoesNotHaveExpectedValue(firstName, actual, expected, refIndex)); } return(result); } if (expected.Equals(actual)) { return(result); } if (actual != null) { var commonType = actual.GetType().FindCommonNumericalType(expected.GetType()); // we silently convert numerical value if (commonType != null) { var convertedActual = Convert.ChangeType(actual, commonType); var convertedExpected = Convert.ChangeType(expected, commonType); if (convertedExpected.Equals(convertedActual)) { return(result); } } if (firstSeen.Contains(actual)) { result.Add(DifferenceDetails.DoesNotHaveExpectedValue(firstName, actual, expected, 0)); return(result); } firstSeen = new List <object>(firstSeen) { actual }; if (actual.IsAnEnumeration(false) && expected.IsAnEnumeration(false)) { return(ValueDifferenceEnumerable(actual as IEnumerable, firstName, expected as IEnumerable, firstSeen)); } } result.Add(DifferenceDetails.DoesNotHaveExpectedValue(firstName, actual, expected, refIndex)); return(result); }
internal static AggregatedDifference FluentEquals <TS, TE>(TS sut, TE expected, EqualityMode mode, IEqualityComparer comparer = null) { var result = new AggregatedDifference(); if (comparer != null) { result.SetAsDifferent(!comparer.Equals(expected, sut)); return(result); } switch (mode) { case EqualityMode.FluentEquals: return(DifferenceFinders.ValueDifference(sut, SutLabel, expected)); case EqualityMode.OperatorEq: case EqualityMode.OperatorNeq: var actualType = sut.GetTypeWithoutThrowingException(); var expectedType = expected.GetTypeWithoutThrowingException(); var operatorName = mode == EqualityMode.OperatorEq ? "op_Equality" : "op_Inequality"; var ope = actualType .GetMethod(operatorName, new[] { actualType, expectedType }) ?? expectedType .GetMethod(operatorName, new[] { actualType, expectedType }); if (ope != null) { var ret = (bool)ope.Invoke(null, new object[] { sut, expected }); if (mode == EqualityMode.OperatorNeq) { ret = !ret; } result.SetAsDifferent(!ret); } else { result.SetAsDifferent(!Equals(sut, expected)); } break; case EqualityMode.Equals: result.SetAsDifferent(!Equals(expected, sut)); break; default: throw new NotSupportedException(); } return(result); }
private static AggregatedDifference ValueDifferenceArray(Array firstArray, string firstName, Array secondArray, ICollection <object> firstSeen) { var valueDifferences = new AggregatedDifference(); if (firstArray.Rank != secondArray.Rank) { valueDifferences.Add(DifferenceDetails.DoesNotHaveExpectedValue(firstName + ".Rank", firstArray.Rank, secondArray.Rank, 0)); return(valueDifferences); } for (var i = 0; i < firstArray.Rank; i++) { if (firstArray.SizeOfDimension(i) == secondArray.SizeOfDimension(i)) { continue; } valueDifferences.Add(DifferenceDetails.DoesNotHaveExpectedValue($"{firstName}.Dimension({i})", firstArray.SizeOfDimension(i), secondArray.SizeOfDimension(i), i)); return(valueDifferences); } var indices = new int[firstArray.Rank]; var secondIndices = new int[secondArray.Rank]; for (var i = 0; i < firstArray.Length; i++) { var temp = i; var label = new StringBuilder("["); for (var j = 0; j < firstArray.Rank; j++) { var currentIndex = temp % firstArray.SizeOfDimension(j); label.Append(currentIndex.ToString()); label.Append(j < firstArray.Rank - 1 ? "," : "]"); indices[j] = currentIndex + firstArray.GetLowerBound(j); secondIndices[j] = currentIndex + secondArray.GetLowerBound(j); temp /= firstArray.SizeOfDimension(j); } var firstEntry = firstArray.GetValue(indices); var secondEntry = secondArray.GetValue(secondIndices); valueDifferences.Merge(ValueDifference(firstEntry, firstName + label, secondEntry, i, firstSeen)); } return(valueDifferences); }
private static AggregatedDifference ValueDifference <TA, TE>(TA firstItem, string firstName, TE otherItem, string secondName, int refIndex, List <object> firstSeen, List <object> secondSeen) { var result = new AggregatedDifference(); if (firstItem == null) { if (otherItem != null) { result.Add(new DifferenceDetails(firstName, null, secondName, otherItem, refIndex)); } return(result); } if (firstItem.Equals(otherItem)) { return(result); } if (otherItem != null) { if (firstItem.GetType().IsArray&& otherItem.GetType().IsArray) { return(ValueDifferenceArray(firstItem as Array, firstName, otherItem as Array, secondName, firstSeen, secondSeen)); } if (firstItem is IDictionary firstDico && otherItem is IDictionary secondDico) { return(ValueDifferenceDictionary(firstDico, firstName, secondDico, secondName, firstSeen, secondSeen)); } else if (!(firstItem is string) && !(otherItem is string) && firstItem is IEnumerable first && otherItem is IEnumerable second) { return(ValueDifferenceEnumerable(first, firstName, second, secondName, refIndex, firstSeen, secondSeen)); } if (firstItem.GetType().IsNumerical() && otherItem.GetType().IsNumerical()) { var changeType = Convert.ChangeType(firstItem, otherItem.GetType(), null); if (otherItem.Equals(changeType)) { return(result); } } }
private static AggregatedDifference AnonymousTypeDifference <TA, TE>(TA actual, TE expected, Type type, AggregatedDifference result) { var criteria = new ClassMemberCriteria(BindingFlags.Instance); criteria.SetPublic(); criteria.CaptureProperties(); criteria.CaptureFields(); // use field based comparison var wrapper = ReflectionWrapper.BuildFromInstance(type, expected, criteria); var actualWrapped = ReflectionWrapper.BuildFromInstance(actual.GetType(), actual, criteria); foreach (var match in actualWrapped.MemberMatches(wrapper).Where(match => !match.DoValuesMatches)) { result.Add(DifferenceDetails.FromMatch(match)); } return(result); }
private static AggregatedDifference ValueDifferenceEnumerable(IEnumerable firstItem, string firstName, IEnumerable otherItem, ICollection <object> firstSeen) { if (firstItem.GetType().IsArray&& otherItem.GetType().IsArray) { return(ValueDifferenceArray(firstItem as Array, firstName, otherItem as Array, firstSeen)); } if (firstItem is IDictionary firstDico && otherItem is IDictionary secondDico) { return(ValueDifferenceDictionary(firstDico, firstName, secondDico, firstSeen)); } var valueDifferences = new AggregatedDifference(); var scanner = otherItem.GetEnumerator(); var index = 0; foreach (var item in firstItem) { var firstItemName = $"{firstName}[{index}]"; if (!scanner.MoveNext()) { valueDifferences.Add(DifferenceDetails.WasNotExpected(firstItemName, item, index)); break; } valueDifferences.Merge(ValueDifference(item, firstItemName, scanner.Current, index, firstSeen)); index++; } if (scanner.MoveNext()) { valueDifferences.Add(DifferenceDetails.WasNotFound($"{firstName}[{index}]", scanner.Current, index)); } return(valueDifferences); }
private static AggregatedDifference ValueDifferenceArray(Array firstArray, string firstName, Array secondArray, ICollection <object> firstSeen) { var valueDifferences = new AggregatedDifference(); // TODO: consider providing more details when dimension(s) differs if (firstArray.Rank != secondArray.Rank) { valueDifferences.Add(DifferenceDetails.DoesNotHaveExpectedAttribute(firstName + ".Rank", firstArray.Rank, secondArray.Rank, 0)); return(valueDifferences); } for (var i = 0; i < firstArray.Rank; i++) { if (firstArray.SizeOfDimension(i) == secondArray.SizeOfDimension(i)) { continue; } valueDifferences.Add(DifferenceDetails.DoesNotHaveExpectedAttribute($"{firstName}.Dimension({i})", firstArray.SizeOfDimension(i), secondArray.SizeOfDimension(i), i)); return(valueDifferences); } return(ScanEnumeration(firstArray, secondArray, (index) => { var temp = index; var indices = new int[firstArray.Rank]; for (var j = 0; j < firstArray.Rank; j++) { var currentIndex = temp % firstArray.SizeOfDimension(j); indices[firstArray.Rank - j - 1] = currentIndex; temp /= firstArray.SizeOfDimension(j); } return $"actual[{string.Join(",", indices.Select(x=> x.ToString()).ToArray())}]"; }, firstSeen)); }
private static AggregatedDifference FluentEquals <TS, TE>(TS instance, TE expected, EqualityMode mode) { var result = new AggregatedDifference(); var ret = false; switch (mode) { case EqualityMode.FluentEquals: return(ValueDifference(instance, SutLabel, expected, ExpectedLabel)); case EqualityMode.OperatorEq: case EqualityMode.OperatorNeq: ret = Equals(instance, expected); var actualType = instance.GetTypeWithoutThrowingException(); var expectedType = expected.GetTypeWithoutThrowingException(); var operatorName = mode == EqualityMode.OperatorEq ? "op_Equality" : "op_Inequality"; var ope = actualType .GetMethod(operatorName, new[] { actualType, expectedType }) ?? expectedType .GetMethod(operatorName, new[] { actualType, expectedType }); if (ope != null) { ret = (bool)ope.Invoke(null, new object[] { instance, expected }); if (mode == EqualityMode.OperatorNeq) { ret = !ret; } } break; } if (!ret) { result.Add(new DifferenceDetails(SutLabel, instance, ExpectedLabel, expected, 0)); } return(result); }
/// <summary> /// Check Equality between actual and expected and provides details regarding differences, if any. /// </summary> /// <remarks> /// Is recursive. /// Algorithm focuses on value comparison, to better match expectations. Here is a summary of the logic: /// 1. deals with expected = null case /// 2. tries Equals, if success, values are considered equals /// 3. if there is recursion (self referencing object), values are assumed as different /// 4. if both values are numerical, compare them after conversion if needed. /// 5. if expected is an anonymous type, use a property based comparison /// 6. if both are enumerations, perform enumeration comparison /// 7. report values as different. /// </remarks> /// <typeparam name="TA">type of the actual value</typeparam> /// <typeparam name="TE">type of the expected value</typeparam> /// <param name="actual">actual value</param> /// <param name="firstName">name/label to use for messages</param> /// <param name="expected">expected value</param> /// <param name="refIndex">reference index (for collections)</param> /// <param name="firstSeen">track recursion</param> /// <returns></returns> private static AggregatedDifference ValueDifference <TA, TE>(TA actual, string firstName, TE expected, int refIndex, ICollection <object> firstSeen) { var result = new AggregatedDifference(); // handle null case first if (expected == null) { if (actual != null) { result.Add(DifferenceDetails.DoesNotHaveExpectedValue(firstName, actual, null, refIndex)); } return(result); } // if both equals from a BCL perspective, we are done. if (EqualityHelper.CustomEquals(expected, actual)) { return(result); } // handle actual is null if (actual == null) { result.Add(DifferenceDetails.DoesNotHaveExpectedValue(firstName, null, expected, refIndex)); return(result); } // do not recurse if (firstSeen.Contains(actual)) { result.Add(DifferenceDetails.DoesNotHaveExpectedValue(firstName, actual, expected, 0)); return(result); } firstSeen = new List <object>(firstSeen) { actual }; // deals with numerical var type = expected.GetType(); var commonType = actual.GetType().FindCommonNumericalType(type); // we silently convert numerical values if (commonType != null) { return(NumericalValueDifference(actual, firstName, expected, refIndex, commonType, result)); } if (type.TypeIsAnonymous()) { return(AnonymousTypeDifference(actual, expected, type, result)); } // handle enumeration if (actual.IsAnEnumeration(false) && expected.IsAnEnumeration(false)) { return(ValueDifferenceEnumerable(actual as IEnumerable, firstName, expected as IEnumerable, firstSeen)); } result.Add(DifferenceDetails.DoesNotHaveExpectedValue(firstName, actual, expected, refIndex)); return(result); }
private static AggregatedDifference ValueDifferenceDictionary(IReadOnlyDictionary <object, object> sutDictionary, string sutName, IReadOnlyDictionary <object, object> expectedDictionary, ICollection <object> firstItemsSeen) { var valueDifferences = new AggregatedDifference { IsEquivalent = true }; using var actualKeyIterator = sutDictionary.Keys.GetEnumerator(); using var expectedKeyIterator = expectedDictionary.Keys.GetEnumerator(); var stillExpectedKeys = true; var stillActualKeys = true; var index = 0; for (;;) { stillExpectedKeys = stillExpectedKeys && expectedKeyIterator.MoveNext(); stillActualKeys = stillActualKeys && actualKeyIterator.MoveNext(); if (!stillExpectedKeys) { // no more expected keys if (!stillActualKeys) { // we're done break; } // the sut has extra key(s) valueDifferences.Add(DifferenceDetails.WasNotExpected( $"{sutName}[{actualKeyIterator.Current.ToStringProperlyFormatted()}]", sutDictionary[actualKeyIterator.Current], index)); valueDifferences.IsEquivalent = false; } else if (!stillActualKeys) { // key not found valueDifferences.IsEquivalent = false; valueDifferences.Add(DifferenceDetails.WasNotFound( $"{sutName}[{expectedKeyIterator.Current.ToStringProperlyFormatted()}]", // ReSharper disable once AssignNullToNotNullAttribute new DictionaryEntry(expectedKeyIterator.Current, expectedDictionary[expectedKeyIterator.Current]), 0)); } else { var actualKey = actualKeyIterator.Current; var actualKeyName = $"{sutName} key[{index}]"; var itemDiffs = ValueDifference(actualKey, actualKeyName, expectedKeyIterator.Current, index, firstItemsSeen); if (!expectedDictionary.TryGetValue(actualKey !, out _)) { valueDifferences.Add(DifferenceDetails.WasNotExpected( $"{sutName}'s key {actualKey.ToStringProperlyFormatted()}", sutDictionary[actualKey], index)); valueDifferences.IsEquivalent = false; }
private static AggregatedDifference NumericalValueDifference <TA, TE>(TA actual, string firstName, TE expected, int refIndex, Type commonType, AggregatedDifference result) { var convertedActual = Convert.ChangeType(actual, commonType); var convertedExpected = Convert.ChangeType(expected, commonType); if (convertedExpected.Equals(convertedActual)) { return(result); } result.Add(DifferenceDetails.DoesNotHaveExpectedValue(firstName, actual, expected, refIndex)); return(result); }
public void Merge(AggregatedDifference other) { this.details.AddRange(other.details); }
private static AggregatedDifference ValueDifferenceDictionary(IDictionary sutDico, string sutName, IDictionary expectedDico, ICollection <object> firstItemsSeen) { var valueDifferences = new AggregatedDifference { IsEquivalent = true }; var actualKeyIterator = sutDico.Keys.GetEnumerator(); var expectedKeyIterator = expectedDico.Keys.GetEnumerator(); var stillExpectedKeys = true; var stillActualKeys = true; var index = 0; for (;;) { stillExpectedKeys = stillExpectedKeys && expectedKeyIterator.MoveNext(); stillActualKeys = stillActualKeys && actualKeyIterator.MoveNext(); if (!stillExpectedKeys) { // no more expected keys if (!stillActualKeys) { // we're done break; } // the sut has extra key(s) valueDifferences.Add(DifferenceDetails.WasNotExpected($"{sutName}[{actualKeyIterator.Current.ToStringProperlyFormatted()}]", sutDico[actualKeyIterator.Current], index)); valueDifferences.IsEquivalent = false; } else if (!stillActualKeys) { // key not found valueDifferences.IsEquivalent = false; valueDifferences.Add(DifferenceDetails.WasNotFound($"{sutName}[{expectedKeyIterator.Current.ToStringProperlyFormatted()}]", expectedDico[expectedKeyIterator.Current], 0)); } else { var actualKey = actualKeyIterator.Current; var itemDiffs = ValueDifference(actualKey, $"{sutName} key[{index}]", expectedKeyIterator.Current, index, firstItemsSeen); if (!itemDiffs.IsDifferent) { // same key, check the values var keyAsString = actualKey.ToStringProperlyFormatted(); itemDiffs = ValueDifference(sutDico[actualKey], $"{sutName}[{keyAsString}]", expectedDico[actualKey], index, firstItemsSeen); valueDifferences.IsEquivalent &= !itemDiffs.IsDifferent; } else if (valueDifferences.IsEquivalent) { // check if the dictionaries are equivalent anyway valueDifferences.IsEquivalent = expectedDico.Contains(actualKey) && FluentEquals(sutDico[actualKey], expectedDico[actualKey]); } valueDifferences.Merge(itemDiffs); } index++; } return(valueDifferences); }
private static AggregatedDifference ScanEnumeration(IEnumerable firstItem, IEnumerable otherItem, Func <int, string> namingCallback, ICollection <object> firstSeen) { var index = 0; var mayBeEquivalent = true; var expected = new List <KeyValuePair <object, int> >(); var unexpected = new List <KeyValuePair <object, int> >(); var aggregatedDifferences = new Dictionary <int, AggregatedDifference>(); var valueDifferences = new AggregatedDifference(); var scanner = otherItem.GetEnumerator(); foreach (var item in firstItem) { var firstItemName = namingCallback(index); if (!scanner.MoveNext()) { valueDifferences.Add(DifferenceDetails.WasNotExpected(firstItemName, item, index)); unexpected.Add(new KeyValuePair <object, int>(item, index)); break; } var aggregatedDifference = ValueDifference(item, firstItemName, scanner.Current, index, firstSeen); if (aggregatedDifference.IsDifferent) { aggregatedDifferences.Add(index, aggregatedDifference); if (!aggregatedDifference.IsEquivalent) { // try to see it was at a different position var indexOrigin = expected.FindIndex(pair => FluentEquivalent(pair.Key, item)); if (indexOrigin >= 0) { // we found the value at another index valueDifferences.Add(DifferenceDetails.WasFoundElseWhere(firstItemName, item, index, expected[indexOrigin].Value)); expected.RemoveAt(indexOrigin); aggregatedDifferences.Remove(indexOrigin); aggregatedDifferences.Remove(index); } else { unexpected.Add(new KeyValuePair <object, int>(item, index)); } // what about the expected value var indexOther = unexpected.FindIndex(pair => FluentEquivalent(pair.Key, scanner.Current)); if (indexOther >= 0) { valueDifferences.Add(DifferenceDetails.WasFoundElseWhere(firstItemName, unexpected[indexOther].Key, unexpected[indexOther].Value, index)); aggregatedDifferences.Remove(unexpected[indexOther].Value); unexpected.RemoveAt(indexOther); } else { expected.Add(new KeyValuePair <object, int>(scanner.Current, index)); } } } index++; } if (scanner.MoveNext()) { valueDifferences.Add(DifferenceDetails.WasNotFound(namingCallback(index), scanner.Current, index)); mayBeEquivalent = false; } foreach (var differencesValue in aggregatedDifferences.Values) { valueDifferences.Merge(differencesValue); } for (var i = 0; i < Math.Min(unexpected.Count, expected.Count); i++) { //aggregatedDifferences.Remove(unexpected[i].Value); valueDifferences.Add(DifferenceDetails.WasFoundInsteadOf(namingCallback(unexpected[i].Value), unexpected[i].Key, expected[i].Key)); } if (mayBeEquivalent && valueDifferences.IsDifferent) { valueDifferences.IsEquivalent = expected.Count == 0 && unexpected.Count == 0; } return(valueDifferences); }
private static AggregatedDifference ValueDifferenceDictionary(IReadOnlyDictionary <object, object> sutDico, string sutName, IReadOnlyDictionary <object, object> expectedDico, ICollection <object> firstItemsSeen) { // TODO: improve error messages var valueDifferences = new AggregatedDifference { IsEquivalent = true }; var actualKeyIterator = sutDico.Keys.GetEnumerator(); var expectedKeyIterator = expectedDico.Keys.GetEnumerator(); var stillExpectedKeys = true; var stillActualKeys = true; var index = 0; for (; ;) { stillExpectedKeys = stillExpectedKeys && expectedKeyIterator.MoveNext(); stillActualKeys = stillActualKeys && actualKeyIterator.MoveNext(); if (!stillExpectedKeys) { // no more expected keys if (!stillActualKeys) { // we're done break; } // the sut has extra key(s) valueDifferences.Add(DifferenceDetails.WasNotExpected($"{sutName}[{actualKeyIterator.Current.ToStringProperlyFormatted()}]", sutDico[actualKeyIterator.Current], index)); valueDifferences.IsEquivalent = false; } else if (!stillActualKeys) { // key not found valueDifferences.IsEquivalent = false; valueDifferences.Add(DifferenceDetails.WasNotFound($"{sutName}[{expectedKeyIterator.Current.ToStringProperlyFormatted()}]", expectedDico[expectedKeyIterator.Current], 0)); } else { var actualKey = actualKeyIterator.Current; var actualKeyName = $"{sutName} key[{index}]"; var itemDiffs = ValueDifference(actualKey, actualKeyName, expectedKeyIterator.Current, index, firstItemsSeen); if (!itemDiffs.IsDifferent) { // same key, check the values itemDiffs = ValueDifference(sutDico[actualKey], $"{sutName}[{actualKey.ToStringProperlyFormatted()}]", expectedDico[actualKey], index, firstItemsSeen); valueDifferences.IsEquivalent &= (!itemDiffs.IsDifferent || itemDiffs.IsEquivalent); } else //if (valueDifferences.IsEquivalent) { // check if the dictionaries are equivalent anyway var expectedIndex = expectedDico.ContainsKey(actualKey) ? expectedDico.Keys.ToList().FindIndex(x => x == actualKey) : -1; if (expectedIndex >= 0) { itemDiffs = ValueDifference(sutDico[actualKey], $"{sutName}[{actualKey.ToStringProperlyFormatted()}]", expectedDico[actualKey], index, firstItemsSeen); valueDifferences.IsEquivalent &= itemDiffs.IsEquivalent || !itemDiffs.IsDifferent; valueDifferences.Add( DifferenceDetails.WasFoundElseWhere($"{sutName} entry {actualKey.ToStringProperlyFormatted()}", expectedDico[actualKey], index, expectedIndex)); } else { valueDifferences.Add(DifferenceDetails.WasNotExpected($"{sutName}'s key {actualKey.ToStringProperlyFormatted()}", sutDico[actualKey], index)); valueDifferences.IsEquivalent = false; } } valueDifferences.Merge(itemDiffs); } index++; } return(valueDifferences); }