Skip to content

Commit

Permalink
SAVEPOINT
Browse files Browse the repository at this point in the history
  • Loading branch information
Dennis Doomen authored and dennisdoomen committed Aug 5, 2023
1 parent b491d16 commit 35fdb9b
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 47 deletions.
30 changes: 8 additions & 22 deletions Src/FluentAssertions/Common/TypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,19 +181,12 @@ public static bool OverridesEquals(this Type type)
/// <returns>
/// Returns <see langword="null"/> if no such property exists.
/// </returns>
public static PropertyInfo FindProperty(this Type type, string propertyName)
public static PropertyInfo FindProperty(this Type type, string propertyName, MemberVisibility memberVisibility)
{
while (type != typeof(object))
{
if (type.GetProperty(propertyName, AllInstanceMembersFlag | BindingFlags.DeclaredOnly) is { } property)
{
return property;
}

type = type.BaseType;
}
var properties = type.GetNonPrivateProperties(memberVisibility);

return null;
return Array.Find(properties, p =>
p.Name == propertyName || p.Name.EndsWith("." + propertyName, StringComparison.OrdinalIgnoreCase));
}

/// <summary>
Expand All @@ -202,19 +195,12 @@ public static PropertyInfo FindProperty(this Type type, string propertyName)
/// <returns>
/// Returns <see langword="null"/> if no such property exists.
/// </returns>
public static FieldInfo FindField(this Type type, string fieldName)
public static FieldInfo FindField(this Type type, string fieldName, MemberVisibility memberVisibility)
{
while (type != typeof(object))
{
if (type.GetField(fieldName, AllInstanceMembersFlag | BindingFlags.DeclaredOnly) is { } field)
{
return field;
}

type = type.BaseType;
}
var fields = type.GetNonPrivateFields(memberVisibility);

return null;
return Array.Find(fields, p =>
p.Name == fieldName || p.Name.EndsWith("." + fieldName, StringComparison.OrdinalIgnoreCase));
}

public static MemberInfo[] GetNonPrivateMembers(this Type typeToReflect, MemberVisibility visibility)
Expand Down
45 changes: 30 additions & 15 deletions Src/FluentAssertions/Common/TypeMemberReflector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ internal sealed class TypeMemberReflector
public TypeMemberReflector(Type typeToReflect, MemberVisibility visibility)
{
NonPrivateProperties = LoadNonPrivateProperties(typeToReflect, visibility);
NonPrivateFields = LoadNonPrivateFields(typeToReflect, visibility);
NonPrivateFields = TypeMemberReflector.LoadNonPrivateFields(typeToReflect, visibility);
NonPrivateMembers = NonPrivateProperties.Concat<MemberInfo>(NonPrivateFields).ToArray();
}

Expand All @@ -31,27 +31,31 @@ private static PropertyInfo[] LoadNonPrivateProperties(Type typeToReflect, Membe
{
IEnumerable<PropertyInfo> query =
from propertyInfo in GetPropertiesFromHierarchy(typeToReflect, visibility)
where HasNonPrivateGetter(propertyInfo)
where HasNonPrivateGetter(propertyInfo, visibility)
where !propertyInfo.IsIndexer()
select propertyInfo;

return query.ToArray();
}

private static List<PropertyInfo> GetPropertiesFromHierarchy(Type typeToReflect, MemberVisibility memberVisibility)
private static PropertyInfo[] GetPropertiesFromHierarchy(Type typeToReflect, MemberVisibility memberVisibility)
{
bool includeInternals = memberVisibility.HasFlag(MemberVisibility.Internal);
bool includeInternal = memberVisibility.HasFlag(MemberVisibility.Internal);

return GetMembersFromHierarchy(typeToReflect, type =>
{
return type
.GetProperties(AllInstanceMembersFlag | BindingFlags.DeclaredOnly)
.Where(property => property.GetMethod?.IsPrivate == false)
.Where(property => includeInternals || property.GetMethod is { IsAssembly: false, IsFamilyOrAssembly: false })
.Where(property => includeInternal || !IsInternal(property))
.ToArray();
});
}

private static bool IsInternal(PropertyInfo property)
{
return property.GetMethod is { IsAssembly: true } or { IsFamilyOrAssembly: true };
}

private static FieldInfo[] LoadNonPrivateFields(Type typeToReflect, MemberVisibility visibility)
{
IEnumerable<FieldInfo> query =
Expand All @@ -63,21 +67,26 @@ from fieldInfo in GetFieldsFromHierarchy(typeToReflect, visibility)
return query.ToArray();
}

private static List<FieldInfo> GetFieldsFromHierarchy(Type typeToReflect, MemberVisibility memberVisibility)
private static FieldInfo[] GetFieldsFromHierarchy(Type typeToReflect, MemberVisibility memberVisibility)
{
bool includeInternals = memberVisibility.HasFlag(MemberVisibility.Internal);
bool includeInternal = memberVisibility.HasFlag(MemberVisibility.Internal);

return GetMembersFromHierarchy(typeToReflect, type =>
{
return type
.GetFields(AllInstanceMembersFlag)
.Where(field => !field.IsPrivate)
.Where(field => includeInternals || (!field.IsAssembly && !field.IsFamilyOrAssembly))
.Where(field => includeInternal || !IsInternal(field))
.ToArray();
});
}

private static List<TMemberInfo> GetMembersFromHierarchy<TMemberInfo>(
private static bool IsInternal(FieldInfo field)
{
return field.IsAssembly || field.IsFamilyOrAssembly;
}

private static TMemberInfo[] GetMembersFromHierarchy<TMemberInfo>(
Type typeToReflect,
Func<Type, IEnumerable<TMemberInfo>> getMembers)
where TMemberInfo : MemberInfo
Expand All @@ -90,7 +99,7 @@ private static List<TMemberInfo> GetMembersFromHierarchy<TMemberInfo>(
return GetClassMembers(typeToReflect, getMembers);
}

private static List<TMemberInfo> GetInterfaceMembers<TMemberInfo>(Type typeToReflect,
private static TMemberInfo[] GetInterfaceMembers<TMemberInfo>(Type typeToReflect,
Func<Type, IEnumerable<TMemberInfo>> getMembers)
where TMemberInfo : MemberInfo
{
Expand Down Expand Up @@ -123,10 +132,10 @@ private static List<TMemberInfo> GetInterfaceMembers<TMemberInfo>(Type typeToRef
members.InsertRange(0, newPropertyInfos);
}

return members;
return members.ToArray();
}

private static List<TMemberInfo> GetClassMembers<TMemberInfo>(Type typeToReflect,
private static TMemberInfo[] GetClassMembers<TMemberInfo>(Type typeToReflect,
Func<Type, IEnumerable<TMemberInfo>> getMembers)
where TMemberInfo : MemberInfo
{
Expand All @@ -145,12 +154,18 @@ private static List<TMemberInfo> GetClassMembers<TMemberInfo>(Type typeToReflect
typeToReflect = typeToReflect.BaseType;
}

return members;
return members.ToArray();
}

private static bool HasNonPrivateGetter(PropertyInfo propertyInfo)
private static bool HasNonPrivateGetter(PropertyInfo propertyInfo, MemberVisibility visibility)
{
MethodInfo getMethod = propertyInfo.GetGetMethod(nonPublic: true);

if (visibility.HasFlag(MemberVisibility.ExplicitlyImplemented))
{
return getMethod is { IsPrivate: false, IsFamily: false } or { IsPrivate: true, IsFinal: true };
}

return getMethod is { IsPrivate: false, IsFamily: false };
}
}
14 changes: 11 additions & 3 deletions Src/FluentAssertions/Equivalency/Matching/MustMatchByNameRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,25 @@ public IMember Match(IMember expectedMember, object subject, INode parent, IEqui

if (options.IncludedProperties != MemberVisibility.None)
{
PropertyInfo propertyInfo = subject.GetType().FindProperty(expectedMember.Name);
PropertyInfo propertyInfo = subject.GetType().FindProperty(
expectedMember.Name,
options.IncludedProperties | MemberVisibility.ExplicitlyImplemented);

subjectMember = propertyInfo is not null && !propertyInfo.IsIndexer() ? new Property(propertyInfo, parent) : null;
}

if (subjectMember is null && options.IncludedFields != MemberVisibility.None)
{
FieldInfo fieldInfo = subject.GetType().FindField(expectedMember.Name);
FieldInfo fieldInfo = subject.GetType().FindField(
expectedMember.Name,
options.IncludedFields | MemberVisibility.ExplicitlyImplemented);

subjectMember = fieldInfo is not null ? new Field(fieldInfo, parent) : null;
}

if ((subjectMember is null || !options.UseRuntimeTyping) && ExpectationImplementsMemberExplicitly(subject, expectedMember))
// TO DO: Why do we need this?
if ((subjectMember is null || !options.UseRuntimeTyping) &&
ExpectationImplementsMemberExplicitly(subject, expectedMember))
{
subjectMember = expectedMember;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@ internal class TryMatchByNameRule : IMemberMatchingRule
{
public IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyAssertionOptions options)
{
PropertyInfo property = subject.GetType().FindProperty(expectedMember.Name);
PropertyInfo property = subject.GetType().FindProperty(expectedMember.Name,
options.IncludedProperties | MemberVisibility.ExplicitlyImplemented);

if (property is not null && !property.IsIndexer())
{
return new Property(property, parent);
}

FieldInfo field = subject.GetType().FindField(expectedMember.Name);
FieldInfo field = subject.GetType()
.FindField(expectedMember.Name, options.IncludedFields | MemberVisibility.ExplicitlyImplemented);

return field is not null ? new Field(field, parent) : null;
}

Expand Down
5 changes: 3 additions & 2 deletions Src/FluentAssertions/Equivalency/MemberFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ public static IMember Create(MemberInfo memberInfo, INode parent)

internal static IMember Find(object target, string memberName, INode parent)
{
PropertyInfo property = target.GetType().FindProperty(memberName);
// TO DO: Do we care about explicitly implemented members
PropertyInfo property = target.GetType().FindProperty(memberName, MemberVisibility.Public);

if (property is not null && !property.IsIndexer())
{
return new Property(property, parent);
}

FieldInfo field = target.GetType().FindField(memberName);
FieldInfo field = target.GetType().FindField(memberName, MemberVisibility.Public);
return field is not null ? new Field(field, parent) : null;
}
}
3 changes: 2 additions & 1 deletion Src/FluentAssertions/Equivalency/MemberVisibility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ public enum MemberVisibility
{
None = 0,
Internal = 1,
Public = 2
Public = 2,
ExplicitlyImplemented = 4
}
10 changes: 8 additions & 2 deletions Tests/Approval.Tests/ApiApproval.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ public Task ApproveApi(string frameworkVersion)
string assemblyPath = Uri.UnescapeDataString(uri.Path);
var containingDirectory = Path.GetDirectoryName(assemblyPath);
var configurationName = new DirectoryInfo(containingDirectory).Parent.Name;

var assemblyFile = Path.GetFullPath(
Path.Combine(
GetSourceDirectory(),
Path.Combine("..", "..", "Src", "FluentAssertions", "bin", configurationName, frameworkVersion, "FluentAssertions.dll")));
Path.Combine("..", "..", "Src", "FluentAssertions", "bin", configurationName, frameworkVersion,
"FluentAssertions.dll")));

var assembly = Assembly.LoadFile(Path.GetFullPath(assemblyFile));
var publicApi = assembly.GeneratePublicApi(options: null);
Expand All @@ -53,6 +55,7 @@ public static Task<CompareResult> OnlyIncludeChanges(string received, string ver
var diff = InlineDiffBuilder.Diff(verified, received);

var builder = new StringBuilder();

foreach (var line in diff.Lines)
{
switch (line.Type)
Expand All @@ -79,9 +82,12 @@ private class TargetFrameworksTheoryData : TheoryData<string>
{
public TargetFrameworksTheoryData()
{
var csproj = Path.Combine(GetSourceDirectory(), Path.Combine("..", "..", "Src", "FluentAssertions", "FluentAssertions.csproj"));
var csproj = Path.Combine(GetSourceDirectory(),
Path.Combine("..", "..", "Src", "FluentAssertions", "FluentAssertions.csproj"));

var project = XDocument.Load(csproj);
var targetFrameworks = project.XPathSelectElement("/Project/PropertyGroup/TargetFrameworks");

foreach (string targetFramework in targetFrameworks!.Value.Split(';'))
{
Add(targetFramework);
Expand Down
21 changes: 21 additions & 0 deletions Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1946,6 +1946,27 @@ public void When_respecting_runtime_types_explicit_interface_member_on_expectati
action.Should().Throw<XunitException>();
}

[Fact]
public void Can_find_explicitly_implemented_properties_on_the_subject()
{
// Arrange
IPerson person = new Person();
person.Name = "Bob";

// Act / Assert
person.Should().BeEquivalentTo(new { Name = "Bob" });
}

private interface IPerson
{
string Name { get; set; }
}

private class Person : IPerson
{
string IPerson.Name { get; set; }
}

[Fact]
public void Excluding_an_interface_property_through_inheritance_should_work()
{
Expand Down

0 comments on commit 35fdb9b

Please sign in to comment.