Beispiel #1
0
 public ComparerContext(Action <string> logInfo, Action <string> logDetail, ComparerConfiguration configuration)
 {
     _logInfo         = logInfo;
     _logDetail       = logDetail;
     _ignoredElements = configuration.Ignore;
     _severities      = configuration.Severities;
 }
Beispiel #2
0
        private IEnumerable <ApiTestData> GetResults()
        {
            using (AssemblyLoader assemblyLoader = new AssemblyLoader())
            {
                object[] customAttributes = GetType().GetCustomAttributes(typeof(ApiTestAttribute), true);
                if (customAttributes.Length == 0)
                {
                    throw new Exception(string.Format("The test class should define the {0} at least once", typeof(ApiTestAttribute)));
                }
                foreach (object customAttribute in customAttributes)
                {
                    ApiTestAttribute apiTestAttribute = (ApiTestAttribute)customAttribute;

                    Stream comparerConfigurationStream          = GetReadStream(apiTestAttribute.ComparerConfigurationPath);
                    ComparerConfiguration comparerConfiguration = ConfigurationLoader.LoadComparerConfiguration(comparerConfigurationStream);

                    ApiComparer apiComparer = ApiComparer.CreateInstance(assemblyLoader.ReflectionOnlyLoad(apiTestAttribute.ReferenceVersionPath),
                                                                         assemblyLoader.ReflectionOnlyLoad(apiTestAttribute.NewVersionPath))
                                              .WithComparerConfiguration(comparerConfiguration)
                                              .Build();
                    apiComparer.CheckApi();
                    yield return(new ApiTestData(apiComparer.ComparerResult, apiTestAttribute.Category, apiTestAttribute.Explicit, apiTestAttribute.HandleWarningsAsErrors));
                }
            }
        }
Beispiel #3
0
        /// <summary>
        /// Filters members based on the configuration
        /// </summary>
        /// <param name="configuration"></param>
        /// <param name="member"></param>
        /// <returns></returns>
        private static bool FilterMember(ComparerConfiguration configuration, MemberInfo member)
        {
            var isField    = member is FieldInfo;
            var isProperty = member is PropertyInfo;


            if (!isField && !isProperty)
            {
                return(false);
            }

            if (isField && !configuration.CompareFields)
            {
                return(false);
            }

            var memberType = (member as PropertyInfo)?.PropertyType ?? (member as FieldInfo).FieldType;

            if (configuration.IgnoredTypes.Contains(memberType))
            {
                return(false);
            }

            var memberConfiguration = configuration.GetMemberConfiguration(member.Name);

            return(!memberConfiguration.Ignored);
        }
Beispiel #4
0
        public void When_configuration_file_only_has_comments_return_default_configuration()
        {
            string document = "# my project configuration";
            Stream stream   = new Builder(document).Stream;

            ComparerConfiguration configuration = ConfigurationLoader.LoadComparerConfiguration(stream);

            Assert.IsNotNull(configuration);
        }
Beispiel #5
0
        /// <summary>
        /// Gets the ObectConfiguration for the specified type or returns a DefaultConfiguration
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        ComparerConfiguration IBuilderEngine.GetObjectConfiguration(Type type)
        {
            if (_configurations.ContainsKey(type))
            {
                return(_configurations[type]);
            }
            var configuration = new ComparerConfiguration(this);

            _configurations[type] = configuration;
            return(configuration);
        }
Beispiel #6
0
        public void When_reading_example_configuration_should_load_configuration()
        {
            string document = File.ReadAllText(@"TestProvider\ExampleConfiguration.txt");
            Stream stream   = new Builder(document).Stream;

            ComparerConfiguration configuration = ConfigurationLoader.LoadComparerConfiguration(stream);

            Assert.AreEqual("MyCompany.MyNamespace.MyClass.SomeMember", configuration.Ignore[0]);
            Assert.AreEqual("MyCompany.MyNamespace.MyClass2", configuration.Ignore[1]);
            Assert.AreEqual(Severity.Warning, configuration.Severities.ParameterNameChanged);
            Assert.AreEqual(Severity.Hint, configuration.Severities.AssemblyNameChanged);
        }
Beispiel #7
0
 public Check(string referencePath, string newPath, string htmlPath, string xmlPath, string configPath, bool verbose)
 {
     using (AssemblyLoader assemblyLoader = new AssemblyLoader())
     {
         _referenceAssembly = assemblyLoader.ReflectionOnlyLoad(referencePath);
         _newAssembly       = assemblyLoader.ReflectionOnlyLoad(newPath);
     }
     _htmlStream            = GetWriteStream(htmlPath);
     _xmlStream             = GetWriteStream(xmlPath);
     _comparerConfiguration = ConfigurationLoader.LoadComparerConfiguration(GetReadStream(configPath));
     _log = verbose ? (Action <string>)(System.Console.WriteLine) : s => { };
 }
Beispiel #8
0
        /// <inheritdoc />
        /// <exception cref="ArgumentNullException"></exception>
        public void SetConfiguration(ComparerConfiguration comparerConfiguration)
        {
            if (comparerConfiguration == null)
            {
                throw new ArgumentNullException(nameof(comparerConfiguration));
            }

            DeepComparisonService    = comparerConfiguration.DeepComparisonService ?? throw new InvalidOperationException($"Unable to set configuration without a {nameof(IDeepComparisonService)}.");
            PropertyCache            = comparerConfiguration.PropertyCache ?? throw new InvalidOperationException($"Unable to set a configuration without a {nameof(IPropertyCache)}.");
            ObjectCache              = comparerConfiguration.ObjectCache ?? throw new InvalidOperationException($"Unable to set a configuration without a {nameof(IObjectCache)}.");
            CircularReferenceMonitor = comparerConfiguration.CircularReferenceMonitor ?? throw new InvalidOperationException($"Unable to set a configuration without a {nameof(ICircularReferenceMonitor)}.");
        }
Beispiel #9
0
        /// <summary>
        /// Configures how this engine should handle comparison of the specified type
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public IComparerConfiguration <T> Configure <T>() where T : class
        {
            var type = typeof(T);

            if (_configurations.ContainsKey(type))
            {
                throw new Exception($"The type {type.Name} is already configured.");
            }
            var typeConfiguration = new ComparerConfiguration <T>(this);

            _configurations[type] = typeConfiguration;
            return(typeConfiguration);
        }
Beispiel #10
0
        public void SetConfiguration_NullIObjectCache_ThrowsInvalidOperationExceptionException()
        {
            // Arrange
            var comparer      = CreateComparer();
            var configuration = new ComparerConfiguration()
            {
                DeepComparisonService    = new Mock <IDeepComparisonService>().Object,
                PropertyCache            = new Mock <IPropertyCache>().Object,
                CircularReferenceMonitor = new Mock <ICircularReferenceMonitor>().Object,
                ObjectCache = null
            };

            // Act/Assert
            Assert.Throws <InvalidOperationException>(() => comparer.SetConfiguration(configuration));
        }
Beispiel #11
0
        public static CompiledComparer <T> Build <T>(ComparerConfiguration configuration) where T : class
        {
            var type = typeof(T);

            var ctx = new Context()
            {
                ObjectA = Expression.Parameter(type, "a"),
                ObjectB = Expression.Parameter(type, "b"),
                List    = Expression.Variable(_updateListType, "list"),
            };
            var retLabel = Expression.Label(_updateListType);

            // Initialize the List<Difference> variable
            var blocks = new List <Expression> {
                Expression.Assign(ctx.List, Expression.New(_updateListType)),
            };

            // Expression blocks to compare the objects
            var expression = GetExpressionsForType(type, ctx, configuration, new HashSet <Type>());

            if (expression != null)
            {
                blocks.Add(expression);
            }
            // Return the List<Difference>
            blocks.Add(Expression.Label(retLabel, ctx.List));

            // Compile the expression blocks to a lambda expression
            var body        = Expression.Block(_updateListType, new[] { ctx.List }, blocks);
            var comparator  = Expression.Lambda <Func <T, T, List <Difference> > >(body, (ParameterExpression)ctx.ObjectA, (ParameterExpression)ctx.ObjectB);
            var newComparer = new CompiledComparer <T>(comparator.Compile());

#if DEBUG
            try
            {
                var propertyInfo = typeof(Expression).GetProperty("DebugView", BindingFlags.Instance | BindingFlags.NonPublic);
                DebugInfo.Add(type.FullName, propertyInfo.GetValue(comparator) as string);
            }
            catch
            {
                // Do nothing, this is debug only
            }
#endif
            return(newComparer);
        }
Beispiel #12
0
        public void SetConfiguration_ValidConfiguration_ReturnsSuccessfully()
        {
            // Arrange
            var comparer      = CreateComparer();
            var configuration = new ComparerConfiguration()
            {
                DeepComparisonService    = new Mock <IDeepComparisonService>().Object,
                PropertyCache            = new Mock <IPropertyCache>().Object,
                CircularReferenceMonitor = new Mock <ICircularReferenceMonitor>().Object,
                ObjectCache = new Mock <IObjectCache>().Object
            };

            // Act
            comparer.SetConfiguration(configuration);

            // Assert
            Assert.True(true);
        }
        public static ComparerConfiguration LoadComparerConfiguration(Stream stream)
        {
            if (stream == null || stream.Length == 0)
            {
                return(new ComparerConfiguration());
            }

            using (var reader = new StreamReader(stream))
            {
                Deserializer deserializer = new DeserializerBuilder()
                                            .WithNamingConvention(new CamelCaseNamingConvention())
                                            .Build();

                ComparerConfiguration configuration = deserializer.Deserialize <ComparerConfiguration>(reader);

                return(configuration ?? new ComparerConfiguration());
            }
        }
Beispiel #14
0
        /// <summary>
        /// Filters members based on the configuration
        /// </summary>
        /// <param name="configuration"></param>
        /// <param name="member"></param>
        /// <returns></returns>
        private static bool FilterMember(ComparerConfiguration configuration, MemberInfo member)
        {
            var isField    = member is FieldInfo;
            var isProperty = member is PropertyInfo;

            if (!isField && !isProperty)
            {
                return(false);
            }

            if (isField && !configuration.CompareFields)
            {
                return(false);
            }

            var memberConfiguration = configuration.GetMemberConfiguration(member.Name);

            return(!memberConfiguration.Ignored);
        }
        public ScopedComparison CreateScope(object objA, object objB, DeepComparisonOptions options)
        {
            var configuration = new ComparerConfiguration()
            {
                DeepComparisonService    = new DeepComparisonService(),
                PropertyCache            = new PropertyCache(),
                ObjectCache              = new ObjectCache(),
                CircularReferenceMonitor = new CircularReferenceMonitor()
            };

            foreach (var comparer in options.DeepEqualityComparers)
            {
                comparer.SetConfiguration(configuration);
            }

            return(new ScopedComparison()
            {
                A = objA,
                B = objB,
                ComparisonOptions = options,
                DeepComparisonService = configuration.DeepComparisonService
            });
        }
        void RunApiCheck(string packageName)
        {
            Console.WriteLine(">>>> Checking Api breaking change for: " + packageName + Environment.NewLine);

            var refAssemblyPath = Path.Combine(Directory.GetCurrentDirectory(), @"..\..\..\Resource\refAssemblies\" + packageName + ".dll");
            var devAssemblyPath = Path.Combine(Directory.GetCurrentDirectory(), @"..\..\..\Resource\devAssemblies\" + packageName + ".dll");
            var reportPath      = Path.Combine(Directory.GetCurrentDirectory(), @"..\..\..\Resource\reports\" + packageName + ".report.xml");
            var sb      = new StringBuilder();
            var succeed = true;

            try
            {
                using (AssemblyLoader assemblyLoader = new AssemblyLoader())
                {
                    // load assemblies
                    Assembly refAssembly = assemblyLoader.ReflectionOnlyLoad(refAssemblyPath);
                    Assembly devAssembly = assemblyLoader.ReflectionOnlyLoad(devAssemblyPath);

                    // configuration
                    ComparerConfiguration configuration = new ComparerConfiguration();
                    configuration.Severities.ParameterNameChanged = Severity.Warning;
                    configuration.Severities.AssemblyNameChanged  = Severity.Hint;
                    foreach (var allowedBreakingChange in _allowedApiBreakingChanges)
                    {
                        configuration.Ignore.Add(allowedBreakingChange);
                    }

                    // compare assemblies and write xml report
                    using (var stream = new FileStream(reportPath, FileMode.Create))
                    {
                        ApiComparer.CreateInstance(refAssembly, devAssembly)
                        .WithComparerConfiguration(configuration)
                        .WithDetailLogging(s => Console.WriteLine(s))
                        .WithInfoLogging(s => Console.WriteLine(s))
                        .WithXmlReport(stream)
                        .Build()
                        .CheckApi();
                    }
                }

                var scenarioList = new List <string>()
                {
                    "ChangedAttribute", "ChangedElement", "RemovedElement"
                };

                // check the scenarios that we might have a breaking change
                foreach (var scenario in scenarioList)
                {
                    XElement doc = XElement.Load(reportPath);

                    foreach (XElement change in doc.Descendants(scenario))
                    {
                        if (change.Attribute("Severity") != null && "Error".Equals(change.Attribute("Severity").Value))
                        {
                            succeed = false;

                            // append the parent, for instance,
                            if (change.Parent != null)
                            {
                                sb.AppendLine($"In {change.Parent.Attribute("Context").Value} : {change.Parent.Attribute("Name").Value}");
                            }

                            sb.AppendLine(change.ToString());
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                throw new ApiChangeException("Assembly comparison failed.", ex);
            }

            if (!succeed)
            {
                throw new ApiChangeException($"The following breaking changes are found: {Environment.NewLine} {sb.ToString()}");
            }
        }
Beispiel #17
0
        public void When_providing_null_stream_should_return_default_configuration()
        {
            ComparerConfiguration configuration = ConfigurationLoader.LoadComparerConfiguration(null);

            Assert.IsNotNull(configuration);
        }
Beispiel #18
0
        public void When_configuration_file_is_empty_should_return_default_configuration()
        {
            ComparerConfiguration configuration = ConfigurationLoader.LoadComparerConfiguration(new MemoryStream());

            Assert.IsNotNull(configuration);
        }
Beispiel #19
0
 /// <summary> Sets the comparer configuration. </summary>
 ///
 /// <param name="configuration">  The configuration used by the comparer. </param>
 ///
 /// <returns> The instance of the builder. </returns>
 public ApiComparerBuilder WithComparerConfiguration(ComparerConfiguration configuration)
 {
     _configuration = configuration;
     return(this);
 }
Beispiel #20
0
        /// <summary>
        /// Generates the Expression Tree required to test for null and then
        /// recursively test a nested object in the current object
        /// </summary>
        /// <param name="type"></param>
        /// <param name="ctx"></param>
        /// <param name="memberType"></param>
        /// <param name="configuration"></param>
        /// <param name="hierarchy"></param>
        /// <returns></returns>
        private static Expression GetSafeguardedRecursiveExpression(Type type, Context ctx, Type memberType, ComparerConfiguration configuration, HashSet <Type> hierarchy)
        {
            var tempA       = Expression.Parameter(memberType, "tempA");
            var tempB       = Expression.Parameter(memberType, "tempB");
            var nullChecked = new NullChecked(ctx, memberType);

            var blockExpressions = new List <Expression>()
            {
                Expression.Assign(tempA, nullChecked.PropA),
                Expression.Assign(tempB, nullChecked.PropB),
            };

            var recursiveCtx = new Context()
            {
                ObjectA = tempA,
                ObjectB = tempB,
                Name    = ctx.Name,
                List    = ctx.List,
            };

            var expression = GetExpressionsForType(type, recursiveCtx, configuration, hierarchy);

            if (expression != null)
            {
                blockExpressions.Add(expression);
            }

            return(Expression.Block(
                       new[] { tempA, tempB },
                       blockExpressions
                       ));
        }
Beispiel #21
0
        /// <summary>
        /// Creates the list of expressions required to compile a lambda
        /// to compare two objects of type T
        /// </summary>
        /// <param name="type">Type to compare</param>
        /// <param name="ctx">Compiler Context containing the required expressions</param>
        /// <param name="configuration">The configuration of the current type</param>
        /// <param name="hierarchy">Parent types to avoid circular references like Parent.Child.Parent</param>
        /// <returns></returns>
        private static Expression GetExpressionsForType(Type type, Context ctx, ComparerConfiguration configuration, HashSet <Type> hierarchy)
        {
            if (hierarchy.Contains(type))
            {
                // Break out of circular reference
                return(null);
            }

            var members = type.GetMembers(BindingFlags.Public | BindingFlags.Instance)
                          .Where(x => FilterMember(configuration, x));

            // Keep track of types in the hierarchy to avoid circular references
            hierarchy.Add(type);
            var prefix = ctx.Name;

            var expressions = new List <Expression>();

            foreach (var member in members)
            {
                string memberName = member.Name;
                Type   memberType = null;

                var property = member as PropertyInfo;
                if (property != null)
                {
                    memberType = property.PropertyType;
                }

                var field = member as FieldInfo;
                if (field != null)
                {
                    memberType = field.FieldType;
                }

                var memberConfiguration     = configuration.GetMemberConfiguration(memberName);
                var enumerableConfiguration = memberConfiguration as EnumerableConfiguration;

                ctx.MemberA = Expression.PropertyOrField(ctx.ObjectA, memberName);
                ctx.MemberB = Expression.PropertyOrField(ctx.ObjectB, memberName);
                ctx.Name    = string.IsNullOrEmpty(prefix) ? memberName : $"{prefix}.{memberName}";
                if (IsValueType(type))
                {
                    // Compare the members of a struct
                    expressions.Add(GetStructMemberCompareExpression(ctx, memberName));
                }
                else if (IsSimpleType(memberType))
                {
                    // ValueType, simply compare value with an if (a.X != b.X)
                    expressions.Add(GetMemberCompareExpression(ctx, memberName));
                }
                else if (IsIDictionaryType(memberType))
                {
                    // Static call to CollectionComparer.CompareIDictionary<K,V> to compare IDictionary properties
                    expressions.Add(GetIDictionaryMemberExpression(ctx, configuration.Engine, memberType));
                }
                else if (IsIEnumerableType(memberType))
                {
                    expressions.Add(GetIEnumerableMemberExpression(ctx, configuration.Engine, enumerableConfiguration, memberType));
                }
                else
                {
                    // Recursively compare nested types
                    expressions.Add(GetSafeguardedRecursiveExpression(memberType, ctx, memberType, configuration, hierarchy));
                }
            }

            // Pop the current type from the Hierarchy stack
            hierarchy.Remove(type);

            if (!expressions.Any())
            {
                return(null); // Object has no properties
            }

            //Value types cannot be null, so just return the expressions
            if (IsValueType(type))
            {
                return(Expression.Block(expressions));
            }

            // Check if both objects are null
            return(Expression.IfThen(
                       Expression.Not(
                           Expression.AndAlso(
                               Expression.Equal(ctx.ObjectA, Expression.Constant(null)),
                               Expression.Equal(ctx.ObjectB, Expression.Constant(null)))),
                       Expression.Block(expressions)));
        }
Beispiel #22
0
 public Builder(Assembly assembly1, Assembly assembly2)
 {
     _configuration = new ComparerConfiguration();
     _comparer      = new AssemblyComparer(assembly1, assembly2, new ComparerContext(s => { }, s => { }, _configuration));
 }
Beispiel #23
0
 private ApiComparer(Assembly referenceVersion, Assembly newVersion, Action <string> logInfo, Action <string> logDetail, Stream htmlOutput, Stream xmlOutput, ComparerConfiguration configuration)
 {
     _referenceVersion = referenceVersion;
     _newVersion       = newVersion;
     _htmlOutput       = htmlOutput;
     _xmlOutput        = xmlOutput;
     _comparerContext  = new ComparerContext(logInfo, logDetail, configuration);
 }
Beispiel #24
0
        /// <summary>
        /// Creates the list of expressions required to compile a lambda
        /// to compare two objects of type T
        /// </summary>
        /// <param name="type">Type to compare</param>
        /// <param name="ctx">Compiler Context containing the required expressions</param>
        /// <param name="configuration">The configuration of the current type</param>
        /// <param name="hierarchy">Parent types to avoid circular references like Parent.Child.Parent</param>
        /// <returns></returns>
        private static Expression GetExpressionsForType(Type type, Context ctx, ComparerConfiguration configuration, HashSet <Type> hierarchy)
        {
            if (hierarchy.Contains(type))
            {
                // Break out of circular reference
                return(null);
            }

            PropertyInfo[] properties = type.GetProperties();

            // Keep track of types in the hierarchy to avoid circular references
            hierarchy.Add(type);
            var prefix = ctx.Name;

            var expressions = new List <Expression>();

            foreach (var prop in properties)
            {
                var propMethodInfo          = prop.GetGetMethod();
                var propType                = prop.PropertyType;
                var propertyConfiguration   = configuration.GetPropertyConfiguration(propMethodInfo.Name);
                var enumerableConfiguration = propertyConfiguration as EnumerableConfiguration;
                if (propertyConfiguration.Ignored)
                {
                    continue;
                }
                ctx.PropA = Expression.Property(ctx.A, prop);
                ctx.PropB = Expression.Property(ctx.B, prop);
                ctx.Name  = string.IsNullOrEmpty(prefix) ? prop.Name : $"{prefix}.{prop.Name}";
                if (IsSimpleType(propType))
                {
                    // ValueType, simply compare value with an if (a.X != b.X)
                    expressions.Add(GetPropertyCompareExpression(ctx, prop));
                }
                else if (propType.GetInterfaces().Any(x =>
                                                      x.IsGenericType &&
                                                      x.GetGenericTypeDefinition() == _genericIDictionaryType))
                {
                    // Static call to CollectionComparer.CompareIDictionary<K,V> to compare IDictionary properties
                    expressions.Add(GetIDictionaryPropertyExpression(ctx, configuration.Engine, propType));
                }
                else if (propType.GetInterfaces().Any(x =>
                                                      x.IsGenericType &&
                                                      x.GetGenericTypeDefinition() == _genericIEnumerableType))
                {
                    expressions.Add(GetIEnumerablePropertyExpression(ctx, configuration.Engine, enumerableConfiguration, propType));
                }
                else
                {
                    // Recursively compare nested types
                    expressions.Add(GetSafeguardedRecursiveExpression(propType, ctx, propMethodInfo, configuration, hierarchy));
                }
            }

            // Pop the current type from the Hierarchy stack
            hierarchy.Remove(type);

            if (!expressions.Any())
            {
                return(null); // Object has no properties
            }
            // Check if both objects are null
            return(Expression.IfThen(
                       Expression.Not(
                           Expression.AndAlso(
                               Expression.Equal(ctx.A, Expression.Constant(null)),
                               Expression.Equal(ctx.B, Expression.Constant(null)))),
                       Expression.Block(expressions)));
        }