Skip to content

Commit

Permalink
Added property ExcludeNonBrowsable to IEquivalencyAssertionOptions.cs…
Browse files Browse the repository at this point in the history
…. Implemented it in SelfReferenceEquivalencyAssertionOptions.cs and CollectionMemberAssertionOptionsDecorator.cs and UsersOfGetClosedGenericInterfaces.cs.

Added property IsBrowsable to IMember.cs and implemented it in Field.cs and Property.cs.
Added benchmark CheckIfMemberIsBrowsable.
Adjusted the implementation of AssertMemberEquality in StructuralEqualityEquivalencyStep.cs to combine these fields to allow for non-browsable members to be skipped when checking equivalence.
Added automated tests of the new functionality to SelectionRulesSpec.cs.
Accepted API changes into the approved API.
Updated objectgraphs.md to document the new non-browsable "hidden" members exclusion feature.
Updated releases.md to describe the new feature.
  • Loading branch information
logiclrd committed Mar 16, 2022
1 parent 721975a commit 91ea4fb
Show file tree
Hide file tree
Showing 18 changed files with 435 additions and 14 deletions.
Expand Up @@ -61,6 +61,8 @@ public IEnumerable<IEquivalencyStep> UserEquivalencySteps

public MemberVisibility IncludedFields => inner.IncludedFields;

public bool ExcludeNonBrowsable => inner.ExcludeNonBrowsable;

public bool CompareRecordsByValue => inner.CompareRecordsByValue;

public EqualityStrategy GetEqualityStrategy(Type type)
Expand Down
15 changes: 15 additions & 0 deletions Src/FluentAssertions/Equivalency/Field.cs
@@ -1,4 +1,5 @@
using System;
using System.ComponentModel;
using System.Reflection;
using FluentAssertions.Common;

Expand All @@ -10,6 +11,7 @@ namespace FluentAssertions.Equivalency
public class Field : Node, IMember
{
private readonly FieldInfo fieldInfo;
private bool? isBrowsable;

public Field(FieldInfo fieldInfo, INode parent)
: this(fieldInfo.ReflectedType, fieldInfo, parent)
Expand Down Expand Up @@ -42,5 +44,18 @@ public object GetValue(object obj)
public CSharpAccessModifier GetterAccessibility => fieldInfo.GetCSharpAccessModifier();

public CSharpAccessModifier SetterAccessibility => fieldInfo.GetCSharpAccessModifier();

public bool IsBrowsable
{
get
{
if (isBrowsable == null)
{
isBrowsable = fieldInfo.GetCustomAttribute<EditorBrowsableAttribute>() is not { State: EditorBrowsableState.Never };
}

return isBrowsable.Value;
}
}
}
}
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;

using FluentAssertions.Equivalency.Tracing;

namespace FluentAssertions.Equivalency
Expand Down Expand Up @@ -71,6 +73,12 @@ public interface IEquivalencyAssertionOptions
/// </summary>
MemberVisibility IncludedFields { get; }

/// <summary>
/// Gets a value indicating whether members marked with [EditorBrowsable]
/// and an EditorBrowsableState of Never should be excluded.
/// </summary>
bool ExcludeNonBrowsable { get; }

/// <summary>
/// Gets a value indicating whether records should be compared by value instead of their members
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions Src/FluentAssertions/Equivalency/IMember.cs
Expand Up @@ -32,5 +32,10 @@ public interface IMember : INode
/// Gets the access modifier for the setter of this member.
/// </summary>
CSharpAccessModifier SetterAccessibility { get; }

/// <summary>
/// Gets a value indicating whether the member is browsable in the source code editor. This is controlled with the [EditorBrowsable] attribute.
/// </summary>
bool IsBrowsable { get; }
}
}
15 changes: 15 additions & 0 deletions Src/FluentAssertions/Equivalency/Property.cs
@@ -1,4 +1,5 @@
using System;
using System.ComponentModel;
using System.Reflection;
using FluentAssertions.Common;

Expand All @@ -11,6 +12,7 @@ namespace FluentAssertions.Equivalency
public class Property : Node, IMember
{
private readonly PropertyInfo propertyInfo;
private bool? isBrowsable;

public Property(PropertyInfo propertyInfo, INode parent)
: this(propertyInfo.ReflectedType, propertyInfo, parent)
Expand Down Expand Up @@ -43,5 +45,18 @@ public object GetValue(object obj)
public CSharpAccessModifier GetterAccessibility => propertyInfo.GetGetMethod(nonPublic: true).GetCSharpAccessModifier();

public CSharpAccessModifier SetterAccessibility => propertyInfo.GetSetMethod(nonPublic: true).GetCSharpAccessModifier();

public bool IsBrowsable
{
get
{
if (isBrowsable == null)
{
isBrowsable = propertyInfo.GetCustomAttribute<EditorBrowsableAttribute>() is not { State: EditorBrowsableState.Never };
}

return isBrowsable.Value;
}
}
}
}
Expand Up @@ -56,6 +56,7 @@ public abstract class SelfReferenceEquivalencyAssertionOptions<TSelf> : IEquival

private MemberVisibility includedProperties;
private MemberVisibility includedFields;
private bool excludeNonBrowsable;

private bool compareRecordsByValue;

Expand All @@ -80,6 +81,7 @@ protected SelfReferenceEquivalencyAssertionOptions(IEquivalencyAssertionOptions
useRuntimeTyping = defaults.UseRuntimeTyping;
includedProperties = defaults.IncludedProperties;
includedFields = defaults.IncludedFields;
excludeNonBrowsable = defaults.ExcludeNonBrowsable;
compareRecordsByValue = defaults.CompareRecordsByValue;

ConversionSelector = defaults.ConversionSelector.Clone();
Expand Down Expand Up @@ -162,6 +164,8 @@ IEnumerable<IMemberSelectionRule> IEquivalencyAssertionOptions.SelectionRules

MemberVisibility IEquivalencyAssertionOptions.IncludedFields => includedFields;

bool IEquivalencyAssertionOptions.ExcludeNonBrowsable => excludeNonBrowsable;

public bool CompareRecordsByValue => compareRecordsByValue;

EqualityStrategy IEquivalencyAssertionOptions.GetEqualityStrategy(Type requestedType)
Expand Down Expand Up @@ -312,6 +316,26 @@ public TSelf ExcludingProperties()
return (TSelf)this;
}

/// <summary>
/// Instructs the comparison to include non-browsable members (members with an EditorBrowsableState of Never).
/// </summary>
/// <returns></returns>
public TSelf IncludingNonBrowsableMembers()
{
excludeNonBrowsable = false;
return (TSelf)this;
}

/// <summary>
/// Instructs the comparison to exclude non-browsable members (members with an EditorBrowsableState of Never).
/// </summary>
/// <returns></returns>
public TSelf ExcludingNonBrowsableMembers()
{
excludeNonBrowsable = true;
return (TSelf)this;
}

/// <summary>
/// Instructs the comparison to respect the expectation's runtime type.
/// </summary>
Expand Down Expand Up @@ -727,6 +751,15 @@ public override string ToString()
builder.AppendLine("- Compare records by their members");
}

if (excludeNonBrowsable)
{
builder.AppendLine("- Exclude non-browsable members");
}
else
{
builder.AppendLine("- Include non-browsable members");
}

foreach (Type valueType in valueTypes)
{
builder.AppendLine($"- Compare {valueType} by value");
Expand Down
Expand Up @@ -55,21 +55,24 @@ public class StructuralEqualityEquivalencyStep : IEquivalencyStep
IMember matchingMember = FindMatchFor(selectedMember, context.CurrentNode, comparands.Subject, options);
if (matchingMember is not null)
{
var nestedComparands = new Comparands
if (!options.ExcludeNonBrowsable || matchingMember.IsBrowsable)
{
Subject = matchingMember.GetValue(comparands.Subject),
Expectation = selectedMember.GetValue(comparands.Expectation),
CompileTimeType = selectedMember.Type
};
var nestedComparands = new Comparands
{
Subject = matchingMember.GetValue(comparands.Subject),
Expectation = selectedMember.GetValue(comparands.Expectation),
CompileTimeType = selectedMember.Type
};

if (selectedMember.Name != matchingMember.Name)
{
// In case the matching process selected a different member on the subject,
// adjust the current member so that assertion failures report the proper name.
selectedMember.Name = matchingMember.Name;
}
if (selectedMember.Name != matchingMember.Name)
{
// In case the matching process selected a different member on the subject,
// adjust the current member so that assertion failures report the proper name.
selectedMember.Name = matchingMember.Name;
}

parent.RecursivelyAssertEquality(nestedComparands, context.AsNestedMember(selectedMember));
parent.RecursivelyAssertEquality(nestedComparands, context.AsNestedMember(selectedMember));
}
}
}

Expand Down
Expand Up @@ -803,6 +803,7 @@ namespace FluentAssertions.Equivalency
public System.Type DeclaringType { get; set; }
public override string Description { get; }
public FluentAssertions.Common.CSharpAccessModifier GetterAccessibility { get; }
public bool IsBrowsable { get; }
public System.Type ReflectedType { get; }
public FluentAssertions.Common.CSharpAccessModifier SetterAccessibility { get; }
public object GetValue(object obj) { }
Expand All @@ -823,6 +824,7 @@ namespace FluentAssertions.Equivalency
FluentAssertions.Equivalency.ConversionSelector ConversionSelector { get; }
FluentAssertions.Equivalency.CyclicReferenceHandling CyclicReferenceHandling { get; }
FluentAssertions.Equivalency.EnumEquivalencyHandling EnumEquivalencyHandling { get; }
bool ExcludeNonBrowsable { get; }
FluentAssertions.Equivalency.MemberVisibility IncludedFields { get; }
FluentAssertions.Equivalency.MemberVisibility IncludedProperties { get; }
bool IsRecursive { get; }
Expand Down Expand Up @@ -858,6 +860,7 @@ namespace FluentAssertions.Equivalency
{
System.Type DeclaringType { get; }
FluentAssertions.Common.CSharpAccessModifier GetterAccessibility { get; }
bool IsBrowsable { get; }
System.Type ReflectedType { get; }
FluentAssertions.Common.CSharpAccessModifier SetterAccessibility { get; }
object GetValue(object obj);
Expand Down Expand Up @@ -961,6 +964,7 @@ namespace FluentAssertions.Equivalency
public System.Type DeclaringType { get; }
public override string Description { get; }
public FluentAssertions.Common.CSharpAccessModifier GetterAccessibility { get; }
public bool IsBrowsable { get; }
public System.Type ReflectedType { get; }
public FluentAssertions.Common.CSharpAccessModifier SetterAccessibility { get; }
public object GetValue(object obj) { }
Expand Down Expand Up @@ -989,6 +993,7 @@ namespace FluentAssertions.Equivalency
public TSelf ExcludingFields() { }
public TSelf ExcludingMissingMembers() { }
public TSelf ExcludingNestedObjects() { }
public TSelf ExcludingNonBrowsableMembers() { }
public TSelf ExcludingProperties() { }
public TSelf IgnoringCyclicReferences() { }
public TSelf Including(System.Linq.Expressions.Expression<System.Func<FluentAssertions.Equivalency.IMemberInfo, bool>> predicate) { }
Expand All @@ -998,6 +1003,7 @@ namespace FluentAssertions.Equivalency
public TSelf IncludingInternalFields() { }
public TSelf IncludingInternalProperties() { }
public TSelf IncludingNestedObjects() { }
public TSelf IncludingNonBrowsableMembers() { }
public TSelf IncludingProperties() { }
public TSelf RespectingDeclaredTypes() { }
public TSelf RespectingRuntimeTypes() { }
Expand Down
Expand Up @@ -803,6 +803,7 @@ namespace FluentAssertions.Equivalency
public System.Type DeclaringType { get; set; }
public override string Description { get; }
public FluentAssertions.Common.CSharpAccessModifier GetterAccessibility { get; }
public bool IsBrowsable { get; }
public System.Type ReflectedType { get; }
public FluentAssertions.Common.CSharpAccessModifier SetterAccessibility { get; }
public object GetValue(object obj) { }
Expand All @@ -823,6 +824,7 @@ namespace FluentAssertions.Equivalency
FluentAssertions.Equivalency.ConversionSelector ConversionSelector { get; }
FluentAssertions.Equivalency.CyclicReferenceHandling CyclicReferenceHandling { get; }
FluentAssertions.Equivalency.EnumEquivalencyHandling EnumEquivalencyHandling { get; }
bool ExcludeNonBrowsable { get; }
FluentAssertions.Equivalency.MemberVisibility IncludedFields { get; }
FluentAssertions.Equivalency.MemberVisibility IncludedProperties { get; }
bool IsRecursive { get; }
Expand Down Expand Up @@ -858,6 +860,7 @@ namespace FluentAssertions.Equivalency
{
System.Type DeclaringType { get; }
FluentAssertions.Common.CSharpAccessModifier GetterAccessibility { get; }
bool IsBrowsable { get; }
System.Type ReflectedType { get; }
FluentAssertions.Common.CSharpAccessModifier SetterAccessibility { get; }
object GetValue(object obj);
Expand Down Expand Up @@ -961,6 +964,7 @@ namespace FluentAssertions.Equivalency
public System.Type DeclaringType { get; }
public override string Description { get; }
public FluentAssertions.Common.CSharpAccessModifier GetterAccessibility { get; }
public bool IsBrowsable { get; }
public System.Type ReflectedType { get; }
public FluentAssertions.Common.CSharpAccessModifier SetterAccessibility { get; }
public object GetValue(object obj) { }
Expand Down Expand Up @@ -989,6 +993,7 @@ namespace FluentAssertions.Equivalency
public TSelf ExcludingFields() { }
public TSelf ExcludingMissingMembers() { }
public TSelf ExcludingNestedObjects() { }
public TSelf ExcludingNonBrowsableMembers() { }
public TSelf ExcludingProperties() { }
public TSelf IgnoringCyclicReferences() { }
public TSelf Including(System.Linq.Expressions.Expression<System.Func<FluentAssertions.Equivalency.IMemberInfo, bool>> predicate) { }
Expand All @@ -998,6 +1003,7 @@ namespace FluentAssertions.Equivalency
public TSelf IncludingInternalFields() { }
public TSelf IncludingInternalProperties() { }
public TSelf IncludingNestedObjects() { }
public TSelf IncludingNonBrowsableMembers() { }
public TSelf IncludingProperties() { }
public TSelf RespectingDeclaredTypes() { }
public TSelf RespectingRuntimeTypes() { }
Expand Down
Expand Up @@ -803,6 +803,7 @@ namespace FluentAssertions.Equivalency
public System.Type DeclaringType { get; set; }
public override string Description { get; }
public FluentAssertions.Common.CSharpAccessModifier GetterAccessibility { get; }
public bool IsBrowsable { get; }
public System.Type ReflectedType { get; }
public FluentAssertions.Common.CSharpAccessModifier SetterAccessibility { get; }
public object GetValue(object obj) { }
Expand All @@ -823,6 +824,7 @@ namespace FluentAssertions.Equivalency
FluentAssertions.Equivalency.ConversionSelector ConversionSelector { get; }
FluentAssertions.Equivalency.CyclicReferenceHandling CyclicReferenceHandling { get; }
FluentAssertions.Equivalency.EnumEquivalencyHandling EnumEquivalencyHandling { get; }
bool ExcludeNonBrowsable { get; }
FluentAssertions.Equivalency.MemberVisibility IncludedFields { get; }
FluentAssertions.Equivalency.MemberVisibility IncludedProperties { get; }
bool IsRecursive { get; }
Expand Down Expand Up @@ -858,6 +860,7 @@ namespace FluentAssertions.Equivalency
{
System.Type DeclaringType { get; }
FluentAssertions.Common.CSharpAccessModifier GetterAccessibility { get; }
bool IsBrowsable { get; }
System.Type ReflectedType { get; }
FluentAssertions.Common.CSharpAccessModifier SetterAccessibility { get; }
object GetValue(object obj);
Expand Down Expand Up @@ -961,6 +964,7 @@ namespace FluentAssertions.Equivalency
public System.Type DeclaringType { get; }
public override string Description { get; }
public FluentAssertions.Common.CSharpAccessModifier GetterAccessibility { get; }
public bool IsBrowsable { get; }
public System.Type ReflectedType { get; }
public FluentAssertions.Common.CSharpAccessModifier SetterAccessibility { get; }
public object GetValue(object obj) { }
Expand Down Expand Up @@ -989,6 +993,7 @@ namespace FluentAssertions.Equivalency
public TSelf ExcludingFields() { }
public TSelf ExcludingMissingMembers() { }
public TSelf ExcludingNestedObjects() { }
public TSelf ExcludingNonBrowsableMembers() { }
public TSelf ExcludingProperties() { }
public TSelf IgnoringCyclicReferences() { }
public TSelf Including(System.Linq.Expressions.Expression<System.Func<FluentAssertions.Equivalency.IMemberInfo, bool>> predicate) { }
Expand All @@ -998,6 +1003,7 @@ namespace FluentAssertions.Equivalency
public TSelf IncludingInternalFields() { }
public TSelf IncludingInternalProperties() { }
public TSelf IncludingNestedObjects() { }
public TSelf IncludingNonBrowsableMembers() { }
public TSelf IncludingProperties() { }
public TSelf RespectingDeclaredTypes() { }
public TSelf RespectingRuntimeTypes() { }
Expand Down

0 comments on commit 91ea4fb

Please sign in to comment.