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); }
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); }
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); }
/// <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); }