Skip to content

Commit

Permalink
Changed ExcludeNonBrowsable in IEquivalencyAssertionOptions into sepa…
Browse files Browse the repository at this point in the history
…rate properties IgnoreNonBrowsableOnSubject and ExcludeNonBrowsableOnExpectation.

Updated implementations of the interface accordingly.
Subject: Added new selection rule ExcludeNonBrowsableMembersRule.cs and updated SelfReferenceEquivalencyAssertionOptions.SelectionRules to inject an instance of it after AllPropertiesSelectionRule and AllFieldsSelectionRule but before explicit member selection rules.
Expectation: Updated AssertMemberEquality in StructuralEqualityEquivalencyStep.cs to factor in whether the selected member is browsable.
Updated the ToString implementation in SelfReferenceEquivalencyAssertionOptions.cs to consider ignoreNonBrowsableOnSubject and excludeNonBrowsableOnExpectation independently.
Added tests exercising all combinations of non-browsable fields and properties in the subject and expectation.
Reran AcceptApiChanges.ps1.
  • Loading branch information
logiclrd committed Mar 16, 2022
1 parent 91ea4fb commit 14babeb
Show file tree
Hide file tree
Showing 13 changed files with 399 additions and 42 deletions.
Expand Up @@ -61,7 +61,9 @@ public IEnumerable<IEquivalencyStep> UserEquivalencySteps

public MemberVisibility IncludedFields => inner.IncludedFields;

public bool ExcludeNonBrowsable => inner.ExcludeNonBrowsable;
public bool IgnoreNonBrowsableOnSubject => inner.IgnoreNonBrowsableOnSubject;

public bool ExcludeNonBrowsableOnExpectation => inner.ExcludeNonBrowsableOnExpectation;

public bool CompareRecordsByValue => inner.CompareRecordsByValue;

Expand Down
10 changes: 8 additions & 2 deletions Src/FluentAssertions/Equivalency/IEquivalencyAssertionOptions.cs
Expand Up @@ -74,10 +74,16 @@ public interface IEquivalencyAssertionOptions
MemberVisibility IncludedFields { get; }

/// <summary>
/// Gets a value indicating whether members marked with [EditorBrowsable]
/// Gets a value indicating whether members on the subject marked with [EditorBrowsable]
/// and an EditorBrowsableState of Never should be treated as though they don't exist.
/// </summary>
bool IgnoreNonBrowsableOnSubject { get; }

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

/// <summary>
/// Gets a value indicating whether records should be compared by value instead of their members
Expand Down
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace FluentAssertions.Equivalency.Selection
{
internal class ExcludeNonBrowsableMembersRule : IMemberSelectionRule
{
public bool IncludesMembers => false;

public IEnumerable<IMember> SelectMembers(INode currentNode, IEnumerable<IMember> selectedMembers, MemberSelectionContext context)
{
return selectedMembers.Where(member => member.IsBrowsable);
}
}
}
Expand Up @@ -56,7 +56,8 @@ public abstract class SelfReferenceEquivalencyAssertionOptions<TSelf> : IEquival

private MemberVisibility includedProperties;
private MemberVisibility includedFields;
private bool excludeNonBrowsable;
private bool ignoreNonBrowsableOnSubject;
private bool excludeNonBrowsableOnExpectation;

private bool compareRecordsByValue;

Expand All @@ -81,7 +82,8 @@ protected SelfReferenceEquivalencyAssertionOptions(IEquivalencyAssertionOptions
useRuntimeTyping = defaults.UseRuntimeTyping;
includedProperties = defaults.IncludedProperties;
includedFields = defaults.IncludedFields;
excludeNonBrowsable = defaults.ExcludeNonBrowsable;
ignoreNonBrowsableOnSubject = defaults.IgnoreNonBrowsableOnSubject;
excludeNonBrowsableOnExpectation = defaults.ExcludeNonBrowsableOnExpectation;
compareRecordsByValue = defaults.CompareRecordsByValue;

ConversionSelector = defaults.ConversionSelector.Clone();
Expand Down Expand Up @@ -117,6 +119,11 @@ IEnumerable<IMemberSelectionRule> IEquivalencyAssertionOptions.SelectionRules
yield return new AllFieldsSelectionRule();
}

if (excludeNonBrowsableOnExpectation)
{
yield return new ExcludeNonBrowsableMembersRule();
}

foreach (IMemberSelectionRule rule in selectionRules)
{
yield return rule;
Expand Down Expand Up @@ -164,7 +171,9 @@ IEnumerable<IMemberSelectionRule> IEquivalencyAssertionOptions.SelectionRules

MemberVisibility IEquivalencyAssertionOptions.IncludedFields => includedFields;

bool IEquivalencyAssertionOptions.ExcludeNonBrowsable => excludeNonBrowsable;
bool IEquivalencyAssertionOptions.IgnoreNonBrowsableOnSubject => ignoreNonBrowsableOnSubject;

bool IEquivalencyAssertionOptions.ExcludeNonBrowsableOnExpectation => excludeNonBrowsableOnExpectation;

public bool CompareRecordsByValue => compareRecordsByValue;

Expand Down Expand Up @@ -322,17 +331,25 @@ public TSelf ExcludingProperties()
/// <returns></returns>
public TSelf IncludingNonBrowsableMembers()
{
excludeNonBrowsable = false;
excludeNonBrowsableOnExpectation = false;
return (TSelf)this;
}

/// <summary>
/// Instructs the comparison to exclude non-browsable members (members with an EditorBrowsableState of Never).
/// They can be marked non-browsable in the subject or the expectation. It is not required that they be marked
/// non-browsable in both the subject and the expectation.
/// </summary>
/// <returns></returns>
public TSelf ExcludingNonBrowsableMembers()
{
excludeNonBrowsable = true;
excludeNonBrowsableOnExpectation = true;
return (TSelf)this;
}

public TSelf IgnoringNonBrowsableMembersOnSubject()
{
ignoreNonBrowsableOnSubject = true;
return (TSelf)this;
}

Expand Down Expand Up @@ -722,6 +739,11 @@ public override string ToString()
.Append(useRuntimeTyping ? "runtime" : "declared")
.AppendLine(" types and members");

if (ignoreNonBrowsableOnSubject)
{
builder.AppendLine("- Do not consider members marked non-browsable on the subject");
}

if (isRecursive)
{
if (allowInfiniteRecursion)
Expand Down Expand Up @@ -751,15 +773,6 @@ 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 All @@ -770,6 +783,15 @@ public override string ToString()
builder.AppendLine($"- Compare {type} by its members");
}

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

foreach (IMemberSelectionRule rule in selectionRules)
{
builder.Append("- ").AppendLine(rule.ToString());
Expand Down
Expand Up @@ -55,24 +55,21 @@ public class StructuralEqualityEquivalencyStep : IEquivalencyStep
IMember matchingMember = FindMatchFor(selectedMember, context.CurrentNode, comparands.Subject, options);
if (matchingMember is not null)
{
if (!options.ExcludeNonBrowsable || matchingMember.IsBrowsable)
var nestedComparands = new Comparands
{
var nestedComparands = new Comparands
{
Subject = matchingMember.GetValue(comparands.Subject),
Expectation = selectedMember.GetValue(comparands.Expectation),
CompileTimeType = selectedMember.Type
};
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;
}

parent.RecursivelyAssertEquality(nestedComparands, context.AsNestedMember(selectedMember));
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));
}
}

Expand All @@ -85,6 +82,11 @@ public class StructuralEqualityEquivalencyStep : IEquivalencyStep
where match is not null
select match;

if (config.IgnoreNonBrowsableOnSubject)
{
query = query.Where(member => member.IsBrowsable);
}

return query.FirstOrDefault();
}

Expand Down
Expand Up @@ -824,7 +824,8 @@ namespace FluentAssertions.Equivalency
FluentAssertions.Equivalency.ConversionSelector ConversionSelector { get; }
FluentAssertions.Equivalency.CyclicReferenceHandling CyclicReferenceHandling { get; }
FluentAssertions.Equivalency.EnumEquivalencyHandling EnumEquivalencyHandling { get; }
bool ExcludeNonBrowsable { get; }
bool ExcludeNonBrowsableOnExpectation { get; }
bool IgnoreNonBrowsableOnSubject { get; }
FluentAssertions.Equivalency.MemberVisibility IncludedFields { get; }
FluentAssertions.Equivalency.MemberVisibility IncludedProperties { get; }
bool IsRecursive { get; }
Expand Down Expand Up @@ -996,6 +997,7 @@ namespace FluentAssertions.Equivalency
public TSelf ExcludingNonBrowsableMembers() { }
public TSelf ExcludingProperties() { }
public TSelf IgnoringCyclicReferences() { }
public TSelf IgnoringNonBrowsableMembersOnSubject() { }
public TSelf Including(System.Linq.Expressions.Expression<System.Func<FluentAssertions.Equivalency.IMemberInfo, bool>> predicate) { }
public TSelf IncludingAllDeclaredProperties() { }
public TSelf IncludingAllRuntimeProperties() { }
Expand Down
Expand Up @@ -815,6 +815,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 @@ -835,6 +836,8 @@ namespace FluentAssertions.Equivalency
FluentAssertions.Equivalency.ConversionSelector ConversionSelector { get; }
FluentAssertions.Equivalency.CyclicReferenceHandling CyclicReferenceHandling { get; }
FluentAssertions.Equivalency.EnumEquivalencyHandling EnumEquivalencyHandling { get; }
bool ExcludeNonBrowsableOnExpectation { get; }
bool IgnoreNonBrowsableOnSubject { get; }
FluentAssertions.Equivalency.MemberVisibility IncludedFields { get; }
FluentAssertions.Equivalency.MemberVisibility IncludedProperties { get; }
bool IsRecursive { get; }
Expand Down Expand Up @@ -870,6 +873,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 @@ -973,6 +977,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 @@ -1001,15 +1006,18 @@ namespace FluentAssertions.Equivalency
public TSelf ExcludingFields() { }
public TSelf ExcludingMissingMembers() { }
public TSelf ExcludingNestedObjects() { }
public TSelf ExcludingNonBrowsableMembers() { }
public TSelf ExcludingProperties() { }
public TSelf IgnoringCyclicReferences() { }
public TSelf IgnoringNonBrowsableMembersOnSubject() { }
public TSelf Including(System.Linq.Expressions.Expression<System.Func<FluentAssertions.Equivalency.IMemberInfo, bool>> predicate) { }
public TSelf IncludingAllDeclaredProperties() { }
public TSelf IncludingAllRuntimeProperties() { }
public TSelf IncludingFields() { }
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 @@ -824,7 +824,8 @@ namespace FluentAssertions.Equivalency
FluentAssertions.Equivalency.ConversionSelector ConversionSelector { get; }
FluentAssertions.Equivalency.CyclicReferenceHandling CyclicReferenceHandling { get; }
FluentAssertions.Equivalency.EnumEquivalencyHandling EnumEquivalencyHandling { get; }
bool ExcludeNonBrowsable { get; }
bool ExcludeNonBrowsableOnExpectation { get; }
bool IgnoreNonBrowsableOnSubject { get; }
FluentAssertions.Equivalency.MemberVisibility IncludedFields { get; }
FluentAssertions.Equivalency.MemberVisibility IncludedProperties { get; }
bool IsRecursive { get; }
Expand Down Expand Up @@ -996,6 +997,7 @@ namespace FluentAssertions.Equivalency
public TSelf ExcludingNonBrowsableMembers() { }
public TSelf ExcludingProperties() { }
public TSelf IgnoringCyclicReferences() { }
public TSelf IgnoringNonBrowsableMembersOnSubject() { }
public TSelf Including(System.Linq.Expressions.Expression<System.Func<FluentAssertions.Equivalency.IMemberInfo, bool>> predicate) { }
public TSelf IncludingAllDeclaredProperties() { }
public TSelf IncludingAllRuntimeProperties() { }
Expand Down
Expand Up @@ -824,7 +824,8 @@ namespace FluentAssertions.Equivalency
FluentAssertions.Equivalency.ConversionSelector ConversionSelector { get; }
FluentAssertions.Equivalency.CyclicReferenceHandling CyclicReferenceHandling { get; }
FluentAssertions.Equivalency.EnumEquivalencyHandling EnumEquivalencyHandling { get; }
bool ExcludeNonBrowsable { get; }
bool ExcludeNonBrowsableOnExpectation { get; }
bool IgnoreNonBrowsableOnSubject { get; }
FluentAssertions.Equivalency.MemberVisibility IncludedFields { get; }
FluentAssertions.Equivalency.MemberVisibility IncludedProperties { get; }
bool IsRecursive { get; }
Expand Down Expand Up @@ -996,6 +997,7 @@ namespace FluentAssertions.Equivalency
public TSelf ExcludingNonBrowsableMembers() { }
public TSelf ExcludingProperties() { }
public TSelf IgnoringCyclicReferences() { }
public TSelf IgnoringNonBrowsableMembersOnSubject() { }
public TSelf Including(System.Linq.Expressions.Expression<System.Func<FluentAssertions.Equivalency.IMemberInfo, bool>> predicate) { }
public TSelf IncludingAllDeclaredProperties() { }
public TSelf IncludingAllRuntimeProperties() { }
Expand Down
Expand Up @@ -817,7 +817,8 @@ namespace FluentAssertions.Equivalency
FluentAssertions.Equivalency.ConversionSelector ConversionSelector { get; }
FluentAssertions.Equivalency.CyclicReferenceHandling CyclicReferenceHandling { get; }
FluentAssertions.Equivalency.EnumEquivalencyHandling EnumEquivalencyHandling { get; }
bool ExcludeNonBrowsable { get; }
bool ExcludeNonBrowsableOnExpectation { get; }
bool IgnoreNonBrowsableOnSubject { get; }
FluentAssertions.Equivalency.MemberVisibility IncludedFields { get; }
FluentAssertions.Equivalency.MemberVisibility IncludedProperties { get; }
bool IsRecursive { get; }
Expand Down Expand Up @@ -989,6 +990,7 @@ namespace FluentAssertions.Equivalency
public TSelf ExcludingNonBrowsableMembers() { }
public TSelf ExcludingProperties() { }
public TSelf IgnoringCyclicReferences() { }
public TSelf IgnoringNonBrowsableMembersOnSubject() { }
public TSelf Including(System.Linq.Expressions.Expression<System.Func<FluentAssertions.Equivalency.IMemberInfo, bool>> predicate) { }
public TSelf IncludingAllDeclaredProperties() { }
public TSelf IncludingAllRuntimeProperties() { }
Expand Down
Expand Up @@ -824,7 +824,8 @@ namespace FluentAssertions.Equivalency
FluentAssertions.Equivalency.ConversionSelector ConversionSelector { get; }
FluentAssertions.Equivalency.CyclicReferenceHandling CyclicReferenceHandling { get; }
FluentAssertions.Equivalency.EnumEquivalencyHandling EnumEquivalencyHandling { get; }
bool ExcludeNonBrowsable { get; }
bool ExcludeNonBrowsableOnExpectation { get; }
bool IgnoreNonBrowsableOnSubject { get; }
FluentAssertions.Equivalency.MemberVisibility IncludedFields { get; }
FluentAssertions.Equivalency.MemberVisibility IncludedProperties { get; }
bool IsRecursive { get; }
Expand Down Expand Up @@ -996,6 +997,7 @@ namespace FluentAssertions.Equivalency
public TSelf ExcludingNonBrowsableMembers() { }
public TSelf ExcludingProperties() { }
public TSelf IgnoringCyclicReferences() { }
public TSelf IgnoringNonBrowsableMembersOnSubject() { }
public TSelf Including(System.Linq.Expressions.Expression<System.Func<FluentAssertions.Equivalency.IMemberInfo, bool>> predicate) { }
public TSelf IncludingAllDeclaredProperties() { }
public TSelf IncludingAllRuntimeProperties() { }
Expand Down
4 changes: 3 additions & 1 deletion Tests/Benchmarks/UsersOfGetClosedGenericInterfaces.cs
Expand Up @@ -69,7 +69,9 @@ private class Config : IEquivalencyAssertionOptions

public MemberVisibility IncludedFields => throw new NotImplementedException();

public bool ExcludeNonBrowsable => throw new NotImplementedException();
public bool IgnoreNonBrowsableOnSubject => throw new NotImplementedException();

public bool ExcludeNonBrowsableOnExpectation => throw new NotImplementedException();

public bool CompareRecordsByValue => throw new NotImplementedException();

Expand Down

0 comments on commit 14babeb

Please sign in to comment.