Skip to content

Commit

Permalink
SAVEPOINT
Browse files Browse the repository at this point in the history
  • Loading branch information
dennisdoomen committed Dec 13, 2024
1 parent b088f1e commit 4b93077
Show file tree
Hide file tree
Showing 39 changed files with 364 additions and 144 deletions.
1 change: 1 addition & 0 deletions FluentAssertions.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/XamlNaming/UserRules/=XAML_005FRESOURCE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>

<s:String x:Key="/Default/Environment/Editor/MatchingBraceHighlighting/Style/@EntryValue">OUTLINE</s:String>
<s:String x:Key="/Default/Environment/Hierarchy/Build/SolutionBuilderNext/FileVerbosityLevel/@EntryValue">Minimal</s:String>
<s:String x:Key="/Default/Environment/Hierarchy/PsiConfigurationSettingsKey/LocationType/@EntryValue">SOLUTION_FOLDER</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002EDaemon_002ESettings_002EMigration_002ESwaWarningsModeSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
Expand Down
2 changes: 1 addition & 1 deletion Src/FluentAssertions/Common/MemberPath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ internal class MemberPath
private static readonly MemberPathSegmentEqualityComparer MemberPathSegmentEqualityComparer = new();

public MemberPath(IMember member, string parentPath)
: this(member.ReflectedType, member.DeclaringType, parentPath.Combine(member.Name))
: this(member.ReflectedType, member.DeclaringType, parentPath.Combine(member.Expectation.Name ?? member.Subject.Name))
{
}

Expand Down
2 changes: 1 addition & 1 deletion Src/FluentAssertions/Common/TypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public static bool IsEquivalentTo(this IMember property, IMember otherProperty)
{
return (property.DeclaringType.IsSameOrInherits(otherProperty.DeclaringType) ||
otherProperty.DeclaringType.IsSameOrInherits(property.DeclaringType)) &&
property.Name == otherProperty.Name;
property.Subject.Name == otherProperty.Subject.Name;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ internal static class AssertionChainExtensions
/// </summary>
public static AssertionChain For(this AssertionChain chain, IEquivalencyValidationContext context)
{
chain.OverrideCallerIdentifier(() => context.CurrentNode.Description);
chain.OverrideCallerIdentifier(() => context.CurrentNode.Subject.Description);

return chain
.WithReportable("configuration", () => context.Options.ToString())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,14 @@ public bool IsCyclicReference(object expectation)
bool compareByMembers = expectation is not null && Options.GetEqualityStrategy(expectation.GetType())
is EqualityStrategy.Members or EqualityStrategy.ForceMembers;

var reference = new ObjectReference(expectation, CurrentNode.PathAndName, compareByMembers);
var reference = new ObjectReference(expectation, CurrentNode.Subject.PathAndName, compareByMembers);
return CyclicReferenceDetector.IsCyclicReference(reference);
}

public ITraceWriter TraceWriter { get; set; }

public override string ToString()
{
return Invariant($"{{Path=\"{CurrentNode.Description}\"}}");
return Invariant($"{{Path=\"{CurrentNode.Subject.PathAndName}\"}}");
}
}
2 changes: 1 addition & 1 deletion Src/FluentAssertions/Equivalency/EquivalencyValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ private static void AssertEquivalencyForCyclicReference(Comparands comparands, A

private void TryToProveNodesAreEquivalent(Comparands comparands, IEquivalencyValidationContext context)
{
using var _ = context.Tracer.WriteBlock(node => node.Description);
using var _ = context.Tracer.WriteBlock(node => node.Subject.Description);

foreach (IEquivalencyStep step in AssertionOptions.EquivalencyPlan)
{
Expand Down
2 changes: 1 addition & 1 deletion Src/FluentAssertions/Equivalency/Execution/ObjectInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public ObjectInfo(Comparands comparands, INode currentNode)
{
Type = currentNode.Type;
ParentType = currentNode.ParentType;
Path = currentNode.PathAndName;
Path = currentNode.Expectation.PathAndName;
CompileTimeType = comparands.CompileTimeType;
RuntimeType = comparands.RuntimeType;
}
Expand Down
15 changes: 4 additions & 11 deletions Src/FluentAssertions/Equivalency/Field.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,21 @@
namespace FluentAssertions.Equivalency;

/// <summary>
/// A specialized type of <see cref="INode "/> that represents a field of an object in a structural equivalency assertion.
/// A specialized type of <see cref="INode"/> that represents a field of an object in a structural equivalency assertion.
/// </summary>
internal class Field : Node, IMember
{
private readonly FieldInfo fieldInfo;
private bool? isBrowsable;

public Field(FieldInfo fieldInfo, INode parent)
: this(fieldInfo.ReflectedType, fieldInfo, parent)
{
}

public Field(Type reflectedType, FieldInfo fieldInfo, INode parent)
{
this.fieldInfo = fieldInfo;
DeclaringType = fieldInfo.DeclaringType;
ReflectedType = reflectedType;
Path = parent.PathAndName;
ReflectedType = fieldInfo.ReflectedType;
Subject = new Pathway(parent.Subject.PathAndName, fieldInfo.Name, pathAndName => $"field {parent.GetSubjectId().Combine(pathAndName)}");
Expectation = new Pathway(parent.Expectation.PathAndName, fieldInfo.Name, pathAndName => $"field {pathAndName}");
GetSubjectId = parent.GetSubjectId;
Name = fieldInfo.Name;
Type = fieldInfo.FieldType;
ParentType = fieldInfo.DeclaringType;
RootIsCollection = parent.RootIsCollection;
Expand All @@ -40,8 +35,6 @@ public object GetValue(object obj)

public Type DeclaringType { get; set; }

public override string Description => $"field {GetSubjectId().Combine(PathAndName)}";

public CSharpAccessModifier GetterAccessibility => fieldInfo.GetCSharpAccessModifier();

public CSharpAccessModifier SetterAccessibility => fieldInfo.GetCSharpAccessModifier();
Expand Down
32 changes: 4 additions & 28 deletions Src/FluentAssertions/Equivalency/INode.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using JetBrains.Annotations;

namespace FluentAssertions.Equivalency;

Expand All @@ -15,14 +14,6 @@ public interface INode
/// </summary>
GetSubjectId GetSubjectId { get; }

/// <summary>
/// Gets the name of this node.
/// </summary>
/// <example>
/// "Property2"
/// </example>
string Name { get; set; }

/// <summary>
/// Gets the type of this node, e.g. the type of the field or property, or the type of the collection item.
/// </summary>
Expand All @@ -34,24 +25,17 @@ public interface INode
/// <value>
/// Is <see langword="null"/> for the root object.
/// </value>
[CanBeNull]
Type ParentType { get; }

/// <summary>
/// Gets the path from the root object UNTIL the current node, separated by dots or index/key brackets.
/// Gets the path from the root of the subject upto and including to the current node.
/// </summary>
/// <example>
/// "Parent[0].Property2"
/// </example>
string Path { get; }
Pathway Subject { get; set; }

/// <summary>
/// Gets the full path from the root object up to and including the name of the node.
/// Gets the path from the root of the expectation upto and including to the current node.
/// </summary>
/// <example>
/// "Parent[0]"
/// </example>
string PathAndName { get; }
Pathway Expectation { get; }

/// <summary>
/// Gets a zero-based number representing the depth within the object graph
Expand All @@ -62,14 +46,6 @@ public interface INode
/// </remarks>
int Depth { get; }

/// <summary>
/// Gets the path including the description of the subject.
/// </summary>
/// <example>
/// "property subject.Parent[0].Property2"
/// </example>
string Description { get; }

/// <summary>
/// Gets a value indicating whether the current node is the root.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public MappedMemberMatchingRule(string expectationMemberName, string subjectMemb
public IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyOptions options, AssertionChain assertionChain)
{
if (parent.Type.IsSameOrInherits(typeof(TExpectation)) && subject is TSubject &&
expectedMember.Name == expectationMemberName)
expectedMember.Subject.Name == expectationMemberName)
{
var member = MemberFactory.Find(subject, subjectMemberName, parent);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public IMember Match(IMember expectedMember, object subject, INode parent, IEqui
path = path.WithCollectionAsRoot();
}

if (path.IsEquivalentTo(expectedMember.PathAndName))
if (path.IsEquivalentTo(expectedMember.Subject.PathAndName))
{
var member = MemberFactory.Find(subject, subjectPath.MemberName, parent);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public IMember Match(IMember expectedMember, object subject, INode parent, IEqui
if (options.IncludedProperties != MemberVisibility.None)
{
PropertyInfo propertyInfo = subject.GetType().FindProperty(
expectedMember.Name,
expectedMember.Subject.Name,
options.IncludedProperties | MemberVisibility.ExplicitlyImplemented | MemberVisibility.DefaultInterfaceProperties);

subjectMember = propertyInfo is not null && !propertyInfo.IsIndexer() ? new Property(propertyInfo, parent) : null;
Expand All @@ -25,7 +25,7 @@ public IMember Match(IMember expectedMember, object subject, INode parent, IEqui
if (subjectMember is null && options.IncludedFields != MemberVisibility.None)
{
FieldInfo fieldInfo = subject.GetType().FindField(
expectedMember.Name,
expectedMember.Subject.Name,
options.IncludedFields);

subjectMember = fieldInfo is not null ? new Field(fieldInfo, parent) : null;
Expand All @@ -34,12 +34,12 @@ public IMember Match(IMember expectedMember, object subject, INode parent, IEqui
if (subjectMember is null)
{
assertionChain.FailWith(
$"Expectation has {expectedMember.Description} that the other object does not have.");
$"Expectation has {expectedMember.Expectation} that the other object does not have.");
}
else if (options.IgnoreNonBrowsableOnSubject && !subjectMember.IsBrowsable)
{
assertionChain.FailWith(
$"Expectation has {expectedMember.Description} that is non-browsable in the other object, and non-browsable " +
$"Expectation has {expectedMember.Expectation} that is non-browsable in the other object, and non-browsable " +
"members on the subject are ignored with the current configuration");
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public IMember Match(IMember expectedMember, object subject, INode parent, IEqui
{
if (options.IncludedProperties != MemberVisibility.None)
{
PropertyInfo property = subject.GetType().FindProperty(expectedMember.Name,
PropertyInfo property = subject.GetType().FindProperty(expectedMember.Subject.Name,
options.IncludedProperties | MemberVisibility.ExplicitlyImplemented);

if (property is not null && !property.IsIndexer())
Expand All @@ -23,7 +23,7 @@ public IMember Match(IMember expectedMember, object subject, INode parent, IEqui
}

FieldInfo field = subject.GetType()
.FindField(expectedMember.Name, options.IncludedFields);
.FindField(expectedMember.Subject.Name, options.IncludedFields);

return field is not null ? new Field(field, parent) : null;
}
Expand Down
58 changes: 25 additions & 33 deletions Src/FluentAssertions/Equivalency/Node.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@ internal class Node : INode

private GetSubjectId subjectIdProvider;

private string path;
private string name;
private string pathAndName;
private string cachedSubjectId;
private Pathway subject;

public GetSubjectId GetSubjectId
{
Expand All @@ -27,41 +25,32 @@ public GetSubjectId GetSubjectId

public Type ParentType { get; protected set; }

public string Path
public Pathway Subject
{
get => path;
protected set
{
path = value;
pathAndName = null;
}
}

public string PathAndName => pathAndName ??= Path.Combine(Name);

public string Name
{
get => name;
get => subject;
set
{
name = value;
pathAndName = null;
subject = value;
if (Expectation is null)
{
Expectation = new Pathway(value);
}
}
}

public virtual string Description => $"{GetSubjectId().Combine(PathAndName)}";
public Pathway Expectation { get; protected set; }

public bool IsRoot
{
get
{
// If the root is a collection, we need treat the objects in that collection as the root of the graph because all options
// refer to the type of the collection items.
return PathAndName.Length == 0 || (RootIsCollection && IsFirstIndex);
return Subject.PathAndName.Length == 0 || (RootIsCollection && IsFirstIndex);
}
}

private bool IsFirstIndex => MatchFirstIndex.IsMatch(PathAndName);
private bool IsFirstIndex => MatchFirstIndex.IsMatch(Subject.PathAndName);

public bool RootIsCollection { get; protected set; }

Expand All @@ -70,7 +59,7 @@ public int Depth
get
{
const char memberSeparator = '.';
return PathAndName.Count(chr => chr == memberSeparator);
return Subject.PathAndName.Count(chr => chr == memberSeparator);
}
}

Expand All @@ -84,8 +73,7 @@ public static INode From<T>(GetSubjectId getSubjectId)
return new Node
{
subjectIdProvider = () => getSubjectId() ?? "root",
Name = string.Empty,
Path = string.Empty,
Subject = new Pathway(string.Empty, string.Empty, _ => getSubjectId()),
Type = typeof(T),
ParentType = null,
RootIsCollection = IsCollection(typeof(T))
Expand All @@ -94,25 +82,29 @@ public static INode From<T>(GetSubjectId getSubjectId)

public static INode FromCollectionItem<T>(string index, INode parent)
{
Pathway.GetDescription getDescription = pathAndName => $"{parent.GetSubjectId().Combine(pathAndName)}";

return new Node
{
Type = typeof(T),
ParentType = parent.Type,
Name = "[" + index + "]",
Path = parent.PathAndName,
Subject = new Pathway(parent.Subject, "[" + index + "]", getDescription),
Expectation = new Pathway(parent.Expectation, "[" + index + "]", getDescription),
GetSubjectId = parent.GetSubjectId,
RootIsCollection = parent.RootIsCollection
};
}

public static INode FromDictionaryItem<T>(object key, INode parent)
{
Pathway.GetDescription getDescription = pathAndName => $"{parent.GetSubjectId().Combine(pathAndName)}";

return new Node
{
Type = typeof(T),
ParentType = parent.Type,
Name = "[" + key + "]",
Path = parent.PathAndName,
Subject = new Pathway(parent.Subject, "[" + key + "]", getDescription),
Expectation = new Pathway(parent.Expectation, "[" + key + "]", getDescription),
GetSubjectId = parent.GetSubjectId,
RootIsCollection = parent.RootIsCollection
};
Expand All @@ -138,19 +130,19 @@ public override bool Equals(object obj)
return Equals((Node)obj);
}

private bool Equals(Node other) => (Type, Name, Path) == (other.Type, other.Name, other.Path);
private bool Equals(Node other) => (Type, Subject.Name, Subject.Path) == (other.Type, other.Subject.Name, other.Subject.Path);

public override int GetHashCode()
{
unchecked
{
#pragma warning disable CA1307
int hashCode = Type.GetHashCode();
hashCode = (hashCode * 397) + Path.GetHashCode();
hashCode = (hashCode * 397) + Name.GetHashCode();
hashCode = (hashCode * 397) + Subject.Path.GetHashCode();
hashCode = (hashCode * 397) + Subject.Name.GetHashCode();
return hashCode;
}
}

public override string ToString() => Description;
public override string ToString() => Subject.Description;
}
Loading

0 comments on commit 4b93077

Please sign in to comment.