Skip to content

Commit e9ee2cd

Browse files
committed
+tck 2.9 blackbox verification should fully drive publisher
It should not use the helper publisher as it may signal asynchronously as well as onComplete in undefined ways - we need to control the publisher in this case because of the very specific test scenario
1 parent 6fbc3c6 commit e9ee2cd

File tree

9 files changed

+75
-43
lines changed

9 files changed

+75
-43
lines changed

examples/src/test/java/org/reactivestreams/example/unicast/AsyncSubscriberTest.java

+7-3
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@
1717

1818
@Test // Must be here for TestNG to find and run this, do not remove
1919
public class AsyncSubscriberTest extends SubscriberBlackboxVerification<Integer> {
20-
final static long DefaultTimeoutMillis = 100;
20+
final static long DEFAULT_TIMEOUT_MILLIS = 100;
2121

2222
private ExecutorService e;
2323
@BeforeClass void before() { e = Executors.newFixedThreadPool(4); }
2424
@AfterClass void after() { if (e != null) e.shutdown(); }
2525

2626
public AsyncSubscriberTest() {
27-
super(new TestEnvironment(DefaultTimeoutMillis));
27+
super(new TestEnvironment(DEFAULT_TIMEOUT_MILLIS));
2828
}
2929

3030
@Override public Subscriber<Integer> createSubscriber() {
@@ -53,11 +53,15 @@ public AsyncSubscriberTest() {
5353
};
5454

5555
new NumberIterablePublisher(0, 10, e).subscribe(sub);
56-
latch.await(DefaultTimeoutMillis * 10, TimeUnit.MILLISECONDS);
56+
latch.await(DEFAULT_TIMEOUT_MILLIS * 10, TimeUnit.MILLISECONDS);
5757
assertEquals(i.get(), 45);
5858
}
5959

6060
@Override public Integer createElement(int element) {
6161
return element;
6262
}
63+
64+
@Override public Publisher<Integer> createHelperPublisher(long elements) {
65+
return super.createHelperPublisher(elements);
66+
}
6367
}

tck/README.md

+39-21
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ Specification rule abides the following naming convention: `spec###_DESC` where:
5151
```
5252

5353
The prefixes of the names of the test methods are used in order to signify the character of the test. For example, these are the kinds of prefixes you may find:
54-
"required_", "optional_", "stochastic_", "untested_".
54+
"`required_`", "`optional_`", "`stochastic_`", "`untested_`".
5555

5656
Explanations:
5757

@@ -180,36 +180,44 @@ Subscriber rules Verification is split up into two files (styles) of tests.
180180

181181
The Blackbox Verification tests do not require the implementation under test to be modified at all, yet they are *not* able to verify most rules. In Whitebox Verification, more control over `request()` calls etc. is required in order to validate rules more precisely.
182182

183-
### Helper Publisher implementations
183+
### createElement and Helper Publisher implementations
184184
Since testing a `Subscriber` is not possible without a corresponding `Publisher` the TCK Subscriber Verifications
185-
both provide a default "helper publisher" to drive its test and also alow to replace this Publisher with a custom implementation.
185+
both provide a default "*helper publisher*" to drive its test and also alow to replace this Publisher with a custom implementation.
186+
The helper publisher is an asynchronous publisher by default - meaning that your subscriber can not blindly assume single threaded execution.
186187

187-
For simple Subscribers which are able to consume elements of *any type*, it is **highly recommmended** to extend the
188-
SubscriberVerification (described below) classes providing the element type `java.lang.Integer`, like so: `... extends SubscriberBlackboxVerification<Integer>`.
189-
The reason for this is, that the TCK contains a default Publisher implementation which is able to signal `Integer` elements,
190-
thus alowing the implementer to strictly focus on only implementing a proper `Subscriber`, instead of having to implement
191-
an additional Publisher only in order to drive the Subscribers tests. This is especially important for library implementers
192-
which only want to implement a Subscriber – and do not want to spend time or thought on implementing a valid Publisher.
188+
While the `Publisher` implementation is provided, creating the signal elements is not – this is because a given Subscriber
189+
may for example only work with `HashedMessage` or some other specific kind of signal. The TCK is unable to generate such
190+
special messages automatically, so we provide the `T createElement(Integer id)` method to be implemented as part of
191+
Subscriber Verifications which should take the given ID and return an element of type `T` (where `T` is the type of
192+
elements flowing into the `Subscriber<T>`, as known thanks to `... extends WhiteboxSubscriberVerification<T>`) representing
193+
an element of the stream that will be passed on to the Subscriber.
193194

194-
If however any SubscriberVerification class is extended using a custom element type, e.g. like this `... extends SubscriberBlackboxVerification<Message>`,
195-
*the TCK will immediatly fail the entire subscriber test class* as it is unable to properly create signals of type `Message`
196-
(which can be some custom message type the `Subscriber` is able to consume). The exception thrown (`UnableToProvidePublisherException`)
197-
contains some information and directs the implementer towards implementing a custom helper publisher,
198-
which is done by overriding the `Publisher<T> createHelperPublisher(long elements)` method:
195+
The simplest valid implemenation is to return the incoming `id` *as the element* in a verification using `Integer`s as element types:
196+
197+
```java
198+
public class MySubscriberTest extends SubscriberBlackboxVerification<Integer> {
199+
200+
// ...
201+
202+
@Override
203+
public Integer createElement(int element) { return element; }
204+
}
205+
```
206+
207+
208+
The `createElement` method MAY be called from multiple
209+
threads, so in case of more complicated implementations, please be aware of this fact.
210+
211+
**Very Advanced**: While we do not expect many implementations having to do so, it is possible to take full control of the `Publisher`
212+
which will be driving the TCKs test. You can do this by implementing the `createHelperPublisher` method in which you can implement your
213+
own Publisher which will then be used by the TCK to drive your Subscriber tests:
199214

200215
```java
201216
@Override public Publisher<Message> createHelperPublisher(long elements) {
202217
return new Publisher<Message>() { /* IMPL HERE */ };
203218
}
204219
```
205220

206-
Summing up, we recommend implementing Subscribers which are able to consume any type of element, in this case the TCK
207-
should be driven using `Integer` elements as default publishers are already implemented for this type. If the
208-
`Subscriber` is unable to consume `Integer` elements, the implementer MUST implement a custom `Publisher<T>` that will
209-
be able to signal the required element types. It is of course both possible and recommended to re-use existing
210-
implemenations (which can be seen in the examples sub-project) to create these custom Publishers – an example of
211-
such re-use can be found in [ProvidedHelperPublisherForSubscriberVerificationTest#createStringPublisher](https://github.com/reactive-streams/reactive-streams/blob/master/tck/src/test/java/org/reactivestreams/tck/ProvidedHelperPublisherForSubscriberVerificationTest.java#L215)
212-
213221
### Subscriber Blackbox Verification
214222

215223
Blackbox Verification does not require any additional work except from providing a `Subscriber` and `Publisher` instances to the TCK:
@@ -235,6 +243,11 @@ public class MySubscriberBlackboxVerificationTest extends SubscriberBlackboxVeri
235243
public Subscriber<Integer> createSubscriber() {
236244
return new MySubscriber<Integer>();
237245
}
246+
247+
@Override
248+
public Integer createElement(int element) {
249+
return element;
250+
}
238251
}
239252
```
240253

@@ -310,6 +323,11 @@ public class MySubscriberWhiteboxVerificationTest extends SubscriberWhiteboxVeri
310323
};
311324
}
312325

326+
@Override
327+
public Integer createElement(int element) {
328+
return element;
329+
}
330+
313331
}
314332
```
315333

tck/src/main/java/org/reactivestreams/tck/IdentityProcessorVerification.java

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import org.reactivestreams.Publisher;
55
import org.reactivestreams.Subscriber;
66
import org.reactivestreams.Subscription;
7-
import org.reactivestreams.tck.Annotations.Subscribers;
87
import org.reactivestreams.tck.TestEnvironment.ManualPublisher;
98
import org.reactivestreams.tck.TestEnvironment.ManualSubscriber;
109
import org.reactivestreams.tck.TestEnvironment.ManualSubscriberWithSubscriptionSupport;

tck/src/main/java/org/reactivestreams/tck/SubscriberBlackboxVerification.java

+19-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import java.util.concurrent.ExecutorService;
1818
import java.util.concurrent.Executors;
19+
import java.util.concurrent.atomic.AtomicBoolean;
1920

2021
import static org.reactivestreams.tck.SubscriberWhiteboxVerification.BlackboxSubscriberProxy;
2122

@@ -224,7 +225,24 @@ public void required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalW
224225
blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() {
225226
@Override
226227
public void run(BlackboxTestStage stage) throws Throwable {
227-
final Publisher<T> pub = createHelperPublisher(0);
228+
final Publisher<T> pub = new Publisher<T>() {
229+
@Override public void subscribe(final Subscriber<? super T> s) {
230+
s.onSubscribe(new Subscription() {
231+
private boolean completed = false;
232+
233+
@Override public void request(long n) {
234+
if (!completed) {
235+
completed = true;
236+
s.onComplete(); // Publisher now realises that it is in fact already completed
237+
}
238+
}
239+
240+
@Override public void cancel() {
241+
// noop, ignore
242+
}
243+
});
244+
}
245+
};
228246

229247
final Subscriber<T> sub = createSubscriber();
230248
final BlackboxSubscriberProxy<T> probe = stage.createBlackboxSubscriberProxy(env, sub);

tck/src/main/java/org/reactivestreams/tck/WithHelperPublisher.java

+3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ public abstract class WithHelperPublisher<T> {
2727
* Sometimes the Subscriber may be limited in what type of element it is able to consume, this you may have to implement
2828
* this method such that the emitted element matches the Subscribers requirements. Simplest implementations would be
2929
* to simply pass in the {@code element} as payload of your custom element, such as appending it to a String or other identifier.
30+
* <p/>
31+
* <b>Warning:</b> This method may be called concurrently by the helper publisher, thus it should be implemented in a
32+
* thread-safe manner.
3033
*
3134
* @return element of the matching type {@code T} that will be delivered to the tested Subscriber
3235
*/

tck/src/main/java/org/reactivestreams/tck/support/HelperPublisher.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ public HelperPublisher(final int from, final int to, final Function<Integer, T>
2222
else try {
2323
return create.apply(at++);
2424
} catch (Throwable t) {
25-
throw new HelperPublisherException(
26-
String.format("Failed to create element in %s for id %s!", getClass().getSimpleName(), at - 1), t);
25+
throw new IllegalStateException(String.format("Failed to create element for id %d!", at - 1), t);
2726
}
2827
}
2928
@Override public void remove() { throw new UnsupportedOperationException(); }

tck/src/main/java/org/reactivestreams/tck/support/HelperPublisherException.java

-7
This file was deleted.

tck/src/main/java/org/reactivestreams/tck/support/InfiniteHelperPublisher.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public InfiniteHelperPublisher(final Function<Integer, T> create, final Executor
1818
try {
1919
return create.apply(at++); // Wraps around on overflow
2020
} catch (Throwable t) {
21-
throw new HelperPublisherException(
21+
throw new IllegalStateException(
2222
String.format("Failed to create element in %s for id %s!", getClass().getSimpleName(), at - 1), t);
2323
}
2424
}

tck/src/test/java/org/reactivestreams/tck/IdentityProcessorVerificationTest.java

+5-7
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,20 @@ public class IdentityProcessorVerificationTest extends TCKVerificationSupport {
2828
public void required_spec104_mustCallOnErrorOnAllItsSubscribersIfItEncountersANonRecoverableError_shouldBeIgnored() throws Throwable {
2929
requireTestSkip(new ThrowingRunnable() {
3030
@Override public void run() throws Throwable {
31-
new IdentityProcessorVerification(newTestEnvironment(), DEFAULT_TIMEOUT_MILLIS){
32-
@Override public Processor createIdentityProcessor(int bufferSize) {
31+
new IdentityProcessorVerification<Integer>(newTestEnvironment(), DEFAULT_TIMEOUT_MILLIS){
32+
@Override public Processor<Integer, Integer> createIdentityProcessor(int bufferSize) {
3333
return new NoopProcessor();
3434
}
3535

3636
@Override public ExecutorService publisherExecutorService() { return ex; }
3737

38-
@Override public Object createElement(int element) {
39-
return null;
40-
}
38+
@Override public Integer createElement(int element) { return element; }
4139

42-
@Override public Publisher createHelperPublisher(long elements) {
40+
@Override public Publisher<Integer> createHelperPublisher(long elements) {
4341
return SKIP;
4442
}
4543

46-
@Override public Publisher createErrorStatePublisher() {
44+
@Override public Publisher<Integer> createErrorStatePublisher() {
4745
return SKIP;
4846
}
4947

0 commit comments

Comments
 (0)