-
Notifications
You must be signed in to change notification settings - Fork 28
/
Copy pathTCKVerificationSupport.cs
146 lines (127 loc) · 5.98 KB
/
TCKVerificationSupport.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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
using System;
using Xunit;
using Xunit.Abstractions;
using Reactive.Streams.TCK.Support;
namespace Reactive.Streams.TCK.Tests.Support
{
/// <summary>
/// Provides assertions to validate the TCK tests themselves,
/// with the goal of guaranteeing proper error messages when an implementation does not pass a given TCK test.
///
/// "Quis custodiet ipsos custodes?" -- Iuvenalis
/// </summary>
// ReSharper disable once InconsistentNaming
public class TCKVerificationSupport
{
// INTERNAL ASSERTION METHODS //
/// <summary>
/// Runs given code block and expects it to fail with an "Expected onError" failure.
/// Use this method to validate that TCK tests fail with meaningful errors instead of NullPointerExceptions etc.
/// </summary>
/// <param name="throwingRun">encapsulates test case which we expect to fail</param>
/// <param name="messagePart">the exception failing the test (inside the run parameter) must contain this message part in one of it's causes</param>
public void RequireTestFailure(Action throwingRun, string messagePart)
{
try
{
throwingRun();
}
catch (Exception ex)
{
if (FindDeepErrorMessage(ex, messagePart))
return;
throw new Exception($"Expected TCK to fail with '... {messagePart} ...', " +
$"yet `{ex.GetType().Name}({ex.Message})` was thrown and test would fail with not useful error message!", ex);
}
throw new Exception($"Expected TCK to fail with '... {messagePart} ...', " +
"yet no exception was thrown and test would pass unexpectedly!");
}
/// <summary>
/// Runs given code block and expects it fail with an <see cref="SkipException"/>
/// </summary>
/// <param name="throwingRun">encapsulates test case which we expect to be skipped</param>
/// <param name="messagePart">the exception failing the test (inside the run parameter) must contain this message part in one of it's causes</param>
public void RequireTestSkip(Action throwingRun, string messagePart)
{
try
{
throwingRun();
}
catch (SkipException ignore)
{
if(ignore.Message.Contains(messagePart))
return;
throw new Exception($"Expected TCK to skip this test with '... {messagePart} ...', " +
$"yet it skipped with ({ignore.Message}) instead!", ignore);
}
catch (Exception ex)
{
throw new Exception(
$"Expected TCK to skip this test, yet it threw {ex.GetType().Name}({ex.Message}) instead!", ex);
}
throw new Exception($"Expected TCK to fail with '... {messagePart} ...', " +
"yet no exception was thrown and test would pass unexpectedly!");
}
/// <summary>
/// This publisher does NOT fulfil all Publisher spec requirements.
/// It's just the bare minimum to enable this test to fail the Subscriber tests.
/// </summary>
public IPublisher<int> NewSimpleIntsPublisher(long maxElementsToEmit)
=> new SimpleIntsPublisher(maxElementsToEmit);
private sealed class SimpleIntsPublisher : IPublisher<int>
{
private sealed class SimpleIntsSubscribtion : ISubscription
{
private readonly SimpleIntsPublisher _publisher;
private readonly AtomicCounterLong _nums = new AtomicCounterLong();
private readonly AtomicBoolean _active = new AtomicBoolean(true);
public SimpleIntsSubscribtion(SimpleIntsPublisher publisher)
{
_publisher = publisher;
}
public void Request(long n)
{
var thisDemand = n;
while (_active.Value && thisDemand > 0 && _nums.Current < _publisher._maxElementsToEmit)
{
_publisher._subscriber.OnNext((int)_nums.GetAndIncrement());
thisDemand--;
}
if (_nums.Current == _publisher._maxElementsToEmit)
_publisher._subscriber.OnComplete();
}
public void Cancel() => _active.Value = false;
}
private ISubscriber<int> _subscriber;
private readonly long _maxElementsToEmit;
public SimpleIntsPublisher(long maxElementsToEmit)
{
_maxElementsToEmit = maxElementsToEmit;
}
public void Subscribe(ISubscriber<int> subscriber)
{
_subscriber = subscriber;
subscriber.OnSubscribe(new SimpleIntsSubscribtion(this));
}
}
/// <summary>
/// Looks for expected error message prefix inside of causes of thrown throwable.
/// </summary>
/// <returns>true if one of the causes indeed contains expected error, false otherwise</returns>
public bool FindDeepErrorMessage(Exception exception, string messagePart)
=> FindDeepErrorMessage(exception, messagePart, 5);
private bool FindDeepErrorMessage(Exception exception, string messagePart, int depth)
{
if (exception is NullReferenceException)
{
Assert.Fail($"{typeof(NullReferenceException).Name} was thrown, definitely not a helpful error!",
exception);
}
if (exception == null || depth == 0)
return false;
var message = exception.Message;
return message.Contains(messagePart) ||
FindDeepErrorMessage(exception.InnerException, messagePart, depth - 1);
}
}
}