4
4
import org .reactivestreams .Publisher ;
5
5
import org .reactivestreams .Subscriber ;
6
6
import org .reactivestreams .Subscription ;
7
+ import org .reactivestreams .tck .support .NonFatal ;
7
8
import org .reactivestreams .tck .support .TCKVerificationSupport ;
8
9
import org .testng .annotations .AfterClass ;
9
10
import org .testng .annotations .BeforeClass ;
10
11
import org .testng .annotations .Test ;
11
12
12
- import java .util .concurrent .ExecutorService ;
13
- import java .util .concurrent .Executors ;
13
+ import java .util .concurrent .* ;
14
+ import java .util .concurrent .atomic . AtomicLong ;
14
15
15
16
/**
16
17
* Validates that the TCK's {@link IdentityProcessorVerification} fails with nice human readable errors.
@@ -27,25 +28,36 @@ public class IdentityProcessorVerificationTest extends TCKVerificationSupport {
27
28
@ Test
28
29
public void required_spec104_mustCallOnErrorOnAllItsSubscribersIfItEncountersANonRecoverableError_shouldBeIgnored () throws Throwable {
29
30
requireTestSkip (new ThrowingRunnable () {
30
- @ Override public void run () throws Throwable {
31
- new IdentityProcessorVerification <Integer >(newTestEnvironment (), DEFAULT_TIMEOUT_MILLIS ){
32
- @ Override public Processor <Integer , Integer > createIdentityProcessor (int bufferSize ) {
31
+ @ Override
32
+ public void run () throws Throwable {
33
+ new IdentityProcessorVerification <Integer >(newTestEnvironment (), DEFAULT_TIMEOUT_MILLIS ) {
34
+ @ Override
35
+ public Processor <Integer , Integer > createIdentityProcessor (int bufferSize ) {
33
36
return new NoopProcessor ();
34
37
}
35
38
36
- @ Override public ExecutorService publisherExecutorService () { return ex ; }
39
+ @ Override
40
+ public ExecutorService publisherExecutorService () {
41
+ return ex ;
42
+ }
37
43
38
- @ Override public Integer createElement (int element ) { return element ; }
44
+ @ Override
45
+ public Integer createElement (int element ) {
46
+ return element ;
47
+ }
39
48
40
- @ Override public Publisher <Integer > createHelperPublisher (long elements ) {
49
+ @ Override
50
+ public Publisher <Integer > createHelperPublisher (long elements ) {
41
51
return SKIP ;
42
52
}
43
53
44
- @ Override public Publisher <Integer > createFailedPublisher () {
54
+ @ Override
55
+ public Publisher <Integer > createFailedPublisher () {
45
56
return SKIP ;
46
57
}
47
58
48
- @ Override public long maxSupportedSubscribers () {
59
+ @ Override
60
+ public long maxSupportedSubscribers () {
49
61
return 1 ; // can only support 1 subscribe => unable to run this test
50
62
}
51
63
}.required_spec104_mustCallOnErrorOnAllItsSubscribersIfItEncountersANonRecoverableError ();
@@ -115,6 +127,145 @@ public void required_spec104_mustCallOnErrorOnAllItsSubscribersIfItEncountersANo
115
127
}, "Did not receive expected error on downstream within " + DEFAULT_TIMEOUT_MILLIS );
116
128
}
117
129
130
+ @ Test
131
+ public void required_spec104_mustCallOnErrorOnAllItsSubscribersIfItEncountersANonRecoverableError_shouldAllowSignalingElementAfterBothDownstreamsDemand () throws Throwable {
132
+ final TestEnvironment env = newTestEnvironment ();
133
+ new IdentityProcessorVerification <Integer >(env , DEFAULT_TIMEOUT_MILLIS ) {
134
+ @ Override
135
+ public Processor <Integer , Integer > createIdentityProcessor (int bufferSize ) { // knowingly ignoring buffer size, acting as-if 0
136
+ return new Processor <Integer , Integer >() {
137
+
138
+ private volatile Subscription upstreamSubscription ;
139
+
140
+ private final CopyOnWriteArrayList <MySubscription > subs = new CopyOnWriteArrayList <MySubscription >();
141
+ private final CopyOnWriteArrayList <Subscriber <? super Integer >> subscribers = new CopyOnWriteArrayList <Subscriber <? super Integer >>();
142
+ private final AtomicLong demand1 = new AtomicLong ();
143
+ private final AtomicLong demand2 = new AtomicLong ();
144
+ private final CountDownLatch awaitLatch = new CountDownLatch (2 ); // to know when both subscribers have signalled demand
145
+
146
+ @ Override
147
+ public void subscribe (final Subscriber <? super Integer > s ) {
148
+ int subscriberCount = subs .size ();
149
+ switch (subscriberCount ) {
150
+ case 0 :
151
+ s .onSubscribe (createSubscription (awaitLatch , s , demand1 ));
152
+ break ;
153
+ case 1 :
154
+ s .onSubscribe (createSubscription (awaitLatch , s , demand2 ));
155
+ break ;
156
+ default :
157
+ throw new RuntimeException (String .format ("This for-test-purposes-processor supports only 2 subscribers, yet got %s!" , subscriberCount ));
158
+ }
159
+ }
160
+
161
+ @ Override
162
+ public void onSubscribe (Subscription s ) {
163
+ this .upstreamSubscription = s ;
164
+ }
165
+
166
+ @ Override
167
+ public void onNext (Integer elem ) {
168
+ for (Subscriber <? super Integer > subscriber : subscribers ) {
169
+ try {
170
+ subscriber .onNext (elem );
171
+ } catch (Throwable t ) {
172
+ env .flop (t , String .format ("Calling onNext on [%s] should not throw! See https://github.com/reactive-streams/reactive-streams-jvm#2.13" , subscriber ));
173
+ }
174
+ }
175
+ }
176
+
177
+ @ Override
178
+ public void onError (Throwable t ) {
179
+ for (Subscriber <? super Integer > subscriber : subscribers ) {
180
+ try {
181
+ subscriber .onError (t );
182
+ } catch (Exception ex ) {
183
+ env .flop (ex , String .format ("Calling onError on [%s] should not throw! See https://github.com/reactive-streams/reactive-streams-jvm#2.13" , subscriber ));
184
+ }
185
+ }
186
+ }
187
+
188
+ @ Override
189
+ public void onComplete () {
190
+ for (Subscriber <? super Integer > subscriber : subscribers ) {
191
+ try {
192
+ subscriber .onComplete ();
193
+ } catch (Exception ex ) {
194
+ env .flop (ex , String .format ("Calling onComplete on [%s] should not throw! See https://github.com/reactive-streams/reactive-streams-jvm#2.13" , subscriber ));
195
+ }
196
+ }
197
+ }
198
+
199
+ private Subscription createSubscription (CountDownLatch awaitLatch , final Subscriber <? super Integer > s , final AtomicLong demand ) {
200
+ final MySubscription sub = new MySubscription (awaitLatch , s , demand );
201
+ subs .add (sub );
202
+ subscribers .add (s );
203
+ return sub ;
204
+ }
205
+
206
+ final class MySubscription implements Subscription {
207
+ private final CountDownLatch awaitLatch ;
208
+ private final Subscriber <? super Integer > s ;
209
+ private final AtomicLong demand ;
210
+
211
+ public MySubscription (CountDownLatch awaitTwoLatch , Subscriber <? super Integer > s , AtomicLong demand ) {
212
+ this .awaitLatch = awaitTwoLatch ;
213
+ this .s = s ;
214
+ this .demand = demand ;
215
+ }
216
+
217
+ @ Override
218
+ public void request (final long n ) {
219
+ ex .execute (new Runnable () {
220
+ @ Override
221
+ public void run () {
222
+ if (demand .get () >= 0 ) {
223
+ demand .addAndGet (n );
224
+ awaitLatch .countDown ();
225
+ try {
226
+ awaitLatch .await (env .defaultTimeoutMillis (), TimeUnit .MILLISECONDS );
227
+ final long d = demand .getAndSet (0 );
228
+ if (d > 0 ) upstreamSubscription .request (d );
229
+ } catch (InterruptedException e ) {
230
+ env .flop (e , "Interrupted while awaiting for all downstreams to signal some demand." );
231
+ } catch (Throwable t ) {
232
+ env .flop (t , "Subscription#request has thrown an exception, which is illegal!" );
233
+ }
234
+ } // else cancel was called, do nothing
235
+ }
236
+ });
237
+ }
238
+
239
+ @ Override
240
+ public void cancel () {
241
+ demand .set (-1 ); // marks subscription as cancelled
242
+ }
243
+
244
+ @ Override
245
+ public String toString () {
246
+ return String .format ("IdentityProcessorVerificationTest:MySubscription(%s, demand = %s)" , s , demand );
247
+ }
248
+ }
249
+ };
250
+ }
251
+
252
+ @ Override
253
+ public ExecutorService publisherExecutorService () {
254
+ return ex ;
255
+ }
256
+
257
+ @ Override
258
+ public Integer createElement (int element ) {
259
+ return element ;
260
+ }
261
+
262
+ @ Override
263
+ public Publisher <Integer > createFailedPublisher () {
264
+ return SKIP ;
265
+ }
266
+ }.required_spec104_mustCallOnErrorOnAllItsSubscribersIfItEncountersANonRecoverableError ();
267
+ }
268
+
118
269
// FAILING IMPLEMENTATIONS //
119
270
120
271
final Publisher <Integer > SKIP = null ;
0 commit comments