-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathAssertionService.cs
More file actions
113 lines (98 loc) · 3.85 KB
/
Copy pathAssertionService.cs
File metadata and controls
113 lines (98 loc) · 3.85 KB
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.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using TedToolkit.Assertions.AssertionItems;
using TedToolkit.Assertions.Assertions;
using TedToolkit.Assertions.Exceptions;
using TedToolkit.Assertions.Execution;
namespace TedToolkit.Assertions;
[DebuggerNonUserCode]
file class DefaultStrategy(
AssertionType minRaisingExceptionType,
[StringSyntax(StringSyntaxAttribute.DateTimeFormat)]
string timeFormat,
Func<CallerInfo, string>? callerInfoFormate) : IAssertionStrategy
{
public object? HandleFailure(AssertionScope scope, IReadOnlyList<IAssertion> assertions)
{
var stringBuilder = new StringBuilder();
var messageCount = 0;
foreach (var assertion in assertions)
{
if (assertion.Type < minRaisingExceptionType) continue;
foreach (var assertionItem in assertion.Items)
{
stringBuilder.AppendLine(
$"{++messageCount:D2}. [{assertionItem.Time.ToString(timeFormat)}] {assertionItem.Message}");
stringBuilder.AppendLine(callerInfoFormate?.Invoke(assertion.CallerInfo) ??
$" {assertion.CallerInfo}");
}
}
if (messageCount is 0) return null;
stringBuilder.Insert(0,
$"[{DateTimeOffset.Now.ToString(timeFormat)}][{messageCount} message(s)]{scope.Context}\n");
throw new AssertionException(stringBuilder.ToString());
}
public object? HandleFailure(AssertionScope scope, AssertionType assertionType, AssertionItem assertion,
object? tag, CallerInfo callerInfo)
{
if (assertionType < minRaisingExceptionType) return null;
var message = $"{assertion.Message}\nwhen [{assertion.Time.ToString(timeFormat)}]{scope.Context}";
throw new AssertionException(message);
}
}
/// <summary>
/// The service of the assertions.
/// </summary>
public readonly struct AssertionService(IAssertionStrategy strategy)
{
private static readonly ConcurrentBag<AssertionService> CurrentServices = [];
private static readonly AsyncLocal<MergedAssertionStrategy> StrategyAsyncLocal = new();
internal static MergedAssertionStrategy MergedStrategy =>
StrategyAsyncLocal.Value ??= CalculateMergedStrategy();
private static void CheckCurrentServices()
{
if (CurrentServices.Count > 0) return;
CurrentServices.Add(new AssertionService(AssertionType.Must));
}
private static MergedAssertionStrategy CalculateMergedStrategy()
{
CheckCurrentServices();
return new MergedAssertionStrategy([..CurrentServices.Select(s => s.Strategy)]);
}
/// <summary>
/// Create the service by the min raising exception type
/// </summary>
/// <param name="minRaisingExceptionType"></param>
/// <param name="timeFormat"></param>
/// <param name="callerInfoFormat"></param>
public AssertionService(AssertionType minRaisingExceptionType, string timeFormat = "yyyy-MM-dd HH:mm:ss.fff zzz",
Func<CallerInfo, string>? callerInfoFormat = null)
: this(new DefaultStrategy(minRaisingExceptionType, timeFormat, callerInfoFormat))
{
}
/// <summary>
/// Set the service
/// </summary>
/// <param name="service"></param>
public static void Add(AssertionService service)
{
CurrentServices.Add(service);
StrategyAsyncLocal.Value = CalculateMergedStrategy();
}
/// <summary>
/// Clear the services
/// </summary>
public static void Clear()
{
while (CurrentServices.TryTake(out _))
{
}
StrategyAsyncLocal.Value = CalculateMergedStrategy();
}
/// <summary>
/// The default push / scope strategy.
/// </summary>
public IAssertionStrategy Strategy { get; } = strategy;
}