-
-
Notifications
You must be signed in to change notification settings - Fork 540
/
AssertionRuleEquivalencyStep.cs
113 lines (93 loc) · 4.23 KB
/
AssertionRuleEquivalencyStep.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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
using System;
using System.Linq.Expressions;
using FluentAssertions.Common;
using FluentAssertions.Equivalency.Execution;
using FluentAssertions.Execution;
namespace FluentAssertions.Equivalency.Steps;
public class AssertionRuleEquivalencyStep<TSubject> : IEquivalencyStep
{
private readonly Func<IObjectInfo, bool> predicate;
private readonly string description;
private readonly Action<IAssertionContext<TSubject>> assertionAction;
private readonly AutoConversionStep converter = new();
public AssertionRuleEquivalencyStep(
Expression<Func<IObjectInfo, bool>> predicate,
Action<IAssertionContext<TSubject>> assertionAction)
{
this.predicate = predicate.Compile();
this.assertionAction = assertionAction;
description = predicate.ToString();
}
public EquivalencyResult Handle(Comparands comparands, AssertionChain assertionChain, IEquivalencyValidationContext context,
IValidateChildNodeEquivalency nestedValidator)
{
bool success = false;
using (var scope = new AssertionScope())
{
// Try without conversion
if (AppliesTo(comparands, context.CurrentNode))
{
success = ExecuteAssertion(comparands, context);
}
bool converted = false;
if (!success && context.Options.ConversionSelector.RequiresConversion(comparands, context.CurrentNode))
{
// Convert into a child context
context = context.Clone();
converter.Handle(comparands, assertionChain, context, nestedValidator);
converted = true;
}
if (converted && AppliesTo(comparands, context.CurrentNode))
{
// Try again after conversion
success = ExecuteAssertion(comparands, context);
if (assertionChain.Succeeded)
{
// If the assertion succeeded after conversion, discard the failures from
// the previous attempt. If it didn't, let the scope throw with those failures.
scope.Discard();
}
}
}
return success ? EquivalencyResult.EquivalencyProven : EquivalencyResult.ContinueWithNext;
}
private bool AppliesTo(Comparands comparands, INode currentNode) => predicate(new ObjectInfo(comparands, currentNode));
private bool ExecuteAssertion(Comparands comparands, IEquivalencyValidationContext context)
{
bool subjectIsNull = comparands.Subject is null;
bool subjectIsValidType =
AssertionScope.Current
.ForCondition(subjectIsNull || comparands.Subject.GetType().IsSameOrInherits(typeof(TSubject)))
.FailWith("Expected " + context.CurrentNode.Description + " from subject to be a {0}{reason}, but found a {1}.",
typeof(TSubject), comparands.Subject?.GetType());
bool expectationIsNull = comparands.Expectation is null;
bool expectationIsValidType =
AssertionScope.Current
.ForCondition(expectationIsNull || comparands.Expectation.GetType().IsSameOrInherits(typeof(TSubject)))
.FailWith(
"Expected " + context.CurrentNode.Description + " from expectation to be a {0}{reason}, but found a {1}.",
typeof(TSubject), comparands.Expectation?.GetType());
if (subjectIsValidType && expectationIsValidType)
{
if ((subjectIsNull || expectationIsNull) && !CanBeNull<TSubject>())
{
return false;
}
assertionAction(AssertionContext<TSubject>.CreateFrom(comparands, context));
return true;
}
return false;
}
private static bool CanBeNull<T>() => !typeof(T).IsValueType || Nullable.GetUnderlyingType(typeof(T)) is not null;
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>
/// A string that represents the current object.
/// </returns>
/// <filterpriority>2</filterpriority>
public override string ToString()
{
return "Invoke Action<" + typeof(TSubject).Name + "> when " + description;
}
}