Skip to content

Commit

Permalink
Merge pull request #1939 from jnyrup/perfomance20220527
Browse files Browse the repository at this point in the history
  • Loading branch information
jnyrup committed Jun 2, 2022
2 parents bf92abf + a741e66 commit 1f4db92
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 23 deletions.
Expand Up @@ -845,7 +845,7 @@ public AndConstraint<TAssertions> Contain(IEnumerable<T> expected, string becaus

using (var scope = new AssertionScope())
{
scope.AddReportable("configuration", options.ToString());
scope.AddReportable("configuration", () => options.ToString());

foreach (T actualItem in Subject)
{
Expand Down Expand Up @@ -2199,7 +2199,7 @@ public AndConstraint<TAssertions> NotContain(IEnumerable<T> unexpected, string b
Execute.Assertion
.BecauseOf(because, becauseArgs)
.WithExpectation("Expected {context:collection} {0} not to contain equivalent of {1}{reason}, ", Subject, unexpected)
.AddReportable("configuration", options.ToString());
.AddReportable("configuration", () => options.ToString());

if (foundIndices.Count == 1)
{
Expand Down
26 changes: 19 additions & 7 deletions Src/FluentAssertions/Equivalency/ConversionSelector.cs
Expand Up @@ -27,8 +27,19 @@ public ConversionSelectorRule(Func<IObjectInfo, bool> predicate, string descript
}
}

private List<ConversionSelectorRule> inclusions = new();
private List<ConversionSelectorRule> exclusions = new();
private readonly List<ConversionSelectorRule> inclusions;
private readonly List<ConversionSelectorRule> exclusions;

public ConversionSelector()
: this(new List<ConversionSelectorRule>(), new List<ConversionSelectorRule>())
{
}

private ConversionSelector(List<ConversionSelectorRule> inclusions, List<ConversionSelectorRule> exclusions)
{
this.inclusions = inclusions;
this.exclusions = exclusions;
}

public void IncludeAll()
{
Expand All @@ -55,6 +66,11 @@ public void Exclude(Expression<Func<IObjectInfo, bool>> predicate)

public bool RequiresConversion(Comparands comparands, INode currentNode)
{
if (inclusions.Count == 0)
{
return false;
}

var objectInfo = new ObjectInfo(comparands, currentNode);

return inclusions.Any(p => p.Predicate(objectInfo)) && !exclusions.Any(p => p.Predicate(objectInfo));
Expand Down Expand Up @@ -84,10 +100,6 @@ public override string ToString()

public ConversionSelector Clone()
{
return new ConversionSelector
{
inclusions = new List<ConversionSelectorRule>(inclusions),
exclusions = new List<ConversionSelectorRule>(exclusions),
};
return new ConversionSelector(new List<ConversionSelectorRule>(inclusions), new List<ConversionSelectorRule>(exclusions));
}
}
6 changes: 4 additions & 2 deletions Src/FluentAssertions/Equivalency/EquivalencyValidator.cs
@@ -1,4 +1,5 @@
using System;
using FluentAssertions.Equivalency.Tracing;
using FluentAssertions.Execution;

namespace FluentAssertions.Equivalency;
Expand All @@ -15,7 +16,7 @@ public void AssertEquality(Comparands comparands, EquivalencyValidationContext c
using var scope = new AssertionScope();

scope.AssumeSingleCaller();
scope.AddReportable("configuration", context.Options.ToString());
scope.AddReportable("configuration", () => context.Options.ToString());
scope.BecauseOf(context.Reason);

RecursivelyAssertEquality(comparands, context);
Expand Down Expand Up @@ -64,12 +65,13 @@ private void RunStepsUntilEquivalencyIsProven(Comparands comparands, IEquivalenc
{
using var _ = context.Tracer.WriteBlock(node => node.Description);

Func<IEquivalencyStep, GetTraceMessage> getMessage = step => _ => $"Equivalency was proven by {step.GetType().Name}";
foreach (IEquivalencyStep step in AssertionOptions.EquivalencyPlan)
{
var result = step.Handle(comparands, context, this);
if (result == EquivalencyResult.AssertionCompleted)
{
context.Tracer.WriteLine(_ => $"Equivalency was proven by {step.GetType().Name}");
context.Tracer.WriteLine(getMessage(step));
return;
}
}
Expand Down
34 changes: 29 additions & 5 deletions Src/FluentAssertions/Equivalency/Node.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using FluentAssertions.Common;
Expand All @@ -13,15 +14,35 @@ public class Node : INode
{
private static readonly Regex MatchFirstIndex = new(@"^\[\d+\]$");

private string path;
private string name;
private string pathAndName;

public GetSubjectId GetSubjectId { get; protected set; } = () => string.Empty;

public Type Type { get; protected set; }

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

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

public string Name { get; set; }
public string Name
{
get => name;
set
{
name = value;
pathAndName = null;
}
}

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

Expand Down Expand Up @@ -109,14 +130,17 @@ public override bool Equals(object obj)
return Equals((Node)obj);
}

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

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

Expand Down
Expand Up @@ -15,22 +15,24 @@ public class StructuralEqualityEquivalencyStep : IEquivalencyStep
return EquivalencyResult.ContinueWithNext;
}

bool expectationIsNotNull = AssertionScope.Current
.ForCondition(comparands.Expectation is not null)
if (comparands.Expectation is null)
{
AssertionScope.Current
.BecauseOf(context.Reason)
.FailWith(
"Expected {context:subject} to be <null>{reason}, but found {0}.",
comparands.Subject);

bool subjectIsNotNull = AssertionScope.Current
.ForCondition(comparands.Subject is not null)
}
else if (comparands.Subject is null)
{
AssertionScope.Current
.BecauseOf(context.Reason)
.FailWith(
"Expected {context:object} to be {0}{reason}, but found {1}.",
comparands.Expectation,
comparands.Subject);

if (expectationIsNotNull && subjectIsNotNull)
}
else
{
IMember[] selectedMembers = GetMembersFromExpectation(context.CurrentNode, comparands, context.Options).ToArray();
if (context.CurrentNode.IsRoot && !selectedMembers.Any())
Expand Down
16 changes: 16 additions & 0 deletions Tests/FluentAssertions.Equivalency.Specs/CyclicReferencesSpecs.cs
Expand Up @@ -158,6 +158,22 @@ public void
act.Should().NotThrow();
}

[Fact]
public void Allowing_infinite_recursion_is_described_in_the_failure_message()
{
// Arrange
var recursiveClass1 = new ClassWithFiniteRecursiveProperty(1);
var recursiveClass2 = new ClassWithFiniteRecursiveProperty(2);

// Act
Action act = () => recursiveClass1.Should().BeEquivalentTo(recursiveClass2,
options => options.AllowingInfiniteRecursion());

// Assert
act.Should().Throw<XunitException>()
.WithMessage("*Recurse indefinitely*");
}

[Fact]
public void When_injecting_a_null_config_to_BeEquivalentTo_it_should_throw()
{
Expand Down
96 changes: 96 additions & 0 deletions Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.cs
Expand Up @@ -51,6 +51,102 @@ public void When_specific_properties_have_been_specified_it_should_ignore_the_ot
act.Should().NotThrow();
}

[Fact]
public void A_member_included_by_path_is_described_in_the_failure_message()
{
// Arrange
var subject = new
{
Name = "John"
};

var customer = new
{
Name = "Jack"
};

// Act
Action act = () => subject.Should().BeEquivalentTo(customer, options => options
.Including(d => d.Name));

// Assert
act.Should().Throw<XunitException>()
.WithMessage("*Include*Name*");
}

[Fact]
public void A_member_included_by_predicate_is_described_in_the_failure_message()
{
// Arrange
var subject = new
{
Name = "John"
};

var customer = new
{
Name = "Jack"
};

// Act
Action act = () => subject.Should().BeEquivalentTo(customer, options => options
.Including(ctx => ctx.Path == "Name"));

// Assert
act.Should().Throw<XunitException>()
.WithMessage("*Include member when*Name*");
}

[Fact]
public void A_member_excluded_by_path_is_described_in_the_failure_message()
{
// Arrange
var subject = new
{
Name = "John",
Age = 13
};

var customer = new
{
Name = "Jack",
Age = 37
};

// Act
Action act = () => subject.Should().BeEquivalentTo(customer, options => options
.Excluding(d => d.Age));

// Assert
act.Should().Throw<XunitException>()
.WithMessage("*Exclude*Age*");
}

[Fact]
public void A_member_excluded_by_predicate_is_described_in_the_failure_message()
{
// Arrange
var subject = new
{
Name = "John",
Age = 13
};

var customer = new
{
Name = "Jack",
Age = 37
};

// Act
Action act = () => subject.Should().BeEquivalentTo(customer, options => options
.Excluding(ctx => ctx.Path == "Age"));

// Assert
act.Should().Throw<XunitException>()
.WithMessage("*Exclude member when*Age*");
}

[Fact]
public void When_a_predicate_for_properties_to_include_has_been_specified_it_should_ignore_the_other_properties()
{
Expand Down

0 comments on commit 1f4db92

Please sign in to comment.