Ejemplo n.º 1
0
        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);
        }
Ejemplo n.º 2
0
        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);
        }
Ejemplo n.º 3
0
        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);
        }
Ejemplo n.º 4
0
        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);
                    }
                }
            }
Ejemplo n.º 5
0
        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);
        }
Ejemplo n.º 6
0
        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);
        }
Ejemplo n.º 7
0
        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));
        }
Ejemplo n.º 8
0
        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);
        }
Ejemplo n.º 9
0
        /// <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);
        }
Ejemplo n.º 10
0
        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;
                    }
Ejemplo n.º 11
0
        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);
        }
Ejemplo n.º 12
0
 public void Merge(AggregatedDifference other)
 {
     this.details.AddRange(other.details);
 }
Ejemplo n.º 13
0
        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);
        }
Ejemplo n.º 14
0
        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);
        }
Ejemplo n.º 15
0
        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);
        }