public ComparerContext(Action <string> logInfo, Action <string> logDetail, ComparerConfiguration configuration) { _logInfo = logInfo; _logDetail = logDetail; _ignoredElements = configuration.Ignore; _severities = configuration.Severities; }
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)); } } }
/// <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); }
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); }
/// <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); }
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); }
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 => { }; }
/// <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)}."); }
/// <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); }
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)); }
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); }
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()); } }
/// <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()}"); } }
public void When_providing_null_stream_should_return_default_configuration() { ComparerConfiguration configuration = ConfigurationLoader.LoadComparerConfiguration(null); Assert.IsNotNull(configuration); }
public void When_configuration_file_is_empty_should_return_default_configuration() { ComparerConfiguration configuration = ConfigurationLoader.LoadComparerConfiguration(new MemoryStream()); Assert.IsNotNull(configuration); }
/// <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); }
/// <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 )); }
/// <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))); }
public Builder(Assembly assembly1, Assembly assembly2) { _configuration = new ComparerConfiguration(); _comparer = new AssemblyComparer(assembly1, assembly2, new ComparerContext(s => { }, s => { }, _configuration)); }
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); }
/// <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))); }