< Summary

Class:Microsoft.Azure.Search.Tests.Utilities.ModelComparer`1
Assembly:Search.Management.Tests
File(s):C:\Git\azure-sdk-for-net\sdk\search\Microsoft.Azure.Management.Search\tests\ModelComparer\ModelComparer.cs
Covered lines:51
Uncovered lines:7
Coverable lines:58
Total lines:182
Line coverage:87.9% (51 of 58)
Covered branches:26
Total branches:40
Branch coverage:65% (26 of 40)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.ctor()-0%0%
.ctor(...)-100%100%
Equals(...)-100%100%
GetHashCode(...)-0%0%
.ctor(...)-100%50%
get_AreBothNull()-100%100%
get_ShouldIgnoreProperty()-100%100%
System.Collections.IEqualityComparer.GetHashCode(...)-0%0%
System.Collections.IEqualityComparer.Equals(...)-100%100%
CompareRecursive(...)-100%100%
CompareEnumerables(...)-83.33%66.67%
ComparePolymorphicObjects(...)-100%100%
CompareProperty(...)-100%100%
CompareProperties(...)-100%100%
ComparePossibleIntegers(...)-80%50%
CompareEquatables(...)-100%100%

File(s)

C:\Git\azure-sdk-for-net\sdk\search\Microsoft.Azure.Management.Search\tests\ModelComparer\ModelComparer.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License. See License.txt in the project root for
 3// license information.
 4
 5namespace Microsoft.Azure.Search.Tests.Utilities
 6{
 7    using System;
 8    using System.Collections;
 9    using System.Collections.Generic;
 10    using System.Linq;
 11    using System.Reflection;
 12
 13    /// <summary>
 14    /// Compares instances of a type for structural equality, taking into account public properties, collections, and di
 15    /// </summary>
 16    /// <typeparam name="T">Type of objects to compare.</typeparam>
 17    /// <remarks>
 18    /// <para>
 19    /// Supported types include enums, primitives, nullables, List, Dictionary, and DTOs having public properties
 20    /// composed of the supported types (including other nested DTOs). The rules are different for different types.
 21    /// </para>
 22    /// <para>
 23    /// Any type implementing IEnumerable, including dictionaries, are compared element by element and compose with the 
 24    /// rules on element type.
 25    /// </para>
 26    /// <para>
 27    /// DTOs are compared by the values of their public properties. Note that comparisons are performed according to the
 28    /// type of the objects (which must match), not the apparent static type of the references.
 29    /// </para>
 30    /// <para>
 31    /// Enums, primitive types, and any other type not falling into the other categories are compared using Object.Equal
 32    /// </para>
 33    /// </remarks>
 34    public sealed class ModelComparer<T> : IEqualityComparer<T>
 35    {
 36        private readonly IEqualityComparer _comparer;
 37
 038        public ModelComparer() : this(areBothNull: (x, y) => x == null && y == null, shouldIgnoreProperty: _ => false)
 39        {
 040        }
 41
 86042        public ModelComparer(Func<object, object, bool> areBothNull, Func<PropertyInfo, bool> shouldIgnoreProperty)
 43        {
 86044            _comparer = new DynamicModelComparer(typeof(T), areBothNull, shouldIgnoreProperty);
 86045        }
 46
 87447        public bool Equals(T x, T y) => _comparer.Equals(x, y);
 48
 049        public int GetHashCode(T obj) => obj?.GetHashCode() ?? 0;
 50
 51        private class DynamicModelComparer : IEqualityComparer
 52        {
 53            private readonly Type _type;
 54
 1708455            public DynamicModelComparer(Type type, Func<object, object, bool> areBothNull, Func<PropertyInfo, bool> shou
 56            {
 1708457                AreBothNull = areBothNull ?? throw new ArgumentNullException(nameof(areBothNull));
 1708458                ShouldIgnoreProperty = shouldIgnoreProperty ?? throw new ArgumentNullException(nameof(shouldIgnoreProper
 59
 1708460                _type = type;
 1708461            }
 62
 1921463            private Func<object, object, bool> AreBothNull { get; }
 64
 2751465            private Func<PropertyInfo, bool> ShouldIgnoreProperty { get; }
 66
 067            int IEqualityComparer.GetHashCode(object obj) => obj?.GetHashCode() ?? 0;
 68
 69            bool IEqualityComparer.Equals(object x, object y)
 70            {
 1776271                if (_type.CanBeNull() && (x == null || y == null))
 72                {
 299073                    return AreBothNull(x, y);
 74                }
 75
 76                // At this point x and y are guaranteed to be non-null (possibly because they are boxed value types).
 77
 1477278                if (_type.ImplementsGenericEquatable())
 79                {
 677480                    return CompareEquatables(x, y);
 81                }
 82
 799883                Type enumerable = _type.GetIEnumerable();
 799884                if (enumerable != null)
 85                {
 64286                    return CompareEnumerables(enumerable, x, y);
 87                }
 88
 735689                Type actualType = x.GetType();
 735690                if (y.GetType() != actualType)
 91                {
 92                    // The only case where x and y can have different types and still be equal is integer comparison.
 893                    return ComparePossibleIntegers(x, y);
 94                }
 95
 96                // At this point x and y are guaranteed to be of the same dynamic type.
 97
 734898                if (_type != actualType)
 99                {
 4452100                    return ComparePolymorphicObjects(actualType, x, y);
 101                }
 102
 103                // At this point, the dynamic type of x and y are guaranteed to match the given type.
 104
 2896105                PropertyInfo[] properties = _type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
 2896106                if (properties.Any())
 107                {
 2192108                    return CompareProperties(properties, x, y);
 109                }
 704110                else if (_type.IsReferenceType())
 111                {
 112                    // We have two instances of the same reference type with no public properties. With no other way to 
 113                    // to assume they're equal.
 8114                    return true;
 115                }
 116
 696117                return x.Equals(y);
 118            }
 119
 120            private bool CompareRecursive(Type type, object x, object y)
 121            {
 15582122                IEqualityComparer comparer = new DynamicModelComparer(type, AreBothNull, ShouldIgnoreProperty);
 15582123                return comparer.Equals(x, y);
 124            }
 125
 126            private bool CompareEnumerables(Type enumerable, object x, object y)
 127            {
 642128                Type elementType = enumerable.GenericTypeArguments.First();
 642129                IEnumerator xs = ((IEnumerable)x).GetEnumerator();
 642130                IEnumerator ys = ((IEnumerable)y).GetEnumerator();
 131
 642132                IEqualityComparer elementComparer = new DynamicModelComparer(elementType, AreBothNull, ShouldIgnorePrope
 133
 642134                xs.Reset();
 642135                ys.Reset();
 136
 1948137                while (xs.MoveNext())
 138                {
 1306139                    if (!ys.MoveNext())
 140                    {
 0141                        return false;
 142                    }
 143
 1306144                    if (!elementComparer.Equals(xs.Current, ys.Current))
 145                    {
 0146                        return false;
 147                    }
 148                }
 149
 642150                return !ys.MoveNext();
 151            }
 152
 4452153            private bool ComparePolymorphicObjects(Type derivedType, object x, object y) => CompareRecursive(derivedType
 154
 155            private bool CompareProperty(PropertyInfo property, object x, object y) =>
 11130156                CompareRecursive(property.PropertyType, property.GetValue(x), property.GetValue(y));
 157
 158            private bool CompareProperties(PropertyInfo[] properties, object x, object y) =>
 24612159                properties.Where(p => !ShouldIgnoreProperty(p)).All(p => CompareProperty(p, x, y));
 160
 161            private bool ComparePossibleIntegers(object x, object y)
 162            {
 8163                if (!x.GetType().IsInteger() || !y.GetType().IsInteger())
 164                {
 0165                    return false;
 166                }
 167
 8168                long xLong = Convert.ToInt64(x);
 8169                long yLong = Convert.ToInt64(y);
 170
 8171                return xLong == yLong;
 172            }
 173
 174            private bool CompareEquatables(object x, object y)
 175            {
 6774176                Type boundEquatable = typeof(IEquatable<>).MakeGenericType(_type);
 6774177                MethodInfo equals = boundEquatable.GetMethod("Equals");
 6774178                return (bool)equals.Invoke(x, new[] { y });
 179            }
 180        }
 181    }
 182}