-
-
Notifications
You must be signed in to change notification settings - Fork 540
/
EquivalencyValidator.cs
89 lines (71 loc) · 3.42 KB
/
EquivalencyValidator.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
using System;
using FluentAssertions.Equivalency.Tracing;
using FluentAssertions.Execution;
namespace FluentAssertions.Equivalency;
/// <summary>
/// Is responsible for validating the equivalency of a subject with another object.
/// </summary>
public class EquivalencyValidator : IValidateChildNodeEquivalency
{
private const int MaxDepth = 10;
public void AssertEquality(Comparands comparands, EquivalencyValidationContext context)
{
using var scope = new AssertionScope();
var getIdentifierOnce = new Lazy<string>(() => scope.GetIdentifier());
var assertion = AssertionChain.GetOrCreate(() => scope, () => getIdentifierOnce.Value);
assertion.WithReportable("configuration", () => context.Options.ToString());
assertion.BecauseOf(context.Reason);
RecursivelyAssertEquivalencyOf(comparands, assertion, context);
if (context.TraceWriter is not null)
{
assertion.AppendTracing(context.TraceWriter.ToString());
}
}
private void RecursivelyAssertEquivalencyOf(Comparands comparands, AssertionChain assertionChain, IEquivalencyValidationContext context)
{
AssertEquivalencyOf(comparands, assertionChain, context);
}
public void AssertEquivalencyOf(Comparands comparands, AssertionChain assertionChain, IEquivalencyValidationContext context)
{
if (ShouldContinueThisDeep(context.CurrentNode, context.Options, assertionChain))
{
TrackWhatIsNeededToProvideContextToFailures(assertionChain, comparands, context.CurrentNode);
if (!context.IsCyclicReference(comparands.Expectation))
{
TryToProveNodesAreEquivalent(assertionChain, comparands, context);
}
}
}
private static bool ShouldContinueThisDeep(INode currentNode, IEquivalencyOptions options,
AssertionChain assertionChain)
{
bool shouldRecurse = options.AllowInfiniteRecursion || currentNode.Depth <= MaxDepth;
if (!shouldRecurse)
{
// This will throw, unless we're inside an AssertionScope
assertionChain.FailWith($"The maximum recursion depth of {MaxDepth} was reached. ");
}
return shouldRecurse;
}
private static void TrackWhatIsNeededToProvideContextToFailures(AssertionChain assertionChain, Comparands comparands, INode currentNode)
{
assertionChain.Context = new Lazy<string>(() => currentNode.Description);
assertionChain.TrackComparands(comparands.Subject, comparands.Expectation);
}
private void TryToProveNodesAreEquivalent(AssertionChain assertionChain, Comparands comparands, IEquivalencyValidationContext context)
{
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, assertionChain, context, this);
if (result == EquivalencyResult.EquivalencyProven)
{
context.Tracer.WriteLine(getMessage(step));
return;
}
}
throw new NotSupportedException(
$"Do not know how to compare {comparands.Subject} and {comparands.Expectation}. Please report an issue through https://www.fluentassertions.com.");
}
}