diff --git a/_includes/announce-1.0.0.md b/_includes/announce-1.0.0.md
new file mode 100644
index 0000000..f605d63
--- /dev/null
+++ b/_includes/announce-1.0.0.md
@@ -0,0 +1,53 @@
+#Reactive Streams 1.0.0 is here!
+
+
+It is with great pleasure that we—the *Reactive Streams Special Interest Group*—are announcing the immediate availability of the final form of _Reactive Streams_—after countless hours of prototyping, discussions, debate, evaluations, programming, testing, specifying requirements, documenting, and refining—we are confident that we have found the essential solution to the problem that we set out to solve:
+
+>[…] provide a standard for asynchronous stream processing with non-blocking back pressure.“ - [reactive-streams.org](http://www.reactive-streams.org)
+
+The artifacts, documentation and specifications are released under [Creative Commons Zero](http://creativecommons.org/publicdomain/zero/1.0) into the Public Domain.
+
+##Documentation
+
+* [Specification](https://github.com/reactive-streams/reactive-streams-jvm/tree/v1.0.0#specification)
+* [Java API Documentation](http://www.reactive-streams.org/reactive-streams-1.0.0-javadoc)
+* [TCK README](https://github.com/reactive-streams/reactive-streams-jvm/blob/v1.0.0/tck/README.md)
+
+##Artifacts
+
+* `org.reactivestreams:reactive-streams:1.0.0`—the Reactive Streams interfaces
+
+* `org.reactivestreams:reactive-streams-tck:1.0.0`—the Reactive Streams TCK
+
+* `org.reactivestreams:reactive-streams-examples:1.0.0`—documented and verified example implementations to draw inspiration from.
+
+
+## Implementations
+
+
+We are also proud to let _Reactive Streams_ `1.0.0` be announced to the world accompanied with a multitude of compliant implementations verified by the TCK for 1.0.0, listed below in alphabetical order:
+
+* [Akka](http://akka.io/) Streams *(version `1.0-RC2`)*
+ * See this [Activator template](http://www.typesafe.com/activator/template/akka-stream-scala) and the [documentation](http://doc.akka.io/docs/akka-stream-and-http-experimental/1.0-RC2/index.html).
+* [MongoDB](http://mongodb.org) *(version `1.0.0`)*
+ * For the documentation see [here](http://mongodb.github.io/mongo-java-driver-reactivestreams).
+* [Ratpack](http://www.ratpack.io) *(version `0.9.16`)*
+ * See the [“Streams”](http://www.ratpack.io/manual/current/streams.html) chapter of the manual.
+* Reactive Rabbit *(version `1.0.0`)*
+ * Driver for RabbitMQ/AMQP, see [here](https://github.com/ScalaConsultants/reactive-rabbit).
+* [Reactor](http://projectreactor.io/) *(version `2.0.1.RELEASE`)*
+ * For the documentation see [here](http://projectreactor.io/docs/reference/streams.html).
+* [RxJava](http://reactivex.io/) *(version `1.0.0`)*
+ * See [github.com/ReactiveX/RxJavaReactiveStreams](https://github.com/ReactiveX/RxJavaReactiveStreams).
+* [Slick](http://slick.typesafe.com/) *(version `3.0.0`)*
+ * See the [“Streaming”](http://slick.typesafe.com/doc/3.0.0/dbio.html#streaming) section of the manual.
+* [Vert.x 3.0](http://vertx.io) *(version `milestone-5a`)*
+ * Vert.x 3.0 is currently in alpha. The Reactive Streams implementation can be found [here](https://github.com/vert-x3/vertx-reactive-streams).
+
+##Credits
+
+We'd like to thank everyone involved, all [contributors](https://github.com/reactive-streams/reactive-streams-jvm/graphs/contributors), and everyone who has given feedback during the development of this project.
+
+*Warm regards,
+
+the Reactive Streams Special Interest Group*
diff --git a/_includes/index.md b/_includes/index.md
index 5940ba9..2dd1b63 100644
--- a/_includes/index.md
+++ b/_includes/index.md
@@ -24,67 +24,43 @@ We anticipate that acceptance of this Reactive Streams specification and experie
The basic semantics define how the transmission of stream elements is regulated through back-pressure. How elements are transmitted, their representation during transfer, or how back-pressure is signaled is not part of this specification.
-#### JVM Interfaces
+#### JVM Interfaces (Completed)
This working group applies the basic semantics to a set of programming interfaces whose main purpose is to allow the interoperation of different conforming implementations and language bindings for passing streams between objects and threads within the JVM, using the shared memory heap.
-This work is performed in the [reactive-streams-jvm](https://github.com/reactive-streams/reactive-streams-jvm/) repository.
-
-#### JavaScript Interfaces
-
-This working group defines a minimal set of object properties for observing a stream of elements within a JavaScript runtime environment. The goal is to provide a testable specification that allows different implementations to interoperate within that same runtime environment.
-
-This work is performed in the [reactive-streams-js](https://github.com/reactive-streams/reactive-streams-js/) repository.
-
-#### Network Protocols
-
-This working group defines network protocols for passing reactive streams over various transport media that involve serialization and deserialization of the data elements. Examples of such transports are TCP, UDP, HTTP and WebSockets.
-
-This work is performed in the [reactive-streams-io](https://github.com/reactive-streams/reactive-streams-io/) repository.
-
-## Current State
-
-As of Apr 10, 2015 we have released version 1.0.0-RC5 of Reactive Streams for the JVM, including Java [API](http://www.reactive-streams.org/reactive-streams-1.0.0.RC5-javadoc), a textual [Specification](https://github.com/reactive-streams/reactive-streams-jvm/blob/v1.0.0.RC5/README.md#specification) and a [TCK](http://www.reactive-streams.org/reactive-streams-tck-1.0.0.RC5-javadoc). Corresponding code artifacts are available on Maven Central:
+As of *April 30, 2015* we have released version 1.0.0 of Reactive Streams for the JVM, including Java [API](/reactive-streams-1.0.0-javadoc), a textual [Specification](https://github.com/reactive-streams/reactive-streams-jvm/blob/v1.0.0/README.md#specification), a [TCK](/reactive-streams-tck-1.0.0-javadoc) and [implementation examples](/reactive-streams-examples-1.0.0-javadoc). Corresponding code artifacts are available on Maven Central:
Each package has a page that contains a list of its classes and interfaces, with a summary for each. This page can contain six categories:
+Each class, interface, nested class and nested interface has its own separate page. Each of these pages has three sections consisting of a class/interface description, summary tables, and detailed member descriptions:
+Each summary entry contains the first sentence from the detailed description for that item. The summary entries are alphabetical, while the detailed descriptions are in the order they appear in the source code. This preserves the logical groupings established by the programmer.
+Each annotation type has its own separate page with the following sections:
+Each enum has its own separate page with the following sections:
+There is a Class Hierarchy page for all packages, plus a hierarchy for each package. Each hierarchy page contains a list of classes and a list of interfaces. The classes are organized by inheritance structure starting with java.lang.Object
. The interfaces do not inherit from java.lang.Object
.
The Deprecated API page lists all of the API that have been deprecated. A deprecated API is not recommended for use, generally due to improvements, and a replacement API is usually given. Deprecated APIs may be removed in future implementations.
+The Index contains an alphabetic list of all classes, interfaces, constructors, methods, and fields.
+These links take you to the next or previous class, interface, package, or related page.
+These links show and hide the HTML frames. All pages are available with or without frames.
+The All Classes link shows all classes and interfaces except non-static nested types.
+Each serializable or externalizable class has a description of its serialization fields and methods. This information is of interest to re-implementors, not to developers using the API. While there is no link in the navigation bar, you can get to this information by going to any serialized class and clicking "Serialized Form" in the "See also" section of the class description.
+The Constant Field Values page lists the static final fields and their values.
+Publisher
to stop sending data and clean up resources.Publisher
in response to requests to request(long)
.Publisher.subscribe(Subscriber)
.Subscriber
+ and a Publisher
and obeys the contracts of both.Publisher
is a provider of a potentially unbounded number of sequenced elements, publishing them according to
+ the demand received from its Subscriber
(s).Publisher
until demand is signaled via this method.Publisher
to start streaming data.Subscriber.onSubscribe(Subscription)
once after passing an instance of Subscriber
to Publisher.subscribe(Subscriber)
.T
- the type of element signaled to the Subscriber
R
- the type of element signaled by the Publisher
public interface Processor<T,R> +extends Subscriber<T>, Publisher<R>+
Subscriber
+ and a Publisher
and obeys the contracts of both.onComplete, onError, onNext, onSubscribe
T
- the type of element signaled.public interface Publisher<T>+
Publisher
is a provider of a potentially unbounded number of sequenced elements, publishing them according to
+ the demand received from its Subscriber
(s).
+
+ A Publisher
can serve multiple Subscriber
s subscribed subscribe(Subscriber)
dynamically
+ at various points in time.
Modifier and Type | +Method and Description | +
---|---|
void |
+subscribe(Subscriber<? super T> s)
+Request
+Publisher to start streaming data. |
+
void subscribe(Subscriber<? super T> s)+
Publisher
to start streaming data.
+
+ This is a "factory method" and can be called multiple times, each time starting a new Subscription
.
+
+ Each Subscription
will work for only a single Subscriber
.
+
+ A Subscriber
should only subscribe once to a single Publisher
.
+
+ If the Publisher
rejects the subscription attempt or otherwise fails it will
+ signal the error via Subscriber.onError(java.lang.Throwable)
.
s
- the Subscriber
that will consume signals from this Publisher
T
- the type of element signaled.public interface Subscriber<T>+
onSubscribe(Subscription)
once after passing an instance of Subscriber
to Publisher.subscribe(Subscriber)
.
+
+ No further notifications will be received until Subscription.request(long)
is called.
+
+ After signaling demand: +
onNext(Object)
up to the maximum number defined by Subscription.request(long)
onError(Throwable)
or onComplete()
which signals a terminal state after which no further events will be sent.
+
+ Demand can be signaled via Subscription.request(long)
whenever the Subscriber
instance is capable of handling more.
Modifier and Type | +Method and Description | +
---|---|
void |
+onComplete()
+Successful terminal state.
+ |
+
void |
+onError(java.lang.Throwable t)
+Failed terminal state.
+ |
+
void |
+onNext(T t)
+Data notification sent by the
+Publisher in response to requests to Subscription.request(long) . |
+
void |
+onSubscribe(Subscription s)
+Invoked after calling
+Publisher.subscribe(Subscriber) . |
+
void onSubscribe(Subscription s)+
Publisher.subscribe(Subscriber)
.
+
+ No data will start flowing until Subscription.request(long)
is invoked.
+
+ It is the responsibility of this Subscriber
instance to call Subscription.request(long)
whenever more data is wanted.
+
+ The Publisher
will send notifications only in response to Subscription.request(long)
.
s
- Subscription
that allows requesting data via Subscription.request(long)
void onNext(T t)+
Publisher
in response to requests to Subscription.request(long)
.t
- the element signaledvoid onError(java.lang.Throwable t)+
+ No further events will be sent even if Subscription.request(long)
is invoked again.
t
- the throwable signaledvoid onComplete()+
+ No further events will be sent even if Subscription.request(long)
is invoked again.
public interface Subscription+
Subscription
represents a one-to-one lifecycle of a Subscriber
subscribing to a Publisher
.
+
+ It can only be used once by a single Subscriber
.
+
+ It is used to both signal desire for data and cancel demand (and allow resource cleanup).
Modifier and Type | +Method and Description | +
---|---|
void |
+cancel()
+Request the
+Publisher to stop sending data and clean up resources. |
+
void |
+request(long n)
+No events will be sent by a
+Publisher until demand is signaled via this method. |
+
void request(long n)+
Publisher
until demand is signaled via this method.
+
+ It can be called however often and whenever needed—but the outstanding cumulative demand must never exceed Long.MAX_VALUE.
+ An outstanding cumulative demand of Long.MAX_VALUE may be treated by the Publisher
as "effectively unbounded".
+
+ Whatever has been requested can be sent by the Publisher
so only signal demand for what can be safely handled.
+
+ A Publisher
can send less than is requested if the stream ends but
+ then must emit either Subscriber.onError(Throwable)
or Subscriber.onComplete()
.
n
- the strictly positive number of elements to requests to the upstream Publisher
Interface | +Description | +
---|---|
Processor<T,R> | +
+ A Processor represents a processing stage—which is both a
+Subscriber
+ and a Publisher and obeys the contracts of both. |
+
Publisher<T> | +
+ A
+Publisher is a provider of a potentially unbounded number of sequenced elements, publishing them according to
+ the demand received from its Subscriber (s). |
+
Subscriber<T> | +
+ Will receive call to
+Subscriber.onSubscribe(Subscription) once after passing an instance of Subscriber to Publisher.subscribe(Subscriber) . |
+
Subscription | ++ + | +
001package org.reactivestreams; +002 +003/** +004 * A Processor represents a processing stage—which is both a {@link Subscriber} +005 * and a {@link Publisher} and obeys the contracts of both. +006 * +007 * @param <T> the type of element signaled to the {@link Subscriber} +008 * @param <R> the type of element signaled by the {@link Publisher} +009 */ +010public interface Processor<T, R> extends Subscriber<T>, Publisher<R> { +011} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams; +002 +003/** +004 * A {@link Publisher} is a provider of a potentially unbounded number of sequenced elements, publishing them according to +005 * the demand received from its {@link Subscriber}(s). +006 * <p> +007 * A {@link Publisher} can serve multiple {@link Subscriber}s subscribed {@link #subscribe(Subscriber)} dynamically +008 * at various points in time. +009 * +010 * @param <T> the type of element signaled. +011 */ +012public interface Publisher<T> { +013 +014 /** +015 * Request {@link Publisher} to start streaming data. +016 * <p> +017 * This is a "factory method" and can be called multiple times, each time starting a new {@link Subscription}. +018 * <p> +019 * Each {@link Subscription} will work for only a single {@link Subscriber}. +020 * <p> +021 * A {@link Subscriber} should only subscribe once to a single {@link Publisher}. +022 * <p> +023 * If the {@link Publisher} rejects the subscription attempt or otherwise fails it will +024 * signal the error via {@link Subscriber#onError}. +025 * +026 * @param s the {@link Subscriber} that will consume signals from this {@link Publisher} +027 */ +028 public void subscribe(Subscriber<? super T> s); +029} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams; +002 +003/** +004 * Will receive call to {@link #onSubscribe(Subscription)} once after passing an instance of {@link Subscriber} to {@link Publisher#subscribe(Subscriber)}. +005 * <p> +006 * No further notifications will be received until {@link Subscription#request(long)} is called. +007 * <p> +008 * After signaling demand: +009 * <ul> +010 * <li>One or more invocations of {@link #onNext(Object)} up to the maximum number defined by {@link Subscription#request(long)}</li> +011 * <li>Single invocation of {@link #onError(Throwable)} or {@link Subscriber#onComplete()} which signals a terminal state after which no further events will be sent. +012 * </ul> +013 * <p> +014 * Demand can be signaled via {@link Subscription#request(long)} whenever the {@link Subscriber} instance is capable of handling more. +015 * +016 * @param <T> the type of element signaled. +017 */ +018public interface Subscriber<T> { +019 /** +020 * Invoked after calling {@link Publisher#subscribe(Subscriber)}. +021 * <p> +022 * No data will start flowing until {@link Subscription#request(long)} is invoked. +023 * <p> +024 * It is the responsibility of this {@link Subscriber} instance to call {@link Subscription#request(long)} whenever more data is wanted. +025 * <p> +026 * The {@link Publisher} will send notifications only in response to {@link Subscription#request(long)}. +027 * +028 * @param s +029 * {@link Subscription} that allows requesting data via {@link Subscription#request(long)} +030 */ +031 public void onSubscribe(Subscription s); +032 +033 /** +034 * Data notification sent by the {@link Publisher} in response to requests to {@link Subscription#request(long)}. +035 * +036 * @param t the element signaled +037 */ +038 public void onNext(T t); +039 +040 /** +041 * Failed terminal state. +042 * <p> +043 * No further events will be sent even if {@link Subscription#request(long)} is invoked again. +044 * +045 * @param t the throwable signaled +046 */ +047 public void onError(Throwable t); +048 +049 /** +050 * Successful terminal state. +051 * <p> +052 * No further events will be sent even if {@link Subscription#request(long)} is invoked again. +053 */ +054 public void onComplete(); +055} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams; +002 +003/** +004 * A {@link Subscription} represents a one-to-one lifecycle of a {@link Subscriber} subscribing to a {@link Publisher}. +005 * <p> +006 * It can only be used once by a single {@link Subscriber}. +007 * <p> +008 * It is used to both signal desire for data and cancel demand (and allow resource cleanup). +009 * +010 */ +011public interface Subscription { +012 /** +013 * No events will be sent by a {@link Publisher} until demand is signaled via this method. +014 * <p> +015 * It can be called however often and whenever needed—but the outstanding cumulative demand must never exceed Long.MAX_VALUE. +016 * An outstanding cumulative demand of Long.MAX_VALUE may be treated by the {@link Publisher} as "effectively unbounded". +017 * <p> +018 * Whatever has been requested can be sent by the {@link Publisher} so only signal demand for what can be safely handled. +019 * <p> +020 * A {@link Publisher} can send less than is requested if the stream ends but +021 * then must emit either {@link Subscriber#onError(Throwable)} or {@link Subscriber#onComplete()}. +022 * +023 * @param n the strictly positive number of elements to requests to the upstream {@link Publisher} +024 */ +025 public void request(long n); +026 +027 /** +028 * Request the {@link Publisher} to stop sending data and clean up resources. +029 * <p> +030 * Data may still be sent to meet previously signalled demand after calling cancel as this request is asynchronous. +031 */ +032 public void cancel(); +033} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
Each package has a page that contains a list of its classes and interfaces, with a summary for each. This page can contain six categories:
+Each class, interface, nested class and nested interface has its own separate page. Each of these pages has three sections consisting of a class/interface description, summary tables, and detailed member descriptions:
+Each summary entry contains the first sentence from the detailed description for that item. The summary entries are alphabetical, while the detailed descriptions are in the order they appear in the source code. This preserves the logical groupings established by the programmer.
+Each annotation type has its own separate page with the following sections:
+Each enum has its own separate page with the following sections:
+There is a Class Hierarchy page for all packages, plus a hierarchy for each package. Each hierarchy page contains a list of classes and a list of interfaces. The classes are organized by inheritance structure starting with java.lang.Object
. The interfaces do not inherit from java.lang.Object
.
The Deprecated API page lists all of the API that have been deprecated. A deprecated API is not recommended for use, generally due to improvements, and a replacement API is usually given. Deprecated APIs may be removed in future implementations.
+The Index contains an alphabetic list of all classes, interfaces, constructors, methods, and fields.
+These links take you to the next or previous class, interface, package, or related page.
+These links show and hide the HTML frames. All pages are available with or without frames.
+The All Classes link shows all classes and interfaces except non-static nested types.
+Each serializable or externalizable class has a description of its serialization fields and methods. This information is of interest to re-implementors, not to developers using the API. While there is no link in the navigation bar, you can get to this information by going to any serialized class and clicking "Serialized Form" in the "See also" section of the class description.
+The Constant Field Values page lists the static final fields and their values.
+public class AsyncIterablePublisher<T> +extends java.lang.Object +implements org.reactivestreams.Publisher<T>+
Constructor and Description | +
---|
AsyncIterablePublisher(java.lang.Iterable<T> elements,
+ java.util.concurrent.Executor executor) |
+
AsyncIterablePublisher(java.lang.Iterable<T> elements,
+ int batchSize,
+ java.util.concurrent.Executor executor) |
+
Modifier and Type | +Method and Description | +
---|---|
void |
+subscribe(org.reactivestreams.Subscriber<? super T> s) |
+
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
public AsyncIterablePublisher(java.lang.Iterable<T> elements, + java.util.concurrent.Executor executor)+
public AsyncIterablePublisher(java.lang.Iterable<T> elements, + int batchSize, + java.util.concurrent.Executor executor)+
public abstract class AsyncSubscriber<T> +extends java.lang.Object +implements org.reactivestreams.Subscriber<T>, java.lang.Runnable+
Modifier | +Constructor and Description | +
---|---|
protected |
+AsyncSubscriber(java.util.concurrent.Executor executor) |
+
Modifier and Type | +Method and Description | +
---|---|
void |
+onComplete() |
+
void |
+onError(java.lang.Throwable t) |
+
void |
+onNext(T element) |
+
void |
+onSubscribe(org.reactivestreams.Subscription s) |
+
void |
+run() |
+
protected void |
+whenComplete() |
+
protected void |
+whenError(java.lang.Throwable error) |
+
protected abstract boolean |
+whenNext(T element) |
+
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
protected AsyncSubscriber(java.util.concurrent.Executor executor)+
protected void whenComplete()+
protected void whenError(java.lang.Throwable error)+
public final void onSubscribe(org.reactivestreams.Subscription s)+
onSubscribe
in interface org.reactivestreams.Subscriber<T>
public final void onNext(T element)+
onNext
in interface org.reactivestreams.Subscriber<T>
public final void onError(java.lang.Throwable t)+
onError
in interface org.reactivestreams.Subscriber<T>
public final void onComplete()+
onComplete
in interface org.reactivestreams.Subscriber<T>
public final void run()+
run
in interface java.lang.Runnable
public class InfiniteIncrementNumberPublisher +extends AsyncIterablePublisher<java.lang.Integer>+
Constructor and Description | +
---|
InfiniteIncrementNumberPublisher(java.util.concurrent.Executor executor) |
+
subscribe
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
public InfiniteIncrementNumberPublisher(java.util.concurrent.Executor executor)+
public class NumberIterablePublisher +extends AsyncIterablePublisher<java.lang.Integer>+
Constructor and Description | +
---|
NumberIterablePublisher(int from,
+ int to,
+ java.util.concurrent.Executor executor) |
+
subscribe
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
public NumberIterablePublisher(int from, + int to, + java.util.concurrent.Executor executor)+
public abstract class SyncSubscriber<T> +extends java.lang.Object +implements org.reactivestreams.Subscriber<T>+
Constructor and Description | +
---|
SyncSubscriber() |
+
Modifier and Type | +Method and Description | +
---|---|
protected abstract boolean |
+foreach(T element) |
+
void |
+onComplete() |
+
void |
+onError(java.lang.Throwable t) |
+
void |
+onNext(T element) |
+
void |
+onSubscribe(org.reactivestreams.Subscription s) |
+
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
public SyncSubscriber()+
public void onSubscribe(org.reactivestreams.Subscription s)+
onSubscribe
in interface org.reactivestreams.Subscriber<T>
public void onNext(T element)+
onNext
in interface org.reactivestreams.Subscriber<T>
public void onError(java.lang.Throwable t)+
onError
in interface org.reactivestreams.Subscriber<T>
public void onComplete()+
onComplete
in interface org.reactivestreams.Subscriber<T>
Class | +Description | +
---|---|
AsyncIterablePublisher<T> | +
+ AsyncIterablePublisher is an implementation of Reactive Streams `Publisher`
+ which executes asynchronously, using a provided `Executor` and produces elements
+ from a given `Iterable` in a "unicast" configuration to its `Subscribers`.
+ |
+
AsyncSubscriber<T> | +
+ AsyncSubscriber is an implementation of Reactive Streams `Subscriber`,
+ it runs asynchronously (on an Executor), requests one element
+ at a time, and invokes a user-defined method to process each element.
+ |
+
InfiniteIncrementNumberPublisher | ++ |
NumberIterablePublisher | ++ |
SyncSubscriber<T> | +
+ SyncSubscriber is an implementation of Reactive Streams `Subscriber`,
+ it runs synchronously (on the Publisher's thread) and requests one element
+ at a time and invokes a user-defined method to process each element.
+ |
+
001package org.reactivestreams.example.unicast; +002 +003import org.reactivestreams.Publisher; +004import org.reactivestreams.Subscriber; +005import org.reactivestreams.Subscription; +006 +007import java.util.Iterator; +008import java.util.Collections; +009import java.util.concurrent.Executor; +010import java.util.concurrent.atomic.AtomicBoolean; +011import java.util.concurrent.ConcurrentLinkedQueue; +012 +013/** +014 * AsyncIterablePublisher is an implementation of Reactive Streams `Publisher` +015 * which executes asynchronously, using a provided `Executor` and produces elements +016 * from a given `Iterable` in a "unicast" configuration to its `Subscribers`. +017 * +018 * NOTE: The code below uses a lot of try-catches to show the reader where exceptions can be expected, and where they are forbidden. +019 */ +020public class AsyncIterablePublisher<T> implements Publisher<T> { +021 private final static int DEFAULT_BATCHSIZE = 1024; +022 +023 private final Iterable<T> elements; // This is our data source / generator +024 private final Executor executor; // This is our thread pool, which will make sure that our Publisher runs asynchronously to its Subscribers +025 private final int batchSize; // In general, if one uses an `Executor`, one should be nice nad not hog a thread for too long, this is the cap for that, in elements +026 +027 public AsyncIterablePublisher(final Iterable<T> elements, final Executor executor) { +028 this(elements, DEFAULT_BATCHSIZE, executor); +029 } +030 +031 public AsyncIterablePublisher(final Iterable<T> elements, final int batchSize, final Executor executor) { +032 if (elements == null) throw null; +033 if (executor == null) throw null; +034 if (batchSize < 1) throw new IllegalArgumentException("batchSize must be greater than zero!"); +035 this.elements = elements; +036 this.executor = executor; +037 this.batchSize = batchSize; +038 } +039 +040 @Override +041 public void subscribe(final Subscriber<? super T> s) { +042 // As per rule 1.11, we have decided to support multiple subscribers in a unicast configuration +043 // for this `Publisher` implementation. +044 // As per 2.13, this method must return normally (i.e. not throw) +045 new SubscriptionImpl(s).init(); +046 } +047 +048 // These represent the protocol of the `AsyncIterablePublishers` SubscriptionImpls +049 static interface Signal {}; +050 enum Cancel implements Signal { Instance; }; +051 enum Subscribe implements Signal { Instance; }; +052 enum Send implements Signal { Instance; }; +053 static final class Request implements Signal { +054 final long n; +055 Request(final long n) { +056 this.n = n; +057 } +058 }; +059 +060 // This is our implementation of the Reactive Streams `Subscription`, +061 // which represents the association between a `Publisher` and a `Subscriber`. +062 final class SubscriptionImpl implements Subscription, Runnable { +063 final Subscriber<? super T> subscriber; // We need a reference to the `Subscriber` so we can talk to it +064 private boolean cancelled = false; // This flag will track whether this `Subscription` is to be considered cancelled or not +065 private long demand = 0; // Here we track the current demand, i.e. what has been requested but not yet delivered +066 private Iterator<T> iterator; // This is our cursor into the data stream, which we will send to the `Subscriber` +067 +068 SubscriptionImpl(final Subscriber<? super T> subscriber) { +069 // As per rule 1.09, we need to throw a `java.lang.NullPointerException` if the `Subscriber` is `null` +070 if (subscriber == null) throw null; +071 this.subscriber = subscriber; +072 } +073 +074 // This `ConcurrentLinkedQueue` will track signals that are sent to this `Subscription`, like `request` and `cancel` +075 private final ConcurrentLinkedQueue<Signal> inboundSignals = new ConcurrentLinkedQueue<Signal>(); +076 +077 // We are using this `AtomicBoolean` to make sure that this `Subscription` doesn't run concurrently with itself, +078 // which would violate rule 1.3 among others (no concurrent notifications). +079 private final AtomicBoolean on = new AtomicBoolean(false); +080 +081 // This method will register inbound demand from our `Subscriber` and validate it against rule 3.9 and rule 3.17 +082 private void doRequest(final long n) { +083 if (n < 1) +084 terminateDueTo(new IllegalArgumentException(subscriber + " violated the Reactive Streams rule 3.9 by requesting a non-positive number of elements.")); +085 else if (demand + n < 1) { +086 // As governed by rule 3.17, when demand overflows `Long.MAX_VALUE` we treat the signalled demand as "effectively unbounded" +087 demand = Long.MAX_VALUE; // Here we protect from the overflow and treat it as "effectively unbounded" +088 doSend(); // Then we proceed with sending data downstream +089 } else { +090 demand += n; // Here we record the downstream demand +091 doSend(); // Then we can proceed with sending data downstream +092 } +093 } +094 +095 // This handles cancellation requests, and is idempotent, thread-safe and not synchronously performing heavy computations as specified in rule 3.5 +096 private void doCancel() { +097 cancelled = true; +098 } +099 +100 // Instead of executing `subscriber.onSubscribe` synchronously from within `Publisher.subscribe` +101 // we execute it asynchronously, this is to avoid executing the user code (`Iterable.iterator`) on the calling thread. +102 // It also makes it easier to follow rule 1.9 +103 private void doSubscribe() { +104 try { +105 iterator = elements.iterator(); +106 if (iterator == null) +107 iterator = Collections.<T>emptyList().iterator(); // So we can assume that `iterator` is never null +108 } catch(final Throwable t) { +109 subscriber.onSubscribe(new Subscription() { // We need to make sure we signal onSubscribe before onError, obeying rule 1.9 +110 @Override public void cancel() {} +111 @Override public void request(long n) {} +112 }); +113 terminateDueTo(t); // Here we send onError, obeying rule 1.09 +114 } +115 +116 if (!cancelled) { +117 // Deal with setting up the subscription with the subscriber +118 try { +119 subscriber.onSubscribe(this); +120 } catch(final Throwable t) { // Due diligence to obey 2.13 +121 terminateDueTo(new IllegalStateException(subscriber + " violated the Reactive Streams rule 2.13 by throwing an exception from onSubscribe.", t)); +122 } +123 +124 // Deal with already complete iterators promptly +125 boolean hasElements = false; +126 try { +127 hasElements = iterator.hasNext(); +128 } catch(final Throwable t) { +129 terminateDueTo(t); // If hasNext throws, there's something wrong and we need to signal onError as per 1.2, 1.4, +130 } +131 +132 // If we don't have anything to deliver, we're already done, so lets do the right thing and +133 // not wait for demand to deliver `onComplete` as per rule 1.2 and 1.3 +134 if (!hasElements) { +135 try { +136 doCancel(); // Rule 1.6 says we need to consider the `Subscription` cancelled when `onComplete` is signalled +137 subscriber.onComplete(); +138 } catch(final Throwable t) { // As per rule 2.13, `onComplete` is not allowed to throw exceptions, so we do what we can, and log this. +139 (new IllegalStateException(subscriber + " violated the Reactive Streams rule 2.13 by throwing an exception from onComplete.", t)).printStackTrace(System.err); +140 } +141 } +142 } +143 } +144 +145 // This is our behavior for producing elements downstream +146 private void doSend() { +147 try { +148 // In order to play nice with the `Executor` we will only send at-most `batchSize` before +149 // rescheduing ourselves and relinquishing the current thread. +150 int leftInBatch = batchSize; +151 do { +152 T next; +153 boolean hasNext; +154 try { +155 next = iterator.next(); // We have already checked `hasNext` when subscribing, so we can fall back to testing -after- `next` is called. +156 hasNext = iterator.hasNext(); // Need to keep track of End-of-Stream +157 } catch (final Throwable t) { +158 terminateDueTo(t); // If `next` or `hasNext` throws (they can, since it is user-provided), we need to treat the stream as errored as per rule 1.4 +159 return; +160 } +161 subscriber.onNext(next); // Then we signal the next element downstream to the `Subscriber` +162 if (!hasNext) { // If we are at End-of-Stream +163 doCancel(); // We need to consider this `Subscription` as cancelled as per rule 1.6 +164 subscriber.onComplete(); // Then we signal `onComplete` as per rule 1.2 and 1.5 +165 } +166 } while (!cancelled // This makes sure that rule 1.8 is upheld, i.e. we need to stop signalling "eventually" +167 && --leftInBatch > 0 // This makes sure that we only send `batchSize` number of elements in one go (so we can yield to other Runnables) +168 && --demand > 0); // This makes sure that rule 1.1 is upheld (sending more than was demanded) +169 +170 if (!cancelled && demand > 0) // If the `Subscription` is still alive and well, and we have demand to satisfy, we signal ourselves to send more data +171 signal(Send.Instance); +172 } catch(final Throwable t) { +173 // We can only get here if `onNext` or `onComplete` threw, and they are not allowed to according to 2.13, so we can only cancel and log here. +174 doCancel(); // Make sure that we are cancelled, since we cannot do anything else since the `Subscriber` is faulty. +175 (new IllegalStateException(subscriber + " violated the Reactive Streams rule 2.13 by throwing an exception from onNext or onComplete.", t)).printStackTrace(System.err); +176 } +177 } +178 +179 // This is a helper method to ensure that we always `cancel` when we signal `onError` as per rule 1.6 +180 private void terminateDueTo(final Throwable t) { +181 cancelled = true; // When we signal onError, the subscription must be considered as cancelled, as per rule 1.6 +182 try { +183 subscriber.onError(t); // Then we signal the error downstream, to the `Subscriber` +184 } catch(final Throwable t2) { // If `onError` throws an exception, this is a spec violation according to rule 1.9, and all we can do is to log it. +185 (new IllegalStateException(subscriber + " violated the Reactive Streams rule 2.13 by throwing an exception from onError.", t2)).printStackTrace(System.err); +186 } +187 } +188 +189 // What `signal` does is that it sends signals to the `Subscription` asynchronously +190 private void signal(final Signal signal) { +191 if (inboundSignals.offer(signal)) // No need to null-check here as ConcurrentLinkedQueue does this for us +192 tryScheduleToExecute(); // Then we try to schedule it for execution, if it isn't already +193 } +194 +195 // This is the main "event loop" if you so will +196 @Override public final void run() { +197 if(on.get()) { // establishes a happens-before relationship with the end of the previous run +198 try { +199 final Signal s = inboundSignals.poll(); // We take a signal off the queue +200 if (!cancelled) { // to make sure that we follow rule 1.8, 3.6 and 3.7 +201 +202 // Below we simply unpack the `Signal`s and invoke the corresponding methods +203 if (s instanceof Request) +204 doRequest(((Request)s).n); +205 else if (s == Send.Instance) +206 doSend(); +207 else if (s == Cancel.Instance) +208 doCancel(); +209 else if (s == Subscribe.Instance) +210 doSubscribe(); +211 } +212 } finally { +213 on.set(false); // establishes a happens-before relationship with the beginning of the next run +214 if(!inboundSignals.isEmpty()) // If we still have signals to process +215 tryScheduleToExecute(); // Then we try to schedule ourselves to execute again +216 } +217 } +218 } +219 +220 // This method makes sure that this `Subscription` is only running on one Thread at a time, +221 // this is important to make sure that we follow rule 1.3 +222 private final void tryScheduleToExecute() { +223 if(on.compareAndSet(false, true)) { +224 try { +225 executor.execute(this); +226 } catch(Throwable t) { // If we can't run on the `Executor`, we need to fail gracefully +227 if (!cancelled) { +228 doCancel(); // First of all, this failure is not recoverable, so we need to follow rule 1.4 and 1.6 +229 try { +230 terminateDueTo(new IllegalStateException("Publisher terminated due to unavailable Executor.", t)); +231 } finally { +232 inboundSignals.clear(); // We're not going to need these anymore +233 // This subscription is cancelled by now, but letting it become schedulable again means +234 // that we can drain the inboundSignals queue if anything arrives after clearing +235 on.set(false); +236 } +237 } +238 } +239 } +240 } +241 +242 // Our implementation of `Subscription.request` sends a signal to the Subscription that more elements are in demand +243 @Override public void request(final long n) { +244 signal(new Request(n)); +245 } +246 // Our implementation of `Subscription.cancel` sends a signal to the Subscription that the `Subscriber` is not interested in any more elements +247 @Override public void cancel() { +248 signal(Cancel.Instance); +249 } +250 // The reason for the `init` method is that we want to ensure the `SubscriptionImpl` +251 // is completely constructed before it is exposed to the thread pool, therefor this +252 // method is only intended to be invoked once, and immediately after the constructor has +253 // finished. +254 void init() { +255 signal(Subscribe.Instance); +256 } +257 }; +258} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.example.unicast; +002 +003import org.reactivestreams.Subscriber; +004import org.reactivestreams.Subscription; +005 +006import java.util.concurrent.Executor; +007import java.util.concurrent.atomic.AtomicBoolean; +008import java.util.concurrent.ConcurrentLinkedQueue; +009 +010/** +011 * AsyncSubscriber is an implementation of Reactive Streams `Subscriber`, +012 * it runs asynchronously (on an Executor), requests one element +013 * at a time, and invokes a user-defined method to process each element. +014 * +015 * NOTE: The code below uses a lot of try-catches to show the reader where exceptions can be expected, and where they are forbidden. +016 */ +017public abstract class AsyncSubscriber<T> implements Subscriber<T>, Runnable { +018 +019 // Signal represents the asynchronous protocol between the Publisher and Subscriber +020 private static interface Signal {} +021 +022 private enum OnComplete implements Signal { Instance; } +023 +024 private static class OnError implements Signal { +025 public final Throwable error; +026 public OnError(final Throwable error) { this.error = error; } +027 } +028 +029 private static class OnNext<T> implements Signal { +030 public final T next; +031 public OnNext(final T next) { this.next = next; } +032 } +033 +034 private static class OnSubscribe implements Signal { +035 public final Subscription subscription; +036 public OnSubscribe(final Subscription subscription) { this.subscription = subscription; } +037 } +038 +039 private Subscription subscription; // Obeying rule 3.1, we make this private! +040 private boolean done; // It's useful to keep track of whether this Subscriber is done or not +041 private final Executor executor; // This is the Executor we'll use to be asynchronous, obeying rule 2.2 +042 +043 // Only one constructor, and it's only accessible for the subclasses +044 protected AsyncSubscriber(Executor executor) { +045 if (executor == null) throw null; +046 this.executor = executor; +047 } +048 +049 // Showcases a convenience method to idempotently marking the Subscriber as "done", so we don't want to process more elements +050 // herefor we also need to cancel our `Subscription`. +051 private final void done() { +052 //On this line we could add a guard against `!done`, but since rule 3.7 says that `Subscription.cancel()` is idempotent, we don't need to. +053 done = true; // If we `foreach` throws an exception, let's consider ourselves done (not accepting more elements) +054 if (subscription != null) { // If we are bailing out before we got a `Subscription` there's little need for cancelling it. +055 try { +056 subscription.cancel(); // Cancel the subscription +057 } catch(final Throwable t) { +058 //Subscription.cancel is not allowed to throw an exception, according to rule 3.15 +059 (new IllegalStateException(subscription + " violated the Reactive Streams rule 3.15 by throwing an exception from cancel.", t)).printStackTrace(System.err); +060 } +061 } +062 } +063 +064 // This method is invoked when the OnNext signals arrive +065 // Returns whether more elements are desired or not, and if no more elements are desired, +066 // for convenience. +067 protected abstract boolean whenNext(final T element); +068 +069 // This method is invoked when the OnComplete signal arrives +070 // override this method to implement your own custom onComplete logic. +071 protected void whenComplete() { } +072 +073 // This method is invoked if the OnError signal arrives +074 // override this method to implement your own custom onError logic. +075 protected void whenError(Throwable error) { } +076 +077 private final void handleOnSubscribe(final Subscription s) { +078 if (s == null) { +079 // Getting a null `Subscription` here is not valid so lets just ignore it. +080 } else if (subscription != null) { // If someone has made a mistake and added this Subscriber multiple times, let's handle it gracefully +081 try { +082 s.cancel(); // Cancel the additional subscription to follow rule 2.5 +083 } catch(final Throwable t) { +084 //Subscription.cancel is not allowed to throw an exception, according to rule 3.15 +085 (new IllegalStateException(s + " violated the Reactive Streams rule 3.15 by throwing an exception from cancel.", t)).printStackTrace(System.err); +086 } +087 } else { +088 // We have to assign it locally before we use it, if we want to be a synchronous `Subscriber` +089 // Because according to rule 3.10, the Subscription is allowed to call `onNext` synchronously from within `request` +090 subscription = s; +091 try { +092 // If we want elements, according to rule 2.1 we need to call `request` +093 // And, according to rule 3.2 we are allowed to call this synchronously from within the `onSubscribe` method +094 s.request(1); // Our Subscriber is unbuffered and modest, it requests one element at a time +095 } catch(final Throwable t) { +096 // Subscription.request is not allowed to throw according to rule 3.16 +097 (new IllegalStateException(s + " violated the Reactive Streams rule 3.16 by throwing an exception from request.", t)).printStackTrace(System.err); +098 } +099 } +100 } +101 +102 private final void handleOnNext(final T element) { +103 if (!done) { // If we aren't already done +104 if(subscription == null) { // Technically this check is not needed, since we are expecting Publishers to conform to the spec +105 // Check for spec violation of 2.1 and 1.09 +106 (new IllegalStateException("Someone violated the Reactive Streams rule 1.09 and 2.1 by signalling OnNext before `Subscription.request`. (no Subscription)")).printStackTrace(System.err); +107 } else { +108 try { +109 if (whenNext(element)) { +110 try { +111 subscription.request(1); // Our Subscriber is unbuffered and modest, it requests one element at a time +112 } catch(final Throwable t) { +113 // Subscription.request is not allowed to throw according to rule 3.16 +114 (new IllegalStateException(subscription + " violated the Reactive Streams rule 3.16 by throwing an exception from request.", t)).printStackTrace(System.err); +115 } +116 } else { +117 done(); // This is legal according to rule 2.6 +118 } +119 } catch(final Throwable t) { +120 done(); +121 try { +122 onError(t); +123 } catch(final Throwable t2) { +124 //Subscriber.onError is not allowed to throw an exception, according to rule 2.13 +125 (new IllegalStateException(this + " violated the Reactive Streams rule 2.13 by throwing an exception from onError.", t2)).printStackTrace(System.err); +126 } +127 } +128 } +129 } +130 } +131 +132 // Here it is important that we do not violate 2.2 and 2.3 by calling methods on the `Subscription` or `Publisher` +133 private void handleOnComplete() { +134 if (subscription == null) { // Technically this check is not needed, since we are expecting Publishers to conform to the spec +135 // Publisher is not allowed to signal onComplete before onSubscribe according to rule 1.09 +136 (new IllegalStateException("Publisher violated the Reactive Streams rule 1.09 signalling onComplete prior to onSubscribe.")).printStackTrace(System.err); +137 } else { +138 done = true; // Obey rule 2.4 +139 whenComplete(); +140 } +141 } +142 +143 // Here it is important that we do not violate 2.2 and 2.3 by calling methods on the `Subscription` or `Publisher` +144 private void handleOnError(final Throwable error) { +145 if (subscription == null) { // Technically this check is not needed, since we are expecting Publishers to conform to the spec +146 // Publisher is not allowed to signal onError before onSubscribe according to rule 1.09 +147 (new IllegalStateException("Publisher violated the Reactive Streams rule 1.09 signalling onError prior to onSubscribe.")).printStackTrace(System.err); +148 } else { +149 done = true; // Obey rule 2.4 +150 whenError(error); +151 } +152 } +153 +154 // We implement the OnX methods on `Subscriber` to send Signals that we will process asycnhronously, but only one at a time +155 +156 @Override public final void onSubscribe(final Subscription s) { +157 // As per rule 2.13, we need to throw a `java.lang.NullPointerException` if the `Subscription` is `null` +158 if (s == null) throw null; +159 +160 signal(new OnSubscribe(s)); +161 } +162 +163 @Override public final void onNext(final T element) { +164 // As per rule 2.13, we need to throw a `java.lang.NullPointerException` if the `element` is `null` +165 if (element == null) throw null; +166 +167 signal(new OnNext<T>(element)); +168 } +169 +170 @Override public final void onError(final Throwable t) { +171 // As per rule 2.13, we need to throw a `java.lang.NullPointerException` if the `Throwable` is `null` +172 if (t == null) throw null; +173 +174 signal(new OnError(t)); +175 } +176 +177 @Override public final void onComplete() { +178 signal(OnComplete.Instance); +179 } +180 +181 // This `ConcurrentLinkedQueue` will track signals that are sent to this `Subscriber`, like `OnComplete` and `OnNext` , +182 // and obeying rule 2.11 +183 private final ConcurrentLinkedQueue<Signal> inboundSignals = new ConcurrentLinkedQueue<Signal>(); +184 +185 // We are using this `AtomicBoolean` to make sure that this `Subscriber` doesn't run concurrently with itself, +186 // obeying rule 2.7 and 2.11 +187 private final AtomicBoolean on = new AtomicBoolean(false); +188 +189 @SuppressWarnings("unchecked") +190 @Override public final void run() { +191 if(on.get()) { // establishes a happens-before relationship with the end of the previous run +192 try { +193 final Signal s = inboundSignals.poll(); // We take a signal off the queue +194 if (!done) { // If we're done, we shouldn't process any more signals, obeying rule 2.8 +195 // Below we simply unpack the `Signal`s and invoke the corresponding methods +196 if (s instanceof OnNext<?>) +197 handleOnNext(((OnNext<T>)s).next); +198 else if (s instanceof OnSubscribe) +199 handleOnSubscribe(((OnSubscribe)s).subscription); +200 else if (s instanceof OnError) // We are always able to handle OnError, obeying rule 2.10 +201 handleOnError(((OnError)s).error); +202 else if (s == OnComplete.Instance) // We are always able to handle OnError, obeying rule 2.9 +203 handleOnComplete(); +204 } +205 } finally { +206 on.set(false); // establishes a happens-before relationship with the beginning of the next run +207 if(!inboundSignals.isEmpty()) // If we still have signals to process +208 tryScheduleToExecute(); // Then we try to schedule ourselves to execute again +209 } +210 } +211 } +212 +213 // What `signal` does is that it sends signals to the `Subscription` asynchronously +214 private void signal(final Signal signal) { +215 if (inboundSignals.offer(signal)) // No need to null-check here as ConcurrentLinkedQueue does this for us +216 tryScheduleToExecute(); // Then we try to schedule it for execution, if it isn't already +217 } +218 +219 // This method makes sure that this `Subscriber` is only executing on one Thread at a time +220 private final void tryScheduleToExecute() { +221 if(on.compareAndSet(false, true)) { +222 try { +223 executor.execute(this); +224 } catch(Throwable t) { // If we can't run on the `Executor`, we need to fail gracefully and not violate rule 2.13 +225 if (!done) { +226 try { +227 done(); // First of all, this failure is not recoverable, so we need to cancel our subscription +228 } finally { +229 inboundSignals.clear(); // We're not going to need these anymore +230 // This subscription is cancelled by now, but letting the Subscriber become schedulable again means +231 // that we can drain the inboundSignals queue if anything arrives after clearing +232 on.set(false); +233 } +234 } +235 } +236 } +237 } +238} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.example.unicast; +002 +003import java.util.Iterator; +004import java.util.concurrent.Executor; +005 +006import org.reactivestreams.Subscription; +007import org.reactivestreams.Subscriber; +008import org.reactivestreams.Publisher; +009 +010public class InfiniteIncrementNumberPublisher extends AsyncIterablePublisher<Integer> { +011 public InfiniteIncrementNumberPublisher(final Executor executor) { +012 super(new Iterable<Integer>() { +013 @Override public Iterator<Integer> iterator() { +014 return new Iterator<Integer>() { +015 private int at = 0; +016 @Override public boolean hasNext() { return true; } +017 @Override public Integer next() { return at++; } // Wraps around on overflow +018 @Override public void remove() { throw new UnsupportedOperationException(); } +019 }; +020 } +021 }, executor); +022 } +023} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.example.unicast; +002 +003import java.util.Collections; +004import java.util.Iterator; +005import java.util.concurrent.Executor; +006import org.reactivestreams.Subscription; +007import org.reactivestreams.Subscriber; +008import org.reactivestreams.Publisher; +009 +010public class NumberIterablePublisher extends AsyncIterablePublisher<Integer> { +011 public NumberIterablePublisher(final int from, final int to, final Executor executor) { +012 super(new Iterable<Integer>() { +013 { if(from > to) throw new IllegalArgumentException("from must be equal or greater than to!"); } +014 @Override public Iterator<Integer> iterator() { +015 return new Iterator<Integer>() { +016 private int at = from; +017 @Override public boolean hasNext() { return at < to; } +018 @Override public Integer next() { +019 if (!hasNext()) return Collections.<Integer>emptyList().iterator().next(); +020 else return at++; +021 } +022 @Override public void remove() { throw new UnsupportedOperationException(); } +023 }; +024 } +025 }, executor); +026 } +027} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.example.unicast; +002 +003import org.reactivestreams.Subscriber; +004import org.reactivestreams.Subscription; +005 +006/** +007 * SyncSubscriber is an implementation of Reactive Streams `Subscriber`, +008 * it runs synchronously (on the Publisher's thread) and requests one element +009 * at a time and invokes a user-defined method to process each element. +010 * +011 * NOTE: The code below uses a lot of try-catches to show the reader where exceptions can be expected, and where they are forbidden. +012 */ +013public abstract class SyncSubscriber<T> implements Subscriber<T> { +014 private Subscription subscription; // Obeying rule 3.1, we make this private! +015 private boolean done = false; +016 +017 @Override public void onSubscribe(final Subscription s) { +018 // As per rule 2.13, we need to throw a `java.lang.NullPointerException` if the `Subscription` is `null` +019 if (s == null) throw null; +020 +021 if (subscription != null) { // If someone has made a mistake and added this Subscriber multiple times, let's handle it gracefully +022 try { +023 s.cancel(); // Cancel the additional subscription +024 } catch(final Throwable t) { +025 //Subscription.cancel is not allowed to throw an exception, according to rule 3.15 +026 (new IllegalStateException(s + " violated the Reactive Streams rule 3.15 by throwing an exception from cancel.", t)).printStackTrace(System.err); +027 } +028 } else { +029 // We have to assign it locally before we use it, if we want to be a synchronous `Subscriber` +030 // Because according to rule 3.10, the Subscription is allowed to call `onNext` synchronously from within `request` +031 subscription = s; +032 try { +033 // If we want elements, according to rule 2.1 we need to call `request` +034 // And, according to rule 3.2 we are allowed to call this synchronously from within the `onSubscribe` method +035 s.request(1); // Our Subscriber is unbuffered and modest, it requests one element at a time +036 } catch(final Throwable t) { +037 // Subscription.request is not allowed to throw according to rule 3.16 +038 (new IllegalStateException(s + " violated the Reactive Streams rule 3.16 by throwing an exception from request.", t)).printStackTrace(System.err); +039 } +040 } +041 } +042 +043 @Override public void onNext(final T element) { +044 if (subscription == null) { // Technically this check is not needed, since we are expecting Publishers to conform to the spec +045 (new IllegalStateException("Publisher violated the Reactive Streams rule 1.09 signalling onNext prior to onSubscribe.")).printStackTrace(System.err); +046 } else { +047 // As per rule 2.13, we need to throw a `java.lang.NullPointerException` if the `element` is `null` +048 if (element == null) throw null; +049 +050 if (!done) { // If we aren't already done +051 try { +052 if (foreach(element)) { +053 try { +054 subscription.request(1); // Our Subscriber is unbuffered and modest, it requests one element at a time +055 } catch (final Throwable t) { +056 // Subscription.request is not allowed to throw according to rule 3.16 +057 (new IllegalStateException(subscription + " violated the Reactive Streams rule 3.16 by throwing an exception from request.", t)).printStackTrace(System.err); +058 } +059 } else { +060 done(); +061 } +062 } catch (final Throwable t) { +063 done(); +064 try { +065 onError(t); +066 } catch (final Throwable t2) { +067 //Subscriber.onError is not allowed to throw an exception, according to rule 2.13 +068 (new IllegalStateException(this + " violated the Reactive Streams rule 2.13 by throwing an exception from onError.", t2)).printStackTrace(System.err); +069 } +070 } +071 } +072 } +073 } +074 +075 // Showcases a convenience method to idempotently marking the Subscriber as "done", so we don't want to process more elements +076 // herefor we also need to cancel our `Subscription`. +077 private void done() { +078 //On this line we could add a guard against `!done`, but since rule 3.7 says that `Subscription.cancel()` is idempotent, we don't need to. +079 done = true; // If we `foreach` throws an exception, let's consider ourselves done (not accepting more elements) +080 try { +081 subscription.cancel(); // Cancel the subscription +082 } catch(final Throwable t) { +083 //Subscription.cancel is not allowed to throw an exception, according to rule 3.15 +084 (new IllegalStateException(subscription + " violated the Reactive Streams rule 3.15 by throwing an exception from cancel.", t)).printStackTrace(System.err); +085 } +086 } +087 +088 // This method is left as an exercise to the reader/extension point +089 // Returns whether more elements are desired or not, and if no more elements are desired +090 protected abstract boolean foreach(final T element); +091 +092 @Override public void onError(final Throwable t) { +093 if (subscription == null) { // Technically this check is not needed, since we are expecting Publishers to conform to the spec +094 (new IllegalStateException("Publisher violated the Reactive Streams rule 1.09 signalling onError prior to onSubscribe.")).printStackTrace(System.err); +095 } else { +096 // As per rule 2.13, we need to throw a `java.lang.NullPointerException` if the `Throwable` is `null` +097 if (t == null) throw null; +098 // Here we are not allowed to call any methods on the `Subscription` or the `Publisher`, as per rule 2.3 +099 // And anyway, the `Subscription` is considered to be cancelled if this method gets called, as per rule 2.4 +100 } +101 } +102 +103 @Override public void onComplete() { +104 if (subscription == null) { // Technically this check is not needed, since we are expecting Publishers to conform to the spec +105 (new IllegalStateException("Publisher violated the Reactive Streams rule 1.09 signalling onComplete prior to onSubscribe.")).printStackTrace(System.err); +106 } else { +107 // Here we are not allowed to call any methods on the `Subscription` or the `Publisher`, as per rule 2.3 +108 // And anyway, the `Subscription` is considered to be cancelled if this method gets called, as per rule 2.4 +109 } +110 } +111} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
Modifier and Type | +Constant Field | +Value | +
---|---|---|
+
+public static final java.lang.String |
+SKIPPING_NO_ERROR_PUBLISHER_AVAILABLE |
+"Skipping because no error state Publisher provided, and the test requires it. Please implement PublisherVerification#createFailedPublisher to run this test." |
+
+
+public static final java.lang.String |
+SKIPPING_OPTIONAL_TEST_FAILED |
+"Skipping, because provided Publisher does not pass this *additional* verification." |
+
Modifier and Type | +Constant Field | +Value | +
---|---|---|
+
+public static final int |
+TEST_BUFFER_SIZE |
+16 |
+
The Overview page is the front page of this API document and provides a list of all packages with a summary for each. This page can also contain an overall description of the set of packages.
+Each package has a page that contains a list of its classes and interfaces, with a summary for each. This page can contain six categories:
+Each class, interface, nested class and nested interface has its own separate page. Each of these pages has three sections consisting of a class/interface description, summary tables, and detailed member descriptions:
+Each summary entry contains the first sentence from the detailed description for that item. The summary entries are alphabetical, while the detailed descriptions are in the order they appear in the source code. This preserves the logical groupings established by the programmer.
+Each annotation type has its own separate page with the following sections:
+Each enum has its own separate page with the following sections:
+There is a Class Hierarchy page for all packages, plus a hierarchy for each package. Each hierarchy page contains a list of classes and a list of interfaces. The classes are organized by inheritance structure starting with java.lang.Object
. The interfaces do not inherit from java.lang.Object
.
The Deprecated API page lists all of the API that have been deprecated. A deprecated API is not recommended for use, generally due to improvements, and a replacement API is usually given. Deprecated APIs may be removed in future implementations.
+The Index contains an alphabetic list of all classes, interfaces, constructors, methods, and fields.
+These links take you to the next or previous class, interface, package, or related page.
+These links show and hide the HTML frames. All pages are available with or without frames.
+The All Classes link shows all classes and interfaces except non-static nested types.
+Each serializable or externalizable class has a description of its serialization fields and methods. This information is of interest to re-implementors, not to developers using the API. While there is no link in the navigation bar, you can get to this information by going to any serialized class and clicking "Serialized Form" in the "See also" section of the class description.
+The Constant Field Values page lists the static final fields and their values.
+Subscription
actually solves the "unbounded recursion" problem by not allowing the number of
+ recursive calls to exceed the number returned by this method.Subscription
actually solves the "unbounded recursion" problem by not allowing the number of
+ recursive calls to exceed the number returned by this method.TestEnvironment#printlnDebug
is true, print debug message to std out.DEFAULT_TIMEOUT_MILLIS
as long and returns the value if present OR its default value.PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS
as long and returns the value if present,
+ OR its default value (PublisherVerification.DEFAULT_PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS
).method
method in stack trace.Subscription
is cancelled.Subscription
is cancelled.Subscription
is cancelled.Publisher
PublisherVerification.maxElementsFromPublisher()
to mark that the given Publisher
,
+ is not able to signal completion.Publisher
specification rules.true
in order to skip executing tests marked as Stochastic
.true
in order to skip executing tests marked as Stochastic
.n
times.SubscriberBlackboxVerification.BlackboxTestStage.sub()
Subscriber
, providing certain assertions on methods being called on the Subscriber.Subscriber
and Subscription
+ specification rules, without any modifications to the tested implementation (also known as "Black Box" testing).SubscriberWhiteboxVerification.WhiteboxTestStage
over to the test.SubscriberWhiteboxVerification.WhiteboxTestStage
without performing any additional setup,
+ like the SubscriberWhiteboxVerification.subscriberTest(SubscriberWhiteboxVerification.TestStageTestRun)
would.Subscriber
and Subscription
specification rules.Subscriber
decorator and should be used in pub.subscriber(...)
calls,
+ in order to allow intercepting calls on the underlying Subscriber
.TestEnvironment.ManualSubscriberWithSubscriptionSupport
+ but does not accumulate values signalled via onNext
, thus it can not be used to assert
+ values signalled to this subscriber.Subscriber
implementation which can be steered by test code and asserted on.TestEnvironment.defaultTimeoutMillis()
and then verifies that no asynchronous errors
+ were signalled pior to, or during that time (by calling flop()
).verifyNoAsyncErrors
should be used when errors still could be signalled
+ asynchronously during TestEnvironment.defaultTimeoutMillis()
time.flop()
).id
value.public class IdentityProcessorVerification.ManualSubscriberWithErrorCollection<A> +extends TestEnvironment.ManualSubscriberWithSubscriptionSupport<A>+
env
Constructor and Description | +
---|
ManualSubscriberWithErrorCollection(TestEnvironment env) |
+
Modifier and Type | +Method and Description | +
---|---|
void |
+expectError(java.lang.Throwable cause) |
+
void |
+expectError(java.lang.Throwable cause,
+ long timeoutMillis) |
+
void |
+onError(java.lang.Throwable cause) |
+
onComplete, onNext, onSubscribe
expectCompletion, expectCompletion, expectCompletion, expectCompletion, expectError, expectError, expectError, expectError, expectErrorWithMessage, expectErrorWithMessage, expectNext, expectNext, expectNone, expectNone, expectNone, expectNone, nextElement, nextElement, nextElement, nextElement, nextElementOrEndOfStream, nextElementOrEndOfStream, nextElementOrEndOfStream, nextElements, nextElements, nextElements, nextElements, request, requestEndOfStream, requestEndOfStream, requestEndOfStream, requestEndOfStream, requestNextElement, requestNextElement, requestNextElement, requestNextElement, requestNextElementOrEndOfStream, requestNextElementOrEndOfStream, requestNextElementOrEndOfStream, requestNextElements, requestNextElements, requestNextElements
cancel
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
public ManualSubscriberWithErrorCollection(TestEnvironment env)+
public void onError(java.lang.Throwable cause)+
onError
in interface org.reactivestreams.Subscriber<A>
onError
in class TestEnvironment.ManualSubscriberWithSubscriptionSupport<A>
public void expectError(java.lang.Throwable cause) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectError(java.lang.Throwable cause, + long timeoutMillis) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public abstract class IdentityProcessorVerification.TestSetup +extends TestEnvironment.ManualPublisher<T>+
cancelled, env, pendingDemand, requests, subscriber
Constructor and Description | +
---|
TestSetup(TestEnvironment env,
+ int testBufferSize) |
+
Modifier and Type | +Method and Description | +
---|---|
void |
+expectNextElement(TestEnvironment.ManualSubscriber<T> sub,
+ T expected) |
+
TestEnvironment.ManualSubscriber<T> |
+newSubscriber() |
+
T |
+nextT() |
+
T |
+sendNextTFromUpstream() |
+
expectCancelling, expectCancelling, expectExactRequest, expectExactRequest, expectNoRequest, expectNoRequest, expectRequest, expectRequest, sendCompletion, sendError, sendNext, subscribe
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
public TestSetup(TestEnvironment env, + int testBufferSize) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public TestEnvironment.ManualSubscriber<T> newSubscriber() + throws java.lang.InterruptedException+
java.lang.InterruptedException
public T nextT() + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectNextElement(TestEnvironment.ManualSubscriber<T> sub, + T expected) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public T sendNextTFromUpstream() + throws java.lang.InterruptedException+
java.lang.InterruptedException
public abstract class IdentityProcessorVerification<T> +extends WithHelperPublisher<T> +implements SubscriberWhiteboxVerificationRules, PublisherVerificationRules+
Modifier and Type | +Class and Description | +
---|---|
class |
+IdentityProcessorVerification.ManualSubscriberWithErrorCollection<A> |
+
class |
+IdentityProcessorVerification.TestSetup |
+
Constructor and Description | +
---|
IdentityProcessorVerification(TestEnvironment env)
+Test class must specify the expected time it takes for the publisher to
+ shut itself down when the the last downstream
+Subscription is cancelled. |
+
IdentityProcessorVerification(TestEnvironment env,
+ long publisherReferenceGCTimeoutMillis)
+Test class must specify the expected time it takes for the publisher to
+ shut itself down when the the last downstream
+Subscription is cancelled. |
+
IdentityProcessorVerification(TestEnvironment env,
+ long publisherReferenceGCTimeoutMillis,
+ int processorBufferSize)
+Test class must specify the expected time it takes for the publisher to
+ shut itself down when the the last downstream
+Subscription is cancelled. |
+
createElement, createHelperPublisher, publisherExecutorService
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
public IdentityProcessorVerification(TestEnvironment env)+
Subscription
is cancelled.
+
+ The processor will be required to be able to buffer TestEnvironment.TEST_BUFFER_SIZE
elements.public IdentityProcessorVerification(TestEnvironment env, + long publisherReferenceGCTimeoutMillis)+
Subscription
is cancelled.
+
+ The processor will be required to be able to buffer TestEnvironment.TEST_BUFFER_SIZE
elements.publisherReferenceGCTimeoutMillis
- used to determine after how much time a reference to a Subscriber should be already dropped by the Publisher.public IdentityProcessorVerification(TestEnvironment env, + long publisherReferenceGCTimeoutMillis, + int processorBufferSize)+
Subscription
is cancelled.publisherReferenceGCTimeoutMillis
- used to determine after how much time a reference to a Subscriber should be already dropped by the Publisher.processorBufferSize
- number of elements the processor is required to be able to buffer.public abstract org.reactivestreams.Processor<T,T> createIdentityProcessor(int bufferSize)+
bufferSize
- number of elements the processor is required to be able to buffer.public abstract org.reactivestreams.Publisher<T> createFailedPublisher()+
Publisher
returned by this method is hand out a subscription,
+ followed by signalling onError
on it, as specified by Rule 1.9.
+
+ If you ignore these additional tests, return null
from this method.public long maxElementsFromPublisher()+
1
from this method.
+
+ Defaults to Long.MAX_VALUE - 1
, meaning that the Publisher can be produce a huge but NOT an unbounded number of elements.
+
+ To mark your Publisher will *never* signal an onComplete
override this method and return Long.MAX_VALUE
,
+ which will result in *skipping all tests which require an onComplete to be triggered* (!).public long boundedDepthOfOnNextAndRequestRecursion()+
Subscription
actually solves the "unbounded recursion" problem by not allowing the number of
+ recursive calls to exceed the number returned by this method.public boolean skipStochasticTests()+
true
in order to skip executing tests marked as Stochastic
.
+ Such tests MAY sometimes fail even though the implpublic long maxSupportedSubscribers()+
Publisher
under test to support multiple Subscribers,
+ yet the spec does not require all publishers to be able to do so, thus – if an implementation
+ supports only a limited number of subscribers (e.g. only 1 subscriber, also known as "no fanout")
+ you MUST return that number from this method by overriding it.public void setUp() + throws java.lang.Exception+
java.lang.Exception
public org.reactivestreams.Publisher<T> createPublisher(long elements)+
public void required_validate_maxElementsFromPublisher() + throws java.lang.Exception+
required_validate_maxElementsFromPublisher
in interface PublisherVerificationRules
java.lang.Exception
public void required_validate_boundedDepthOfOnNextAndRequestRecursion() + throws java.lang.Exception+
required_validate_boundedDepthOfOnNextAndRequestRecursion
in interface PublisherVerificationRules
java.lang.Exception
public void required_createPublisher1MustProduceAStreamOfExactly1Element() + throws java.lang.Throwable+
required_createPublisher1MustProduceAStreamOfExactly1Element
in interface PublisherVerificationRules
java.lang.Throwable
public void required_createPublisher3MustProduceAStreamOfExactly3Elements() + throws java.lang.Throwable+
required_createPublisher3MustProduceAStreamOfExactly3Elements
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements() + throws java.lang.Throwable+
required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec102_maySignalLessThanRequestedAndTerminateSubscription() + throws java.lang.Throwable+
required_spec102_maySignalLessThanRequestedAndTerminateSubscription
in interface PublisherVerificationRules
java.lang.Throwable
public void stochastic_spec103_mustSignalOnMethodsSequentially() + throws java.lang.Throwable+
stochastic_spec103_mustSignalOnMethodsSequentially
in interface PublisherVerificationRules
java.lang.Throwable
public void optional_spec104_mustSignalOnErrorWhenFails() + throws java.lang.Throwable+
optional_spec104_mustSignalOnErrorWhenFails
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates() + throws java.lang.Throwable+
required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates
in interface PublisherVerificationRules
java.lang.Throwable
public void optional_spec105_emptyStreamMustTerminateBySignallingOnComplete() + throws java.lang.Throwable+
optional_spec105_emptyStreamMustTerminateBySignallingOnComplete
in interface PublisherVerificationRules
java.lang.Throwable
public void untested_spec106_mustConsiderSubscriptionCancelledAfterOnErrorOrOnCompleteHasBeenCalled() + throws java.lang.Throwable+
untested_spec106_mustConsiderSubscriptionCancelledAfterOnErrorOrOnCompleteHasBeenCalled
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled() + throws java.lang.Throwable+
required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled
in interface PublisherVerificationRules
java.lang.Throwable
public void untested_spec107_mustNotEmitFurtherSignalsOnceOnErrorHasBeenSignalled() + throws java.lang.Throwable+
untested_spec107_mustNotEmitFurtherSignalsOnceOnErrorHasBeenSignalled
in interface PublisherVerificationRules
java.lang.Throwable
public void untested_spec108_possiblyCanceledSubscriptionShouldNotReceiveOnErrorOrOnCompleteSignals() + throws java.lang.Throwable+
untested_spec108_possiblyCanceledSubscriptionShouldNotReceiveOnErrorOrOnCompleteSignals
in interface PublisherVerificationRules
java.lang.Throwable
public void untested_spec109_subscribeShouldNotThrowNonFatalThrowable() + throws java.lang.Throwable+
untested_spec109_subscribeShouldNotThrowNonFatalThrowable
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec109_subscribeThrowNPEOnNullSubscriber() + throws java.lang.Throwable+
required_spec109_subscribeThrowNPEOnNullSubscriber
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe() + throws java.lang.Throwable+
required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec109_mustIssueOnSubscribeForNonNullSubscriber() + throws java.lang.Throwable+
required_spec109_mustIssueOnSubscribeForNonNullSubscriber
in interface PublisherVerificationRules
java.lang.Throwable
public void untested_spec110_rejectASubscriptionRequestIfTheSameSubscriberSubscribesTwice() + throws java.lang.Throwable+
untested_spec110_rejectASubscriptionRequestIfTheSameSubscriberSubscribesTwice
in interface PublisherVerificationRules
java.lang.Throwable
public void optional_spec111_maySupportMultiSubscribe() + throws java.lang.Throwable+
optional_spec111_maySupportMultiSubscribe
in interface PublisherVerificationRules
java.lang.Throwable
public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingOneByOne() + throws java.lang.Throwable+
optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingOneByOne
in interface PublisherVerificationRules
java.lang.Throwable
public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfront() + throws java.lang.Throwable+
optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfront
in interface PublisherVerificationRules
java.lang.Throwable
public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfrontAndCompleteAsExpected() + throws java.lang.Throwable+
optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfrontAndCompleteAsExpected
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe() + throws java.lang.Throwable+
required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec303_mustNotAllowUnboundedRecursion() + throws java.lang.Throwable+
required_spec303_mustNotAllowUnboundedRecursion
in interface PublisherVerificationRules
java.lang.Throwable
public void untested_spec304_requestShouldNotPerformHeavyComputations() + throws java.lang.Exception+
untested_spec304_requestShouldNotPerformHeavyComputations
in interface PublisherVerificationRules
java.lang.Exception
public void untested_spec305_cancelMustNotSynchronouslyPerformHeavyCompuatation() + throws java.lang.Exception+
untested_spec305_cancelMustNotSynchronouslyPerformHeavyCompuatation
in interface PublisherVerificationRules
java.lang.Exception
public void required_spec306_afterSubscriptionIsCancelledRequestMustBeNops() + throws java.lang.Throwable+
required_spec306_afterSubscriptionIsCancelledRequestMustBeNops
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops() + throws java.lang.Throwable+
required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec309_requestZeroMustSignalIllegalArgumentException() + throws java.lang.Throwable+
required_spec309_requestZeroMustSignalIllegalArgumentException
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec309_requestNegativeNumberMustSignalIllegalArgumentException() + throws java.lang.Throwable+
required_spec309_requestNegativeNumberMustSignalIllegalArgumentException
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling() + throws java.lang.Throwable+
required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber() + throws java.lang.Throwable+
required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec317_mustSupportAPendingElementCountUpToLongMaxValue() + throws java.lang.Throwable+
required_spec317_mustSupportAPendingElementCountUpToLongMaxValue
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec317_mustSupportACumulativePendingElementCountUpToLongMaxValue() + throws java.lang.Throwable+
required_spec317_mustSupportACumulativePendingElementCountUpToLongMaxValue
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue() + throws java.lang.Throwable+
required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec104_mustCallOnErrorOnAllItsSubscribersIfItEncountersANonRecoverableError() + throws java.lang.Throwable+
java.lang.Throwable
public org.reactivestreams.Subscriber<T> createSubscriber(SubscriberWhiteboxVerification.WhiteboxSubscriberProbe<T> probe)+
public void mustImmediatelyPassOnOnErrorEventsReceivedFromItsUpstreamToItsDownstream() + throws java.lang.Exception+
java.lang.Exception
public void required_exerciseWhiteboxHappyPath() + throws java.lang.Throwable+
required_exerciseWhiteboxHappyPath
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void required_spec201_mustSignalDemandViaSubscriptionRequest() + throws java.lang.Throwable+
required_spec201_mustSignalDemandViaSubscriptionRequest
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void untested_spec202_shouldAsynchronouslyDispatch() + throws java.lang.Exception+
untested_spec202_shouldAsynchronouslyDispatch
in interface SubscriberWhiteboxVerificationRules
java.lang.Exception
public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() + throws java.lang.Throwable+
required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() + throws java.lang.Throwable+
required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() + throws java.lang.Exception+
untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError
in interface SubscriberWhiteboxVerificationRules
java.lang.Exception
public void required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() + throws java.lang.Throwable+
required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid() + throws java.lang.Exception+
untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid
in interface SubscriberWhiteboxVerificationRules
java.lang.Exception
public void untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() + throws java.lang.Exception+
untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization
in interface SubscriberWhiteboxVerificationRules
java.lang.Exception
public void required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() + throws java.lang.Throwable+
required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() + throws java.lang.Throwable+
required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() + throws java.lang.Throwable+
required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() + throws java.lang.Throwable+
required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall() + throws java.lang.Throwable+
required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() + throws java.lang.Exception+
untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents
in interface SubscriberWhiteboxVerificationRules
java.lang.Exception
public void untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation() + throws java.lang.Throwable+
untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void untested_spec213_failingOnSignalInvocation() + throws java.lang.Exception+
untested_spec213_failingOnSignalInvocation
in interface SubscriberWhiteboxVerificationRules
java.lang.Exception
public void required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() + throws java.lang.Throwable+
required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() + throws java.lang.Throwable+
required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull() + throws java.lang.Throwable+
required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void untested_spec301_mustNotBeCalledOutsideSubscriberContext() + throws java.lang.Exception+
untested_spec301_mustNotBeCalledOutsideSubscriberContext
in interface SubscriberWhiteboxVerificationRules
java.lang.Exception
public void required_spec308_requestMustRegisterGivenNumberElementsToBeProduced() + throws java.lang.Throwable+
required_spec308_requestMustRegisterGivenNumberElementsToBeProduced
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber() + throws java.lang.Exception+
untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber
in interface SubscriberWhiteboxVerificationRules
java.lang.Exception
public void untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError() + throws java.lang.Exception+
untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError
in interface SubscriberWhiteboxVerificationRules
java.lang.Exception
public void untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() + throws java.lang.Exception+
untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists
in interface SubscriberWhiteboxVerificationRules
java.lang.Exception
public void untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError() + throws java.lang.Exception+
untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError
in interface SubscriberWhiteboxVerificationRules
java.lang.Exception
public void untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() + throws java.lang.Exception+
untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber
in interface SubscriberWhiteboxVerificationRules
java.lang.Exception
public void required_mustRequestFromUpstreamForElementsThatHaveBeenRequestedLongAgo() + throws java.lang.Throwable+
java.lang.Throwable
public void notVerified()+
public void notVerified(java.lang.String message)+
public void optionalMultipleSubscribersTest(long requiredSubscribersSupport, + Function<java.lang.Long,IdentityProcessorVerification.TestSetup> body) + throws java.lang.Throwable+
java.lang.Throwable
public static interface PublisherVerification.PublisherTestRun<T>+
Modifier and Type | +Method and Description | +
---|---|
void |
+run(org.reactivestreams.Publisher<T> pub) |
+
public abstract class PublisherVerification<T> +extends java.lang.Object +implements PublisherVerificationRules+
Publisher
specification rules.Publisher
Modifier and Type | +Class and Description | +
---|---|
static interface |
+PublisherVerification.PublisherTestRun<T> |
+
Modifier and Type | +Field and Description | +
---|---|
static java.lang.String |
+SKIPPING_NO_ERROR_PUBLISHER_AVAILABLE |
+
static java.lang.String |
+SKIPPING_OPTIONAL_TEST_FAILED |
+
Constructor and Description | +
---|
PublisherVerification(TestEnvironment env)
+Constructs a new verification class using the given env and configuration.
+ |
+
PublisherVerification(TestEnvironment env,
+ long publisherReferenceGCTimeoutMillis)
+Constructs a new verification class using the given env and configuration.
+ |
+
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
public static final java.lang.String SKIPPING_NO_ERROR_PUBLISHER_AVAILABLE+
public static final java.lang.String SKIPPING_OPTIONAL_TEST_FAILED+
public PublisherVerification(TestEnvironment env, + long publisherReferenceGCTimeoutMillis)+
publisherReferenceGCTimeoutMillis
- used to determine after how much time a reference to a Subscriber should be already dropped by the Publisher.public PublisherVerification(TestEnvironment env)+
publisherReferenceGCTimeoutMillis
will be obtained by using envPublisherReferenceGCTimeoutMillis()
.public static long envPublisherReferenceGCTimeoutMillis()+
PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS
as long and returns the value if present,
+ OR its default value (DEFAULT_PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS
).
+
+ This value is used to determine after how much time a reference to a Subscriber should be already dropped by the Publisher.java.lang.IllegalArgumentException
- when unable to parse the env variablepublic abstract org.reactivestreams.Publisher<T> createPublisher(long elements)+
public abstract org.reactivestreams.Publisher<T> createFailedPublisher()+
Publisher
returned by this method is hand out a subscription,
+ followed by signalling onError
on it, as specified by Rule 1.9.
+
+ If you ignore these additional tests, return null
from this method.public long maxElementsFromPublisher()+
1
from this method.
+
+ Defaults to Long.MAX_VALUE - 1
, meaning that the Publisher can be produce a huge but NOT an unbounded number of elements.
+
+ To mark your Publisher will *never* signal an onComplete
override this method and return Long.MAX_VALUE
,
+ which will result in *skipping all tests which require an onComplete to be triggered* (!).public boolean skipStochasticTests()+
true
in order to skip executing tests marked as Stochastic
.
+ Such tests MAY sometimes fail even though the implpublic long boundedDepthOfOnNextAndRequestRecursion()+
Subscription
actually solves the "unbounded recursion" problem by not allowing the number of
+ recursive calls to exceed the number returned by this method.public void setUp() + throws java.lang.Exception+
java.lang.Exception
public void required_createPublisher1MustProduceAStreamOfExactly1Element() + throws java.lang.Throwable+
required_createPublisher1MustProduceAStreamOfExactly1Element
in interface PublisherVerificationRules
java.lang.Throwable
public void required_createPublisher3MustProduceAStreamOfExactly3Elements() + throws java.lang.Throwable+
required_createPublisher3MustProduceAStreamOfExactly3Elements
in interface PublisherVerificationRules
java.lang.Throwable
public void required_validate_maxElementsFromPublisher() + throws java.lang.Exception+
required_validate_maxElementsFromPublisher
in interface PublisherVerificationRules
java.lang.Exception
public void required_validate_boundedDepthOfOnNextAndRequestRecursion() + throws java.lang.Exception+
required_validate_boundedDepthOfOnNextAndRequestRecursion
in interface PublisherVerificationRules
java.lang.Exception
public void required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements() + throws java.lang.Throwable+
required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec102_maySignalLessThanRequestedAndTerminateSubscription() + throws java.lang.Throwable+
required_spec102_maySignalLessThanRequestedAndTerminateSubscription
in interface PublisherVerificationRules
java.lang.Throwable
public void stochastic_spec103_mustSignalOnMethodsSequentially() + throws java.lang.Throwable+
stochastic_spec103_mustSignalOnMethodsSequentially
in interface PublisherVerificationRules
java.lang.Throwable
public void optional_spec104_mustSignalOnErrorWhenFails() + throws java.lang.Throwable+
optional_spec104_mustSignalOnErrorWhenFails
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates() + throws java.lang.Throwable+
required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates
in interface PublisherVerificationRules
java.lang.Throwable
public void optional_spec105_emptyStreamMustTerminateBySignallingOnComplete() + throws java.lang.Throwable+
optional_spec105_emptyStreamMustTerminateBySignallingOnComplete
in interface PublisherVerificationRules
java.lang.Throwable
public void untested_spec106_mustConsiderSubscriptionCancelledAfterOnErrorOrOnCompleteHasBeenCalled() + throws java.lang.Throwable+
untested_spec106_mustConsiderSubscriptionCancelledAfterOnErrorOrOnCompleteHasBeenCalled
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled() + throws java.lang.Throwable+
required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled
in interface PublisherVerificationRules
java.lang.Throwable
public void untested_spec107_mustNotEmitFurtherSignalsOnceOnErrorHasBeenSignalled() + throws java.lang.Throwable+
untested_spec107_mustNotEmitFurtherSignalsOnceOnErrorHasBeenSignalled
in interface PublisherVerificationRules
java.lang.Throwable
public void untested_spec108_possiblyCanceledSubscriptionShouldNotReceiveOnErrorOrOnCompleteSignals() + throws java.lang.Throwable+
untested_spec108_possiblyCanceledSubscriptionShouldNotReceiveOnErrorOrOnCompleteSignals
in interface PublisherVerificationRules
java.lang.Throwable
public void untested_spec109_subscribeShouldNotThrowNonFatalThrowable() + throws java.lang.Throwable+
untested_spec109_subscribeShouldNotThrowNonFatalThrowable
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec109_subscribeThrowNPEOnNullSubscriber() + throws java.lang.Throwable+
required_spec109_subscribeThrowNPEOnNullSubscriber
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec109_mustIssueOnSubscribeForNonNullSubscriber() + throws java.lang.Throwable+
required_spec109_mustIssueOnSubscribeForNonNullSubscriber
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe() + throws java.lang.Throwable+
required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe
in interface PublisherVerificationRules
java.lang.Throwable
public void untested_spec110_rejectASubscriptionRequestIfTheSameSubscriberSubscribesTwice() + throws java.lang.Throwable+
untested_spec110_rejectASubscriptionRequestIfTheSameSubscriberSubscribesTwice
in interface PublisherVerificationRules
java.lang.Throwable
public void optional_spec111_maySupportMultiSubscribe() + throws java.lang.Throwable+
optional_spec111_maySupportMultiSubscribe
in interface PublisherVerificationRules
java.lang.Throwable
public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingOneByOne() + throws java.lang.Throwable+
optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingOneByOne
in interface PublisherVerificationRules
java.lang.Throwable
public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfront() + throws java.lang.Throwable+
optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfront
in interface PublisherVerificationRules
java.lang.Throwable
public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfrontAndCompleteAsExpected() + throws java.lang.Throwable+
optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfrontAndCompleteAsExpected
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe() + throws java.lang.Throwable+
required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec303_mustNotAllowUnboundedRecursion() + throws java.lang.Throwable+
required_spec303_mustNotAllowUnboundedRecursion
in interface PublisherVerificationRules
java.lang.Throwable
public void untested_spec304_requestShouldNotPerformHeavyComputations() + throws java.lang.Exception+
untested_spec304_requestShouldNotPerformHeavyComputations
in interface PublisherVerificationRules
java.lang.Exception
public void untested_spec305_cancelMustNotSynchronouslyPerformHeavyCompuatation() + throws java.lang.Exception+
untested_spec305_cancelMustNotSynchronouslyPerformHeavyCompuatation
in interface PublisherVerificationRules
java.lang.Exception
public void required_spec306_afterSubscriptionIsCancelledRequestMustBeNops() + throws java.lang.Throwable+
required_spec306_afterSubscriptionIsCancelledRequestMustBeNops
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops() + throws java.lang.Throwable+
required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec309_requestZeroMustSignalIllegalArgumentException() + throws java.lang.Throwable+
required_spec309_requestZeroMustSignalIllegalArgumentException
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec309_requestNegativeNumberMustSignalIllegalArgumentException() + throws java.lang.Throwable+
required_spec309_requestNegativeNumberMustSignalIllegalArgumentException
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling() + throws java.lang.Throwable+
required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber() + throws java.lang.Throwable+
required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec317_mustSupportAPendingElementCountUpToLongMaxValue() + throws java.lang.Throwable+
required_spec317_mustSupportAPendingElementCountUpToLongMaxValue
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec317_mustSupportACumulativePendingElementCountUpToLongMaxValue() + throws java.lang.Throwable+
required_spec317_mustSupportACumulativePendingElementCountUpToLongMaxValue
in interface PublisherVerificationRules
java.lang.Throwable
public void required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue() + throws java.lang.Throwable+
required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue
in interface PublisherVerificationRules
java.lang.Throwable
public void activePublisherTest(long elements, + boolean completionSignalRequired, + PublisherVerification.PublisherTestRun<T> body) + throws java.lang.Throwable+
elements
- the number of elements the Publisher under test must be able to emit to run this testcompletionSignalRequired
- true if an onComplete
signal is required by this test to run.
+ If the tested Publisher is unable to signal completion, tests requireing onComplete signals will be skipped.
+ To signal if your Publisher is able to signal completion see maxElementsFromPublisher()
.java.lang.Throwable
public void optionalActivePublisherTest(long elements, + boolean completionSignalRequired, + PublisherVerification.PublisherTestRun<T> body) + throws java.lang.Throwable+
elements
- the number of elements the Publisher under test must be able to emit to run this testcompletionSignalRequired
- true if an onComplete
signal is required by this test to run.
+ If the tested Publisher is unable to signal completion, tests requireing onComplete signals will be skipped.
+ To signal if your Publisher is able to signal completion see maxElementsFromPublisher()
.java.lang.Throwable
public void whenHasErrorPublisherTest(PublisherVerification.PublisherTestRun<T> body) + throws java.lang.Throwable+
java.lang.Throwable
public void potentiallyPendingTest(org.reactivestreams.Publisher<T> pub, + PublisherVerification.PublisherTestRun<T> body) + throws java.lang.Throwable+
java.lang.Throwable
public void potentiallyPendingTest(org.reactivestreams.Publisher<T> pub, + PublisherVerification.PublisherTestRun<T> body, + java.lang.String message) + throws java.lang.Throwable+
java.lang.Throwable
public void stochasticTest(int n, + Function<java.lang.Integer,java.lang.Void> body) + throws java.lang.Throwable+
n
times.
+ All the test runs must pass in order for the stochastic test to pass.java.lang.Throwable
public void notVerified()+
public long publisherUnableToSignalOnComplete()+
maxElementsFromPublisher()
to mark that the given Publisher
,
+ is not able to signal completion. For example it is strictly a time-bound or unbounded source of data.
+
+ Returning this value from maxElementsFromPublisher()
will result in skipping all TCK tests which require onComplete signals!public void notVerified(java.lang.String message)+
public class SubscriberBlackboxVerification.BlackboxTestStage +extends TestEnvironment.ManualPublisher<T>+
Modifier and Type | +Field and Description | +
---|---|
T |
+lastT |
+
org.reactivestreams.Publisher<T> |
+pub |
+
TestEnvironment.ManualSubscriber<T> |
+tees |
+
cancelled, env, pendingDemand, requests, subscriber
Constructor and Description | +
---|
BlackboxTestStage(TestEnvironment env) |
+
BlackboxTestStage(TestEnvironment env,
+ boolean runDefaultInit) |
+
Modifier and Type | +Method and Description | +
---|---|
SubscriberWhiteboxVerification.BlackboxSubscriberProxy<T> |
+createBlackboxSubscriberProxy(TestEnvironment env,
+ org.reactivestreams.Subscriber<T> sub) |
+
org.reactivestreams.Publisher<T> |
+createHelperPublisher(long elements) |
+
T |
+nextT() |
+
T |
+signalNext() |
+
org.reactivestreams.Subscriber<? super T> |
+sub() |
+
SubscriberWhiteboxVerification.BlackboxSubscriberProxy<T> |
+subProxy()
+Proxy for the
+sub() Subscriber , providing certain assertions on methods being called on the Subscriber. |
+
expectCancelling, expectCancelling, expectExactRequest, expectExactRequest, expectNoRequest, expectNoRequest, expectRequest, expectRequest, sendCompletion, sendError, sendNext, subscribe
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
public TestEnvironment.ManualSubscriber<T> tees+
public BlackboxTestStage(TestEnvironment env) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public BlackboxTestStage(TestEnvironment env, + boolean runDefaultInit) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public SubscriberWhiteboxVerification.BlackboxSubscriberProxy<T> subProxy()+
sub()
Subscriber
, providing certain assertions on methods being called on the Subscriber.public org.reactivestreams.Publisher<T> createHelperPublisher(long elements)+
public SubscriberWhiteboxVerification.BlackboxSubscriberProxy<T> createBlackboxSubscriberProxy(TestEnvironment env, + org.reactivestreams.Subscriber<T> sub)+
public T signalNext() + throws java.lang.InterruptedException+
java.lang.InterruptedException
public abstract class SubscriberBlackboxVerification<T> +extends WithHelperPublisher<T> +implements SubscriberBlackboxVerificationRules+
Subscriber
and Subscription
+ specification rules, without any modifications to the tested implementation (also known as "Black Box" testing).
+
+ This verification is NOT able to check many of the rules of the spec, and if you want more
+ verification of your implementation you'll have to implement org.reactivestreams.tck.SubscriberWhiteboxVerification
+ instead.Subscriber
,
+Subscription
Modifier and Type | +Class and Description | +
---|---|
class |
+SubscriberBlackboxVerification.BlackboxTestStage |
+
Modifier and Type | +Field and Description | +
---|---|
protected TestEnvironment |
+env |
+
Modifier | +Constructor and Description | +
---|---|
protected |
+SubscriberBlackboxVerification(TestEnvironment env) |
+
createElement, createHelperPublisher
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
protected final TestEnvironment env+
protected SubscriberBlackboxVerification(TestEnvironment env)+
public abstract org.reactivestreams.Subscriber<T> createSubscriber()+
Subscriber
instance to be subjected to the testing logic.public void triggerRequest(org.reactivestreams.Subscriber<? super T> subscriber)+
public void startPublisherExecutorService()+
public void shutdownPublisherExecutorService()+
public java.util.concurrent.ExecutorService publisherExecutorService()+
WithHelperPublisher
Publisher
publisherExecutorService
in class WithHelperPublisher<T>
public void setUp() + throws java.lang.Exception+
java.lang.Exception
public void required_spec201_blackbox_mustSignalDemandViaSubscriptionRequest() + throws java.lang.Throwable+
required_spec201_blackbox_mustSignalDemandViaSubscriptionRequest
in interface SubscriberBlackboxVerificationRules
java.lang.Throwable
public void untested_spec202_blackbox_shouldAsynchronouslyDispatch() + throws java.lang.Exception+
untested_spec202_blackbox_shouldAsynchronouslyDispatch
in interface SubscriberBlackboxVerificationRules
java.lang.Exception
public void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() + throws java.lang.Throwable+
required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete
in interface SubscriberBlackboxVerificationRules
java.lang.Throwable
public void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() + throws java.lang.Throwable+
required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError
in interface SubscriberBlackboxVerificationRules
java.lang.Throwable
public void untested_spec204_blackbox_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() + throws java.lang.Exception+
untested_spec204_blackbox_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError
in interface SubscriberBlackboxVerificationRules
java.lang.Exception
public void required_spec205_blackbox_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() + throws java.lang.Exception+
required_spec205_blackbox_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal
in interface SubscriberBlackboxVerificationRules
java.lang.Exception
public void untested_spec206_blackbox_mustCallSubscriptionCancelIfItIsNoLongerValid() + throws java.lang.Exception+
untested_spec206_blackbox_mustCallSubscriptionCancelIfItIsNoLongerValid
in interface SubscriberBlackboxVerificationRules
java.lang.Exception
public void untested_spec207_blackbox_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() + throws java.lang.Exception+
untested_spec207_blackbox_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization
in interface SubscriberBlackboxVerificationRules
java.lang.Exception
public void untested_spec208_blackbox_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() + throws java.lang.Throwable+
untested_spec208_blackbox_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel
in interface SubscriberBlackboxVerificationRules
java.lang.Throwable
public void required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() + throws java.lang.Throwable+
required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall
in interface SubscriberBlackboxVerificationRules
java.lang.Throwable
public void required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() + throws java.lang.Throwable+
required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall
in interface SubscriberBlackboxVerificationRules
java.lang.Throwable
public void required_spec210_blackbox_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() + throws java.lang.Throwable+
required_spec210_blackbox_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall
in interface SubscriberBlackboxVerificationRules
java.lang.Throwable
public void untested_spec211_blackbox_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() + throws java.lang.Exception+
untested_spec211_blackbox_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents
in interface SubscriberBlackboxVerificationRules
java.lang.Exception
public void untested_spec212_blackbox_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality() + throws java.lang.Throwable+
untested_spec212_blackbox_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality
in interface SubscriberBlackboxVerificationRules
java.lang.Throwable
public void untested_spec213_blackbox_failingOnSignalInvocation() + throws java.lang.Exception+
untested_spec213_blackbox_failingOnSignalInvocation
in interface SubscriberBlackboxVerificationRules
java.lang.Exception
public void required_spec213_blackbox_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() + throws java.lang.Throwable+
required_spec213_blackbox_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull
in interface SubscriberBlackboxVerificationRules
java.lang.Throwable
public void required_spec213_blackbox_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() + throws java.lang.Throwable+
required_spec213_blackbox_onNext_mustThrowNullPointerExceptionWhenParametersAreNull
in interface SubscriberBlackboxVerificationRules
java.lang.Throwable
public void required_spec213_blackbox_onError_mustThrowNullPointerExceptionWhenParametersAreNull() + throws java.lang.Throwable+
required_spec213_blackbox_onError_mustThrowNullPointerExceptionWhenParametersAreNull
in interface SubscriberBlackboxVerificationRules
java.lang.Throwable
public void untested_spec301_blackbox_mustNotBeCalledOutsideSubscriberContext() + throws java.lang.Exception+
untested_spec301_blackbox_mustNotBeCalledOutsideSubscriberContext
in interface SubscriberBlackboxVerificationRules
java.lang.Exception
public void untested_spec308_blackbox_requestMustRegisterGivenNumberElementsToBeProduced() + throws java.lang.Throwable+
untested_spec308_blackbox_requestMustRegisterGivenNumberElementsToBeProduced
in interface SubscriberBlackboxVerificationRules
java.lang.Throwable
public void untested_spec310_blackbox_requestMaySynchronouslyCallOnNextOnSubscriber() + throws java.lang.Exception+
untested_spec310_blackbox_requestMaySynchronouslyCallOnNextOnSubscriber
in interface SubscriberBlackboxVerificationRules
java.lang.Exception
public void untested_spec311_blackbox_requestMaySynchronouslyCallOnCompleteOrOnError() + throws java.lang.Exception+
untested_spec311_blackbox_requestMaySynchronouslyCallOnCompleteOrOnError
in interface SubscriberBlackboxVerificationRules
java.lang.Exception
public void untested_spec314_blackbox_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() + throws java.lang.Exception+
untested_spec314_blackbox_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists
in interface SubscriberBlackboxVerificationRules
java.lang.Exception
public void untested_spec315_blackbox_cancelMustNotThrowExceptionAndMustSignalOnError() + throws java.lang.Exception+
untested_spec315_blackbox_cancelMustNotThrowExceptionAndMustSignalOnError
in interface SubscriberBlackboxVerificationRules
java.lang.Exception
public void untested_spec316_blackbox_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() + throws java.lang.Exception+
untested_spec316_blackbox_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber
in interface SubscriberBlackboxVerificationRules
java.lang.Exception
public void blackboxSubscriberTest(org.reactivestreams.tck.SubscriberBlackboxVerification.BlackboxTestStageTestRun body) + throws java.lang.Throwable+
java.lang.Throwable
public void blackboxSubscriberWithoutSetupTest(org.reactivestreams.tck.SubscriberBlackboxVerification.BlackboxTestStageTestRun body) + throws java.lang.Throwable+
java.lang.Throwable
public void notVerified()+
public static class SubscriberWhiteboxVerification.BlackboxProbe<T> +extends java.lang.Object +implements SubscriberWhiteboxVerification.SubscriberProbe<T>+
Modifier and Type | +Field and Description | +
---|---|
protected TestEnvironment.Receptacle<T> |
+elements |
+
protected TestEnvironment |
+env |
+
protected TestEnvironment.Promise<java.lang.Throwable> |
+error |
+
protected TestEnvironment.Promise<org.reactivestreams.Subscriber<? super T>> |
+subscriber |
+
Constructor and Description | +
---|
BlackboxProbe(TestEnvironment env,
+ TestEnvironment.Promise<org.reactivestreams.Subscriber<? super T>> subscriber) |
+
Modifier and Type | +Method and Description | +
---|---|
void |
+expectCompletion() |
+
void |
+expectCompletion(long timeoutMillis) |
+
void |
+expectCompletion(long timeoutMillis,
+ java.lang.String msg) |
+
<E extends java.lang.Throwable> |
+expectError(java.lang.Class<E> expected) |
+
<E extends java.lang.Throwable> |
+expectError(java.lang.Class<E> expected,
+ long timeoutMillis) |
+
void |
+expectError(java.lang.Throwable expected) |
+
void |
+expectError(java.lang.Throwable expected,
+ long timeoutMillis) |
+
<E extends java.lang.Throwable> |
+expectErrorWithMessage(java.lang.Class<E> expected,
+ java.lang.String requiredMessagePart) |
+
T |
+expectNext() |
+
void |
+expectNext(T expected) |
+
void |
+expectNext(T expected,
+ long timeoutMillis) |
+
void |
+expectNone() |
+
void |
+expectNone(long withinMillis) |
+
void |
+registerOnComplete()
+Must be called by the test subscriber when it has received an `onComplete` event.
+ |
+
void |
+registerOnError(java.lang.Throwable cause)
+Must be called by the test subscriber when it has received an `onError` event.
+ |
+
void |
+registerOnNext(T element)
+Must be called by the test subscriber when it has received an`onNext` event.
+ |
+
org.reactivestreams.Subscriber<? super T> |
+sub() |
+
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
protected final TestEnvironment env+
protected final TestEnvironment.Promise<org.reactivestreams.Subscriber<? super T>> subscriber+
protected final TestEnvironment.Receptacle<T> elements+
protected final TestEnvironment.Promise<java.lang.Throwable> error+
public BlackboxProbe(TestEnvironment env, + TestEnvironment.Promise<org.reactivestreams.Subscriber<? super T>> subscriber)+
public void registerOnNext(T element)+
SubscriberWhiteboxVerification.SubscriberProbe
registerOnNext
in interface SubscriberWhiteboxVerification.SubscriberProbe<T>
public void registerOnComplete()+
SubscriberWhiteboxVerification.SubscriberProbe
registerOnComplete
in interface SubscriberWhiteboxVerification.SubscriberProbe<T>
public void registerOnError(java.lang.Throwable cause)+
SubscriberWhiteboxVerification.SubscriberProbe
registerOnError
in interface SubscriberWhiteboxVerification.SubscriberProbe<T>
public T expectNext() + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectNext(T expected) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectNext(T expected, + long timeoutMillis) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectCompletion() + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectCompletion(long timeoutMillis) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectCompletion(long timeoutMillis, + java.lang.String msg) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public <E extends java.lang.Throwable> void expectErrorWithMessage(java.lang.Class<E> expected, + java.lang.String requiredMessagePart) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public <E extends java.lang.Throwable> E expectError(java.lang.Class<E> expected) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public <E extends java.lang.Throwable> E expectError(java.lang.Class<E> expected, + long timeoutMillis) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectError(java.lang.Throwable expected) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectError(java.lang.Throwable expected, + long timeoutMillis) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectNone() + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectNone(long withinMillis) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public static class SubscriberWhiteboxVerification.BlackboxSubscriberProxy<T> +extends SubscriberWhiteboxVerification.BlackboxProbe<T> +implements org.reactivestreams.Subscriber<T>+
Subscriber
decorator and should be used in pub.subscriber(...)
calls,
+ in order to allow intercepting calls on the underlying Subscriber
.
+ This delegation allows the proxy to implement SubscriberWhiteboxVerification.BlackboxProbe
assertions.elements, env, error, subscriber
Constructor and Description | +
---|
BlackboxSubscriberProxy(TestEnvironment env,
+ org.reactivestreams.Subscriber<T> subscriber) |
+
Modifier and Type | +Method and Description | +
---|---|
void |
+onComplete() |
+
void |
+onError(java.lang.Throwable cause) |
+
void |
+onNext(T t) |
+
void |
+onSubscribe(org.reactivestreams.Subscription s) |
+
expectCompletion, expectCompletion, expectCompletion, expectError, expectError, expectError, expectError, expectErrorWithMessage, expectNext, expectNext, expectNext, expectNone, expectNone, registerOnComplete, registerOnError, registerOnNext, sub
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
public BlackboxSubscriberProxy(TestEnvironment env, + org.reactivestreams.Subscriber<T> subscriber)+
public void onSubscribe(org.reactivestreams.Subscription s)+
onSubscribe
in interface org.reactivestreams.Subscriber<T>
public void onNext(T t)+
onNext
in interface org.reactivestreams.Subscriber<T>
public void onError(java.lang.Throwable cause)+
onError
in interface org.reactivestreams.Subscriber<T>
public void onComplete()+
onComplete
in interface org.reactivestreams.Subscriber<T>
public static interface SubscriberWhiteboxVerification.SubscriberProbe<T>+
Modifier and Type | +Method and Description | +
---|---|
void |
+registerOnComplete()
+Must be called by the test subscriber when it has received an `onComplete` event.
+ |
+
void |
+registerOnError(java.lang.Throwable cause)
+Must be called by the test subscriber when it has received an `onError` event.
+ |
+
void |
+registerOnNext(T element)
+Must be called by the test subscriber when it has received an`onNext` event.
+ |
+
void registerOnNext(T element)+
void registerOnComplete()+
void registerOnError(java.lang.Throwable cause)+
public static interface SubscriberWhiteboxVerification.SubscriberPuppet+
Modifier and Type | +Method and Description | +
---|---|
void |
+signalCancel() |
+
void |
+triggerRequest(long elements) |
+
void triggerRequest(long elements)+
void signalCancel()+
public static interface SubscriberWhiteboxVerification.SubscriberPuppeteer+
Modifier and Type | +Method and Description | +
---|---|
void |
+registerOnSubscribe(SubscriberWhiteboxVerification.SubscriberPuppet puppet)
+Must be called by the test subscriber when it has successfully registered a subscription
+ inside the `onSubscribe` method.
+ |
+
void registerOnSubscribe(SubscriberWhiteboxVerification.SubscriberPuppet puppet)+
public static class SubscriberWhiteboxVerification.WhiteboxSubscriberProbe<T> +extends SubscriberWhiteboxVerification.BlackboxProbe<T> +implements SubscriberWhiteboxVerification.SubscriberPuppeteer+
Modifier and Type | +Field and Description | +
---|---|
protected TestEnvironment.Promise<SubscriberWhiteboxVerification.SubscriberPuppet> |
+puppet |
+
elements, env, error, subscriber
Constructor and Description | +
---|
WhiteboxSubscriberProbe(TestEnvironment env,
+ TestEnvironment.Promise<org.reactivestreams.Subscriber<? super T>> subscriber) |
+
Modifier and Type | +Method and Description | +
---|---|
void |
+registerOnSubscribe(SubscriberWhiteboxVerification.SubscriberPuppet p)
+Must be called by the test subscriber when it has successfully registered a subscription
+ inside the `onSubscribe` method.
+ |
+
expectCompletion, expectCompletion, expectCompletion, expectError, expectError, expectError, expectError, expectErrorWithMessage, expectNext, expectNext, expectNext, expectNone, expectNone, registerOnComplete, registerOnError, registerOnNext, sub
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
protected TestEnvironment.Promise<SubscriberWhiteboxVerification.SubscriberPuppet> puppet+
public WhiteboxSubscriberProbe(TestEnvironment env, + TestEnvironment.Promise<org.reactivestreams.Subscriber<? super T>> subscriber)+
public void registerOnSubscribe(SubscriberWhiteboxVerification.SubscriberPuppet p)+
SubscriberWhiteboxVerification.SubscriberPuppeteer
registerOnSubscribe
in interface SubscriberWhiteboxVerification.SubscriberPuppeteer
public class SubscriberWhiteboxVerification.WhiteboxTestStage +extends TestEnvironment.ManualPublisher<T>+
Modifier and Type | +Field and Description | +
---|---|
T |
+lastT |
+
SubscriberWhiteboxVerification.WhiteboxSubscriberProbe<T> |
+probe |
+
org.reactivestreams.Publisher<T> |
+pub |
+
TestEnvironment.ManualSubscriber<T> |
+tees |
+
cancelled, env, pendingDemand, requests, subscriber
Constructor and Description | +
---|
WhiteboxTestStage(TestEnvironment env) |
+
WhiteboxTestStage(TestEnvironment env,
+ boolean runDefaultInit) |
+
Modifier and Type | +Method and Description | +
---|---|
org.reactivestreams.Publisher<T> |
+createHelperPublisher(long elements) |
+
SubscriberWhiteboxVerification.WhiteboxSubscriberProbe<T> |
+createWhiteboxSubscriberProbe(TestEnvironment env) |
+
T |
+nextT() |
+
SubscriberWhiteboxVerification.WhiteboxSubscriberProbe<T> |
+probe() |
+
SubscriberWhiteboxVerification.SubscriberPuppet |
+puppet() |
+
T |
+signalNext() |
+
org.reactivestreams.Subscriber<? super T> |
+sub() |
+
void |
+verifyNoAsyncErrors() |
+
expectCancelling, expectCancelling, expectExactRequest, expectExactRequest, expectNoRequest, expectNoRequest, expectRequest, expectRequest, sendCompletion, sendError, sendNext, subscribe
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
public TestEnvironment.ManualSubscriber<T> tees+
public SubscriberWhiteboxVerification.WhiteboxSubscriberProbe<T> probe+
public WhiteboxTestStage(TestEnvironment env) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public WhiteboxTestStage(TestEnvironment env, + boolean runDefaultInit) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public SubscriberWhiteboxVerification.SubscriberPuppet puppet()+
public SubscriberWhiteboxVerification.WhiteboxSubscriberProbe<T> probe()+
public org.reactivestreams.Publisher<T> createHelperPublisher(long elements)+
public SubscriberWhiteboxVerification.WhiteboxSubscriberProbe<T> createWhiteboxSubscriberProbe(TestEnvironment env)+
public T signalNext() + throws java.lang.InterruptedException+
java.lang.InterruptedException
public T nextT() + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void verifyNoAsyncErrors()+
public abstract class SubscriberWhiteboxVerification<T> +extends WithHelperPublisher<T> +implements SubscriberWhiteboxVerificationRules+
Subscriber
and Subscription
specification rules.Subscriber
,
+Subscription
Modifier and Type | +Class and Description | +
---|---|
static class |
+SubscriberWhiteboxVerification.BlackboxProbe<T> |
+
static class |
+SubscriberWhiteboxVerification.BlackboxSubscriberProxy<T>
+This class is intented to be used as
+Subscriber decorator and should be used in pub.subscriber(...) calls,
+ in order to allow intercepting calls on the underlying Subscriber . |
+
static interface |
+SubscriberWhiteboxVerification.SubscriberProbe<T> |
+
static interface |
+SubscriberWhiteboxVerification.SubscriberPuppet |
+
static interface |
+SubscriberWhiteboxVerification.SubscriberPuppeteer |
+
static class |
+SubscriberWhiteboxVerification.WhiteboxSubscriberProbe<T> |
+
class |
+SubscriberWhiteboxVerification.WhiteboxTestStage |
+
Modifier | +Constructor and Description | +
---|---|
protected |
+SubscriberWhiteboxVerification(TestEnvironment env) |
+
createElement, createHelperPublisher
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
protected SubscriberWhiteboxVerification(TestEnvironment env)+
public abstract org.reactivestreams.Subscriber<T> createSubscriber(SubscriberWhiteboxVerification.WhiteboxSubscriberProbe<T> probe)+
Subscriber
instance to be subjected to the testing logic.
+
+ In order to be meaningfully testable your Subscriber must inform the given
+ `WhiteboxSubscriberProbe` of the respective events having been received.public void startPublisherExecutorService()+
public void shutdownPublisherExecutorService()+
public java.util.concurrent.ExecutorService publisherExecutorService()+
WithHelperPublisher
Publisher
publisherExecutorService
in class WithHelperPublisher<T>
public void setUp() + throws java.lang.Exception+
java.lang.Exception
public void required_exerciseWhiteboxHappyPath() + throws java.lang.Throwable+
required_exerciseWhiteboxHappyPath
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void required_spec201_mustSignalDemandViaSubscriptionRequest() + throws java.lang.Throwable+
required_spec201_mustSignalDemandViaSubscriptionRequest
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void untested_spec202_shouldAsynchronouslyDispatch() + throws java.lang.Exception+
untested_spec202_shouldAsynchronouslyDispatch
in interface SubscriberWhiteboxVerificationRules
java.lang.Exception
public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() + throws java.lang.Throwable+
required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() + throws java.lang.Throwable+
required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() + throws java.lang.Exception+
untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError
in interface SubscriberWhiteboxVerificationRules
java.lang.Exception
public void required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() + throws java.lang.Throwable+
required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid() + throws java.lang.Exception+
untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid
in interface SubscriberWhiteboxVerificationRules
java.lang.Exception
public void untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() + throws java.lang.Exception+
untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization
in interface SubscriberWhiteboxVerificationRules
java.lang.Exception
public void required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() + throws java.lang.Throwable+
required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() + throws java.lang.Throwable+
required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() + throws java.lang.Throwable+
required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() + throws java.lang.Throwable+
required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall() + throws java.lang.Throwable+
required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() + throws java.lang.Exception+
untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents
in interface SubscriberWhiteboxVerificationRules
java.lang.Exception
public void untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation() + throws java.lang.Throwable+
untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void untested_spec213_failingOnSignalInvocation() + throws java.lang.Exception+
untested_spec213_failingOnSignalInvocation
in interface SubscriberWhiteboxVerificationRules
java.lang.Exception
public void required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() + throws java.lang.Throwable+
required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() + throws java.lang.Throwable+
required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull() + throws java.lang.Throwable+
required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void untested_spec301_mustNotBeCalledOutsideSubscriberContext() + throws java.lang.Exception+
untested_spec301_mustNotBeCalledOutsideSubscriberContext
in interface SubscriberWhiteboxVerificationRules
java.lang.Exception
public void required_spec308_requestMustRegisterGivenNumberElementsToBeProduced() + throws java.lang.Throwable+
required_spec308_requestMustRegisterGivenNumberElementsToBeProduced
in interface SubscriberWhiteboxVerificationRules
java.lang.Throwable
public void untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber() + throws java.lang.Exception+
untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber
in interface SubscriberWhiteboxVerificationRules
java.lang.Exception
public void untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError() + throws java.lang.Exception+
untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError
in interface SubscriberWhiteboxVerificationRules
java.lang.Exception
public void untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() + throws java.lang.Exception+
untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists
in interface SubscriberWhiteboxVerificationRules
java.lang.Exception
public void untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError() + throws java.lang.Exception+
untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError
in interface SubscriberWhiteboxVerificationRules
java.lang.Exception
public void untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() + throws java.lang.Exception+
untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber
in interface SubscriberWhiteboxVerificationRules
java.lang.Exception
public void subscriberTest(org.reactivestreams.tck.SubscriberWhiteboxVerification.TestStageTestRun body) + throws java.lang.Throwable+
SubscriberWhiteboxVerification.WhiteboxTestStage
over to the test.
+
+ The test stage is, like in a puppet show, used to orchestrate what each participant should do.
+ Since this is a whitebox test, this allows the stage to completely control when and how to signal / expect signals.java.lang.Throwable
public void subscriberTestWithoutSetup(org.reactivestreams.tck.SubscriberWhiteboxVerification.TestStageTestRun body) + throws java.lang.Throwable+
SubscriberWhiteboxVerification.WhiteboxTestStage
without performing any additional setup,
+ like the subscriberTest(SubscriberWhiteboxVerification.TestStageTestRun)
would.
+
+ Use this method to write tests in which you need full control over when and how the initial subscribe
is signalled.java.lang.Throwable
public void optionalSubscriberTestWithoutSetup(org.reactivestreams.tck.SubscriberWhiteboxVerification.TestStageTestRun body) + throws java.lang.Throwable+
java.lang.Throwable
public void notVerified()+
public void notVerified(java.lang.String msg)+
public static class TestEnvironment.BlackholeSubscriberWithSubscriptionSupport<T> +extends TestEnvironment.ManualSubscriberWithSubscriptionSupport<T>+
TestEnvironment.ManualSubscriberWithSubscriptionSupport
+ but does not accumulate values signalled via onNext
, thus it can not be used to assert
+ values signalled to this subscriber. Instead it may be used to quickly drain a given publisher.env
Constructor and Description | +
---|
BlackholeSubscriberWithSubscriptionSupport(TestEnvironment env) |
+
Modifier and Type | +Method and Description | +
---|---|
T |
+nextElement(long timeoutMillis,
+ java.lang.String errorMsg) |
+
java.util.List<T> |
+nextElements(long elements,
+ long timeoutMillis,
+ java.lang.String errorMsg) |
+
void |
+onNext(T element) |
+
onComplete, onError, onSubscribe
expectCompletion, expectCompletion, expectCompletion, expectCompletion, expectError, expectError, expectError, expectError, expectErrorWithMessage, expectErrorWithMessage, expectNext, expectNext, expectNone, expectNone, expectNone, expectNone, nextElement, nextElement, nextElement, nextElementOrEndOfStream, nextElementOrEndOfStream, nextElementOrEndOfStream, nextElements, nextElements, nextElements, request, requestEndOfStream, requestEndOfStream, requestEndOfStream, requestEndOfStream, requestNextElement, requestNextElement, requestNextElement, requestNextElement, requestNextElementOrEndOfStream, requestNextElementOrEndOfStream, requestNextElementOrEndOfStream, requestNextElements, requestNextElements, requestNextElements
cancel
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
public BlackholeSubscriberWithSubscriptionSupport(TestEnvironment env)+
public void onNext(T element)+
onNext
in interface org.reactivestreams.Subscriber<T>
onNext
in class TestEnvironment.ManualSubscriberWithSubscriptionSupport<T>
public T nextElement(long timeoutMillis, + java.lang.String errorMsg) + throws java.lang.InterruptedException+
nextElement
in class TestEnvironment.ManualSubscriber<T>
java.lang.InterruptedException
public java.util.List<T> nextElements(long elements, + long timeoutMillis, + java.lang.String errorMsg) + throws java.lang.InterruptedException+
nextElements
in class TestEnvironment.ManualSubscriber<T>
java.lang.InterruptedException
public static class TestEnvironment.Latch +extends java.lang.Object+
Constructor and Description | +
---|
Latch(TestEnvironment env) |
+
Modifier and Type | +Method and Description | +
---|---|
void |
+assertClosed(java.lang.String openErrorMsg) |
+
void |
+assertOpen(java.lang.String closedErrorMsg) |
+
void |
+close() |
+
void |
+expectClose(long timeoutMillis,
+ java.lang.String notClosedErrorMsg) |
+
void |
+expectClose(java.lang.String notClosedErrorMsg) |
+
boolean |
+isClosed() |
+
void |
+reOpen() |
+
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
public Latch(TestEnvironment env)+
public void reOpen()+
public boolean isClosed()+
public void close()+
public void assertClosed(java.lang.String openErrorMsg)+
public void assertOpen(java.lang.String closedErrorMsg)+
public void expectClose(java.lang.String notClosedErrorMsg) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectClose(long timeoutMillis, + java.lang.String notClosedErrorMsg) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public static class TestEnvironment.ManualPublisher<T> +extends java.lang.Object +implements org.reactivestreams.Publisher<T>+
Modifier and Type | +Field and Description | +
---|---|
protected TestEnvironment.Latch |
+cancelled |
+
protected TestEnvironment |
+env |
+
protected long |
+pendingDemand |
+
protected TestEnvironment.Receptacle<java.lang.Long> |
+requests |
+
protected TestEnvironment.Promise<org.reactivestreams.Subscriber<? super T>> |
+subscriber |
+
Constructor and Description | +
---|
ManualPublisher(TestEnvironment env) |
+
Modifier and Type | +Method and Description | +
---|---|
void |
+expectCancelling() |
+
void |
+expectCancelling(long timeoutMillis) |
+
void |
+expectExactRequest(long expected) |
+
void |
+expectExactRequest(long expected,
+ long timeoutMillis) |
+
void |
+expectNoRequest() |
+
void |
+expectNoRequest(long timeoutMillis) |
+
long |
+expectRequest() |
+
long |
+expectRequest(long timeoutMillis) |
+
void |
+sendCompletion() |
+
void |
+sendError(java.lang.Throwable cause) |
+
void |
+sendNext(T element) |
+
void |
+subscribe(org.reactivestreams.Subscriber<? super T> s) |
+
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
protected final TestEnvironment env+
protected long pendingDemand+
protected TestEnvironment.Promise<org.reactivestreams.Subscriber<? super T>> subscriber+
protected final TestEnvironment.Receptacle<java.lang.Long> requests+
protected final TestEnvironment.Latch cancelled+
public ManualPublisher(TestEnvironment env)+
public void subscribe(org.reactivestreams.Subscriber<? super T> s)+
subscribe
in interface org.reactivestreams.Publisher<T>
public void sendCompletion()+
public void sendError(java.lang.Throwable cause)+
public long expectRequest() + throws java.lang.InterruptedException+
java.lang.InterruptedException
public long expectRequest(long timeoutMillis) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectExactRequest(long expected) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectExactRequest(long expected, + long timeoutMillis) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectNoRequest() + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectNoRequest(long timeoutMillis) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectCancelling() + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectCancelling(long timeoutMillis) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public static class TestEnvironment.ManualSubscriber<T> +extends TestEnvironment.TestSubscriber<T>+
Subscriber
implementation which can be steered by test code and asserted on.env
Constructor and Description | +
---|
ManualSubscriber(TestEnvironment env) |
+
Modifier and Type | +Method and Description | +
---|---|
void |
+expectCompletion() |
+
void |
+expectCompletion(long timeoutMillis) |
+
void |
+expectCompletion(long timeoutMillis,
+ java.lang.String errorMsg) |
+
void |
+expectCompletion(java.lang.String errorMsg) |
+
<E extends java.lang.Throwable> |
+expectError(java.lang.Class<E> expected) |
+
<E extends java.lang.Throwable> |
+expectError(java.lang.Class<E> expected,
+ long timeoutMillis) |
+
<E extends java.lang.Throwable> |
+expectError(java.lang.Class<E> expected,
+ long timeoutMillis,
+ java.lang.String errorMsg) |
+
<E extends java.lang.Throwable> |
+expectError(java.lang.Class<E> expected,
+ java.lang.String errorMsg) |
+
<E extends java.lang.Throwable> |
+expectErrorWithMessage(java.lang.Class<E> expected,
+ java.lang.String requiredMessagePart) |
+
<E extends java.lang.Throwable> |
+expectErrorWithMessage(java.lang.Class<E> expected,
+ java.lang.String requiredMessagePart,
+ long timeoutMillis) |
+
void |
+expectNext(T expected) |
+
void |
+expectNext(T expected,
+ long timeoutMillis) |
+
void |
+expectNone() |
+
void |
+expectNone(long withinMillis) |
+
void |
+expectNone(long withinMillis,
+ java.lang.String errMsgPrefix) |
+
void |
+expectNone(java.lang.String errMsgPrefix) |
+
T |
+nextElement() |
+
T |
+nextElement(long timeoutMillis) |
+
T |
+nextElement(long timeoutMillis,
+ java.lang.String errorMsg) |
+
T |
+nextElement(java.lang.String errorMsg) |
+
Optional<T> |
+nextElementOrEndOfStream() |
+
Optional<T> |
+nextElementOrEndOfStream(long timeoutMillis) |
+
Optional<T> |
+nextElementOrEndOfStream(long timeoutMillis,
+ java.lang.String errorMsg) |
+
java.util.List<T> |
+nextElements(long elements) |
+
java.util.List<T> |
+nextElements(long elements,
+ long timeoutMillis) |
+
java.util.List<T> |
+nextElements(long elements,
+ long timeoutMillis,
+ java.lang.String errorMsg) |
+
java.util.List<T> |
+nextElements(long elements,
+ java.lang.String errorMsg) |
+
void |
+onComplete() |
+
void |
+onNext(T element) |
+
void |
+request(long elements) |
+
void |
+requestEndOfStream() |
+
void |
+requestEndOfStream(long timeoutMillis) |
+
void |
+requestEndOfStream(long timeoutMillis,
+ java.lang.String errorMsg) |
+
void |
+requestEndOfStream(java.lang.String errorMsg) |
+
T |
+requestNextElement() |
+
T |
+requestNextElement(long timeoutMillis) |
+
T |
+requestNextElement(long timeoutMillis,
+ java.lang.String errorMsg) |
+
T |
+requestNextElement(java.lang.String errorMsg) |
+
Optional<T> |
+requestNextElementOrEndOfStream(long timeoutMillis) |
+
Optional<T> |
+requestNextElementOrEndOfStream(long timeoutMillis,
+ java.lang.String errorMsg) |
+
Optional<T> |
+requestNextElementOrEndOfStream(java.lang.String errorMsg) |
+
java.util.List<T> |
+requestNextElements(long elements) |
+
java.util.List<T> |
+requestNextElements(long elements,
+ long timeoutMillis) |
+
java.util.List<T> |
+requestNextElements(long elements,
+ long timeoutMillis,
+ java.lang.String errorMsg) |
+
cancel, onError, onSubscribe
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
public ManualSubscriber(TestEnvironment env)+
public void onNext(T element)+
onNext
in interface org.reactivestreams.Subscriber<T>
onNext
in class TestEnvironment.TestSubscriber<T>
public void onComplete()+
onComplete
in interface org.reactivestreams.Subscriber<T>
onComplete
in class TestEnvironment.TestSubscriber<T>
public void request(long elements)+
public T requestNextElement() + throws java.lang.InterruptedException+
java.lang.InterruptedException
public T requestNextElement(long timeoutMillis) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public T requestNextElement(java.lang.String errorMsg) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public T requestNextElement(long timeoutMillis, + java.lang.String errorMsg) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public Optional<T> requestNextElementOrEndOfStream(java.lang.String errorMsg) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public Optional<T> requestNextElementOrEndOfStream(long timeoutMillis) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public Optional<T> requestNextElementOrEndOfStream(long timeoutMillis, + java.lang.String errorMsg) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void requestEndOfStream() + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void requestEndOfStream(long timeoutMillis) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void requestEndOfStream(java.lang.String errorMsg) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void requestEndOfStream(long timeoutMillis, + java.lang.String errorMsg) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public java.util.List<T> requestNextElements(long elements) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public java.util.List<T> requestNextElements(long elements, + long timeoutMillis) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public java.util.List<T> requestNextElements(long elements, + long timeoutMillis, + java.lang.String errorMsg) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public T nextElement() + throws java.lang.InterruptedException+
java.lang.InterruptedException
public T nextElement(long timeoutMillis) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public T nextElement(java.lang.String errorMsg) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public T nextElement(long timeoutMillis, + java.lang.String errorMsg) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public Optional<T> nextElementOrEndOfStream() + throws java.lang.InterruptedException+
java.lang.InterruptedException
public Optional<T> nextElementOrEndOfStream(long timeoutMillis) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public Optional<T> nextElementOrEndOfStream(long timeoutMillis, + java.lang.String errorMsg) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public java.util.List<T> nextElements(long elements) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public java.util.List<T> nextElements(long elements, + java.lang.String errorMsg) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public java.util.List<T> nextElements(long elements, + long timeoutMillis) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public java.util.List<T> nextElements(long elements, + long timeoutMillis, + java.lang.String errorMsg) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectNext(T expected) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectNext(T expected, + long timeoutMillis) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectCompletion() + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectCompletion(long timeoutMillis) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectCompletion(java.lang.String errorMsg) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectCompletion(long timeoutMillis, + java.lang.String errorMsg) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public <E extends java.lang.Throwable> void expectErrorWithMessage(java.lang.Class<E> expected, + java.lang.String requiredMessagePart) + throws java.lang.Exception+
java.lang.Exception
public <E extends java.lang.Throwable> void expectErrorWithMessage(java.lang.Class<E> expected, + java.lang.String requiredMessagePart, + long timeoutMillis) + throws java.lang.Exception+
java.lang.Exception
public <E extends java.lang.Throwable> E expectError(java.lang.Class<E> expected) + throws java.lang.Exception+
java.lang.Exception
public <E extends java.lang.Throwable> E expectError(java.lang.Class<E> expected, + long timeoutMillis) + throws java.lang.Exception+
java.lang.Exception
public <E extends java.lang.Throwable> E expectError(java.lang.Class<E> expected, + java.lang.String errorMsg) + throws java.lang.Exception+
java.lang.Exception
public <E extends java.lang.Throwable> E expectError(java.lang.Class<E> expected, + long timeoutMillis, + java.lang.String errorMsg) + throws java.lang.Exception+
java.lang.Exception
public void expectNone() + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectNone(java.lang.String errMsgPrefix) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectNone(long withinMillis) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void expectNone(long withinMillis, + java.lang.String errMsgPrefix) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public static class TestEnvironment.ManualSubscriberWithSubscriptionSupport<T> +extends TestEnvironment.ManualSubscriber<T>+
env
Constructor and Description | +
---|
ManualSubscriberWithSubscriptionSupport(TestEnvironment env) |
+
Modifier and Type | +Method and Description | +
---|---|
void |
+onComplete() |
+
void |
+onError(java.lang.Throwable cause) |
+
void |
+onNext(T element) |
+
void |
+onSubscribe(org.reactivestreams.Subscription s) |
+
expectCompletion, expectCompletion, expectCompletion, expectCompletion, expectError, expectError, expectError, expectError, expectErrorWithMessage, expectErrorWithMessage, expectNext, expectNext, expectNone, expectNone, expectNone, expectNone, nextElement, nextElement, nextElement, nextElement, nextElementOrEndOfStream, nextElementOrEndOfStream, nextElementOrEndOfStream, nextElements, nextElements, nextElements, nextElements, request, requestEndOfStream, requestEndOfStream, requestEndOfStream, requestEndOfStream, requestNextElement, requestNextElement, requestNextElement, requestNextElement, requestNextElementOrEndOfStream, requestNextElementOrEndOfStream, requestNextElementOrEndOfStream, requestNextElements, requestNextElements, requestNextElements
cancel
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
public ManualSubscriberWithSubscriptionSupport(TestEnvironment env)+
public void onNext(T element)+
onNext
in interface org.reactivestreams.Subscriber<T>
onNext
in class TestEnvironment.ManualSubscriber<T>
public void onComplete()+
onComplete
in interface org.reactivestreams.Subscriber<T>
onComplete
in class TestEnvironment.ManualSubscriber<T>
public void onSubscribe(org.reactivestreams.Subscription s)+
onSubscribe
in interface org.reactivestreams.Subscriber<T>
onSubscribe
in class TestEnvironment.TestSubscriber<T>
public void onError(java.lang.Throwable cause)+
onError
in interface org.reactivestreams.Subscriber<T>
onError
in class TestEnvironment.TestSubscriber<T>
public static class TestEnvironment.Promise<T> +extends java.lang.Object+
Constructor and Description | +
---|
Promise(TestEnvironment env) |
+
Modifier and Type | +Method and Description | +
---|---|
void |
+complete(T value)
+Allows using expectCompletion to await for completion of the value and complete it _then_
+ |
+
static <T> TestEnvironment.Promise<T> |
+completed(TestEnvironment env,
+ T value) |
+
void |
+completeImmediatly(T value)
+Completes the promise right away, it is not possible to expectCompletion on a Promise completed this way
+ |
+
void |
+expectCompletion(long timeoutMillis,
+ java.lang.String errorMsg) |
+
boolean |
+isCompleted() |
+
T |
+value() |
+
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
public Promise(TestEnvironment env)+
public static <T> TestEnvironment.Promise<T> completed(TestEnvironment env, + T value)+
public boolean isCompleted()+
public void complete(T value)+
public void completeImmediatly(T value)+
public void expectCompletion(long timeoutMillis, + java.lang.String errorMsg) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public static class TestEnvironment.Receptacle<T> +extends java.lang.Object+
Modifier and Type | +Method and Description | +
---|---|
void |
+add(T value) |
+
void |
+complete() |
+
void |
+expectCompletion(long timeoutMillis,
+ java.lang.String errorMsg) |
+
<E extends java.lang.Throwable> |
+expectError(java.lang.Class<E> clazz,
+ long timeoutMillis,
+ java.lang.String errorMsg) |
+
void |
+expectNone(long withinMillis,
+ java.lang.String errorMsgPrefix) |
+
T |
+next(long timeoutMillis,
+ java.lang.String errorMsg) |
+
java.util.List<T> |
+nextN(long elements,
+ long timeoutMillis,
+ java.lang.String errorMsg) |
+
Optional<T> |
+nextOrEndOfStream(long timeoutMillis,
+ java.lang.String errorMsg) |
+
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
public void complete()+
public T next(long timeoutMillis, + java.lang.String errorMsg) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public Optional<T> nextOrEndOfStream(long timeoutMillis, + java.lang.String errorMsg) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public java.util.List<T> nextN(long elements, + long timeoutMillis, + java.lang.String errorMsg) + throws java.lang.InterruptedException+
timeoutMillis
- total timeout time for awaiting all elements
number of elementsjava.lang.InterruptedException
public void expectCompletion(long timeoutMillis, + java.lang.String errorMsg) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public <E extends java.lang.Throwable> E expectError(java.lang.Class<E> clazz, + long timeoutMillis, + java.lang.String errorMsg) + throws java.lang.Exception+
java.lang.Exception
public void expectNone(long withinMillis, + java.lang.String errorMsgPrefix) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public static class TestEnvironment.TestSubscriber<T> +extends java.lang.Object +implements org.reactivestreams.Subscriber<T>+
Modifier and Type | +Field and Description | +
---|---|
protected TestEnvironment |
+env |
+
Constructor and Description | +
---|
TestSubscriber(TestEnvironment env) |
+
Modifier and Type | +Method and Description | +
---|---|
void |
+cancel() |
+
void |
+onComplete() |
+
void |
+onError(java.lang.Throwable cause) |
+
void |
+onNext(T element) |
+
void |
+onSubscribe(org.reactivestreams.Subscription subscription) |
+
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
protected final TestEnvironment env+
public TestSubscriber(TestEnvironment env)+
public void onError(java.lang.Throwable cause)+
onError
in interface org.reactivestreams.Subscriber<T>
public void onComplete()+
onComplete
in interface org.reactivestreams.Subscriber<T>
public void onNext(T element)+
onNext
in interface org.reactivestreams.Subscriber<T>
public void onSubscribe(org.reactivestreams.Subscription subscription)+
onSubscribe
in interface org.reactivestreams.Subscriber<T>
public void cancel()+
public class TestEnvironment +extends java.lang.Object+
Modifier and Type | +Class and Description | +
---|---|
static class |
+TestEnvironment.BlackholeSubscriberWithSubscriptionSupport<T>
+Similar to
+TestEnvironment.ManualSubscriberWithSubscriptionSupport
+ but does not accumulate values signalled via onNext , thus it can not be used to assert
+ values signalled to this subscriber. |
+
static class |
+TestEnvironment.Latch
+Like a CountDownLatch, but resettable and with some convenience methods
+ |
+
static class |
+TestEnvironment.ManualPublisher<T> |
+
static class |
+TestEnvironment.ManualSubscriber<T>
+Subscriber implementation which can be steered by test code and asserted on. |
+
static class |
+TestEnvironment.ManualSubscriberWithSubscriptionSupport<T> |
+
static class |
+TestEnvironment.Promise<T> |
+
static class |
+TestEnvironment.Receptacle<T> |
+
static class |
+TestEnvironment.TestSubscriber<T> |
+
Modifier and Type | +Field and Description | +
---|---|
static int |
+TEST_BUFFER_SIZE |
+
Constructor and Description | +
---|
TestEnvironment()
+Tests must specify the timeout for expected outcome of asynchronous
+ interactions.
+ |
+
TestEnvironment(boolean printlnDebug)
+Tests must specify the timeout for expected outcome of asynchronous
+ interactions.
+ |
+
TestEnvironment(long defaultTimeoutMillis)
+Tests must specify the timeout for expected outcome of asynchronous
+ interactions.
+ |
+
TestEnvironment(long defaultTimeoutMillis,
+ boolean printlnDebug)
+Tests must specify the timeout for expected outcome of asynchronous
+ interactions.
+ |
+
Modifier and Type | +Method and Description | +
---|---|
void |
+clearAsyncErrors() |
+
void |
+debug(java.lang.String msg)
+If
+TestEnvironment#printlnDebug is true, print debug message to std out. |
+
long |
+defaultTimeoutMillis() |
+
java.lang.Throwable |
+dropAsyncError() |
+
static long |
+envDefaultTimeoutMillis()
+Tries to parse the env variable
+DEFAULT_TIMEOUT_MILLIS as long and returns the value if present OR its default value. |
+
Optional<java.lang.StackTraceElement> |
+findCallerMethodInStackTrace(java.lang.String method)
+Looks for given
+method method in stack trace. |
+
void |
+flop(java.lang.String msg)
+To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously.
+ |
+
void |
+flop(java.lang.Throwable thr)
+To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously.
+ |
+
void |
+flop(java.lang.Throwable thr,
+ java.lang.String msg)
+To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously.
+ |
+
<T> T |
+flopAndFail(java.lang.String msg)
+To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously.
+ |
+
<T> TestEnvironment.ManualSubscriber<T> |
+newBlackholeSubscriber(org.reactivestreams.Publisher<T> pub) |
+
<T> TestEnvironment.ManualSubscriber<T> |
+newManualSubscriber(org.reactivestreams.Publisher<T> pub) |
+
<T> TestEnvironment.ManualSubscriber<T> |
+newManualSubscriber(org.reactivestreams.Publisher<T> pub,
+ long timeoutMillis) |
+
<T> void |
+subscribe(org.reactivestreams.Publisher<T> pub,
+ TestEnvironment.TestSubscriber<T> sub) |
+
<T> void |
+subscribe(org.reactivestreams.Publisher<T> pub,
+ TestEnvironment.TestSubscriber<T> sub,
+ long timeoutMillis) |
+
void |
+verifyNoAsyncErrors()
+Waits for
+defaultTimeoutMillis() and then verifies that no asynchronous errors
+ were signalled pior to, or during that time (by calling flop() ). |
+
void |
+verifyNoAsyncErrors(long delay)
+This version of
+verifyNoAsyncErrors should be used when errors still could be signalled
+ asynchronously during defaultTimeoutMillis() time. |
+
void |
+verifyNoAsyncErrorsNoDelay()
+Verifies that no asynchronous errors were signalled pior to calling this method (by calling
+flop() ). |
+
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
public static final int TEST_BUFFER_SIZE+
public TestEnvironment(long defaultTimeoutMillis, + boolean printlnDebug)+
defaultTimeoutMillis
- default timeout to be used in all expect* methodsprintlnDebug
- if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output,
+ often helpful to pinpoint simple race conditions etc.public TestEnvironment(long defaultTimeoutMillis)+
defaultTimeoutMillis
- default timeout to be used in all expect* methodspublic TestEnvironment(boolean printlnDebug)+
DEFAULT_TIMEOUT_MILLIS
+ or the default value (DEFAULT_TIMEOUT_MILLIS
) will be used.printlnDebug
- if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output,
+ often helpful to pinpoint simple race conditions etc.public TestEnvironment()+
DEFAULT_TIMEOUT_MILLIS
+ or the default value (DEFAULT_TIMEOUT_MILLIS
) will be used.public long defaultTimeoutMillis()+
public static long envDefaultTimeoutMillis()+
DEFAULT_TIMEOUT_MILLIS
as long and returns the value if present OR its default value.java.lang.IllegalArgumentException
- when unable to parse the env variablepublic void flop(java.lang.String msg)+
env.verifyNoAsyncErrorsNoDelay()
at the end of your TCK tests to verify there no flops called during it's execution.
+ To check investigate asyncErrors more closely you can use expectError
methods or collect the error directly
+ from the environment using env.dropAsyncError()
.
+
+ To clear asyncErrors you can call clearAsyncErrors()
public void flop(java.lang.Throwable thr, + java.lang.String msg)+
env.verifyNoAsyncErrorsNoDelay()
at the end of your TCK tests to verify there no flops called during it's execution.
+ To check investigate asyncErrors more closely you can use expectError
methods or collect the error directly
+ from the environment using env.dropAsyncError()
.
+
+ To clear asyncErrors you can call clearAsyncErrors()
public void flop(java.lang.Throwable thr)+
env.verifyNoAsyncErrorsNoDelay()
at the end of your TCK tests to verify there no flops called during it's execution.
+ To check investigate asyncErrors more closely you can use expectError
methods or collect the error directly
+ from the environment using env.dropAsyncError()
.
+
+ To clear asyncErrors you can call clearAsyncErrors()
public <T> T flopAndFail(java.lang.String msg)+
flop(java.lang.String)
which only records the error.
+
+ Use env.verifyNoAsyncErrorsNoDelay()
at the end of your TCK tests to verify there no flops called during it's execution.
+ To check investigate asyncErrors more closely you can use expectError
methods or collect the error directly
+ from the environment using env.dropAsyncError()
.
+
+ To clear asyncErrors you can call clearAsyncErrors()
public <T> void subscribe(org.reactivestreams.Publisher<T> pub, + TestEnvironment.TestSubscriber<T> sub) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public <T> void subscribe(org.reactivestreams.Publisher<T> pub, + TestEnvironment.TestSubscriber<T> sub, + long timeoutMillis) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public <T> TestEnvironment.ManualSubscriber<T> newBlackholeSubscriber(org.reactivestreams.Publisher<T> pub) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public <T> TestEnvironment.ManualSubscriber<T> newManualSubscriber(org.reactivestreams.Publisher<T> pub) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public <T> TestEnvironment.ManualSubscriber<T> newManualSubscriber(org.reactivestreams.Publisher<T> pub, + long timeoutMillis) + throws java.lang.InterruptedException+
java.lang.InterruptedException
public void clearAsyncErrors()+
public java.lang.Throwable dropAsyncError()+
public void verifyNoAsyncErrors()+
defaultTimeoutMillis()
and then verifies that no asynchronous errors
+ were signalled pior to, or during that time (by calling flop()
).public void verifyNoAsyncErrors(long delay)+
verifyNoAsyncErrors
should be used when errors still could be signalled
+ asynchronously during defaultTimeoutMillis()
time.
+
+ It will immediatly check if any async errors were signaled (using flop(String)
,
+ and if no errors encountered wait for another default timeout as the errors may yet be signalled.
+ The initial check is performed in order to fail-fast in case of an already failed test.public void verifyNoAsyncErrorsNoDelay()+
flop()
).
+ This version of verifyNoAsyncError does not wait before checking for asynchronous errors, and is to be used
+ for example in tight loops etc.public void debug(java.lang.String msg)+
TestEnvironment#printlnDebug
is true, print debug message to std out.public Optional<java.lang.StackTraceElement> findCallerMethodInStackTrace(java.lang.String method)+
method
method in stack trace.
+ Can be used to answer questions like "was this method called from onComplete?".T
- type of element to be delivered to the Subscriberpublic abstract class WithHelperPublisher<T> +extends java.lang.Object+
id
value.
+ + Simplest implementations will simply return the incoming id as the element.
Constructor and Description | +
---|
WithHelperPublisher() |
+
Modifier and Type | +Method and Description | +
---|---|
abstract T |
+createElement(int element)
+Implement this method to match your expected element type.
+ |
+
org.reactivestreams.Publisher<T> |
+createHelperPublisher(long elements)
+Helper method required for creating the Publisher to which the tested Subscriber will be subscribed and tested against.
+ |
+
abstract java.util.concurrent.ExecutorService |
+publisherExecutorService()
+ExecutorService to be used by the provided helper
+Publisher |
+
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
public WithHelperPublisher()+
public abstract java.util.concurrent.ExecutorService publisherExecutorService()+
Publisher
public abstract T createElement(int element)+
element
element.
+
+ Sometimes the Subscriber may be limited in what type of element it is able to consume, this you may have to implement
+ this method such that the emitted element matches the Subscribers requirements. Simplest implementations would be
+ to simply pass in the element
as payload of your custom element, such as appending it to a String or other identifier.
+
+ Warning: This method may be called concurrently by the helper publisher, thus it should be implemented in a + thread-safe manner.
T
that will be delivered to the tested Subscriberpublic org.reactivestreams.Publisher<T> createHelperPublisher(long elements)+
+ By default an asynchronously signalling Publisher is provided, which will use createElement(int)
+ to generate elements type your Subscriber is able to consume.
+
+ Sometimes you may want to implement your own custom custom helper Publisher - to validate behaviour of a Subscriber
+ when facing a synchronous Publisher for example. If you do, it MUST emit the exact number of elements asked for
+ (via the elements
parameter) and MUST also must treat the following numbers of elements in these specific ways:
+
elements
is Long.MAX_VALUE
the produced stream must be infinite.
+ elements
is 0
the Publisher
should signal onComplete
immediatly.
+ In other words, it should represent a "completed stream".
+ Interface | +Description | +
---|---|
PublisherVerification.PublisherTestRun<T> | ++ |
SubscriberWhiteboxVerification.SubscriberProbe<T> | ++ |
SubscriberWhiteboxVerification.SubscriberPuppet | ++ |
SubscriberWhiteboxVerification.SubscriberPuppeteer | ++ |
Class | +Description | +
---|---|
IdentityProcessorVerification<T> | ++ |
PublisherVerification<T> | +
+ Provides tests for verifying
+Publisher specification rules. |
+
SubscriberBlackboxVerification<T> | +
+ Provides tests for verifying
+Subscriber and Subscription
+ specification rules, without any modifications to the tested implementation (also known as "Black Box" testing). |
+
SubscriberWhiteboxVerification<T> | +
+ Provides tests for verifying
+Subscriber and Subscription specification rules. |
+
SubscriberWhiteboxVerification.BlackboxProbe<T> | ++ |
SubscriberWhiteboxVerification.BlackboxSubscriberProxy<T> | +
+ This class is intented to be used as
+Subscriber decorator and should be used in pub.subscriber(...) calls,
+ in order to allow intercepting calls on the underlying Subscriber . |
+
SubscriberWhiteboxVerification.WhiteboxSubscriberProbe<T> | ++ |
TestEnvironment | ++ |
TestEnvironment.BlackholeSubscriberWithSubscriptionSupport<T> | +
+ Similar to
+TestEnvironment.ManualSubscriberWithSubscriptionSupport
+ but does not accumulate values signalled via onNext , thus it can not be used to assert
+ values signalled to this subscriber. |
+
TestEnvironment.Latch | +
+ Like a CountDownLatch, but resettable and with some convenience methods
+ |
+
TestEnvironment.ManualPublisher<T> | ++ |
TestEnvironment.ManualSubscriber<T> | +
+Subscriber implementation which can be steered by test code and asserted on. |
+
TestEnvironment.ManualSubscriberWithSubscriptionSupport<T> | ++ |
TestEnvironment.Promise<T> | ++ |
TestEnvironment.Receptacle<T> | ++ |
TestEnvironment.TestSubscriber<T> | ++ |
WithHelperPublisher<T> | +
+ Type which is able to create elements based on a seed
+id value. |
+
public interface Function<In,Out>+
Modifier and Type | +Method and Description | +
---|---|
Out |
+apply(In in) |
+
public class HelperPublisher<T> +extends org.reactivestreams.example.unicast.AsyncIterablePublisher<T>+
Constructor and Description | +
---|
HelperPublisher(int from,
+ int to,
+ Function<java.lang.Integer,T> create,
+ java.util.concurrent.Executor executor) |
+
public HelperPublisher(int from, + int to, + Function<java.lang.Integer,T> create, + java.util.concurrent.Executor executor)+
public class InfiniteHelperPublisher<T> +extends org.reactivestreams.example.unicast.AsyncIterablePublisher<T>+
Constructor and Description | +
---|
InfiniteHelperPublisher(Function<java.lang.Integer,T> create,
+ java.util.concurrent.Executor executor) |
+
public InfiniteHelperPublisher(Function<java.lang.Integer,T> create, + java.util.concurrent.Executor executor)+
public class NonFatal +extends java.lang.Object+
Modifier and Type | +Method and Description | +
---|---|
static boolean |
+isNonFatal(java.lang.Throwable t)
+Returns true if the provided `Throwable` is to be considered non-fatal, or false if it is to be considered fatal
+ |
+
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
public static boolean isNonFatal(java.lang.Throwable t)+
t
- throwable to be matched for fatal-nesspublic static class Optional.Some<T> +extends Optional<T>+
Optional.Some<T>
Modifier and Type | +Method and Description | +
---|---|
T |
+get() |
+
boolean |
+isEmpty() |
+
java.lang.String |
+toString() |
+
empty, isDefined, of
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
public abstract class Optional<T> +extends java.lang.Object+
Modifier and Type | +Class and Description | +
---|---|
static class |
+Optional.Some<T> |
+
Modifier and Type | +Method and Description | +
---|---|
static <T> Optional<T> |
+empty() |
+
abstract T |
+get() |
+
boolean |
+isDefined() |
+
abstract boolean |
+isEmpty() |
+
static <T> Optional<T> |
+of(T it) |
+
java.lang.String |
+toString() |
+
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
public interface PublisherVerificationRules+
void required_validate_maxElementsFromPublisher() + throws java.lang.Exception+
java.lang.Exception
void required_validate_boundedDepthOfOnNextAndRequestRecursion() + throws java.lang.Exception+
java.lang.Exception
void required_createPublisher1MustProduceAStreamOfExactly1Element() + throws java.lang.Throwable+
java.lang.Throwable
void required_createPublisher3MustProduceAStreamOfExactly3Elements() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec102_maySignalLessThanRequestedAndTerminateSubscription() + throws java.lang.Throwable+
java.lang.Throwable
void stochastic_spec103_mustSignalOnMethodsSequentially() + throws java.lang.Throwable+
java.lang.Throwable
void optional_spec104_mustSignalOnErrorWhenFails() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates() + throws java.lang.Throwable+
java.lang.Throwable
void optional_spec105_emptyStreamMustTerminateBySignallingOnComplete() + throws java.lang.Throwable+
java.lang.Throwable
void untested_spec106_mustConsiderSubscriptionCancelledAfterOnErrorOrOnCompleteHasBeenCalled() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled() + throws java.lang.Throwable+
java.lang.Throwable
void untested_spec107_mustNotEmitFurtherSignalsOnceOnErrorHasBeenSignalled() + throws java.lang.Throwable+
java.lang.Throwable
void untested_spec108_possiblyCanceledSubscriptionShouldNotReceiveOnErrorOrOnCompleteSignals() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec109_mustIssueOnSubscribeForNonNullSubscriber() + throws java.lang.Throwable+
java.lang.Throwable
void untested_spec109_subscribeShouldNotThrowNonFatalThrowable() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec109_subscribeThrowNPEOnNullSubscriber() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe() + throws java.lang.Throwable+
java.lang.Throwable
void untested_spec110_rejectASubscriptionRequestIfTheSameSubscriberSubscribesTwice() + throws java.lang.Throwable+
java.lang.Throwable
void optional_spec111_maySupportMultiSubscribe() + throws java.lang.Throwable+
java.lang.Throwable
void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingOneByOne() + throws java.lang.Throwable+
java.lang.Throwable
void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfront() + throws java.lang.Throwable+
java.lang.Throwable
void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfrontAndCompleteAsExpected() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec303_mustNotAllowUnboundedRecursion() + throws java.lang.Throwable+
java.lang.Throwable
void untested_spec304_requestShouldNotPerformHeavyComputations() + throws java.lang.Exception+
java.lang.Exception
void untested_spec305_cancelMustNotSynchronouslyPerformHeavyCompuatation() + throws java.lang.Exception+
java.lang.Exception
void required_spec306_afterSubscriptionIsCancelledRequestMustBeNops() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec309_requestZeroMustSignalIllegalArgumentException() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec309_requestNegativeNumberMustSignalIllegalArgumentException() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec317_mustSupportAPendingElementCountUpToLongMaxValue() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec317_mustSupportACumulativePendingElementCountUpToLongMaxValue() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue() + throws java.lang.Throwable+
java.lang.Throwable
public interface SubscriberBlackboxVerificationRules+
void required_spec201_blackbox_mustSignalDemandViaSubscriptionRequest() + throws java.lang.Throwable+
java.lang.Throwable
void untested_spec202_blackbox_shouldAsynchronouslyDispatch() + throws java.lang.Exception+
java.lang.Exception
void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() + throws java.lang.Throwable+
java.lang.Throwable
void untested_spec204_blackbox_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() + throws java.lang.Exception+
java.lang.Exception
void required_spec205_blackbox_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() + throws java.lang.Exception+
java.lang.Exception
void untested_spec206_blackbox_mustCallSubscriptionCancelIfItIsNoLongerValid() + throws java.lang.Exception+
java.lang.Exception
void untested_spec207_blackbox_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() + throws java.lang.Exception+
java.lang.Exception
void untested_spec208_blackbox_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec210_blackbox_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() + throws java.lang.Throwable+
java.lang.Throwable
void untested_spec211_blackbox_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() + throws java.lang.Exception+
java.lang.Exception
void untested_spec212_blackbox_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality() + throws java.lang.Throwable+
java.lang.Throwable
void untested_spec213_blackbox_failingOnSignalInvocation() + throws java.lang.Exception+
java.lang.Exception
void required_spec213_blackbox_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec213_blackbox_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec213_blackbox_onError_mustThrowNullPointerExceptionWhenParametersAreNull() + throws java.lang.Throwable+
java.lang.Throwable
void untested_spec301_blackbox_mustNotBeCalledOutsideSubscriberContext() + throws java.lang.Exception+
java.lang.Exception
void untested_spec308_blackbox_requestMustRegisterGivenNumberElementsToBeProduced() + throws java.lang.Throwable+
java.lang.Throwable
void untested_spec310_blackbox_requestMaySynchronouslyCallOnNextOnSubscriber() + throws java.lang.Exception+
java.lang.Exception
void untested_spec311_blackbox_requestMaySynchronouslyCallOnCompleteOrOnError() + throws java.lang.Exception+
java.lang.Exception
void untested_spec314_blackbox_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() + throws java.lang.Exception+
java.lang.Exception
void untested_spec315_blackbox_cancelMustNotThrowExceptionAndMustSignalOnError() + throws java.lang.Exception+
java.lang.Exception
void untested_spec316_blackbox_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() + throws java.lang.Exception+
java.lang.Exception
public final class SubscriberBufferOverflowException +extends java.lang.RuntimeException+
Constructor and Description | +
---|
SubscriberBufferOverflowException() |
+
SubscriberBufferOverflowException(java.lang.String message) |
+
SubscriberBufferOverflowException(java.lang.String message,
+ java.lang.Throwable cause) |
+
SubscriberBufferOverflowException(java.lang.Throwable cause) |
+
addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, getSuppressed, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
public SubscriberBufferOverflowException()+
public SubscriberBufferOverflowException(java.lang.String message)+
public SubscriberBufferOverflowException(java.lang.String message, + java.lang.Throwable cause)+
public SubscriberBufferOverflowException(java.lang.Throwable cause)+
public interface SubscriberWhiteboxVerificationRules+
void required_exerciseWhiteboxHappyPath() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec201_mustSignalDemandViaSubscriptionRequest() + throws java.lang.Throwable+
java.lang.Throwable
void untested_spec202_shouldAsynchronouslyDispatch() + throws java.lang.Exception+
java.lang.Exception
void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() + throws java.lang.Throwable+
java.lang.Throwable
void untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() + throws java.lang.Exception+
java.lang.Exception
void required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() + throws java.lang.Throwable+
java.lang.Throwable
void untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid() + throws java.lang.Exception+
java.lang.Exception
void untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() + throws java.lang.Exception+
java.lang.Exception
void required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall() + throws java.lang.Throwable+
java.lang.Throwable
void untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() + throws java.lang.Exception+
java.lang.Exception
void untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation() + throws java.lang.Throwable+
java.lang.Throwable
void untested_spec213_failingOnSignalInvocation() + throws java.lang.Exception+
java.lang.Exception
void required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() + throws java.lang.Throwable+
java.lang.Throwable
void required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull() + throws java.lang.Throwable+
java.lang.Throwable
void untested_spec301_mustNotBeCalledOutsideSubscriberContext() + throws java.lang.Exception+
java.lang.Exception
void required_spec308_requestMustRegisterGivenNumberElementsToBeProduced() + throws java.lang.Throwable+
java.lang.Throwable
void untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber() + throws java.lang.Exception+
java.lang.Exception
void untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError() + throws java.lang.Exception+
java.lang.Exception
void untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() + throws java.lang.Exception+
java.lang.Exception
void untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError() + throws java.lang.Exception+
java.lang.Exception
void untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() + throws java.lang.Exception+
java.lang.Exception
public final class TestException +extends java.lang.RuntimeException+
Subscriber.onError(Throwable)
.Constructor and Description | +
---|
TestException() |
+
addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, getSuppressed, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
public TestException()+
Interface | +Description | +
---|---|
Function<In,Out> | ++ |
PublisherVerificationRules | +
+ Internal TCK use only.
+ |
+
SubscriberBlackboxVerificationRules | +
+ Internal TCK use only.
+ |
+
SubscriberWhiteboxVerificationRules | +
+ Internal TCK use only.
+ |
+
Class | +Description | +
---|---|
HelperPublisher<T> | ++ |
InfiniteHelperPublisher<T> | ++ |
NonFatal | +
+ Copy of scala.control.util.NonFatal in order to not depend on scala-library
+ |
+
Optional<T> | ++ |
Optional.Some<T> | ++ |
Exception | +Description | +
---|---|
SubscriberBufferOverflowException | ++ |
TestException | +
+ Exception used by the TCK to signal failures.
+ |
+
+ + diff --git a/reactive-streams-tck-1.0.0-javadoc/overview-summary.html b/reactive-streams-tck-1.0.0-javadoc/overview-summary.html new file mode 100644 index 0000000..799b86f --- /dev/null +++ b/reactive-streams-tck-1.0.0-javadoc/overview-summary.html @@ -0,0 +1,140 @@ + + + + + + +
Package | +Description | +
---|---|
org.reactivestreams.tck | ++ |
org.reactivestreams.tck.support | ++ |
001package org.reactivestreams.tck; +002 +003import org.reactivestreams.Processor; +004import org.reactivestreams.Publisher; +005import org.reactivestreams.Subscriber; +006import org.reactivestreams.Subscription; +007import org.reactivestreams.tck.TestEnvironment.ManualPublisher; +008import org.reactivestreams.tck.TestEnvironment.ManualSubscriber; +009import org.reactivestreams.tck.TestEnvironment.ManualSubscriberWithSubscriptionSupport; +010import org.reactivestreams.tck.TestEnvironment.Promise; +011import org.reactivestreams.tck.support.Function; +012import org.reactivestreams.tck.support.SubscriberWhiteboxVerificationRules; +013import org.reactivestreams.tck.support.PublisherVerificationRules; +014import org.testng.annotations.BeforeMethod; +015import org.testng.annotations.Test; +016 +017import java.util.HashSet; +018import java.util.Set; +019 +020public abstract class IdentityProcessorVerification<T> extends WithHelperPublisher<T> +021 implements SubscriberWhiteboxVerificationRules, PublisherVerificationRules { +022 +023 private final TestEnvironment env; +024 +025 ////////////////////// DELEGATED TO SPECS ////////////////////// +026 +027 // for delegating tests +028 private final SubscriberWhiteboxVerification<T> subscriberVerification; +029 +030 // for delegating tests +031 private final PublisherVerification<T> publisherVerification; +032 +033 ////////////////// END OF DELEGATED TO SPECS ////////////////// +034 +035 // number of elements the processor under test must be able ot buffer, +036 // without dropping elements. Defaults to `TestEnvironment.TEST_BUFFER_SIZE`. +037 private final int processorBufferSize; +038 +039 /** +040 * Test class must specify the expected time it takes for the publisher to +041 * shut itself down when the the last downstream {@code Subscription} is cancelled. +042 * +043 * The processor will be required to be able to buffer {@code TestEnvironment.TEST_BUFFER_SIZE} elements. +044 */ +045 @SuppressWarnings("unused") +046 public IdentityProcessorVerification(final TestEnvironment env) { +047 this(env, PublisherVerification.envPublisherReferenceGCTimeoutMillis(), TestEnvironment.TEST_BUFFER_SIZE); +048 } +049 +050 /** +051 * Test class must specify the expected time it takes for the publisher to +052 * shut itself down when the the last downstream {@code Subscription} is cancelled. +053 * +054 * The processor will be required to be able to buffer {@code TestEnvironment.TEST_BUFFER_SIZE} elements. +055 * +056 * @param publisherReferenceGCTimeoutMillis used to determine after how much time a reference to a Subscriber should be already dropped by the Publisher. +057 */ +058 @SuppressWarnings("unused") +059 public IdentityProcessorVerification(final TestEnvironment env, long publisherReferenceGCTimeoutMillis) { +060 this(env, publisherReferenceGCTimeoutMillis, TestEnvironment.TEST_BUFFER_SIZE); +061 } +062 +063 /** +064 * Test class must specify the expected time it takes for the publisher to +065 * shut itself down when the the last downstream {@code Subscription} is cancelled. +066 * +067 * @param publisherReferenceGCTimeoutMillis used to determine after how much time a reference to a Subscriber should be already dropped by the Publisher. +068 * @param processorBufferSize number of elements the processor is required to be able to buffer. +069 */ +070 public IdentityProcessorVerification(final TestEnvironment env, long publisherReferenceGCTimeoutMillis, int processorBufferSize) { +071 this.env = env; +072 this.processorBufferSize = processorBufferSize; +073 +074 this.subscriberVerification = new SubscriberWhiteboxVerification<T>(env) { +075 @Override +076 public Subscriber<T> createSubscriber(WhiteboxSubscriberProbe<T> probe) { +077 return IdentityProcessorVerification.this.createSubscriber(probe); +078 } +079 +080 @Override public T createElement(int element) { +081 return IdentityProcessorVerification.this.createElement(element); +082 } +083 +084 @Override +085 public Publisher<T> createHelperPublisher(long elements) { +086 return IdentityProcessorVerification.this.createHelperPublisher(elements); +087 } +088 }; +089 +090 publisherVerification = new PublisherVerification<T>(env, publisherReferenceGCTimeoutMillis) { +091 @Override +092 public Publisher<T> createPublisher(long elements) { +093 return IdentityProcessorVerification.this.createPublisher(elements); +094 } +095 +096 @Override +097 public Publisher<T> createFailedPublisher() { +098 return IdentityProcessorVerification.this.createFailedPublisher(); +099 } +100 +101 @Override +102 public long maxElementsFromPublisher() { +103 return IdentityProcessorVerification.this.maxElementsFromPublisher(); +104 } +105 +106 @Override +107 public long boundedDepthOfOnNextAndRequestRecursion() { +108 return IdentityProcessorVerification.this.boundedDepthOfOnNextAndRequestRecursion(); +109 } +110 +111 @Override +112 public boolean skipStochasticTests() { +113 return IdentityProcessorVerification.this.skipStochasticTests(); +114 } +115 }; +116 } +117 +118 /** +119 * This is the main method you must implement in your test incarnation. +120 * It must create a Publisher, which simply forwards all stream elements from its upstream +121 * to its downstream. It must be able to internally buffer the given number of elements. +122 * +123 * @param bufferSize number of elements the processor is required to be able to buffer. +124 */ +125 public abstract Processor<T, T> createIdentityProcessor(int bufferSize); +126 +127 /** +128 * By implementing this method, additional TCK tests concerning a "failed" publishers will be run. +129 * +130 * The expected behaviour of the {@link Publisher} returned by this method is hand out a subscription, +131 * followed by signalling {@code onError} on it, as specified by Rule 1.9. +132 * +133 * If you ignore these additional tests, return {@code null} from this method. +134 */ +135 public abstract Publisher<T> createFailedPublisher(); +136 +137 /** +138 * Override and return lower value if your Publisher is only able to produce a known number of elements. +139 * For example, if it is designed to return at-most-one element, return {@code 1} from this method. +140 * +141 * Defaults to {@code Long.MAX_VALUE - 1}, meaning that the Publisher can be produce a huge but NOT an unbounded number of elements. +142 * +143 * To mark your Publisher will *never* signal an {@code onComplete} override this method and return {@code Long.MAX_VALUE}, +144 * which will result in *skipping all tests which require an onComplete to be triggered* (!). +145 */ +146 public long maxElementsFromPublisher() { +147 return Long.MAX_VALUE - 1; +148 } +149 +150 /** +151 * In order to verify rule 3.3 of the reactive streams spec, this number will be used to check if a +152 * {@code Subscription} actually solves the "unbounded recursion" problem by not allowing the number of +153 * recursive calls to exceed the number returned by this method. +154 * +155 * @see <a href="https://github.com/reactive-streams/reactive-streams-jvm#3.3">reactive streams spec, rule 3.3</a> +156 * @see PublisherVerification#required_spec303_mustNotAllowUnboundedRecursion() +157 */ +158 public long boundedDepthOfOnNextAndRequestRecursion() { +159 return 1; +160 } +161 +162 /** +163 * Override and return {@code true} in order to skip executing tests marked as {@code Stochastic}. +164 * Such tests MAY sometimes fail even though the impl +165 */ +166 public boolean skipStochasticTests() { +167 return false; +168 } +169 +170 /** +171 * Describes the tested implementation in terms of how many subscribers they can support. +172 * Some tests require the {@code Publisher} under test to support multiple Subscribers, +173 * yet the spec does not require all publishers to be able to do so, thus – if an implementation +174 * supports only a limited number of subscribers (e.g. only 1 subscriber, also known as "no fanout") +175 * you MUST return that number from this method by overriding it. +176 */ +177 public long maxSupportedSubscribers() { +178 return Long.MAX_VALUE; +179 } +180 +181 ////////////////////// TEST ENV CLEANUP ///////////////////////////////////// +182 +183 @BeforeMethod +184 public void setUp() throws Exception { +185 publisherVerification.setUp(); +186 subscriberVerification.setUp(); +187 } +188 +189 ////////////////////// PUBLISHER RULES VERIFICATION /////////////////////////// +190 +191 // A Processor +192 // must obey all Publisher rules on its publishing side +193 public Publisher<T> createPublisher(long elements) { +194 final Processor<T, T> processor = createIdentityProcessor(processorBufferSize); +195 final Publisher<T> pub = createHelperPublisher(elements); +196 pub.subscribe(processor); +197 return processor; // we run the PublisherVerification against this +198 } +199 +200 @Override @Test +201 public void required_validate_maxElementsFromPublisher() throws Exception { +202 publisherVerification.required_validate_maxElementsFromPublisher(); +203 } +204 +205 @Override @Test +206 public void required_validate_boundedDepthOfOnNextAndRequestRecursion() throws Exception { +207 publisherVerification.required_validate_boundedDepthOfOnNextAndRequestRecursion(); +208 } +209 +210 /////////////////////// DELEGATED TESTS, A PROCESSOR "IS A" PUBLISHER ////////////////////// +211 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#4.1 +212 +213 @Test +214 public void required_createPublisher1MustProduceAStreamOfExactly1Element() throws Throwable { +215 publisherVerification.required_createPublisher1MustProduceAStreamOfExactly1Element(); +216 } +217 +218 @Test +219 public void required_createPublisher3MustProduceAStreamOfExactly3Elements() throws Throwable { +220 publisherVerification.required_createPublisher3MustProduceAStreamOfExactly3Elements(); +221 } +222 +223 @Override @Test +224 public void required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements() throws Throwable { +225 publisherVerification.required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements(); +226 } +227 +228 @Override @Test +229 public void required_spec102_maySignalLessThanRequestedAndTerminateSubscription() throws Throwable { +230 publisherVerification.required_spec102_maySignalLessThanRequestedAndTerminateSubscription(); +231 } +232 +233 @Override @Test +234 public void stochastic_spec103_mustSignalOnMethodsSequentially() throws Throwable { +235 publisherVerification.stochastic_spec103_mustSignalOnMethodsSequentially(); +236 } +237 +238 @Override @Test +239 public void optional_spec104_mustSignalOnErrorWhenFails() throws Throwable { +240 publisherVerification.optional_spec104_mustSignalOnErrorWhenFails(); +241 } +242 +243 @Override @Test +244 public void required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates() throws Throwable { +245 publisherVerification.required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates(); +246 } +247 +248 @Override @Test +249 public void optional_spec105_emptyStreamMustTerminateBySignallingOnComplete() throws Throwable { +250 publisherVerification.optional_spec105_emptyStreamMustTerminateBySignallingOnComplete(); +251 } +252 +253 @Override @Test +254 public void untested_spec106_mustConsiderSubscriptionCancelledAfterOnErrorOrOnCompleteHasBeenCalled() throws Throwable { +255 publisherVerification.untested_spec106_mustConsiderSubscriptionCancelledAfterOnErrorOrOnCompleteHasBeenCalled(); +256 } +257 +258 @Override @Test +259 public void required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled() throws Throwable { +260 publisherVerification.required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled(); +261 } +262 +263 @Override @Test +264 public void untested_spec107_mustNotEmitFurtherSignalsOnceOnErrorHasBeenSignalled() throws Throwable { +265 publisherVerification.untested_spec107_mustNotEmitFurtherSignalsOnceOnErrorHasBeenSignalled(); +266 } +267 +268 @Override @Test +269 public void untested_spec108_possiblyCanceledSubscriptionShouldNotReceiveOnErrorOrOnCompleteSignals() throws Throwable { +270 publisherVerification.untested_spec108_possiblyCanceledSubscriptionShouldNotReceiveOnErrorOrOnCompleteSignals(); +271 } +272 +273 @Override @Test +274 public void untested_spec109_subscribeShouldNotThrowNonFatalThrowable() throws Throwable { +275 publisherVerification.untested_spec109_subscribeShouldNotThrowNonFatalThrowable(); +276 } +277 +278 @Override @Test +279 public void required_spec109_subscribeThrowNPEOnNullSubscriber() throws Throwable { +280 publisherVerification.required_spec109_subscribeThrowNPEOnNullSubscriber(); +281 } +282 +283 @Override @Test +284 public void required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe() throws Throwable { +285 publisherVerification.required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe(); +286 } +287 +288 @Override @Test +289 public void required_spec109_mustIssueOnSubscribeForNonNullSubscriber() throws Throwable { +290 publisherVerification.required_spec109_mustIssueOnSubscribeForNonNullSubscriber(); +291 } +292 +293 @Override @Test +294 public void untested_spec110_rejectASubscriptionRequestIfTheSameSubscriberSubscribesTwice() throws Throwable { +295 publisherVerification.untested_spec110_rejectASubscriptionRequestIfTheSameSubscriberSubscribesTwice(); +296 } +297 +298 @Override @Test +299 public void optional_spec111_maySupportMultiSubscribe() throws Throwable { +300 publisherVerification.optional_spec111_maySupportMultiSubscribe(); +301 } +302 +303 @Override @Test +304 public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingOneByOne() throws Throwable { +305 publisherVerification.optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingOneByOne(); +306 } +307 +308 @Override @Test +309 public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfront() throws Throwable { +310 publisherVerification.optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfront(); +311 } +312 +313 @Override @Test +314 public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfrontAndCompleteAsExpected() throws Throwable { +315 publisherVerification.optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfrontAndCompleteAsExpected(); +316 } +317 +318 @Override @Test +319 public void required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe() throws Throwable { +320 publisherVerification.required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe(); +321 } +322 +323 @Override @Test +324 public void required_spec303_mustNotAllowUnboundedRecursion() throws Throwable { +325 publisherVerification.required_spec303_mustNotAllowUnboundedRecursion(); +326 } +327 +328 @Override @Test +329 public void untested_spec304_requestShouldNotPerformHeavyComputations() throws Exception { +330 publisherVerification.untested_spec304_requestShouldNotPerformHeavyComputations(); +331 } +332 +333 @Override @Test +334 public void untested_spec305_cancelMustNotSynchronouslyPerformHeavyCompuatation() throws Exception { +335 publisherVerification.untested_spec305_cancelMustNotSynchronouslyPerformHeavyCompuatation(); +336 } +337 +338 @Override @Test +339 public void required_spec306_afterSubscriptionIsCancelledRequestMustBeNops() throws Throwable { +340 publisherVerification.required_spec306_afterSubscriptionIsCancelledRequestMustBeNops(); +341 } +342 +343 @Override @Test +344 public void required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops() throws Throwable { +345 publisherVerification.required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops(); +346 } +347 +348 @Override @Test +349 public void required_spec309_requestZeroMustSignalIllegalArgumentException() throws Throwable { +350 publisherVerification.required_spec309_requestZeroMustSignalIllegalArgumentException(); +351 } +352 +353 @Override @Test +354 public void required_spec309_requestNegativeNumberMustSignalIllegalArgumentException() throws Throwable { +355 publisherVerification.required_spec309_requestNegativeNumberMustSignalIllegalArgumentException(); +356 } +357 +358 @Override @Test +359 public void required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling() throws Throwable { +360 publisherVerification.required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling(); +361 } +362 +363 @Override @Test +364 public void required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber() throws Throwable { +365 publisherVerification.required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber(); +366 } +367 +368 @Override @Test +369 public void required_spec317_mustSupportAPendingElementCountUpToLongMaxValue() throws Throwable { +370 publisherVerification.required_spec317_mustSupportAPendingElementCountUpToLongMaxValue(); +371 } +372 +373 @Override @Test +374 public void required_spec317_mustSupportACumulativePendingElementCountUpToLongMaxValue() throws Throwable { +375 publisherVerification.required_spec317_mustSupportACumulativePendingElementCountUpToLongMaxValue(); +376 } +377 +378 @Override @Test +379 public void required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue() throws Throwable { +380 publisherVerification.required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue(); +381 } +382 +383 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.4 +384 // for multiple subscribers +385 @Test +386 public void required_spec104_mustCallOnErrorOnAllItsSubscribersIfItEncountersANonRecoverableError() throws Throwable { +387 optionalMultipleSubscribersTest(2, new Function<Long,TestSetup>() { +388 @Override +389 public TestSetup apply(Long aLong) throws Throwable { +390 return new TestSetup(env, processorBufferSize) {{ +391 final ManualSubscriberWithErrorCollection<T> sub1 = new ManualSubscriberWithErrorCollection<T>(env); +392 env.subscribe(processor, sub1); +393 +394 final ManualSubscriberWithErrorCollection<T> sub2 = new ManualSubscriberWithErrorCollection<T>(env); +395 env.subscribe(processor, sub2); +396 +397 sub1.request(1); +398 expectRequest(); +399 final T x = sendNextTFromUpstream(); +400 expectNextElement(sub1, x); +401 sub1.request(1); +402 +403 // sub1 has received one element, and has one demand pending +404 // sub2 has not yet requested anything +405 +406 final Exception ex = new RuntimeException("Test exception"); +407 sendError(ex); +408 sub1.expectError(ex); +409 sub2.expectError(ex); +410 +411 env.verifyNoAsyncErrorsNoDelay(); +412 }}; +413 } +414 }); +415 } +416 +417 ////////////////////// SUBSCRIBER RULES VERIFICATION /////////////////////////// +418 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#4.1 +419 +420 // A Processor +421 // must obey all Subscriber rules on its consuming side +422 public Subscriber<T> createSubscriber(final SubscriberWhiteboxVerification.WhiteboxSubscriberProbe<T> probe) { +423 final Processor<T, T> processor = createIdentityProcessor(processorBufferSize); +424 processor.subscribe( +425 new Subscriber<T>() { +426 private final Promise<Subscription> subs = new Promise<Subscription>(env); +427 +428 @Override +429 public void onSubscribe(final Subscription subscription) { +430 env.debug(String.format("whiteboxSubscriber::onSubscribe(%s)", subscription)); +431 if (subs.isCompleted()) subscription.cancel(); // the Probe must also pass subscriber verification +432 +433 probe.registerOnSubscribe(new SubscriberWhiteboxVerification.SubscriberPuppet() { +434 +435 @Override +436 public void triggerRequest(long elements) { +437 subscription.request(elements); +438 } +439 +440 @Override +441 public void signalCancel() { +442 subscription.cancel(); +443 } +444 }); +445 } +446 +447 @Override +448 public void onNext(T element) { +449 env.debug(String.format("whiteboxSubscriber::onNext(%s)", element)); +450 probe.registerOnNext(element); +451 } +452 +453 @Override +454 public void onComplete() { +455 env.debug("whiteboxSubscriber::onComplete()"); +456 probe.registerOnComplete(); +457 } +458 +459 @Override +460 public void onError(Throwable cause) { +461 env.debug(String.format("whiteboxSubscriber::onError(%s)", cause)); +462 probe.registerOnError(cause); +463 } +464 }); +465 +466 return processor; // we run the SubscriberVerification against this +467 } +468 +469 ////////////////////// OTHER RULE VERIFICATION /////////////////////////// +470 +471 // A Processor +472 // must immediately pass on `onError` events received from its upstream to its downstream +473 @Test +474 public void mustImmediatelyPassOnOnErrorEventsReceivedFromItsUpstreamToItsDownstream() throws Exception { +475 new TestSetup(env, processorBufferSize) {{ +476 final ManualSubscriberWithErrorCollection<T> sub = new ManualSubscriberWithErrorCollection<T>(env); +477 env.subscribe(processor, sub); +478 +479 final Exception ex = new RuntimeException("Test exception"); +480 sendError(ex); +481 sub.expectError(ex); // "immediately", i.e. without a preceding request +482 +483 env.verifyNoAsyncErrorsNoDelay(); +484 }}; +485 } +486 +487 /////////////////////// DELEGATED TESTS, A PROCESSOR "IS A" SUBSCRIBER ////////////////////// +488 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#4.1 +489 +490 @Test +491 public void required_exerciseWhiteboxHappyPath() throws Throwable { +492 subscriberVerification.required_exerciseWhiteboxHappyPath(); +493 } +494 +495 @Override @Test +496 public void required_spec201_mustSignalDemandViaSubscriptionRequest() throws Throwable { +497 subscriberVerification.required_spec201_mustSignalDemandViaSubscriptionRequest(); +498 } +499 +500 @Override @Test +501 public void untested_spec202_shouldAsynchronouslyDispatch() throws Exception { +502 subscriberVerification.untested_spec202_shouldAsynchronouslyDispatch(); +503 } +504 +505 @Override @Test +506 public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() throws Throwable { +507 subscriberVerification.required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete(); +508 } +509 +510 @Override @Test +511 public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() throws Throwable { +512 subscriberVerification.required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError(); +513 } +514 +515 @Override @Test +516 public void untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() throws Exception { +517 subscriberVerification.untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError(); +518 } +519 +520 @Override @Test +521 public void required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() throws Throwable { +522 subscriberVerification.required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal(); +523 } +524 +525 @Override @Test +526 public void untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid() throws Exception { +527 subscriberVerification.untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid(); +528 } +529 +530 @Override @Test +531 public void untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() throws Exception { +532 subscriberVerification.untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization(); +533 } +534 +535 @Override @Test +536 public void required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() throws Throwable { +537 subscriberVerification.required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel(); +538 } +539 +540 @Override @Test +541 public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() throws Throwable { +542 subscriberVerification.required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall(); +543 } +544 +545 @Override @Test +546 public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() throws Throwable { +547 subscriberVerification.required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall(); +548 } +549 +550 @Override @Test +551 public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() throws Throwable { +552 subscriberVerification.required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall(); +553 } +554 +555 @Override @Test +556 public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall() throws Throwable { +557 subscriberVerification.required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall(); +558 } +559 +560 @Override @Test +561 public void untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() throws Exception { +562 subscriberVerification.untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents(); +563 } +564 +565 @Override @Test +566 public void untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation() throws Throwable { +567 subscriberVerification.untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation(); +568 } +569 +570 @Override @Test +571 public void untested_spec213_failingOnSignalInvocation() throws Exception { +572 subscriberVerification.untested_spec213_failingOnSignalInvocation(); +573 } +574 +575 @Override @Test +576 public void required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +577 subscriberVerification.required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull(); +578 } +579 @Override @Test +580 public void required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +581 subscriberVerification.required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull(); +582 } +583 @Override @Test +584 public void required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +585 subscriberVerification.required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull(); +586 } +587 +588 @Override @Test +589 public void untested_spec301_mustNotBeCalledOutsideSubscriberContext() throws Exception { +590 subscriberVerification.untested_spec301_mustNotBeCalledOutsideSubscriberContext(); +591 } +592 +593 @Override @Test +594 public void required_spec308_requestMustRegisterGivenNumberElementsToBeProduced() throws Throwable { +595 subscriberVerification.required_spec308_requestMustRegisterGivenNumberElementsToBeProduced(); +596 } +597 +598 @Override @Test +599 public void untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber() throws Exception { +600 subscriberVerification.untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber(); +601 } +602 +603 @Override @Test +604 public void untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError() throws Exception { +605 subscriberVerification.untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError(); +606 } +607 +608 @Override @Test +609 public void untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() throws Exception { +610 subscriberVerification.untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists(); +611 } +612 +613 @Override @Test +614 public void untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError() throws Exception { +615 subscriberVerification.untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError(); +616 } +617 +618 @Override @Test +619 public void untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() throws Exception { +620 subscriberVerification.untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber(); +621 } +622 +623 /////////////////////// ADDITIONAL "COROLLARY" TESTS ////////////////////// +624 +625 // A Processor +626 // must trigger `requestFromUpstream` for elements that have been requested 'long ago' +627 @Test +628 public void required_mustRequestFromUpstreamForElementsThatHaveBeenRequestedLongAgo() throws Throwable { +629 optionalMultipleSubscribersTest(2, new Function<Long,TestSetup>() { +630 @Override +631 public TestSetup apply(Long subscribers) throws Throwable { +632 return new TestSetup(env, processorBufferSize) {{ +633 ManualSubscriber<T> sub1 = newSubscriber(); +634 sub1.request(20); +635 +636 long totalRequests = expectRequest(); +637 final T x = sendNextTFromUpstream(); +638 expectNextElement(sub1, x); +639 +640 if (totalRequests == 1) { +641 totalRequests += expectRequest(); +642 } +643 final T y = sendNextTFromUpstream(); +644 expectNextElement(sub1, y); +645 +646 if (totalRequests == 2) { +647 totalRequests += expectRequest(); +648 } +649 +650 final ManualSubscriber<T> sub2 = newSubscriber(); +651 +652 // sub1 now has 18 pending +653 // sub2 has 0 pending +654 +655 final T z = sendNextTFromUpstream(); +656 expectNextElement(sub1, z); +657 sub2.expectNone(); // since sub2 hasn't requested anything yet +658 +659 sub2.request(1); +660 expectNextElement(sub2, z); +661 +662 if (totalRequests == 3) { +663 expectRequest(); +664 } +665 +666 // to avoid error messages during test harness shutdown +667 sendCompletion(); +668 sub1.expectCompletion(env.defaultTimeoutMillis()); +669 sub2.expectCompletion(env.defaultTimeoutMillis()); +670 +671 env.verifyNoAsyncErrorsNoDelay(); +672 }}; +673 } +674 }); +675 } +676 +677 /////////////////////// TEST INFRASTRUCTURE ////////////////////// +678 +679 public void notVerified() { +680 publisherVerification.notVerified(); +681 } +682 +683 public void notVerified(String message) { +684 publisherVerification.notVerified(message); +685 } +686 +687 /** +688 * Test for feature that REQUIRES multiple subscribers to be supported by Publisher. +689 */ +690 public void optionalMultipleSubscribersTest(long requiredSubscribersSupport, Function<Long, TestSetup> body) throws Throwable { +691 if (requiredSubscribersSupport > maxSupportedSubscribers()) +692 notVerified(String.format("The Publisher under test only supports %d subscribers, while this test requires at least %d to run.", +693 maxSupportedSubscribers(), requiredSubscribersSupport)); +694 else body.apply(requiredSubscribersSupport); +695 } +696 +697 public abstract class TestSetup extends ManualPublisher<T> { +698 final private ManualSubscriber<T> tees; // gives us access to an infinite stream of T values +699 private Set<T> seenTees = new HashSet<T>(); +700 +701 final Processor<T, T> processor; +702 +703 public TestSetup(TestEnvironment env, int testBufferSize) throws InterruptedException { +704 super(env); +705 tees = env.newManualSubscriber(createHelperPublisher(Long.MAX_VALUE)); +706 processor = createIdentityProcessor(testBufferSize); +707 subscribe(processor); +708 } +709 +710 public ManualSubscriber<T> newSubscriber() throws InterruptedException { +711 return env.newManualSubscriber(processor); +712 } +713 +714 public T nextT() throws InterruptedException { +715 final T t = tees.requestNextElement(); +716 if (seenTees.contains(t)) { +717 env.flop(String.format("Helper publisher illegally produced the same element %s twice", t)); +718 } +719 seenTees.add(t); +720 return t; +721 } +722 +723 public void expectNextElement(ManualSubscriber<T> sub, T expected) throws InterruptedException { +724 final T elem = sub.nextElement(String.format("timeout while awaiting %s", expected)); +725 if (!elem.equals(expected)) { +726 env.flop(String.format("Received `onNext(%s)` on downstream but expected `onNext(%s)`", elem, expected)); +727 } +728 } +729 +730 public T sendNextTFromUpstream() throws InterruptedException { +731 final T x = nextT(); +732 sendNext(x); +733 return x; +734 } +735 } +736 +737 public class ManualSubscriberWithErrorCollection<A> extends ManualSubscriberWithSubscriptionSupport<A> { +738 Promise<Throwable> error; +739 +740 public ManualSubscriberWithErrorCollection(TestEnvironment env) { +741 super(env); +742 error = new Promise<Throwable>(env); +743 } +744 +745 @Override +746 public void onError(Throwable cause) { +747 error.complete(cause); +748 } +749 +750 public void expectError(Throwable cause) throws InterruptedException { +751 expectError(cause, env.defaultTimeoutMillis()); +752 } +753 +754 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +755 public void expectError(Throwable cause, long timeoutMillis) throws InterruptedException { +756 error.expectCompletion(timeoutMillis, "Did not receive expected error on downstream"); +757 if (!cause.equals(error.value())) { +758 env.flop(String.format("Expected error %s but got %s", cause, error.value())); +759 } +760 } +761 } +762} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck; +002 +003import org.reactivestreams.Processor; +004import org.reactivestreams.Publisher; +005import org.reactivestreams.Subscriber; +006import org.reactivestreams.Subscription; +007import org.reactivestreams.tck.TestEnvironment.ManualPublisher; +008import org.reactivestreams.tck.TestEnvironment.ManualSubscriber; +009import org.reactivestreams.tck.TestEnvironment.ManualSubscriberWithSubscriptionSupport; +010import org.reactivestreams.tck.TestEnvironment.Promise; +011import org.reactivestreams.tck.support.Function; +012import org.reactivestreams.tck.support.SubscriberWhiteboxVerificationRules; +013import org.reactivestreams.tck.support.PublisherVerificationRules; +014import org.testng.annotations.BeforeMethod; +015import org.testng.annotations.Test; +016 +017import java.util.HashSet; +018import java.util.Set; +019 +020public abstract class IdentityProcessorVerification<T> extends WithHelperPublisher<T> +021 implements SubscriberWhiteboxVerificationRules, PublisherVerificationRules { +022 +023 private final TestEnvironment env; +024 +025 ////////////////////// DELEGATED TO SPECS ////////////////////// +026 +027 // for delegating tests +028 private final SubscriberWhiteboxVerification<T> subscriberVerification; +029 +030 // for delegating tests +031 private final PublisherVerification<T> publisherVerification; +032 +033 ////////////////// END OF DELEGATED TO SPECS ////////////////// +034 +035 // number of elements the processor under test must be able ot buffer, +036 // without dropping elements. Defaults to `TestEnvironment.TEST_BUFFER_SIZE`. +037 private final int processorBufferSize; +038 +039 /** +040 * Test class must specify the expected time it takes for the publisher to +041 * shut itself down when the the last downstream {@code Subscription} is cancelled. +042 * +043 * The processor will be required to be able to buffer {@code TestEnvironment.TEST_BUFFER_SIZE} elements. +044 */ +045 @SuppressWarnings("unused") +046 public IdentityProcessorVerification(final TestEnvironment env) { +047 this(env, PublisherVerification.envPublisherReferenceGCTimeoutMillis(), TestEnvironment.TEST_BUFFER_SIZE); +048 } +049 +050 /** +051 * Test class must specify the expected time it takes for the publisher to +052 * shut itself down when the the last downstream {@code Subscription} is cancelled. +053 * +054 * The processor will be required to be able to buffer {@code TestEnvironment.TEST_BUFFER_SIZE} elements. +055 * +056 * @param publisherReferenceGCTimeoutMillis used to determine after how much time a reference to a Subscriber should be already dropped by the Publisher. +057 */ +058 @SuppressWarnings("unused") +059 public IdentityProcessorVerification(final TestEnvironment env, long publisherReferenceGCTimeoutMillis) { +060 this(env, publisherReferenceGCTimeoutMillis, TestEnvironment.TEST_BUFFER_SIZE); +061 } +062 +063 /** +064 * Test class must specify the expected time it takes for the publisher to +065 * shut itself down when the the last downstream {@code Subscription} is cancelled. +066 * +067 * @param publisherReferenceGCTimeoutMillis used to determine after how much time a reference to a Subscriber should be already dropped by the Publisher. +068 * @param processorBufferSize number of elements the processor is required to be able to buffer. +069 */ +070 public IdentityProcessorVerification(final TestEnvironment env, long publisherReferenceGCTimeoutMillis, int processorBufferSize) { +071 this.env = env; +072 this.processorBufferSize = processorBufferSize; +073 +074 this.subscriberVerification = new SubscriberWhiteboxVerification<T>(env) { +075 @Override +076 public Subscriber<T> createSubscriber(WhiteboxSubscriberProbe<T> probe) { +077 return IdentityProcessorVerification.this.createSubscriber(probe); +078 } +079 +080 @Override public T createElement(int element) { +081 return IdentityProcessorVerification.this.createElement(element); +082 } +083 +084 @Override +085 public Publisher<T> createHelperPublisher(long elements) { +086 return IdentityProcessorVerification.this.createHelperPublisher(elements); +087 } +088 }; +089 +090 publisherVerification = new PublisherVerification<T>(env, publisherReferenceGCTimeoutMillis) { +091 @Override +092 public Publisher<T> createPublisher(long elements) { +093 return IdentityProcessorVerification.this.createPublisher(elements); +094 } +095 +096 @Override +097 public Publisher<T> createFailedPublisher() { +098 return IdentityProcessorVerification.this.createFailedPublisher(); +099 } +100 +101 @Override +102 public long maxElementsFromPublisher() { +103 return IdentityProcessorVerification.this.maxElementsFromPublisher(); +104 } +105 +106 @Override +107 public long boundedDepthOfOnNextAndRequestRecursion() { +108 return IdentityProcessorVerification.this.boundedDepthOfOnNextAndRequestRecursion(); +109 } +110 +111 @Override +112 public boolean skipStochasticTests() { +113 return IdentityProcessorVerification.this.skipStochasticTests(); +114 } +115 }; +116 } +117 +118 /** +119 * This is the main method you must implement in your test incarnation. +120 * It must create a Publisher, which simply forwards all stream elements from its upstream +121 * to its downstream. It must be able to internally buffer the given number of elements. +122 * +123 * @param bufferSize number of elements the processor is required to be able to buffer. +124 */ +125 public abstract Processor<T, T> createIdentityProcessor(int bufferSize); +126 +127 /** +128 * By implementing this method, additional TCK tests concerning a "failed" publishers will be run. +129 * +130 * The expected behaviour of the {@link Publisher} returned by this method is hand out a subscription, +131 * followed by signalling {@code onError} on it, as specified by Rule 1.9. +132 * +133 * If you ignore these additional tests, return {@code null} from this method. +134 */ +135 public abstract Publisher<T> createFailedPublisher(); +136 +137 /** +138 * Override and return lower value if your Publisher is only able to produce a known number of elements. +139 * For example, if it is designed to return at-most-one element, return {@code 1} from this method. +140 * +141 * Defaults to {@code Long.MAX_VALUE - 1}, meaning that the Publisher can be produce a huge but NOT an unbounded number of elements. +142 * +143 * To mark your Publisher will *never* signal an {@code onComplete} override this method and return {@code Long.MAX_VALUE}, +144 * which will result in *skipping all tests which require an onComplete to be triggered* (!). +145 */ +146 public long maxElementsFromPublisher() { +147 return Long.MAX_VALUE - 1; +148 } +149 +150 /** +151 * In order to verify rule 3.3 of the reactive streams spec, this number will be used to check if a +152 * {@code Subscription} actually solves the "unbounded recursion" problem by not allowing the number of +153 * recursive calls to exceed the number returned by this method. +154 * +155 * @see <a href="https://github.com/reactive-streams/reactive-streams-jvm#3.3">reactive streams spec, rule 3.3</a> +156 * @see PublisherVerification#required_spec303_mustNotAllowUnboundedRecursion() +157 */ +158 public long boundedDepthOfOnNextAndRequestRecursion() { +159 return 1; +160 } +161 +162 /** +163 * Override and return {@code true} in order to skip executing tests marked as {@code Stochastic}. +164 * Such tests MAY sometimes fail even though the impl +165 */ +166 public boolean skipStochasticTests() { +167 return false; +168 } +169 +170 /** +171 * Describes the tested implementation in terms of how many subscribers they can support. +172 * Some tests require the {@code Publisher} under test to support multiple Subscribers, +173 * yet the spec does not require all publishers to be able to do so, thus – if an implementation +174 * supports only a limited number of subscribers (e.g. only 1 subscriber, also known as "no fanout") +175 * you MUST return that number from this method by overriding it. +176 */ +177 public long maxSupportedSubscribers() { +178 return Long.MAX_VALUE; +179 } +180 +181 ////////////////////// TEST ENV CLEANUP ///////////////////////////////////// +182 +183 @BeforeMethod +184 public void setUp() throws Exception { +185 publisherVerification.setUp(); +186 subscriberVerification.setUp(); +187 } +188 +189 ////////////////////// PUBLISHER RULES VERIFICATION /////////////////////////// +190 +191 // A Processor +192 // must obey all Publisher rules on its publishing side +193 public Publisher<T> createPublisher(long elements) { +194 final Processor<T, T> processor = createIdentityProcessor(processorBufferSize); +195 final Publisher<T> pub = createHelperPublisher(elements); +196 pub.subscribe(processor); +197 return processor; // we run the PublisherVerification against this +198 } +199 +200 @Override @Test +201 public void required_validate_maxElementsFromPublisher() throws Exception { +202 publisherVerification.required_validate_maxElementsFromPublisher(); +203 } +204 +205 @Override @Test +206 public void required_validate_boundedDepthOfOnNextAndRequestRecursion() throws Exception { +207 publisherVerification.required_validate_boundedDepthOfOnNextAndRequestRecursion(); +208 } +209 +210 /////////////////////// DELEGATED TESTS, A PROCESSOR "IS A" PUBLISHER ////////////////////// +211 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#4.1 +212 +213 @Test +214 public void required_createPublisher1MustProduceAStreamOfExactly1Element() throws Throwable { +215 publisherVerification.required_createPublisher1MustProduceAStreamOfExactly1Element(); +216 } +217 +218 @Test +219 public void required_createPublisher3MustProduceAStreamOfExactly3Elements() throws Throwable { +220 publisherVerification.required_createPublisher3MustProduceAStreamOfExactly3Elements(); +221 } +222 +223 @Override @Test +224 public void required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements() throws Throwable { +225 publisherVerification.required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements(); +226 } +227 +228 @Override @Test +229 public void required_spec102_maySignalLessThanRequestedAndTerminateSubscription() throws Throwable { +230 publisherVerification.required_spec102_maySignalLessThanRequestedAndTerminateSubscription(); +231 } +232 +233 @Override @Test +234 public void stochastic_spec103_mustSignalOnMethodsSequentially() throws Throwable { +235 publisherVerification.stochastic_spec103_mustSignalOnMethodsSequentially(); +236 } +237 +238 @Override @Test +239 public void optional_spec104_mustSignalOnErrorWhenFails() throws Throwable { +240 publisherVerification.optional_spec104_mustSignalOnErrorWhenFails(); +241 } +242 +243 @Override @Test +244 public void required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates() throws Throwable { +245 publisherVerification.required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates(); +246 } +247 +248 @Override @Test +249 public void optional_spec105_emptyStreamMustTerminateBySignallingOnComplete() throws Throwable { +250 publisherVerification.optional_spec105_emptyStreamMustTerminateBySignallingOnComplete(); +251 } +252 +253 @Override @Test +254 public void untested_spec106_mustConsiderSubscriptionCancelledAfterOnErrorOrOnCompleteHasBeenCalled() throws Throwable { +255 publisherVerification.untested_spec106_mustConsiderSubscriptionCancelledAfterOnErrorOrOnCompleteHasBeenCalled(); +256 } +257 +258 @Override @Test +259 public void required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled() throws Throwable { +260 publisherVerification.required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled(); +261 } +262 +263 @Override @Test +264 public void untested_spec107_mustNotEmitFurtherSignalsOnceOnErrorHasBeenSignalled() throws Throwable { +265 publisherVerification.untested_spec107_mustNotEmitFurtherSignalsOnceOnErrorHasBeenSignalled(); +266 } +267 +268 @Override @Test +269 public void untested_spec108_possiblyCanceledSubscriptionShouldNotReceiveOnErrorOrOnCompleteSignals() throws Throwable { +270 publisherVerification.untested_spec108_possiblyCanceledSubscriptionShouldNotReceiveOnErrorOrOnCompleteSignals(); +271 } +272 +273 @Override @Test +274 public void untested_spec109_subscribeShouldNotThrowNonFatalThrowable() throws Throwable { +275 publisherVerification.untested_spec109_subscribeShouldNotThrowNonFatalThrowable(); +276 } +277 +278 @Override @Test +279 public void required_spec109_subscribeThrowNPEOnNullSubscriber() throws Throwable { +280 publisherVerification.required_spec109_subscribeThrowNPEOnNullSubscriber(); +281 } +282 +283 @Override @Test +284 public void required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe() throws Throwable { +285 publisherVerification.required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe(); +286 } +287 +288 @Override @Test +289 public void required_spec109_mustIssueOnSubscribeForNonNullSubscriber() throws Throwable { +290 publisherVerification.required_spec109_mustIssueOnSubscribeForNonNullSubscriber(); +291 } +292 +293 @Override @Test +294 public void untested_spec110_rejectASubscriptionRequestIfTheSameSubscriberSubscribesTwice() throws Throwable { +295 publisherVerification.untested_spec110_rejectASubscriptionRequestIfTheSameSubscriberSubscribesTwice(); +296 } +297 +298 @Override @Test +299 public void optional_spec111_maySupportMultiSubscribe() throws Throwable { +300 publisherVerification.optional_spec111_maySupportMultiSubscribe(); +301 } +302 +303 @Override @Test +304 public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingOneByOne() throws Throwable { +305 publisherVerification.optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingOneByOne(); +306 } +307 +308 @Override @Test +309 public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfront() throws Throwable { +310 publisherVerification.optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfront(); +311 } +312 +313 @Override @Test +314 public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfrontAndCompleteAsExpected() throws Throwable { +315 publisherVerification.optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfrontAndCompleteAsExpected(); +316 } +317 +318 @Override @Test +319 public void required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe() throws Throwable { +320 publisherVerification.required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe(); +321 } +322 +323 @Override @Test +324 public void required_spec303_mustNotAllowUnboundedRecursion() throws Throwable { +325 publisherVerification.required_spec303_mustNotAllowUnboundedRecursion(); +326 } +327 +328 @Override @Test +329 public void untested_spec304_requestShouldNotPerformHeavyComputations() throws Exception { +330 publisherVerification.untested_spec304_requestShouldNotPerformHeavyComputations(); +331 } +332 +333 @Override @Test +334 public void untested_spec305_cancelMustNotSynchronouslyPerformHeavyCompuatation() throws Exception { +335 publisherVerification.untested_spec305_cancelMustNotSynchronouslyPerformHeavyCompuatation(); +336 } +337 +338 @Override @Test +339 public void required_spec306_afterSubscriptionIsCancelledRequestMustBeNops() throws Throwable { +340 publisherVerification.required_spec306_afterSubscriptionIsCancelledRequestMustBeNops(); +341 } +342 +343 @Override @Test +344 public void required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops() throws Throwable { +345 publisherVerification.required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops(); +346 } +347 +348 @Override @Test +349 public void required_spec309_requestZeroMustSignalIllegalArgumentException() throws Throwable { +350 publisherVerification.required_spec309_requestZeroMustSignalIllegalArgumentException(); +351 } +352 +353 @Override @Test +354 public void required_spec309_requestNegativeNumberMustSignalIllegalArgumentException() throws Throwable { +355 publisherVerification.required_spec309_requestNegativeNumberMustSignalIllegalArgumentException(); +356 } +357 +358 @Override @Test +359 public void required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling() throws Throwable { +360 publisherVerification.required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling(); +361 } +362 +363 @Override @Test +364 public void required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber() throws Throwable { +365 publisherVerification.required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber(); +366 } +367 +368 @Override @Test +369 public void required_spec317_mustSupportAPendingElementCountUpToLongMaxValue() throws Throwable { +370 publisherVerification.required_spec317_mustSupportAPendingElementCountUpToLongMaxValue(); +371 } +372 +373 @Override @Test +374 public void required_spec317_mustSupportACumulativePendingElementCountUpToLongMaxValue() throws Throwable { +375 publisherVerification.required_spec317_mustSupportACumulativePendingElementCountUpToLongMaxValue(); +376 } +377 +378 @Override @Test +379 public void required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue() throws Throwable { +380 publisherVerification.required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue(); +381 } +382 +383 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.4 +384 // for multiple subscribers +385 @Test +386 public void required_spec104_mustCallOnErrorOnAllItsSubscribersIfItEncountersANonRecoverableError() throws Throwable { +387 optionalMultipleSubscribersTest(2, new Function<Long,TestSetup>() { +388 @Override +389 public TestSetup apply(Long aLong) throws Throwable { +390 return new TestSetup(env, processorBufferSize) {{ +391 final ManualSubscriberWithErrorCollection<T> sub1 = new ManualSubscriberWithErrorCollection<T>(env); +392 env.subscribe(processor, sub1); +393 +394 final ManualSubscriberWithErrorCollection<T> sub2 = new ManualSubscriberWithErrorCollection<T>(env); +395 env.subscribe(processor, sub2); +396 +397 sub1.request(1); +398 expectRequest(); +399 final T x = sendNextTFromUpstream(); +400 expectNextElement(sub1, x); +401 sub1.request(1); +402 +403 // sub1 has received one element, and has one demand pending +404 // sub2 has not yet requested anything +405 +406 final Exception ex = new RuntimeException("Test exception"); +407 sendError(ex); +408 sub1.expectError(ex); +409 sub2.expectError(ex); +410 +411 env.verifyNoAsyncErrorsNoDelay(); +412 }}; +413 } +414 }); +415 } +416 +417 ////////////////////// SUBSCRIBER RULES VERIFICATION /////////////////////////// +418 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#4.1 +419 +420 // A Processor +421 // must obey all Subscriber rules on its consuming side +422 public Subscriber<T> createSubscriber(final SubscriberWhiteboxVerification.WhiteboxSubscriberProbe<T> probe) { +423 final Processor<T, T> processor = createIdentityProcessor(processorBufferSize); +424 processor.subscribe( +425 new Subscriber<T>() { +426 private final Promise<Subscription> subs = new Promise<Subscription>(env); +427 +428 @Override +429 public void onSubscribe(final Subscription subscription) { +430 env.debug(String.format("whiteboxSubscriber::onSubscribe(%s)", subscription)); +431 if (subs.isCompleted()) subscription.cancel(); // the Probe must also pass subscriber verification +432 +433 probe.registerOnSubscribe(new SubscriberWhiteboxVerification.SubscriberPuppet() { +434 +435 @Override +436 public void triggerRequest(long elements) { +437 subscription.request(elements); +438 } +439 +440 @Override +441 public void signalCancel() { +442 subscription.cancel(); +443 } +444 }); +445 } +446 +447 @Override +448 public void onNext(T element) { +449 env.debug(String.format("whiteboxSubscriber::onNext(%s)", element)); +450 probe.registerOnNext(element); +451 } +452 +453 @Override +454 public void onComplete() { +455 env.debug("whiteboxSubscriber::onComplete()"); +456 probe.registerOnComplete(); +457 } +458 +459 @Override +460 public void onError(Throwable cause) { +461 env.debug(String.format("whiteboxSubscriber::onError(%s)", cause)); +462 probe.registerOnError(cause); +463 } +464 }); +465 +466 return processor; // we run the SubscriberVerification against this +467 } +468 +469 ////////////////////// OTHER RULE VERIFICATION /////////////////////////// +470 +471 // A Processor +472 // must immediately pass on `onError` events received from its upstream to its downstream +473 @Test +474 public void mustImmediatelyPassOnOnErrorEventsReceivedFromItsUpstreamToItsDownstream() throws Exception { +475 new TestSetup(env, processorBufferSize) {{ +476 final ManualSubscriberWithErrorCollection<T> sub = new ManualSubscriberWithErrorCollection<T>(env); +477 env.subscribe(processor, sub); +478 +479 final Exception ex = new RuntimeException("Test exception"); +480 sendError(ex); +481 sub.expectError(ex); // "immediately", i.e. without a preceding request +482 +483 env.verifyNoAsyncErrorsNoDelay(); +484 }}; +485 } +486 +487 /////////////////////// DELEGATED TESTS, A PROCESSOR "IS A" SUBSCRIBER ////////////////////// +488 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#4.1 +489 +490 @Test +491 public void required_exerciseWhiteboxHappyPath() throws Throwable { +492 subscriberVerification.required_exerciseWhiteboxHappyPath(); +493 } +494 +495 @Override @Test +496 public void required_spec201_mustSignalDemandViaSubscriptionRequest() throws Throwable { +497 subscriberVerification.required_spec201_mustSignalDemandViaSubscriptionRequest(); +498 } +499 +500 @Override @Test +501 public void untested_spec202_shouldAsynchronouslyDispatch() throws Exception { +502 subscriberVerification.untested_spec202_shouldAsynchronouslyDispatch(); +503 } +504 +505 @Override @Test +506 public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() throws Throwable { +507 subscriberVerification.required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete(); +508 } +509 +510 @Override @Test +511 public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() throws Throwable { +512 subscriberVerification.required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError(); +513 } +514 +515 @Override @Test +516 public void untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() throws Exception { +517 subscriberVerification.untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError(); +518 } +519 +520 @Override @Test +521 public void required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() throws Throwable { +522 subscriberVerification.required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal(); +523 } +524 +525 @Override @Test +526 public void untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid() throws Exception { +527 subscriberVerification.untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid(); +528 } +529 +530 @Override @Test +531 public void untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() throws Exception { +532 subscriberVerification.untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization(); +533 } +534 +535 @Override @Test +536 public void required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() throws Throwable { +537 subscriberVerification.required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel(); +538 } +539 +540 @Override @Test +541 public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() throws Throwable { +542 subscriberVerification.required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall(); +543 } +544 +545 @Override @Test +546 public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() throws Throwable { +547 subscriberVerification.required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall(); +548 } +549 +550 @Override @Test +551 public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() throws Throwable { +552 subscriberVerification.required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall(); +553 } +554 +555 @Override @Test +556 public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall() throws Throwable { +557 subscriberVerification.required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall(); +558 } +559 +560 @Override @Test +561 public void untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() throws Exception { +562 subscriberVerification.untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents(); +563 } +564 +565 @Override @Test +566 public void untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation() throws Throwable { +567 subscriberVerification.untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation(); +568 } +569 +570 @Override @Test +571 public void untested_spec213_failingOnSignalInvocation() throws Exception { +572 subscriberVerification.untested_spec213_failingOnSignalInvocation(); +573 } +574 +575 @Override @Test +576 public void required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +577 subscriberVerification.required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull(); +578 } +579 @Override @Test +580 public void required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +581 subscriberVerification.required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull(); +582 } +583 @Override @Test +584 public void required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +585 subscriberVerification.required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull(); +586 } +587 +588 @Override @Test +589 public void untested_spec301_mustNotBeCalledOutsideSubscriberContext() throws Exception { +590 subscriberVerification.untested_spec301_mustNotBeCalledOutsideSubscriberContext(); +591 } +592 +593 @Override @Test +594 public void required_spec308_requestMustRegisterGivenNumberElementsToBeProduced() throws Throwable { +595 subscriberVerification.required_spec308_requestMustRegisterGivenNumberElementsToBeProduced(); +596 } +597 +598 @Override @Test +599 public void untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber() throws Exception { +600 subscriberVerification.untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber(); +601 } +602 +603 @Override @Test +604 public void untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError() throws Exception { +605 subscriberVerification.untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError(); +606 } +607 +608 @Override @Test +609 public void untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() throws Exception { +610 subscriberVerification.untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists(); +611 } +612 +613 @Override @Test +614 public void untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError() throws Exception { +615 subscriberVerification.untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError(); +616 } +617 +618 @Override @Test +619 public void untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() throws Exception { +620 subscriberVerification.untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber(); +621 } +622 +623 /////////////////////// ADDITIONAL "COROLLARY" TESTS ////////////////////// +624 +625 // A Processor +626 // must trigger `requestFromUpstream` for elements that have been requested 'long ago' +627 @Test +628 public void required_mustRequestFromUpstreamForElementsThatHaveBeenRequestedLongAgo() throws Throwable { +629 optionalMultipleSubscribersTest(2, new Function<Long,TestSetup>() { +630 @Override +631 public TestSetup apply(Long subscribers) throws Throwable { +632 return new TestSetup(env, processorBufferSize) {{ +633 ManualSubscriber<T> sub1 = newSubscriber(); +634 sub1.request(20); +635 +636 long totalRequests = expectRequest(); +637 final T x = sendNextTFromUpstream(); +638 expectNextElement(sub1, x); +639 +640 if (totalRequests == 1) { +641 totalRequests += expectRequest(); +642 } +643 final T y = sendNextTFromUpstream(); +644 expectNextElement(sub1, y); +645 +646 if (totalRequests == 2) { +647 totalRequests += expectRequest(); +648 } +649 +650 final ManualSubscriber<T> sub2 = newSubscriber(); +651 +652 // sub1 now has 18 pending +653 // sub2 has 0 pending +654 +655 final T z = sendNextTFromUpstream(); +656 expectNextElement(sub1, z); +657 sub2.expectNone(); // since sub2 hasn't requested anything yet +658 +659 sub2.request(1); +660 expectNextElement(sub2, z); +661 +662 if (totalRequests == 3) { +663 expectRequest(); +664 } +665 +666 // to avoid error messages during test harness shutdown +667 sendCompletion(); +668 sub1.expectCompletion(env.defaultTimeoutMillis()); +669 sub2.expectCompletion(env.defaultTimeoutMillis()); +670 +671 env.verifyNoAsyncErrorsNoDelay(); +672 }}; +673 } +674 }); +675 } +676 +677 /////////////////////// TEST INFRASTRUCTURE ////////////////////// +678 +679 public void notVerified() { +680 publisherVerification.notVerified(); +681 } +682 +683 public void notVerified(String message) { +684 publisherVerification.notVerified(message); +685 } +686 +687 /** +688 * Test for feature that REQUIRES multiple subscribers to be supported by Publisher. +689 */ +690 public void optionalMultipleSubscribersTest(long requiredSubscribersSupport, Function<Long, TestSetup> body) throws Throwable { +691 if (requiredSubscribersSupport > maxSupportedSubscribers()) +692 notVerified(String.format("The Publisher under test only supports %d subscribers, while this test requires at least %d to run.", +693 maxSupportedSubscribers(), requiredSubscribersSupport)); +694 else body.apply(requiredSubscribersSupport); +695 } +696 +697 public abstract class TestSetup extends ManualPublisher<T> { +698 final private ManualSubscriber<T> tees; // gives us access to an infinite stream of T values +699 private Set<T> seenTees = new HashSet<T>(); +700 +701 final Processor<T, T> processor; +702 +703 public TestSetup(TestEnvironment env, int testBufferSize) throws InterruptedException { +704 super(env); +705 tees = env.newManualSubscriber(createHelperPublisher(Long.MAX_VALUE)); +706 processor = createIdentityProcessor(testBufferSize); +707 subscribe(processor); +708 } +709 +710 public ManualSubscriber<T> newSubscriber() throws InterruptedException { +711 return env.newManualSubscriber(processor); +712 } +713 +714 public T nextT() throws InterruptedException { +715 final T t = tees.requestNextElement(); +716 if (seenTees.contains(t)) { +717 env.flop(String.format("Helper publisher illegally produced the same element %s twice", t)); +718 } +719 seenTees.add(t); +720 return t; +721 } +722 +723 public void expectNextElement(ManualSubscriber<T> sub, T expected) throws InterruptedException { +724 final T elem = sub.nextElement(String.format("timeout while awaiting %s", expected)); +725 if (!elem.equals(expected)) { +726 env.flop(String.format("Received `onNext(%s)` on downstream but expected `onNext(%s)`", elem, expected)); +727 } +728 } +729 +730 public T sendNextTFromUpstream() throws InterruptedException { +731 final T x = nextT(); +732 sendNext(x); +733 return x; +734 } +735 } +736 +737 public class ManualSubscriberWithErrorCollection<A> extends ManualSubscriberWithSubscriptionSupport<A> { +738 Promise<Throwable> error; +739 +740 public ManualSubscriberWithErrorCollection(TestEnvironment env) { +741 super(env); +742 error = new Promise<Throwable>(env); +743 } +744 +745 @Override +746 public void onError(Throwable cause) { +747 error.complete(cause); +748 } +749 +750 public void expectError(Throwable cause) throws InterruptedException { +751 expectError(cause, env.defaultTimeoutMillis()); +752 } +753 +754 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +755 public void expectError(Throwable cause, long timeoutMillis) throws InterruptedException { +756 error.expectCompletion(timeoutMillis, "Did not receive expected error on downstream"); +757 if (!cause.equals(error.value())) { +758 env.flop(String.format("Expected error %s but got %s", cause, error.value())); +759 } +760 } +761 } +762} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck; +002 +003import org.reactivestreams.Processor; +004import org.reactivestreams.Publisher; +005import org.reactivestreams.Subscriber; +006import org.reactivestreams.Subscription; +007import org.reactivestreams.tck.TestEnvironment.ManualPublisher; +008import org.reactivestreams.tck.TestEnvironment.ManualSubscriber; +009import org.reactivestreams.tck.TestEnvironment.ManualSubscriberWithSubscriptionSupport; +010import org.reactivestreams.tck.TestEnvironment.Promise; +011import org.reactivestreams.tck.support.Function; +012import org.reactivestreams.tck.support.SubscriberWhiteboxVerificationRules; +013import org.reactivestreams.tck.support.PublisherVerificationRules; +014import org.testng.annotations.BeforeMethod; +015import org.testng.annotations.Test; +016 +017import java.util.HashSet; +018import java.util.Set; +019 +020public abstract class IdentityProcessorVerification<T> extends WithHelperPublisher<T> +021 implements SubscriberWhiteboxVerificationRules, PublisherVerificationRules { +022 +023 private final TestEnvironment env; +024 +025 ////////////////////// DELEGATED TO SPECS ////////////////////// +026 +027 // for delegating tests +028 private final SubscriberWhiteboxVerification<T> subscriberVerification; +029 +030 // for delegating tests +031 private final PublisherVerification<T> publisherVerification; +032 +033 ////////////////// END OF DELEGATED TO SPECS ////////////////// +034 +035 // number of elements the processor under test must be able ot buffer, +036 // without dropping elements. Defaults to `TestEnvironment.TEST_BUFFER_SIZE`. +037 private final int processorBufferSize; +038 +039 /** +040 * Test class must specify the expected time it takes for the publisher to +041 * shut itself down when the the last downstream {@code Subscription} is cancelled. +042 * +043 * The processor will be required to be able to buffer {@code TestEnvironment.TEST_BUFFER_SIZE} elements. +044 */ +045 @SuppressWarnings("unused") +046 public IdentityProcessorVerification(final TestEnvironment env) { +047 this(env, PublisherVerification.envPublisherReferenceGCTimeoutMillis(), TestEnvironment.TEST_BUFFER_SIZE); +048 } +049 +050 /** +051 * Test class must specify the expected time it takes for the publisher to +052 * shut itself down when the the last downstream {@code Subscription} is cancelled. +053 * +054 * The processor will be required to be able to buffer {@code TestEnvironment.TEST_BUFFER_SIZE} elements. +055 * +056 * @param publisherReferenceGCTimeoutMillis used to determine after how much time a reference to a Subscriber should be already dropped by the Publisher. +057 */ +058 @SuppressWarnings("unused") +059 public IdentityProcessorVerification(final TestEnvironment env, long publisherReferenceGCTimeoutMillis) { +060 this(env, publisherReferenceGCTimeoutMillis, TestEnvironment.TEST_BUFFER_SIZE); +061 } +062 +063 /** +064 * Test class must specify the expected time it takes for the publisher to +065 * shut itself down when the the last downstream {@code Subscription} is cancelled. +066 * +067 * @param publisherReferenceGCTimeoutMillis used to determine after how much time a reference to a Subscriber should be already dropped by the Publisher. +068 * @param processorBufferSize number of elements the processor is required to be able to buffer. +069 */ +070 public IdentityProcessorVerification(final TestEnvironment env, long publisherReferenceGCTimeoutMillis, int processorBufferSize) { +071 this.env = env; +072 this.processorBufferSize = processorBufferSize; +073 +074 this.subscriberVerification = new SubscriberWhiteboxVerification<T>(env) { +075 @Override +076 public Subscriber<T> createSubscriber(WhiteboxSubscriberProbe<T> probe) { +077 return IdentityProcessorVerification.this.createSubscriber(probe); +078 } +079 +080 @Override public T createElement(int element) { +081 return IdentityProcessorVerification.this.createElement(element); +082 } +083 +084 @Override +085 public Publisher<T> createHelperPublisher(long elements) { +086 return IdentityProcessorVerification.this.createHelperPublisher(elements); +087 } +088 }; +089 +090 publisherVerification = new PublisherVerification<T>(env, publisherReferenceGCTimeoutMillis) { +091 @Override +092 public Publisher<T> createPublisher(long elements) { +093 return IdentityProcessorVerification.this.createPublisher(elements); +094 } +095 +096 @Override +097 public Publisher<T> createFailedPublisher() { +098 return IdentityProcessorVerification.this.createFailedPublisher(); +099 } +100 +101 @Override +102 public long maxElementsFromPublisher() { +103 return IdentityProcessorVerification.this.maxElementsFromPublisher(); +104 } +105 +106 @Override +107 public long boundedDepthOfOnNextAndRequestRecursion() { +108 return IdentityProcessorVerification.this.boundedDepthOfOnNextAndRequestRecursion(); +109 } +110 +111 @Override +112 public boolean skipStochasticTests() { +113 return IdentityProcessorVerification.this.skipStochasticTests(); +114 } +115 }; +116 } +117 +118 /** +119 * This is the main method you must implement in your test incarnation. +120 * It must create a Publisher, which simply forwards all stream elements from its upstream +121 * to its downstream. It must be able to internally buffer the given number of elements. +122 * +123 * @param bufferSize number of elements the processor is required to be able to buffer. +124 */ +125 public abstract Processor<T, T> createIdentityProcessor(int bufferSize); +126 +127 /** +128 * By implementing this method, additional TCK tests concerning a "failed" publishers will be run. +129 * +130 * The expected behaviour of the {@link Publisher} returned by this method is hand out a subscription, +131 * followed by signalling {@code onError} on it, as specified by Rule 1.9. +132 * +133 * If you ignore these additional tests, return {@code null} from this method. +134 */ +135 public abstract Publisher<T> createFailedPublisher(); +136 +137 /** +138 * Override and return lower value if your Publisher is only able to produce a known number of elements. +139 * For example, if it is designed to return at-most-one element, return {@code 1} from this method. +140 * +141 * Defaults to {@code Long.MAX_VALUE - 1}, meaning that the Publisher can be produce a huge but NOT an unbounded number of elements. +142 * +143 * To mark your Publisher will *never* signal an {@code onComplete} override this method and return {@code Long.MAX_VALUE}, +144 * which will result in *skipping all tests which require an onComplete to be triggered* (!). +145 */ +146 public long maxElementsFromPublisher() { +147 return Long.MAX_VALUE - 1; +148 } +149 +150 /** +151 * In order to verify rule 3.3 of the reactive streams spec, this number will be used to check if a +152 * {@code Subscription} actually solves the "unbounded recursion" problem by not allowing the number of +153 * recursive calls to exceed the number returned by this method. +154 * +155 * @see <a href="https://github.com/reactive-streams/reactive-streams-jvm#3.3">reactive streams spec, rule 3.3</a> +156 * @see PublisherVerification#required_spec303_mustNotAllowUnboundedRecursion() +157 */ +158 public long boundedDepthOfOnNextAndRequestRecursion() { +159 return 1; +160 } +161 +162 /** +163 * Override and return {@code true} in order to skip executing tests marked as {@code Stochastic}. +164 * Such tests MAY sometimes fail even though the impl +165 */ +166 public boolean skipStochasticTests() { +167 return false; +168 } +169 +170 /** +171 * Describes the tested implementation in terms of how many subscribers they can support. +172 * Some tests require the {@code Publisher} under test to support multiple Subscribers, +173 * yet the spec does not require all publishers to be able to do so, thus – if an implementation +174 * supports only a limited number of subscribers (e.g. only 1 subscriber, also known as "no fanout") +175 * you MUST return that number from this method by overriding it. +176 */ +177 public long maxSupportedSubscribers() { +178 return Long.MAX_VALUE; +179 } +180 +181 ////////////////////// TEST ENV CLEANUP ///////////////////////////////////// +182 +183 @BeforeMethod +184 public void setUp() throws Exception { +185 publisherVerification.setUp(); +186 subscriberVerification.setUp(); +187 } +188 +189 ////////////////////// PUBLISHER RULES VERIFICATION /////////////////////////// +190 +191 // A Processor +192 // must obey all Publisher rules on its publishing side +193 public Publisher<T> createPublisher(long elements) { +194 final Processor<T, T> processor = createIdentityProcessor(processorBufferSize); +195 final Publisher<T> pub = createHelperPublisher(elements); +196 pub.subscribe(processor); +197 return processor; // we run the PublisherVerification against this +198 } +199 +200 @Override @Test +201 public void required_validate_maxElementsFromPublisher() throws Exception { +202 publisherVerification.required_validate_maxElementsFromPublisher(); +203 } +204 +205 @Override @Test +206 public void required_validate_boundedDepthOfOnNextAndRequestRecursion() throws Exception { +207 publisherVerification.required_validate_boundedDepthOfOnNextAndRequestRecursion(); +208 } +209 +210 /////////////////////// DELEGATED TESTS, A PROCESSOR "IS A" PUBLISHER ////////////////////// +211 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#4.1 +212 +213 @Test +214 public void required_createPublisher1MustProduceAStreamOfExactly1Element() throws Throwable { +215 publisherVerification.required_createPublisher1MustProduceAStreamOfExactly1Element(); +216 } +217 +218 @Test +219 public void required_createPublisher3MustProduceAStreamOfExactly3Elements() throws Throwable { +220 publisherVerification.required_createPublisher3MustProduceAStreamOfExactly3Elements(); +221 } +222 +223 @Override @Test +224 public void required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements() throws Throwable { +225 publisherVerification.required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements(); +226 } +227 +228 @Override @Test +229 public void required_spec102_maySignalLessThanRequestedAndTerminateSubscription() throws Throwable { +230 publisherVerification.required_spec102_maySignalLessThanRequestedAndTerminateSubscription(); +231 } +232 +233 @Override @Test +234 public void stochastic_spec103_mustSignalOnMethodsSequentially() throws Throwable { +235 publisherVerification.stochastic_spec103_mustSignalOnMethodsSequentially(); +236 } +237 +238 @Override @Test +239 public void optional_spec104_mustSignalOnErrorWhenFails() throws Throwable { +240 publisherVerification.optional_spec104_mustSignalOnErrorWhenFails(); +241 } +242 +243 @Override @Test +244 public void required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates() throws Throwable { +245 publisherVerification.required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates(); +246 } +247 +248 @Override @Test +249 public void optional_spec105_emptyStreamMustTerminateBySignallingOnComplete() throws Throwable { +250 publisherVerification.optional_spec105_emptyStreamMustTerminateBySignallingOnComplete(); +251 } +252 +253 @Override @Test +254 public void untested_spec106_mustConsiderSubscriptionCancelledAfterOnErrorOrOnCompleteHasBeenCalled() throws Throwable { +255 publisherVerification.untested_spec106_mustConsiderSubscriptionCancelledAfterOnErrorOrOnCompleteHasBeenCalled(); +256 } +257 +258 @Override @Test +259 public void required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled() throws Throwable { +260 publisherVerification.required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled(); +261 } +262 +263 @Override @Test +264 public void untested_spec107_mustNotEmitFurtherSignalsOnceOnErrorHasBeenSignalled() throws Throwable { +265 publisherVerification.untested_spec107_mustNotEmitFurtherSignalsOnceOnErrorHasBeenSignalled(); +266 } +267 +268 @Override @Test +269 public void untested_spec108_possiblyCanceledSubscriptionShouldNotReceiveOnErrorOrOnCompleteSignals() throws Throwable { +270 publisherVerification.untested_spec108_possiblyCanceledSubscriptionShouldNotReceiveOnErrorOrOnCompleteSignals(); +271 } +272 +273 @Override @Test +274 public void untested_spec109_subscribeShouldNotThrowNonFatalThrowable() throws Throwable { +275 publisherVerification.untested_spec109_subscribeShouldNotThrowNonFatalThrowable(); +276 } +277 +278 @Override @Test +279 public void required_spec109_subscribeThrowNPEOnNullSubscriber() throws Throwable { +280 publisherVerification.required_spec109_subscribeThrowNPEOnNullSubscriber(); +281 } +282 +283 @Override @Test +284 public void required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe() throws Throwable { +285 publisherVerification.required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe(); +286 } +287 +288 @Override @Test +289 public void required_spec109_mustIssueOnSubscribeForNonNullSubscriber() throws Throwable { +290 publisherVerification.required_spec109_mustIssueOnSubscribeForNonNullSubscriber(); +291 } +292 +293 @Override @Test +294 public void untested_spec110_rejectASubscriptionRequestIfTheSameSubscriberSubscribesTwice() throws Throwable { +295 publisherVerification.untested_spec110_rejectASubscriptionRequestIfTheSameSubscriberSubscribesTwice(); +296 } +297 +298 @Override @Test +299 public void optional_spec111_maySupportMultiSubscribe() throws Throwable { +300 publisherVerification.optional_spec111_maySupportMultiSubscribe(); +301 } +302 +303 @Override @Test +304 public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingOneByOne() throws Throwable { +305 publisherVerification.optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingOneByOne(); +306 } +307 +308 @Override @Test +309 public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfront() throws Throwable { +310 publisherVerification.optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfront(); +311 } +312 +313 @Override @Test +314 public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfrontAndCompleteAsExpected() throws Throwable { +315 publisherVerification.optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfrontAndCompleteAsExpected(); +316 } +317 +318 @Override @Test +319 public void required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe() throws Throwable { +320 publisherVerification.required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe(); +321 } +322 +323 @Override @Test +324 public void required_spec303_mustNotAllowUnboundedRecursion() throws Throwable { +325 publisherVerification.required_spec303_mustNotAllowUnboundedRecursion(); +326 } +327 +328 @Override @Test +329 public void untested_spec304_requestShouldNotPerformHeavyComputations() throws Exception { +330 publisherVerification.untested_spec304_requestShouldNotPerformHeavyComputations(); +331 } +332 +333 @Override @Test +334 public void untested_spec305_cancelMustNotSynchronouslyPerformHeavyCompuatation() throws Exception { +335 publisherVerification.untested_spec305_cancelMustNotSynchronouslyPerformHeavyCompuatation(); +336 } +337 +338 @Override @Test +339 public void required_spec306_afterSubscriptionIsCancelledRequestMustBeNops() throws Throwable { +340 publisherVerification.required_spec306_afterSubscriptionIsCancelledRequestMustBeNops(); +341 } +342 +343 @Override @Test +344 public void required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops() throws Throwable { +345 publisherVerification.required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops(); +346 } +347 +348 @Override @Test +349 public void required_spec309_requestZeroMustSignalIllegalArgumentException() throws Throwable { +350 publisherVerification.required_spec309_requestZeroMustSignalIllegalArgumentException(); +351 } +352 +353 @Override @Test +354 public void required_spec309_requestNegativeNumberMustSignalIllegalArgumentException() throws Throwable { +355 publisherVerification.required_spec309_requestNegativeNumberMustSignalIllegalArgumentException(); +356 } +357 +358 @Override @Test +359 public void required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling() throws Throwable { +360 publisherVerification.required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling(); +361 } +362 +363 @Override @Test +364 public void required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber() throws Throwable { +365 publisherVerification.required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber(); +366 } +367 +368 @Override @Test +369 public void required_spec317_mustSupportAPendingElementCountUpToLongMaxValue() throws Throwable { +370 publisherVerification.required_spec317_mustSupportAPendingElementCountUpToLongMaxValue(); +371 } +372 +373 @Override @Test +374 public void required_spec317_mustSupportACumulativePendingElementCountUpToLongMaxValue() throws Throwable { +375 publisherVerification.required_spec317_mustSupportACumulativePendingElementCountUpToLongMaxValue(); +376 } +377 +378 @Override @Test +379 public void required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue() throws Throwable { +380 publisherVerification.required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue(); +381 } +382 +383 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.4 +384 // for multiple subscribers +385 @Test +386 public void required_spec104_mustCallOnErrorOnAllItsSubscribersIfItEncountersANonRecoverableError() throws Throwable { +387 optionalMultipleSubscribersTest(2, new Function<Long,TestSetup>() { +388 @Override +389 public TestSetup apply(Long aLong) throws Throwable { +390 return new TestSetup(env, processorBufferSize) {{ +391 final ManualSubscriberWithErrorCollection<T> sub1 = new ManualSubscriberWithErrorCollection<T>(env); +392 env.subscribe(processor, sub1); +393 +394 final ManualSubscriberWithErrorCollection<T> sub2 = new ManualSubscriberWithErrorCollection<T>(env); +395 env.subscribe(processor, sub2); +396 +397 sub1.request(1); +398 expectRequest(); +399 final T x = sendNextTFromUpstream(); +400 expectNextElement(sub1, x); +401 sub1.request(1); +402 +403 // sub1 has received one element, and has one demand pending +404 // sub2 has not yet requested anything +405 +406 final Exception ex = new RuntimeException("Test exception"); +407 sendError(ex); +408 sub1.expectError(ex); +409 sub2.expectError(ex); +410 +411 env.verifyNoAsyncErrorsNoDelay(); +412 }}; +413 } +414 }); +415 } +416 +417 ////////////////////// SUBSCRIBER RULES VERIFICATION /////////////////////////// +418 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#4.1 +419 +420 // A Processor +421 // must obey all Subscriber rules on its consuming side +422 public Subscriber<T> createSubscriber(final SubscriberWhiteboxVerification.WhiteboxSubscriberProbe<T> probe) { +423 final Processor<T, T> processor = createIdentityProcessor(processorBufferSize); +424 processor.subscribe( +425 new Subscriber<T>() { +426 private final Promise<Subscription> subs = new Promise<Subscription>(env); +427 +428 @Override +429 public void onSubscribe(final Subscription subscription) { +430 env.debug(String.format("whiteboxSubscriber::onSubscribe(%s)", subscription)); +431 if (subs.isCompleted()) subscription.cancel(); // the Probe must also pass subscriber verification +432 +433 probe.registerOnSubscribe(new SubscriberWhiteboxVerification.SubscriberPuppet() { +434 +435 @Override +436 public void triggerRequest(long elements) { +437 subscription.request(elements); +438 } +439 +440 @Override +441 public void signalCancel() { +442 subscription.cancel(); +443 } +444 }); +445 } +446 +447 @Override +448 public void onNext(T element) { +449 env.debug(String.format("whiteboxSubscriber::onNext(%s)", element)); +450 probe.registerOnNext(element); +451 } +452 +453 @Override +454 public void onComplete() { +455 env.debug("whiteboxSubscriber::onComplete()"); +456 probe.registerOnComplete(); +457 } +458 +459 @Override +460 public void onError(Throwable cause) { +461 env.debug(String.format("whiteboxSubscriber::onError(%s)", cause)); +462 probe.registerOnError(cause); +463 } +464 }); +465 +466 return processor; // we run the SubscriberVerification against this +467 } +468 +469 ////////////////////// OTHER RULE VERIFICATION /////////////////////////// +470 +471 // A Processor +472 // must immediately pass on `onError` events received from its upstream to its downstream +473 @Test +474 public void mustImmediatelyPassOnOnErrorEventsReceivedFromItsUpstreamToItsDownstream() throws Exception { +475 new TestSetup(env, processorBufferSize) {{ +476 final ManualSubscriberWithErrorCollection<T> sub = new ManualSubscriberWithErrorCollection<T>(env); +477 env.subscribe(processor, sub); +478 +479 final Exception ex = new RuntimeException("Test exception"); +480 sendError(ex); +481 sub.expectError(ex); // "immediately", i.e. without a preceding request +482 +483 env.verifyNoAsyncErrorsNoDelay(); +484 }}; +485 } +486 +487 /////////////////////// DELEGATED TESTS, A PROCESSOR "IS A" SUBSCRIBER ////////////////////// +488 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#4.1 +489 +490 @Test +491 public void required_exerciseWhiteboxHappyPath() throws Throwable { +492 subscriberVerification.required_exerciseWhiteboxHappyPath(); +493 } +494 +495 @Override @Test +496 public void required_spec201_mustSignalDemandViaSubscriptionRequest() throws Throwable { +497 subscriberVerification.required_spec201_mustSignalDemandViaSubscriptionRequest(); +498 } +499 +500 @Override @Test +501 public void untested_spec202_shouldAsynchronouslyDispatch() throws Exception { +502 subscriberVerification.untested_spec202_shouldAsynchronouslyDispatch(); +503 } +504 +505 @Override @Test +506 public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() throws Throwable { +507 subscriberVerification.required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete(); +508 } +509 +510 @Override @Test +511 public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() throws Throwable { +512 subscriberVerification.required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError(); +513 } +514 +515 @Override @Test +516 public void untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() throws Exception { +517 subscriberVerification.untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError(); +518 } +519 +520 @Override @Test +521 public void required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() throws Throwable { +522 subscriberVerification.required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal(); +523 } +524 +525 @Override @Test +526 public void untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid() throws Exception { +527 subscriberVerification.untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid(); +528 } +529 +530 @Override @Test +531 public void untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() throws Exception { +532 subscriberVerification.untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization(); +533 } +534 +535 @Override @Test +536 public void required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() throws Throwable { +537 subscriberVerification.required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel(); +538 } +539 +540 @Override @Test +541 public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() throws Throwable { +542 subscriberVerification.required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall(); +543 } +544 +545 @Override @Test +546 public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() throws Throwable { +547 subscriberVerification.required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall(); +548 } +549 +550 @Override @Test +551 public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() throws Throwable { +552 subscriberVerification.required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall(); +553 } +554 +555 @Override @Test +556 public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall() throws Throwable { +557 subscriberVerification.required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall(); +558 } +559 +560 @Override @Test +561 public void untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() throws Exception { +562 subscriberVerification.untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents(); +563 } +564 +565 @Override @Test +566 public void untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation() throws Throwable { +567 subscriberVerification.untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation(); +568 } +569 +570 @Override @Test +571 public void untested_spec213_failingOnSignalInvocation() throws Exception { +572 subscriberVerification.untested_spec213_failingOnSignalInvocation(); +573 } +574 +575 @Override @Test +576 public void required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +577 subscriberVerification.required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull(); +578 } +579 @Override @Test +580 public void required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +581 subscriberVerification.required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull(); +582 } +583 @Override @Test +584 public void required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +585 subscriberVerification.required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull(); +586 } +587 +588 @Override @Test +589 public void untested_spec301_mustNotBeCalledOutsideSubscriberContext() throws Exception { +590 subscriberVerification.untested_spec301_mustNotBeCalledOutsideSubscriberContext(); +591 } +592 +593 @Override @Test +594 public void required_spec308_requestMustRegisterGivenNumberElementsToBeProduced() throws Throwable { +595 subscriberVerification.required_spec308_requestMustRegisterGivenNumberElementsToBeProduced(); +596 } +597 +598 @Override @Test +599 public void untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber() throws Exception { +600 subscriberVerification.untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber(); +601 } +602 +603 @Override @Test +604 public void untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError() throws Exception { +605 subscriberVerification.untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError(); +606 } +607 +608 @Override @Test +609 public void untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() throws Exception { +610 subscriberVerification.untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists(); +611 } +612 +613 @Override @Test +614 public void untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError() throws Exception { +615 subscriberVerification.untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError(); +616 } +617 +618 @Override @Test +619 public void untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() throws Exception { +620 subscriberVerification.untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber(); +621 } +622 +623 /////////////////////// ADDITIONAL "COROLLARY" TESTS ////////////////////// +624 +625 // A Processor +626 // must trigger `requestFromUpstream` for elements that have been requested 'long ago' +627 @Test +628 public void required_mustRequestFromUpstreamForElementsThatHaveBeenRequestedLongAgo() throws Throwable { +629 optionalMultipleSubscribersTest(2, new Function<Long,TestSetup>() { +630 @Override +631 public TestSetup apply(Long subscribers) throws Throwable { +632 return new TestSetup(env, processorBufferSize) {{ +633 ManualSubscriber<T> sub1 = newSubscriber(); +634 sub1.request(20); +635 +636 long totalRequests = expectRequest(); +637 final T x = sendNextTFromUpstream(); +638 expectNextElement(sub1, x); +639 +640 if (totalRequests == 1) { +641 totalRequests += expectRequest(); +642 } +643 final T y = sendNextTFromUpstream(); +644 expectNextElement(sub1, y); +645 +646 if (totalRequests == 2) { +647 totalRequests += expectRequest(); +648 } +649 +650 final ManualSubscriber<T> sub2 = newSubscriber(); +651 +652 // sub1 now has 18 pending +653 // sub2 has 0 pending +654 +655 final T z = sendNextTFromUpstream(); +656 expectNextElement(sub1, z); +657 sub2.expectNone(); // since sub2 hasn't requested anything yet +658 +659 sub2.request(1); +660 expectNextElement(sub2, z); +661 +662 if (totalRequests == 3) { +663 expectRequest(); +664 } +665 +666 // to avoid error messages during test harness shutdown +667 sendCompletion(); +668 sub1.expectCompletion(env.defaultTimeoutMillis()); +669 sub2.expectCompletion(env.defaultTimeoutMillis()); +670 +671 env.verifyNoAsyncErrorsNoDelay(); +672 }}; +673 } +674 }); +675 } +676 +677 /////////////////////// TEST INFRASTRUCTURE ////////////////////// +678 +679 public void notVerified() { +680 publisherVerification.notVerified(); +681 } +682 +683 public void notVerified(String message) { +684 publisherVerification.notVerified(message); +685 } +686 +687 /** +688 * Test for feature that REQUIRES multiple subscribers to be supported by Publisher. +689 */ +690 public void optionalMultipleSubscribersTest(long requiredSubscribersSupport, Function<Long, TestSetup> body) throws Throwable { +691 if (requiredSubscribersSupport > maxSupportedSubscribers()) +692 notVerified(String.format("The Publisher under test only supports %d subscribers, while this test requires at least %d to run.", +693 maxSupportedSubscribers(), requiredSubscribersSupport)); +694 else body.apply(requiredSubscribersSupport); +695 } +696 +697 public abstract class TestSetup extends ManualPublisher<T> { +698 final private ManualSubscriber<T> tees; // gives us access to an infinite stream of T values +699 private Set<T> seenTees = new HashSet<T>(); +700 +701 final Processor<T, T> processor; +702 +703 public TestSetup(TestEnvironment env, int testBufferSize) throws InterruptedException { +704 super(env); +705 tees = env.newManualSubscriber(createHelperPublisher(Long.MAX_VALUE)); +706 processor = createIdentityProcessor(testBufferSize); +707 subscribe(processor); +708 } +709 +710 public ManualSubscriber<T> newSubscriber() throws InterruptedException { +711 return env.newManualSubscriber(processor); +712 } +713 +714 public T nextT() throws InterruptedException { +715 final T t = tees.requestNextElement(); +716 if (seenTees.contains(t)) { +717 env.flop(String.format("Helper publisher illegally produced the same element %s twice", t)); +718 } +719 seenTees.add(t); +720 return t; +721 } +722 +723 public void expectNextElement(ManualSubscriber<T> sub, T expected) throws InterruptedException { +724 final T elem = sub.nextElement(String.format("timeout while awaiting %s", expected)); +725 if (!elem.equals(expected)) { +726 env.flop(String.format("Received `onNext(%s)` on downstream but expected `onNext(%s)`", elem, expected)); +727 } +728 } +729 +730 public T sendNextTFromUpstream() throws InterruptedException { +731 final T x = nextT(); +732 sendNext(x); +733 return x; +734 } +735 } +736 +737 public class ManualSubscriberWithErrorCollection<A> extends ManualSubscriberWithSubscriptionSupport<A> { +738 Promise<Throwable> error; +739 +740 public ManualSubscriberWithErrorCollection(TestEnvironment env) { +741 super(env); +742 error = new Promise<Throwable>(env); +743 } +744 +745 @Override +746 public void onError(Throwable cause) { +747 error.complete(cause); +748 } +749 +750 public void expectError(Throwable cause) throws InterruptedException { +751 expectError(cause, env.defaultTimeoutMillis()); +752 } +753 +754 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +755 public void expectError(Throwable cause, long timeoutMillis) throws InterruptedException { +756 error.expectCompletion(timeoutMillis, "Did not receive expected error on downstream"); +757 if (!cause.equals(error.value())) { +758 env.flop(String.format("Expected error %s but got %s", cause, error.value())); +759 } +760 } +761 } +762} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck; +002 +003import org.reactivestreams.Publisher; +004import org.reactivestreams.Subscriber; +005import org.reactivestreams.Subscription; +006import org.reactivestreams.tck.TestEnvironment.BlackholeSubscriberWithSubscriptionSupport; +007import org.reactivestreams.tck.TestEnvironment.Latch; +008import org.reactivestreams.tck.TestEnvironment.ManualSubscriber; +009import org.reactivestreams.tck.TestEnvironment.ManualSubscriberWithSubscriptionSupport; +010import org.reactivestreams.tck.support.Function; +011import org.reactivestreams.tck.support.Optional; +012import org.reactivestreams.tck.support.PublisherVerificationRules; +013import org.testng.SkipException; +014import org.testng.annotations.BeforeMethod; +015import org.testng.annotations.Test; +016 +017import java.lang.Override; +018import java.lang.ref.ReferenceQueue; +019import java.lang.ref.WeakReference; +020import java.util.ArrayList; +021import java.util.Arrays; +022import java.util.Collections; +023import java.util.List; +024import java.util.Random; +025import java.util.concurrent.atomic.AtomicReference; +026 +027import static org.testng.Assert.assertEquals; +028import static org.testng.Assert.assertTrue; +029 +030/** +031 * Provides tests for verifying {@code Publisher} specification rules. +032 * +033 * @see org.reactivestreams.Publisher +034 */ +035public abstract class PublisherVerification<T> implements PublisherVerificationRules { +036 +037 private static final String PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS_ENV = "PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS"; +038 private static final long DEFAULT_PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS = 300L; +039 +040 private final TestEnvironment env; +041 +042 /** +043 * The amount of time after which a cancelled Subscriber reference should be dropped. +044 * See Rule 3.13 for details. +045 */ +046 private final long publisherReferenceGCTimeoutMillis; +047 +048 /** +049 * Constructs a new verification class using the given env and configuration. +050 * +051 * @param publisherReferenceGCTimeoutMillis used to determine after how much time a reference to a Subscriber should be already dropped by the Publisher. +052 */ +053 public PublisherVerification(TestEnvironment env, long publisherReferenceGCTimeoutMillis) { +054 this.env = env; +055 this.publisherReferenceGCTimeoutMillis = publisherReferenceGCTimeoutMillis; +056 } +057 +058 /** +059 * Constructs a new verification class using the given env and configuration. +060 * +061 * The value for {@code publisherReferenceGCTimeoutMillis} will be obtained by using {@link PublisherVerification#envPublisherReferenceGCTimeoutMillis()}. +062 */ +063 public PublisherVerification(TestEnvironment env) { +064 this.env = env; +065 this.publisherReferenceGCTimeoutMillis = envPublisherReferenceGCTimeoutMillis(); +066 } +067 +068 /** +069 * Tries to parse the env variable {@code PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS} as long and returns the value if present, +070 * OR its default value ({@link PublisherVerification#DEFAULT_PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS}). +071 * +072 * This value is used to determine after how much time a reference to a Subscriber should be already dropped by the Publisher. +073 * +074 * @throws java.lang.IllegalArgumentException when unable to parse the env variable +075 */ +076 public static long envPublisherReferenceGCTimeoutMillis() { +077 final String envMillis = System.getenv(PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS_ENV); +078 if (envMillis == null) return DEFAULT_PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS; +079 else try { +080 return Long.parseLong(envMillis); +081 } catch(NumberFormatException ex) { +082 throw new IllegalArgumentException(String.format("Unable to parse %s env value [%s] as long!", PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS_ENV, envMillis), ex); +083 } +084 } +085 +086 /** +087 * This is the main method you must implement in your test incarnation. +088 * It must create a Publisher for a stream with exactly the given number of elements. +089 * If `elements` is `Long.MAX_VALUE` the produced stream must be infinite. +090 */ +091 public abstract Publisher<T> createPublisher(long elements); +092 +093 /** +094 * By implementing this method, additional TCK tests concerning a "failed" publishers will be run. +095 * +096 * The expected behaviour of the {@link Publisher} returned by this method is hand out a subscription, +097 * followed by signalling {@code onError} on it, as specified by Rule 1.9. +098 * +099 * If you ignore these additional tests, return {@code null} from this method. +100 */ +101 public abstract Publisher<T> createFailedPublisher(); +102 +103 +104 /** +105 * Override and return lower value if your Publisher is only able to produce a known number of elements. +106 * For example, if it is designed to return at-most-one element, return {@code 1} from this method. +107 * +108 * Defaults to {@code Long.MAX_VALUE - 1}, meaning that the Publisher can be produce a huge but NOT an unbounded number of elements. +109 * +110 * To mark your Publisher will *never* signal an {@code onComplete} override this method and return {@code Long.MAX_VALUE}, +111 * which will result in *skipping all tests which require an onComplete to be triggered* (!). +112 */ +113 public long maxElementsFromPublisher() { +114 return Long.MAX_VALUE - 1; +115 } +116 +117 /** +118 * Override and return {@code true} in order to skip executing tests marked as {@code Stochastic}. +119 * Such tests MAY sometimes fail even though the impl +120 */ +121 public boolean skipStochasticTests() { +122 return false; +123 } +124 +125 /** +126 * In order to verify rule 3.3 of the reactive streams spec, this number will be used to check if a +127 * {@code Subscription} actually solves the "unbounded recursion" problem by not allowing the number of +128 * recursive calls to exceed the number returned by this method. +129 * +130 * @see <a href="https://github.com/reactive-streams/reactive-streams-jvm#3.3">reactive streams spec, rule 3.3</a> +131 * @see PublisherVerification#required_spec303_mustNotAllowUnboundedRecursion() +132 */ +133 public long boundedDepthOfOnNextAndRequestRecursion() { +134 return 1; +135 } +136 +137 ////////////////////// TEST ENV CLEANUP ///////////////////////////////////// +138 +139 @BeforeMethod +140 public void setUp() throws Exception { +141 env.clearAsyncErrors(); +142 } +143 +144 ////////////////////// TEST SETUP VERIFICATION ////////////////////////////// +145 +146 @Override @Test +147 public void required_createPublisher1MustProduceAStreamOfExactly1Element() throws Throwable { +148 activePublisherTest(1, true, new PublisherTestRun<T>() { +149 @Override +150 public void run(Publisher<T> pub) throws InterruptedException { +151 ManualSubscriber<T> sub = env.newManualSubscriber(pub); +152 assertTrue(requestNextElementOrEndOfStream(pub, sub).isDefined(), String.format("Publisher %s produced no elements", pub)); +153 sub.requestEndOfStream(); +154 } +155 +156 Optional<T> requestNextElementOrEndOfStream(Publisher<T> pub, ManualSubscriber<T> sub) throws InterruptedException { +157 return sub.requestNextElementOrEndOfStream(String.format("Timeout while waiting for next element from Publisher %s", pub)); +158 } +159 +160 }); +161 } +162 +163 @Override @Test +164 public void required_createPublisher3MustProduceAStreamOfExactly3Elements() throws Throwable { +165 activePublisherTest(3, true, new PublisherTestRun<T>() { +166 @Override +167 public void run(Publisher<T> pub) throws InterruptedException { +168 ManualSubscriber<T> sub = env.newManualSubscriber(pub); +169 assertTrue(requestNextElementOrEndOfStream(pub, sub).isDefined(), String.format("Publisher %s produced no elements", pub)); +170 assertTrue(requestNextElementOrEndOfStream(pub, sub).isDefined(), String.format("Publisher %s produced only 1 element", pub)); +171 assertTrue(requestNextElementOrEndOfStream(pub, sub).isDefined(), String.format("Publisher %s produced only 2 elements", pub)); +172 sub.requestEndOfStream(); +173 } +174 +175 Optional<T> requestNextElementOrEndOfStream(Publisher<T> pub, ManualSubscriber<T> sub) throws InterruptedException { +176 return sub.requestNextElementOrEndOfStream(String.format("Timeout while waiting for next element from Publisher %s", pub)); +177 } +178 +179 }); +180 } +181 +182 @Override @Test +183 public void required_validate_maxElementsFromPublisher() throws Exception { +184 assertTrue(maxElementsFromPublisher() >= 0, "maxElementsFromPublisher MUST return a number >= 0"); +185 } +186 +187 @Override @Test +188 public void required_validate_boundedDepthOfOnNextAndRequestRecursion() throws Exception { +189 assertTrue(boundedDepthOfOnNextAndRequestRecursion() >= 1, "boundedDepthOfOnNextAndRequestRecursion must return a number >= 1"); +190 } +191 +192 +193 ////////////////////// SPEC RULE VERIFICATION /////////////////////////////// +194 +195 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.1 +196 @Override @Test +197 public void required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements() throws Throwable { +198 activePublisherTest(5, false, new PublisherTestRun<T>() { +199 @Override +200 public void run(Publisher<T> pub) throws InterruptedException { +201 +202 ManualSubscriber<T> sub = env.newManualSubscriber(pub); +203 +204 sub.expectNone(String.format("Publisher %s produced value before the first `request`: ", pub)); +205 sub.request(1); +206 sub.nextElement(String.format("Publisher %s produced no element after first `request`", pub)); +207 sub.expectNone(String.format("Publisher %s produced unrequested: ", pub)); +208 +209 sub.request(1); +210 sub.request(2); +211 sub.nextElements(3, env.defaultTimeoutMillis(), String.format("Publisher %s produced less than 3 elements after two respective `request` calls", pub)); +212 +213 sub.expectNone(String.format("Publisher %sproduced unrequested ", pub)); +214 } +215 }); +216 } +217 +218 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.2 +219 @Override @Test +220 public void required_spec102_maySignalLessThanRequestedAndTerminateSubscription() throws Throwable { +221 final int elements = 3; +222 final int requested = 10; +223 +224 activePublisherTest(elements, true, new PublisherTestRun<T>() { +225 @Override +226 public void run(Publisher<T> pub) throws Throwable { +227 final ManualSubscriber<T> sub = env.newManualSubscriber(pub); +228 sub.request(requested); +229 sub.nextElements(elements); +230 sub.expectCompletion(); +231 } +232 }); +233 } +234 +235 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.3 +236 @Override @Test +237 public void stochastic_spec103_mustSignalOnMethodsSequentially() throws Throwable { +238 final int iterations = 100; +239 final int elements = 10; +240 +241 stochasticTest(iterations, new Function<Integer, Void>() { +242 @Override +243 public Void apply(final Integer runNumber) throws Throwable { +244 activePublisherTest(elements, true, new PublisherTestRun<T>() { +245 @Override +246 public void run(Publisher<T> pub) throws Throwable { +247 final Latch completionLatch = new Latch(env); +248 +249 pub.subscribe(new Subscriber<T>() { +250 private Subscription subs; +251 private long gotElements = 0; +252 +253 private ConcurrentAccessBarrier concurrentAccessBarrier = new ConcurrentAccessBarrier(); +254 +255 /** +256 * Concept wise very similar to a {@link org.reactivestreams.tck.TestEnvironment.Latch}, serves to protect +257 * a critical section from concurrent access, with the added benefit of Thread tracking and same-thread-access awareness. +258 * +259 * Since a <i>Synchronous</i> Publisher may choose to synchronously (using the same {@link Thread}) call +260 * {@code onNext} directly from either {@code subscribe} or {@code request} a plain Latch is not enough +261 * to verify concurrent access safety - one needs to track if the caller is not still using the calling thread +262 * to enter subsequent critical sections ("nesting" them effectively). +263 */ +264 final class ConcurrentAccessBarrier { +265 private AtomicReference<Thread> currentlySignallingThread = new AtomicReference<Thread>(null); +266 private volatile String previousSignal = null; +267 +268 public void enterSignal(String signalName) { +269 if((!currentlySignallingThread.compareAndSet(null, Thread.currentThread())) && !isSynchronousSignal()) { +270 env.flop(String.format( +271 "Illegal concurrent access detected (entering critical section)! " + +272 "%s emited %s signal, before %s finished its %s signal.", +273 Thread.currentThread(), signalName, currentlySignallingThread.get(), previousSignal)); +274 } +275 this.previousSignal = signalName; +276 } +277 +278 public void leaveSignal(String signalName) { +279 currentlySignallingThread.set(null); +280 this.previousSignal = signalName; +281 } +282 +283 private boolean isSynchronousSignal() { +284 return (previousSignal != null) && Thread.currentThread().equals(currentlySignallingThread.get()); +285 } +286 +287 } +288 +289 @Override +290 public void onSubscribe(Subscription s) { +291 final String signal = "onSubscribe()"; +292 concurrentAccessBarrier.enterSignal(signal); +293 +294 subs = s; +295 subs.request(1); +296 +297 concurrentAccessBarrier.leaveSignal(signal); +298 } +299 +300 @Override +301 public void onNext(T ignore) { +302 final String signal = String.format("onNext(%s)", ignore); +303 concurrentAccessBarrier.enterSignal(signal); +304 +305 gotElements += 1; +306 if (gotElements <= elements) // requesting one more than we know are in the stream (some Publishers need this) +307 subs.request(1); +308 +309 concurrentAccessBarrier.leaveSignal(signal); +310 } +311 +312 @Override +313 public void onError(Throwable t) { +314 final String signal = String.format("onError(%s)", t.getMessage()); +315 concurrentAccessBarrier.enterSignal(signal); +316 +317 // ignore value +318 +319 concurrentAccessBarrier.leaveSignal(signal); +320 } +321 +322 @Override +323 public void onComplete() { +324 final String signal = "onComplete()"; +325 concurrentAccessBarrier.enterSignal(signal); +326 +327 // entering for completeness +328 +329 concurrentAccessBarrier.leaveSignal(signal); +330 completionLatch.close(); +331 } +332 }); +333 +334 completionLatch.expectClose(elements * env.defaultTimeoutMillis(), "Expected 10 elements to be drained"); +335 } +336 }); +337 return null; +338 } +339 }); +340 } +341 +342 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.4 +343 @Override @Test +344 public void optional_spec104_mustSignalOnErrorWhenFails() throws Throwable { +345 try { +346 whenHasErrorPublisherTest(new PublisherTestRun<T>() { +347 @Override +348 public void run(final Publisher<T> pub) throws InterruptedException { +349 final Latch onErrorlatch = new Latch(env); +350 final Latch onSubscribeLatch = new Latch(env); +351 pub.subscribe(new TestEnvironment.TestSubscriber<T>(env) { +352 @Override +353 public void onSubscribe(Subscription subs) { +354 onSubscribeLatch.assertOpen("Only one onSubscribe call expected"); +355 onSubscribeLatch.close(); +356 } +357 @Override +358 public void onError(Throwable cause) { +359 onSubscribeLatch.assertClosed("onSubscribe should be called prior to onError always"); +360 onErrorlatch.assertOpen(String.format("Error-state Publisher %s called `onError` twice on new Subscriber", pub)); +361 onErrorlatch.close(); +362 } +363 }); +364 +365 onSubscribeLatch.expectClose("Should have received onSubscribe"); +366 onErrorlatch.expectClose(String.format("Error-state Publisher %s did not call `onError` on new Subscriber", pub)); +367 +368 env.verifyNoAsyncErrors(env.defaultTimeoutMillis()); +369 } +370 }); +371 } catch (SkipException se) { +372 throw se; +373 } catch (Throwable ex) { +374 // we also want to catch AssertionErrors and anything the publisher may have thrown inside subscribe +375 // which was wrong of him - he should have signalled on error using onError +376 throw new RuntimeException(String.format("Publisher threw exception (%s) instead of signalling error via onError!", ex.getMessage()), ex); +377 } +378 } +379 +380 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.5 +381 @Override @Test +382 public void required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates() throws Throwable { +383 activePublisherTest(3, true, new PublisherTestRun<T>() { +384 @Override +385 public void run(Publisher<T> pub) throws Throwable { +386 ManualSubscriber<T> sub = env.newManualSubscriber(pub); +387 sub.requestNextElement(); +388 sub.requestNextElement(); +389 sub.requestNextElement(); +390 sub.requestEndOfStream(); +391 sub.expectNone(); +392 } +393 }); +394 } +395 +396 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.5 +397 @Override @Test +398 public void optional_spec105_emptyStreamMustTerminateBySignallingOnComplete() throws Throwable { +399 optionalActivePublisherTest(0, true, new PublisherTestRun<T>() { +400 @Override +401 public void run(Publisher<T> pub) throws Throwable { +402 ManualSubscriber<T> sub = env.newManualSubscriber(pub); +403 sub.request(1); +404 sub.expectCompletion(); +405 sub.expectNone(); +406 } +407 }); +408 } +409 +410 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.6 +411 @Override @Test +412 public void untested_spec106_mustConsiderSubscriptionCancelledAfterOnErrorOrOnCompleteHasBeenCalled() throws Throwable { +413 notVerified(); // not really testable without more control over the Publisher +414 } +415 +416 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.7 +417 @Override @Test +418 public void required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled() throws Throwable { +419 activePublisherTest(1, true, new PublisherTestRun<T>() { +420 @Override +421 public void run(Publisher<T> pub) throws Throwable { +422 ManualSubscriber<T> sub = env.newManualSubscriber(pub); +423 sub.request(10); +424 sub.nextElement(); +425 sub.expectCompletion(); +426 +427 sub.request(10); +428 sub.expectNone(); +429 } +430 }); +431 } +432 +433 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.7 +434 @Override @Test +435 public void untested_spec107_mustNotEmitFurtherSignalsOnceOnErrorHasBeenSignalled() throws Throwable { +436 notVerified(); // can we meaningfully test this, without more control over the publisher? +437 } +438 +439 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.8 +440 @Override @Test +441 public void untested_spec108_possiblyCanceledSubscriptionShouldNotReceiveOnErrorOrOnCompleteSignals() throws Throwable { +442 notVerified(); // can we meaningfully test this? +443 } +444 +445 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.9 +446 @Override @Test +447 public void untested_spec109_subscribeShouldNotThrowNonFatalThrowable() throws Throwable { +448 notVerified(); // can we meaningfully test this? +449 } +450 +451 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.9 +452 @Override @Test +453 public void required_spec109_subscribeThrowNPEOnNullSubscriber() throws Throwable { +454 activePublisherTest(0, false, new PublisherTestRun<T>() { +455 @Override +456 public void run(Publisher<T> pub) throws Throwable { +457 try { +458 pub.subscribe(null); +459 env.flop("Publisher did not throw a NullPointerException when given a null Subscribe in subscribe"); +460 } catch (NullPointerException ignored) { +461 // valid behaviour +462 } +463 env.verifyNoAsyncErrorsNoDelay(); +464 } +465 }); +466 } +467 +468 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.9 +469 @Override @Test +470 public void required_spec109_mustIssueOnSubscribeForNonNullSubscriber() throws Throwable { +471 activePublisherTest(0, false, new PublisherTestRun<T>() { +472 @Override +473 public void run(Publisher<T> pub) throws Throwable { +474 final Latch onSubscribeLatch = new Latch(env); +475 pub.subscribe(new Subscriber<T>() { +476 @Override +477 public void onError(Throwable cause) { +478 onSubscribeLatch.assertClosed("onSubscribe should be called prior to onError always"); +479 } +480 +481 @Override +482 public void onSubscribe(Subscription subs) { +483 onSubscribeLatch.assertOpen("Only one onSubscribe call expected"); +484 onSubscribeLatch.close(); +485 } +486 +487 @Override +488 public void onNext(T elem) { +489 onSubscribeLatch.assertClosed("onSubscribe should be called prior to onNext always"); +490 } +491 +492 @Override +493 public void onComplete() { +494 onSubscribeLatch.assertClosed("onSubscribe should be called prior to onComplete always"); +495 } +496 }); +497 onSubscribeLatch.expectClose("Should have received onSubscribe"); +498 env.verifyNoAsyncErrorsNoDelay(); +499 } +500 }); +501 } +502 +503 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.9 +504 @Override @Test +505 public void required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe() throws Throwable { +506 whenHasErrorPublisherTest(new PublisherTestRun<T>() { +507 @Override +508 public void run(Publisher<T> pub) throws Throwable { +509 final Latch onErrorLatch = new Latch(env); +510 final Latch onSubscribeLatch = new Latch(env); +511 ManualSubscriberWithSubscriptionSupport<T> sub = new ManualSubscriberWithSubscriptionSupport<T>(env) { +512 @Override +513 public void onError(Throwable cause) { +514 onSubscribeLatch.assertClosed("onSubscribe should be called prior to onError always"); +515 onErrorLatch.assertOpen("Only one onError call expected"); +516 onErrorLatch.close(); +517 } +518 +519 @Override +520 public void onSubscribe(Subscription subs) { +521 onSubscribeLatch.assertOpen("Only one onSubscribe call expected"); +522 onSubscribeLatch.close(); +523 } +524 }; +525 pub.subscribe(sub); +526 onSubscribeLatch.expectClose("Should have received onSubscribe"); +527 onErrorLatch.expectClose("Should have received onError"); +528 +529 env.verifyNoAsyncErrorsNoDelay(); +530 } +531 }); +532 } +533 +534 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.10 +535 @Override @Test +536 public void untested_spec110_rejectASubscriptionRequestIfTheSameSubscriberSubscribesTwice() throws Throwable { +537 notVerified(); // can we meaningfully test this? +538 } +539 +540 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.11 +541 @Override @Test +542 public void optional_spec111_maySupportMultiSubscribe() throws Throwable { +543 optionalActivePublisherTest(1, false, new PublisherTestRun<T>() { +544 @Override +545 public void run(Publisher<T> pub) throws Throwable { +546 ManualSubscriber<T> sub1 = env.newManualSubscriber(pub); +547 ManualSubscriber<T> sub2 = env.newManualSubscriber(pub); +548 +549 env.verifyNoAsyncErrors(); +550 } +551 }); +552 } +553 +554 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.11 +555 @Override @Test +556 public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingOneByOne() throws Throwable { +557 optionalActivePublisherTest(5, true, new PublisherTestRun<T>() { // This test is skipped if the publisher is unbounded (never sends onComplete) +558 @Override +559 public void run(Publisher<T> pub) throws InterruptedException { +560 ManualSubscriber<T> sub1 = env.newManualSubscriber(pub); +561 ManualSubscriber<T> sub2 = env.newManualSubscriber(pub); +562 ManualSubscriber<T> sub3 = env.newManualSubscriber(pub); +563 +564 sub1.request(1); +565 T x1 = sub1.nextElement(String.format("Publisher %s did not produce the requested 1 element on 1st subscriber", pub)); +566 sub2.request(2); +567 List<T> y1 = sub2.nextElements(2, String.format("Publisher %s did not produce the requested 2 elements on 2nd subscriber", pub)); +568 sub1.request(1); +569 T x2 = sub1.nextElement(String.format("Publisher %s did not produce the requested 1 element on 1st subscriber", pub)); +570 sub3.request(3); +571 List<T> z1 = sub3.nextElements(3, String.format("Publisher %s did not produce the requested 3 elements on 3rd subscriber", pub)); +572 sub3.request(1); +573 T z2 = sub3.nextElement(String.format("Publisher %s did not produce the requested 1 element on 3rd subscriber", pub)); +574 sub3.request(1); +575 T z3 = sub3.nextElement(String.format("Publisher %s did not produce the requested 1 element on 3rd subscriber", pub)); +576 sub3.requestEndOfStream(String.format("Publisher %s did not complete the stream as expected on 3rd subscriber", pub)); +577 sub2.request(3); +578 List<T> y2 = sub2.nextElements(3, String.format("Publisher %s did not produce the requested 3 elements on 2nd subscriber", pub)); +579 sub2.requestEndOfStream(String.format("Publisher %s did not complete the stream as expected on 2nd subscriber", pub)); +580 sub1.request(2); +581 List<T> x3 = sub1.nextElements(2, String.format("Publisher %s did not produce the requested 2 elements on 1st subscriber", pub)); +582 sub1.request(1); +583 T x4 = sub1.nextElement(String.format("Publisher %s did not produce the requested 1 element on 1st subscriber", pub)); +584 sub1.requestEndOfStream(String.format("Publisher %s did not complete the stream as expected on 1st subscriber", pub)); +585 +586 @SuppressWarnings("unchecked") +587 List<T> r = new ArrayList<T>(Arrays.asList(x1, x2)); +588 r.addAll(x3); +589 r.addAll(Collections.singleton(x4)); +590 +591 List<T> check1 = new ArrayList<T>(y1); +592 check1.addAll(y2); +593 +594 //noinspection unchecked +595 List<T> check2 = new ArrayList<T>(z1); +596 check2.add(z2); +597 check2.add(z3); +598 +599 assertEquals(r, check1, String.format("Publisher %s did not produce the same element sequence for subscribers 1 and 2", pub)); +600 assertEquals(r, check2, String.format("Publisher %s did not produce the same element sequence for subscribers 1 and 3", pub)); +601 } +602 }); +603 } +604 +605 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.11 +606 @Override @Test +607 public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfront() throws Throwable { +608 optionalActivePublisherTest(3, false, new PublisherTestRun<T>() { // This test is skipped if the publisher cannot produce enough elements +609 @Override +610 public void run(Publisher<T> pub) throws Throwable { +611 ManualSubscriber<T> sub1 = env.newManualSubscriber(pub); +612 ManualSubscriber<T> sub2 = env.newManualSubscriber(pub); +613 ManualSubscriber<T> sub3 = env.newManualSubscriber(pub); +614 +615 List<T> received1 = new ArrayList<T>(); +616 List<T> received2 = new ArrayList<T>(); +617 List<T> received3 = new ArrayList<T>(); +618 +619 // if the publisher must touch it's source to notice it's been drained, the OnComplete won't come until we ask for more than it actually contains... +620 // edgy edge case? +621 sub1.request(4); +622 sub2.request(4); +623 sub3.request(4); +624 +625 received1.addAll(sub1.nextElements(3)); +626 received2.addAll(sub2.nextElements(3)); +627 received3.addAll(sub3.nextElements(3)); +628 +629 // NOTE: can't check completion, the Publisher may not be able to signal it +630 // a similar test *with* completion checking is implemented +631 +632 assertEquals(received1, received2, String.format("Expected elements to be signaled in the same sequence to 1st and 2nd subscribers")); +633 assertEquals(received2, received3, String.format("Expected elements to be signaled in the same sequence to 2nd and 3rd subscribers")); +634 } +635 }); +636 } +637 +638 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.11 +639 @Override @Test +640 public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfrontAndCompleteAsExpected() throws Throwable { +641 optionalActivePublisherTest(3, true, new PublisherTestRun<T>() { // This test is skipped if the publisher is unbounded (never sends onComplete) +642 @Override +643 public void run(Publisher<T> pub) throws Throwable { +644 ManualSubscriber<T> sub1 = env.newManualSubscriber(pub); +645 ManualSubscriber<T> sub2 = env.newManualSubscriber(pub); +646 ManualSubscriber<T> sub3 = env.newManualSubscriber(pub); +647 +648 List<T> received1 = new ArrayList<T>(); +649 List<T> received2 = new ArrayList<T>(); +650 List<T> received3 = new ArrayList<T>(); +651 +652 // if the publisher must touch it's source to notice it's been drained, the OnComplete won't come until we ask for more than it actually contains... +653 // edgy edge case? +654 sub1.request(4); +655 sub2.request(4); +656 sub3.request(4); +657 +658 received1.addAll(sub1.nextElements(3)); +659 received2.addAll(sub2.nextElements(3)); +660 received3.addAll(sub3.nextElements(3)); +661 +662 sub1.expectCompletion(); +663 sub2.expectCompletion(); +664 sub3.expectCompletion(); +665 +666 assertEquals(received1, received2, String.format("Expected elements to be signaled in the same sequence to 1st and 2nd subscribers")); +667 assertEquals(received2, received3, String.format("Expected elements to be signaled in the same sequence to 2nd and 3rd subscribers")); +668 } +669 }); +670 } +671 +672 ///////////////////// SUBSCRIPTION TESTS ////////////////////////////////// +673 +674 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.2 +675 @Override @Test +676 public void required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe() throws Throwable { +677 activePublisherTest(6, false, new PublisherTestRun<T>() { +678 @Override +679 public void run(Publisher<T> pub) throws Throwable { +680 ManualSubscriber<T> sub = new ManualSubscriber<T>(env) { +681 @Override +682 public void onSubscribe(Subscription subs) { +683 this.subscription.completeImmediatly(subs); +684 +685 subs.request(1); +686 subs.request(1); +687 subs.request(1); +688 } +689 +690 @Override +691 public void onNext(T element) { +692 Subscription subs = this.subscription.value(); +693 subs.request(1); +694 } +695 }; +696 +697 env.subscribe(pub, sub); +698 +699 long delay = env.defaultTimeoutMillis(); +700 env.verifyNoAsyncErrors(delay); +701 } +702 }); +703 } +704 +705 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.3 +706 @Override @Test +707 public void required_spec303_mustNotAllowUnboundedRecursion() throws Throwable { +708 final long oneMoreThanBoundedLimit = boundedDepthOfOnNextAndRequestRecursion() + 1; +709 +710 activePublisherTest(oneMoreThanBoundedLimit, false, new PublisherTestRun<T>() { +711 @Override +712 public void run(Publisher<T> pub) throws Throwable { +713 final ThreadLocal<Long> stackDepthCounter = new ThreadLocal<Long>() { +714 @Override +715 protected Long initialValue() { +716 return 0L; +717 } +718 }; +719 +720 final Latch runCompleted = new Latch(env); +721 +722 final ManualSubscriber<T> sub = new ManualSubscriberWithSubscriptionSupport<T>(env) { +723 // counts the number of signals received, used to break out from possibly infinite request/onNext loops +724 long signalsReceived = 0L; +725 +726 @Override +727 public void onNext(T element) { +728 // NOT calling super.onNext as this test only cares about stack depths, not the actual values of elements +729 // which also simplifies this test as we do not have to drain the test buffer, which would otherwise be in danger of overflowing +730 +731 signalsReceived += 1; +732 stackDepthCounter.set(stackDepthCounter.get() + 1); +733 env.debug(String.format("%s(recursion depth: %d)::onNext(%s)", this, stackDepthCounter.get(), element)); +734 +735 final long callsUntilNow = stackDepthCounter.get(); +736 if (callsUntilNow > boundedDepthOfOnNextAndRequestRecursion()) { +737 env.flop(String.format("Got %d onNext calls within thread: %s, yet expected recursive bound was %d", +738 callsUntilNow, Thread.currentThread(), boundedDepthOfOnNextAndRequestRecursion())); +739 +740 // stop the recursive call chain +741 runCompleted.close(); +742 return; +743 } else if (signalsReceived >= oneMoreThanBoundedLimit) { +744 // since max number of signals reached, and recursion depth not exceeded, we judge this as a success and +745 // stop the recursive call chain +746 runCompleted.close(); +747 return; +748 } +749 +750 // request more right away, the Publisher must break the recursion +751 subscription.value().request(1); +752 +753 stackDepthCounter.set(stackDepthCounter.get() - 1); +754 } +755 +756 @Override +757 public void onComplete() { +758 super.onComplete(); +759 runCompleted.close(); +760 } +761 +762 @Override +763 public void onError(Throwable cause) { +764 super.onError(cause); +765 runCompleted.close(); +766 } +767 }; +768 +769 try { +770 env.subscribe(pub, sub); +771 +772 sub.request(1); // kick-off the `request -> onNext -> request -> onNext -> ...` +773 +774 final String msg = String.format("Unable to validate call stack depth safety, " + +775 "awaited at-most %s signals (`maxOnNextSignalsInRecursionTest()`) or completion", +776 oneMoreThanBoundedLimit); +777 runCompleted.expectClose(env.defaultTimeoutMillis(), msg); +778 env.verifyNoAsyncErrorsNoDelay(); +779 } finally { +780 // since the request/onNext recursive calls may keep the publisher running "forever", +781 // we MUST cancel it manually before exiting this test case +782 sub.cancel(); +783 } +784 } +785 }); +786 } +787 +788 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.4 +789 @Override @Test +790 public void untested_spec304_requestShouldNotPerformHeavyComputations() throws Exception { +791 notVerified(); // cannot be meaningfully tested, or can it? +792 } +793 +794 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.5 +795 @Override @Test +796 public void untested_spec305_cancelMustNotSynchronouslyPerformHeavyCompuatation() throws Exception { +797 notVerified(); // cannot be meaningfully tested, or can it? +798 } +799 +800 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.6 +801 @Override @Test +802 public void required_spec306_afterSubscriptionIsCancelledRequestMustBeNops() throws Throwable { +803 activePublisherTest(3, false, new PublisherTestRun<T>() { +804 @Override +805 public void run(Publisher<T> pub) throws Throwable { +806 +807 // override ManualSubscriberWithSubscriptionSupport#cancel because by default a ManualSubscriber will drop the +808 // subscription once it's cancelled (as expected). +809 // In this test however it must keep the cancelled Subscription and keep issuing `request(long)` to it. +810 ManualSubscriber<T> sub = new ManualSubscriberWithSubscriptionSupport<T>(env) { +811 @Override +812 public void cancel() { +813 if (subscription.isCompleted()) { +814 subscription.value().cancel(); +815 } else { +816 env.flop("Cannot cancel a subscription before having received it"); +817 } +818 } +819 }; +820 +821 env.subscribe(pub, sub); +822 +823 sub.cancel(); +824 sub.request(1); +825 sub.request(1); +826 sub.request(1); +827 +828 sub.expectNone(); +829 env.verifyNoAsyncErrorsNoDelay(); +830 } +831 }); +832 } +833 +834 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.7 +835 @Override @Test +836 public void required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops() throws Throwable { +837 activePublisherTest(1, false, new PublisherTestRun<T>() { +838 @Override +839 public void run(Publisher<T> pub) throws Throwable { +840 final ManualSubscriber<T> sub = env.newManualSubscriber(pub); +841 +842 // leak the Subscription +843 final Subscription subs = sub.subscription.value(); +844 +845 subs.cancel(); +846 subs.cancel(); +847 subs.cancel(); +848 +849 sub.expectNone(); +850 env.verifyNoAsyncErrorsNoDelay(); +851 } +852 }); +853 } +854 +855 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.9 +856 @Override @Test +857 public void required_spec309_requestZeroMustSignalIllegalArgumentException() throws Throwable { +858 activePublisherTest(10, false, new PublisherTestRun<T>() { +859 @Override public void run(Publisher<T> pub) throws Throwable { +860 final ManualSubscriber<T> sub = env.newManualSubscriber(pub); +861 sub.request(0); +862 sub.expectErrorWithMessage(IllegalArgumentException.class, "3.9"); // we do require implementations to mention the rule number at the very least +863 } +864 }); +865 } +866 +867 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.9 +868 @Override @Test +869 public void required_spec309_requestNegativeNumberMustSignalIllegalArgumentException() throws Throwable { +870 activePublisherTest(10, false, new PublisherTestRun<T>() { +871 @Override +872 public void run(Publisher<T> pub) throws Throwable { +873 final ManualSubscriber<T> sub = env.newManualSubscriber(pub); +874 final Random r = new Random(); +875 sub.request(-r.nextInt(Integer.MAX_VALUE)); +876 sub.expectErrorWithMessage(IllegalArgumentException.class, "3.9"); // we do require implementations to mention the rule number at the very least +877 } +878 }); +879 } +880 +881 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.12 +882 @Override @Test +883 public void required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling() throws Throwable { +884 // the publisher is able to signal more elements than the subscriber will be requesting in total +885 final int publisherElements = 20; +886 +887 final int demand1 = 10; +888 final int demand2 = 5; +889 final int totalDemand = demand1 + demand2; +890 +891 activePublisherTest(publisherElements, false, new PublisherTestRun<T>() { +892 @Override @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +893 public void run(Publisher<T> pub) throws Throwable { +894 final ManualSubscriber<T> sub = env.newManualSubscriber(pub); +895 +896 sub.request(demand1); +897 sub.request(demand2); +898 +899 /* +900 NOTE: The order of the nextElement/cancel calls below is very important (!) +901 +902 If this ordering was reversed, given an asynchronous publisher, +903 the following scenario would be *legal* and would break this test: +904 +905 > AsyncPublisher receives request(10) - it does not emit data right away, it's asynchronous +906 > AsyncPublisher receives request(5) - demand is now 15 +907 ! AsyncPublisher didn't emit any onNext yet (!) +908 > AsyncPublisher receives cancel() - handles it right away, by "stopping itself" for example +909 ! cancel was handled hefore the AsyncPublisher ever got the chance to emit data +910 ! the subscriber ends up never receiving even one element - the test is stuck (and fails, even on valid Publisher) +911 +912 Which is why we must first expect an element, and then cancel, once the producing is "running". +913 */ +914 sub.nextElement(); +915 sub.cancel(); +916 +917 int onNextsSignalled = 1; +918 +919 boolean stillBeingSignalled; +920 do { +921 // put asyncError if onNext signal received +922 sub.expectNone(); +923 Throwable error = env.dropAsyncError(); +924 +925 if (error == null) { +926 stillBeingSignalled = false; +927 } else { +928 onNextsSignalled += 1; +929 stillBeingSignalled = true; +930 } +931 +932 // if the Publisher tries to emit more elements than was requested (and/or ignores cancellation) this will throw +933 assertTrue(onNextsSignalled <= totalDemand, +934 String.format("Publisher signalled [%d] elements, which is more than the signalled demand: %d", +935 onNextsSignalled, totalDemand)); +936 +937 } while (stillBeingSignalled); +938 } +939 }); +940 +941 env.verifyNoAsyncErrorsNoDelay(); +942 } +943 +944 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.13 +945 @Override @Test +946 public void required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber() throws Throwable { +947 final ReferenceQueue<ManualSubscriber<T>> queue = new ReferenceQueue<ManualSubscriber<T>>(); +948 +949 final Function<Publisher<T>, WeakReference<ManualSubscriber<T>>> run = new Function<Publisher<T>, WeakReference<ManualSubscriber<T>>>() { +950 @Override +951 public WeakReference<ManualSubscriber<T>> apply(Publisher<T> pub) throws Exception { +952 final ManualSubscriber<T> sub = env.newManualSubscriber(pub); +953 final WeakReference<ManualSubscriber<T>> ref = new WeakReference<ManualSubscriber<T>>(sub, queue); +954 +955 sub.request(1); +956 sub.nextElement(); +957 sub.cancel(); +958 +959 return ref; +960 } +961 }; +962 +963 activePublisherTest(3, false, new PublisherTestRun<T>() { +964 @Override +965 public void run(Publisher<T> pub) throws Throwable { +966 final WeakReference<ManualSubscriber<T>> ref = run.apply(pub); +967 +968 // cancel may be run asynchronously so we add a sleep before running the GC +969 // to "resolve" the race +970 Thread.sleep(publisherReferenceGCTimeoutMillis); +971 System.gc(); +972 +973 if (!ref.equals(queue.remove(100))) { +974 env.flop(String.format("Publisher %s did not drop reference to test subscriber after subscription cancellation", pub)); +975 } +976 +977 env.verifyNoAsyncErrorsNoDelay(); +978 } +979 }); +980 } +981 +982 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.17 +983 @Override @Test +984 public void required_spec317_mustSupportAPendingElementCountUpToLongMaxValue() throws Throwable { +985 final int totalElements = 3; +986 +987 activePublisherTest(totalElements, true, new PublisherTestRun<T>() { +988 @Override +989 public void run(Publisher<T> pub) throws Throwable { +990 ManualSubscriber<T> sub = env.newManualSubscriber(pub); +991 sub.request(Long.MAX_VALUE); +992 +993 sub.nextElements(totalElements); +994 sub.expectCompletion(); +995 +996 env.verifyNoAsyncErrorsNoDelay(); +997 } +998 }); +999 } +1000 +1001 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.17 +1002 @Override @Test +1003 public void required_spec317_mustSupportACumulativePendingElementCountUpToLongMaxValue() throws Throwable { +1004 final int totalElements = 3; +1005 +1006 activePublisherTest(totalElements, true, new PublisherTestRun<T>() { +1007 @Override +1008 public void run(Publisher<T> pub) throws Throwable { +1009 final ManualSubscriber<T> sub = env.newManualSubscriber(pub); +1010 sub.request(Long.MAX_VALUE / 2); // pending = Long.MAX_VALUE / 2 +1011 sub.request(Long.MAX_VALUE / 2); // pending = Long.MAX_VALUE - 1 +1012 sub.request(1); // pending = Long.MAX_VALUE +1013 +1014 sub.nextElements(totalElements); +1015 sub.expectCompletion(); +1016 +1017 try { +1018 env.verifyNoAsyncErrorsNoDelay(); +1019 } finally { +1020 sub.cancel(); +1021 } +1022 +1023 } +1024 }); +1025 } +1026 +1027 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.17 +1028 @Override @Test +1029 public void required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue() throws Throwable { +1030 activePublisherTest(Integer.MAX_VALUE, false, new PublisherTestRun<T>() { +1031 @Override public void run(Publisher<T> pub) throws Throwable { +1032 final ManualSubscriberWithSubscriptionSupport<T> sub = new BlackholeSubscriberWithSubscriptionSupport<T>(env) { +1033 // arbitrarily set limit on nuber of request calls signalled, we expect overflow after already 2 calls, +1034 // so 10 is relatively high and safe even if arbitrarily chosen +1035 int callsCounter = 10; +1036 +1037 @Override +1038 public void onNext(T element) { +1039 env.debug(String.format("%s::onNext(%s)", this, element)); +1040 if (subscription.isCompleted()) { +1041 if (callsCounter > 0) { +1042 subscription.value().request(Long.MAX_VALUE - 1); +1043 callsCounter--; +1044 } else { +1045 subscription.value().cancel(); +1046 } +1047 } else { +1048 env.flop(String.format("Subscriber::onNext(%s) called before Subscriber::onSubscribe", element)); +1049 } +1050 } +1051 }; +1052 env.subscribe(pub, sub, env.defaultTimeoutMillis()); +1053 +1054 // eventually triggers `onNext`, which will then trigger up to `callsCounter` times `request(Long.MAX_VALUE - 1)` +1055 // we're pretty sure to overflow from those +1056 sub.request(1); +1057 +1058 // no onError should be signalled +1059 try { +1060 env.verifyNoAsyncErrors(env.defaultTimeoutMillis()); +1061 } finally { +1062 sub.cancel(); +1063 } +1064 } +1065 }); +1066 } +1067 +1068 ///////////////////// ADDITIONAL "COROLLARY" TESTS //////////////////////// +1069 +1070 ///////////////////// TEST INFRASTRUCTURE ///////////////////////////////// +1071 +1072 public interface PublisherTestRun<T> { +1073 public void run(Publisher<T> pub) throws Throwable; +1074 } +1075 +1076 /** +1077 * Test for feature that SHOULD/MUST be implemented, using a live publisher. +1078 * +1079 * @param elements the number of elements the Publisher under test must be able to emit to run this test +1080 * @param completionSignalRequired true if an {@code onComplete} signal is required by this test to run. +1081 * If the tested Publisher is unable to signal completion, tests requireing onComplete signals will be skipped. +1082 * To signal if your Publisher is able to signal completion see {@link PublisherVerification#maxElementsFromPublisher()}. +1083 */ +1084 public void activePublisherTest(long elements, boolean completionSignalRequired, PublisherTestRun<T> body) throws Throwable { +1085 if (elements > maxElementsFromPublisher()) { +1086 throw new SkipException(String.format("Unable to run this test, as required elements nr: %d is higher than supported by given producer: %d", elements, maxElementsFromPublisher())); +1087 } else if (completionSignalRequired && maxElementsFromPublisher() == Long.MAX_VALUE) { +1088 throw new SkipException("Unable to run this test, as it requires an onComplete signal, " + +1089 "which this Publisher is unable to provide (as signalled by returning Long.MAX_VALUE from `maxElementsFromPublisher()`)"); +1090 } else { +1091 Publisher<T> pub = createPublisher(elements); +1092 body.run(pub); +1093 env.verifyNoAsyncErrorsNoDelay(); +1094 } +1095 } +1096 +1097 /** +1098 * Test for feature that MAY be implemented. This test will be marked as SKIPPED if it fails. +1099 * +1100 * @param elements the number of elements the Publisher under test must be able to emit to run this test +1101 * @param completionSignalRequired true if an {@code onComplete} signal is required by this test to run. +1102 * If the tested Publisher is unable to signal completion, tests requireing onComplete signals will be skipped. +1103 * To signal if your Publisher is able to signal completion see {@link PublisherVerification#maxElementsFromPublisher()}. +1104 */ +1105 public void optionalActivePublisherTest(long elements, boolean completionSignalRequired, PublisherTestRun<T> body) throws Throwable { +1106 if (elements > maxElementsFromPublisher()) { +1107 throw new SkipException(String.format("Unable to run this test, as required elements nr: %d is higher than supported by given producer: %d", elements, maxElementsFromPublisher())); +1108 } else if (completionSignalRequired && maxElementsFromPublisher() == Long.MAX_VALUE) { +1109 throw new SkipException("Unable to run this test, as it requires an onComplete signal, " + +1110 "which this Publisher is unable to provide (as signalled by returning Long.MAX_VALUE from `maxElementsFromPublisher()`)"); +1111 } else { +1112 +1113 final Publisher<T> pub = createPublisher(elements); +1114 final String skipMessage = "Skipped because tested publisher does NOT implement this OPTIONAL requirement."; +1115 +1116 try { +1117 potentiallyPendingTest(pub, body); +1118 } catch (Exception ex) { +1119 notVerified(skipMessage); +1120 } catch (AssertionError ex) { +1121 notVerified(skipMessage + " Reason for skipping was: " + ex.getMessage()); +1122 } +1123 } +1124 } +1125 +1126 public static final String SKIPPING_NO_ERROR_PUBLISHER_AVAILABLE = +1127 "Skipping because no error state Publisher provided, and the test requires it. " + +1128 "Please implement PublisherVerification#createFailedPublisher to run this test."; +1129 +1130 public static final String SKIPPING_OPTIONAL_TEST_FAILED = +1131 "Skipping, because provided Publisher does not pass this *additional* verification."; +1132 /** +1133 * Additional test for Publisher in error state +1134 */ +1135 public void whenHasErrorPublisherTest(PublisherTestRun<T> body) throws Throwable { +1136 potentiallyPendingTest(createFailedPublisher(), body, SKIPPING_NO_ERROR_PUBLISHER_AVAILABLE); +1137 } +1138 +1139 public void potentiallyPendingTest(Publisher<T> pub, PublisherTestRun<T> body) throws Throwable { +1140 potentiallyPendingTest(pub, body, SKIPPING_OPTIONAL_TEST_FAILED); +1141 } +1142 +1143 public void potentiallyPendingTest(Publisher<T> pub, PublisherTestRun<T> body, String message) throws Throwable { +1144 if (pub != null) { +1145 body.run(pub); +1146 } else { +1147 throw new SkipException(message); +1148 } +1149 } +1150 +1151 /** +1152 * Executes a given test body {@code n} times. +1153 * All the test runs must pass in order for the stochastic test to pass. +1154 */ +1155 public void stochasticTest(int n, Function<Integer, Void> body) throws Throwable { +1156 if (skipStochasticTests()) { +1157 notVerified("Skipping @Stochastic test because `skipStochasticTests()` returned `true`!"); +1158 } +1159 +1160 for (int i = 0; i < n; i++) { +1161 body.apply(i); +1162 } +1163 } +1164 +1165 public void notVerified() { +1166 throw new SkipException("Not verified by this TCK."); +1167 } +1168 +1169 /** +1170 * Return this value from {@link PublisherVerification#maxElementsFromPublisher()} to mark that the given {@link org.reactivestreams.Publisher}, +1171 * is not able to signal completion. For example it is strictly a time-bound or unbounded source of data. +1172 * +1173 * <b>Returning this value from {@link PublisherVerification#maxElementsFromPublisher()} will result in skipping all TCK tests which require onComplete signals!</b> +1174 */ +1175 public long publisherUnableToSignalOnComplete() { +1176 return Long.MAX_VALUE; +1177 } +1178 +1179 public void notVerified(String message) { +1180 throw new SkipException(message); +1181 } +1182 +1183} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck; +002 +003import org.reactivestreams.Publisher; +004import org.reactivestreams.Subscriber; +005import org.reactivestreams.Subscription; +006import org.reactivestreams.tck.TestEnvironment.BlackholeSubscriberWithSubscriptionSupport; +007import org.reactivestreams.tck.TestEnvironment.Latch; +008import org.reactivestreams.tck.TestEnvironment.ManualSubscriber; +009import org.reactivestreams.tck.TestEnvironment.ManualSubscriberWithSubscriptionSupport; +010import org.reactivestreams.tck.support.Function; +011import org.reactivestreams.tck.support.Optional; +012import org.reactivestreams.tck.support.PublisherVerificationRules; +013import org.testng.SkipException; +014import org.testng.annotations.BeforeMethod; +015import org.testng.annotations.Test; +016 +017import java.lang.Override; +018import java.lang.ref.ReferenceQueue; +019import java.lang.ref.WeakReference; +020import java.util.ArrayList; +021import java.util.Arrays; +022import java.util.Collections; +023import java.util.List; +024import java.util.Random; +025import java.util.concurrent.atomic.AtomicReference; +026 +027import static org.testng.Assert.assertEquals; +028import static org.testng.Assert.assertTrue; +029 +030/** +031 * Provides tests for verifying {@code Publisher} specification rules. +032 * +033 * @see org.reactivestreams.Publisher +034 */ +035public abstract class PublisherVerification<T> implements PublisherVerificationRules { +036 +037 private static final String PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS_ENV = "PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS"; +038 private static final long DEFAULT_PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS = 300L; +039 +040 private final TestEnvironment env; +041 +042 /** +043 * The amount of time after which a cancelled Subscriber reference should be dropped. +044 * See Rule 3.13 for details. +045 */ +046 private final long publisherReferenceGCTimeoutMillis; +047 +048 /** +049 * Constructs a new verification class using the given env and configuration. +050 * +051 * @param publisherReferenceGCTimeoutMillis used to determine after how much time a reference to a Subscriber should be already dropped by the Publisher. +052 */ +053 public PublisherVerification(TestEnvironment env, long publisherReferenceGCTimeoutMillis) { +054 this.env = env; +055 this.publisherReferenceGCTimeoutMillis = publisherReferenceGCTimeoutMillis; +056 } +057 +058 /** +059 * Constructs a new verification class using the given env and configuration. +060 * +061 * The value for {@code publisherReferenceGCTimeoutMillis} will be obtained by using {@link PublisherVerification#envPublisherReferenceGCTimeoutMillis()}. +062 */ +063 public PublisherVerification(TestEnvironment env) { +064 this.env = env; +065 this.publisherReferenceGCTimeoutMillis = envPublisherReferenceGCTimeoutMillis(); +066 } +067 +068 /** +069 * Tries to parse the env variable {@code PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS} as long and returns the value if present, +070 * OR its default value ({@link PublisherVerification#DEFAULT_PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS}). +071 * +072 * This value is used to determine after how much time a reference to a Subscriber should be already dropped by the Publisher. +073 * +074 * @throws java.lang.IllegalArgumentException when unable to parse the env variable +075 */ +076 public static long envPublisherReferenceGCTimeoutMillis() { +077 final String envMillis = System.getenv(PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS_ENV); +078 if (envMillis == null) return DEFAULT_PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS; +079 else try { +080 return Long.parseLong(envMillis); +081 } catch(NumberFormatException ex) { +082 throw new IllegalArgumentException(String.format("Unable to parse %s env value [%s] as long!", PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS_ENV, envMillis), ex); +083 } +084 } +085 +086 /** +087 * This is the main method you must implement in your test incarnation. +088 * It must create a Publisher for a stream with exactly the given number of elements. +089 * If `elements` is `Long.MAX_VALUE` the produced stream must be infinite. +090 */ +091 public abstract Publisher<T> createPublisher(long elements); +092 +093 /** +094 * By implementing this method, additional TCK tests concerning a "failed" publishers will be run. +095 * +096 * The expected behaviour of the {@link Publisher} returned by this method is hand out a subscription, +097 * followed by signalling {@code onError} on it, as specified by Rule 1.9. +098 * +099 * If you ignore these additional tests, return {@code null} from this method. +100 */ +101 public abstract Publisher<T> createFailedPublisher(); +102 +103 +104 /** +105 * Override and return lower value if your Publisher is only able to produce a known number of elements. +106 * For example, if it is designed to return at-most-one element, return {@code 1} from this method. +107 * +108 * Defaults to {@code Long.MAX_VALUE - 1}, meaning that the Publisher can be produce a huge but NOT an unbounded number of elements. +109 * +110 * To mark your Publisher will *never* signal an {@code onComplete} override this method and return {@code Long.MAX_VALUE}, +111 * which will result in *skipping all tests which require an onComplete to be triggered* (!). +112 */ +113 public long maxElementsFromPublisher() { +114 return Long.MAX_VALUE - 1; +115 } +116 +117 /** +118 * Override and return {@code true} in order to skip executing tests marked as {@code Stochastic}. +119 * Such tests MAY sometimes fail even though the impl +120 */ +121 public boolean skipStochasticTests() { +122 return false; +123 } +124 +125 /** +126 * In order to verify rule 3.3 of the reactive streams spec, this number will be used to check if a +127 * {@code Subscription} actually solves the "unbounded recursion" problem by not allowing the number of +128 * recursive calls to exceed the number returned by this method. +129 * +130 * @see <a href="https://github.com/reactive-streams/reactive-streams-jvm#3.3">reactive streams spec, rule 3.3</a> +131 * @see PublisherVerification#required_spec303_mustNotAllowUnboundedRecursion() +132 */ +133 public long boundedDepthOfOnNextAndRequestRecursion() { +134 return 1; +135 } +136 +137 ////////////////////// TEST ENV CLEANUP ///////////////////////////////////// +138 +139 @BeforeMethod +140 public void setUp() throws Exception { +141 env.clearAsyncErrors(); +142 } +143 +144 ////////////////////// TEST SETUP VERIFICATION ////////////////////////////// +145 +146 @Override @Test +147 public void required_createPublisher1MustProduceAStreamOfExactly1Element() throws Throwable { +148 activePublisherTest(1, true, new PublisherTestRun<T>() { +149 @Override +150 public void run(Publisher<T> pub) throws InterruptedException { +151 ManualSubscriber<T> sub = env.newManualSubscriber(pub); +152 assertTrue(requestNextElementOrEndOfStream(pub, sub).isDefined(), String.format("Publisher %s produced no elements", pub)); +153 sub.requestEndOfStream(); +154 } +155 +156 Optional<T> requestNextElementOrEndOfStream(Publisher<T> pub, ManualSubscriber<T> sub) throws InterruptedException { +157 return sub.requestNextElementOrEndOfStream(String.format("Timeout while waiting for next element from Publisher %s", pub)); +158 } +159 +160 }); +161 } +162 +163 @Override @Test +164 public void required_createPublisher3MustProduceAStreamOfExactly3Elements() throws Throwable { +165 activePublisherTest(3, true, new PublisherTestRun<T>() { +166 @Override +167 public void run(Publisher<T> pub) throws InterruptedException { +168 ManualSubscriber<T> sub = env.newManualSubscriber(pub); +169 assertTrue(requestNextElementOrEndOfStream(pub, sub).isDefined(), String.format("Publisher %s produced no elements", pub)); +170 assertTrue(requestNextElementOrEndOfStream(pub, sub).isDefined(), String.format("Publisher %s produced only 1 element", pub)); +171 assertTrue(requestNextElementOrEndOfStream(pub, sub).isDefined(), String.format("Publisher %s produced only 2 elements", pub)); +172 sub.requestEndOfStream(); +173 } +174 +175 Optional<T> requestNextElementOrEndOfStream(Publisher<T> pub, ManualSubscriber<T> sub) throws InterruptedException { +176 return sub.requestNextElementOrEndOfStream(String.format("Timeout while waiting for next element from Publisher %s", pub)); +177 } +178 +179 }); +180 } +181 +182 @Override @Test +183 public void required_validate_maxElementsFromPublisher() throws Exception { +184 assertTrue(maxElementsFromPublisher() >= 0, "maxElementsFromPublisher MUST return a number >= 0"); +185 } +186 +187 @Override @Test +188 public void required_validate_boundedDepthOfOnNextAndRequestRecursion() throws Exception { +189 assertTrue(boundedDepthOfOnNextAndRequestRecursion() >= 1, "boundedDepthOfOnNextAndRequestRecursion must return a number >= 1"); +190 } +191 +192 +193 ////////////////////// SPEC RULE VERIFICATION /////////////////////////////// +194 +195 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.1 +196 @Override @Test +197 public void required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements() throws Throwable { +198 activePublisherTest(5, false, new PublisherTestRun<T>() { +199 @Override +200 public void run(Publisher<T> pub) throws InterruptedException { +201 +202 ManualSubscriber<T> sub = env.newManualSubscriber(pub); +203 +204 sub.expectNone(String.format("Publisher %s produced value before the first `request`: ", pub)); +205 sub.request(1); +206 sub.nextElement(String.format("Publisher %s produced no element after first `request`", pub)); +207 sub.expectNone(String.format("Publisher %s produced unrequested: ", pub)); +208 +209 sub.request(1); +210 sub.request(2); +211 sub.nextElements(3, env.defaultTimeoutMillis(), String.format("Publisher %s produced less than 3 elements after two respective `request` calls", pub)); +212 +213 sub.expectNone(String.format("Publisher %sproduced unrequested ", pub)); +214 } +215 }); +216 } +217 +218 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.2 +219 @Override @Test +220 public void required_spec102_maySignalLessThanRequestedAndTerminateSubscription() throws Throwable { +221 final int elements = 3; +222 final int requested = 10; +223 +224 activePublisherTest(elements, true, new PublisherTestRun<T>() { +225 @Override +226 public void run(Publisher<T> pub) throws Throwable { +227 final ManualSubscriber<T> sub = env.newManualSubscriber(pub); +228 sub.request(requested); +229 sub.nextElements(elements); +230 sub.expectCompletion(); +231 } +232 }); +233 } +234 +235 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.3 +236 @Override @Test +237 public void stochastic_spec103_mustSignalOnMethodsSequentially() throws Throwable { +238 final int iterations = 100; +239 final int elements = 10; +240 +241 stochasticTest(iterations, new Function<Integer, Void>() { +242 @Override +243 public Void apply(final Integer runNumber) throws Throwable { +244 activePublisherTest(elements, true, new PublisherTestRun<T>() { +245 @Override +246 public void run(Publisher<T> pub) throws Throwable { +247 final Latch completionLatch = new Latch(env); +248 +249 pub.subscribe(new Subscriber<T>() { +250 private Subscription subs; +251 private long gotElements = 0; +252 +253 private ConcurrentAccessBarrier concurrentAccessBarrier = new ConcurrentAccessBarrier(); +254 +255 /** +256 * Concept wise very similar to a {@link org.reactivestreams.tck.TestEnvironment.Latch}, serves to protect +257 * a critical section from concurrent access, with the added benefit of Thread tracking and same-thread-access awareness. +258 * +259 * Since a <i>Synchronous</i> Publisher may choose to synchronously (using the same {@link Thread}) call +260 * {@code onNext} directly from either {@code subscribe} or {@code request} a plain Latch is not enough +261 * to verify concurrent access safety - one needs to track if the caller is not still using the calling thread +262 * to enter subsequent critical sections ("nesting" them effectively). +263 */ +264 final class ConcurrentAccessBarrier { +265 private AtomicReference<Thread> currentlySignallingThread = new AtomicReference<Thread>(null); +266 private volatile String previousSignal = null; +267 +268 public void enterSignal(String signalName) { +269 if((!currentlySignallingThread.compareAndSet(null, Thread.currentThread())) && !isSynchronousSignal()) { +270 env.flop(String.format( +271 "Illegal concurrent access detected (entering critical section)! " + +272 "%s emited %s signal, before %s finished its %s signal.", +273 Thread.currentThread(), signalName, currentlySignallingThread.get(), previousSignal)); +274 } +275 this.previousSignal = signalName; +276 } +277 +278 public void leaveSignal(String signalName) { +279 currentlySignallingThread.set(null); +280 this.previousSignal = signalName; +281 } +282 +283 private boolean isSynchronousSignal() { +284 return (previousSignal != null) && Thread.currentThread().equals(currentlySignallingThread.get()); +285 } +286 +287 } +288 +289 @Override +290 public void onSubscribe(Subscription s) { +291 final String signal = "onSubscribe()"; +292 concurrentAccessBarrier.enterSignal(signal); +293 +294 subs = s; +295 subs.request(1); +296 +297 concurrentAccessBarrier.leaveSignal(signal); +298 } +299 +300 @Override +301 public void onNext(T ignore) { +302 final String signal = String.format("onNext(%s)", ignore); +303 concurrentAccessBarrier.enterSignal(signal); +304 +305 gotElements += 1; +306 if (gotElements <= elements) // requesting one more than we know are in the stream (some Publishers need this) +307 subs.request(1); +308 +309 concurrentAccessBarrier.leaveSignal(signal); +310 } +311 +312 @Override +313 public void onError(Throwable t) { +314 final String signal = String.format("onError(%s)", t.getMessage()); +315 concurrentAccessBarrier.enterSignal(signal); +316 +317 // ignore value +318 +319 concurrentAccessBarrier.leaveSignal(signal); +320 } +321 +322 @Override +323 public void onComplete() { +324 final String signal = "onComplete()"; +325 concurrentAccessBarrier.enterSignal(signal); +326 +327 // entering for completeness +328 +329 concurrentAccessBarrier.leaveSignal(signal); +330 completionLatch.close(); +331 } +332 }); +333 +334 completionLatch.expectClose(elements * env.defaultTimeoutMillis(), "Expected 10 elements to be drained"); +335 } +336 }); +337 return null; +338 } +339 }); +340 } +341 +342 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.4 +343 @Override @Test +344 public void optional_spec104_mustSignalOnErrorWhenFails() throws Throwable { +345 try { +346 whenHasErrorPublisherTest(new PublisherTestRun<T>() { +347 @Override +348 public void run(final Publisher<T> pub) throws InterruptedException { +349 final Latch onErrorlatch = new Latch(env); +350 final Latch onSubscribeLatch = new Latch(env); +351 pub.subscribe(new TestEnvironment.TestSubscriber<T>(env) { +352 @Override +353 public void onSubscribe(Subscription subs) { +354 onSubscribeLatch.assertOpen("Only one onSubscribe call expected"); +355 onSubscribeLatch.close(); +356 } +357 @Override +358 public void onError(Throwable cause) { +359 onSubscribeLatch.assertClosed("onSubscribe should be called prior to onError always"); +360 onErrorlatch.assertOpen(String.format("Error-state Publisher %s called `onError` twice on new Subscriber", pub)); +361 onErrorlatch.close(); +362 } +363 }); +364 +365 onSubscribeLatch.expectClose("Should have received onSubscribe"); +366 onErrorlatch.expectClose(String.format("Error-state Publisher %s did not call `onError` on new Subscriber", pub)); +367 +368 env.verifyNoAsyncErrors(env.defaultTimeoutMillis()); +369 } +370 }); +371 } catch (SkipException se) { +372 throw se; +373 } catch (Throwable ex) { +374 // we also want to catch AssertionErrors and anything the publisher may have thrown inside subscribe +375 // which was wrong of him - he should have signalled on error using onError +376 throw new RuntimeException(String.format("Publisher threw exception (%s) instead of signalling error via onError!", ex.getMessage()), ex); +377 } +378 } +379 +380 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.5 +381 @Override @Test +382 public void required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates() throws Throwable { +383 activePublisherTest(3, true, new PublisherTestRun<T>() { +384 @Override +385 public void run(Publisher<T> pub) throws Throwable { +386 ManualSubscriber<T> sub = env.newManualSubscriber(pub); +387 sub.requestNextElement(); +388 sub.requestNextElement(); +389 sub.requestNextElement(); +390 sub.requestEndOfStream(); +391 sub.expectNone(); +392 } +393 }); +394 } +395 +396 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.5 +397 @Override @Test +398 public void optional_spec105_emptyStreamMustTerminateBySignallingOnComplete() throws Throwable { +399 optionalActivePublisherTest(0, true, new PublisherTestRun<T>() { +400 @Override +401 public void run(Publisher<T> pub) throws Throwable { +402 ManualSubscriber<T> sub = env.newManualSubscriber(pub); +403 sub.request(1); +404 sub.expectCompletion(); +405 sub.expectNone(); +406 } +407 }); +408 } +409 +410 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.6 +411 @Override @Test +412 public void untested_spec106_mustConsiderSubscriptionCancelledAfterOnErrorOrOnCompleteHasBeenCalled() throws Throwable { +413 notVerified(); // not really testable without more control over the Publisher +414 } +415 +416 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.7 +417 @Override @Test +418 public void required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled() throws Throwable { +419 activePublisherTest(1, true, new PublisherTestRun<T>() { +420 @Override +421 public void run(Publisher<T> pub) throws Throwable { +422 ManualSubscriber<T> sub = env.newManualSubscriber(pub); +423 sub.request(10); +424 sub.nextElement(); +425 sub.expectCompletion(); +426 +427 sub.request(10); +428 sub.expectNone(); +429 } +430 }); +431 } +432 +433 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.7 +434 @Override @Test +435 public void untested_spec107_mustNotEmitFurtherSignalsOnceOnErrorHasBeenSignalled() throws Throwable { +436 notVerified(); // can we meaningfully test this, without more control over the publisher? +437 } +438 +439 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.8 +440 @Override @Test +441 public void untested_spec108_possiblyCanceledSubscriptionShouldNotReceiveOnErrorOrOnCompleteSignals() throws Throwable { +442 notVerified(); // can we meaningfully test this? +443 } +444 +445 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.9 +446 @Override @Test +447 public void untested_spec109_subscribeShouldNotThrowNonFatalThrowable() throws Throwable { +448 notVerified(); // can we meaningfully test this? +449 } +450 +451 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.9 +452 @Override @Test +453 public void required_spec109_subscribeThrowNPEOnNullSubscriber() throws Throwable { +454 activePublisherTest(0, false, new PublisherTestRun<T>() { +455 @Override +456 public void run(Publisher<T> pub) throws Throwable { +457 try { +458 pub.subscribe(null); +459 env.flop("Publisher did not throw a NullPointerException when given a null Subscribe in subscribe"); +460 } catch (NullPointerException ignored) { +461 // valid behaviour +462 } +463 env.verifyNoAsyncErrorsNoDelay(); +464 } +465 }); +466 } +467 +468 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.9 +469 @Override @Test +470 public void required_spec109_mustIssueOnSubscribeForNonNullSubscriber() throws Throwable { +471 activePublisherTest(0, false, new PublisherTestRun<T>() { +472 @Override +473 public void run(Publisher<T> pub) throws Throwable { +474 final Latch onSubscribeLatch = new Latch(env); +475 pub.subscribe(new Subscriber<T>() { +476 @Override +477 public void onError(Throwable cause) { +478 onSubscribeLatch.assertClosed("onSubscribe should be called prior to onError always"); +479 } +480 +481 @Override +482 public void onSubscribe(Subscription subs) { +483 onSubscribeLatch.assertOpen("Only one onSubscribe call expected"); +484 onSubscribeLatch.close(); +485 } +486 +487 @Override +488 public void onNext(T elem) { +489 onSubscribeLatch.assertClosed("onSubscribe should be called prior to onNext always"); +490 } +491 +492 @Override +493 public void onComplete() { +494 onSubscribeLatch.assertClosed("onSubscribe should be called prior to onComplete always"); +495 } +496 }); +497 onSubscribeLatch.expectClose("Should have received onSubscribe"); +498 env.verifyNoAsyncErrorsNoDelay(); +499 } +500 }); +501 } +502 +503 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.9 +504 @Override @Test +505 public void required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe() throws Throwable { +506 whenHasErrorPublisherTest(new PublisherTestRun<T>() { +507 @Override +508 public void run(Publisher<T> pub) throws Throwable { +509 final Latch onErrorLatch = new Latch(env); +510 final Latch onSubscribeLatch = new Latch(env); +511 ManualSubscriberWithSubscriptionSupport<T> sub = new ManualSubscriberWithSubscriptionSupport<T>(env) { +512 @Override +513 public void onError(Throwable cause) { +514 onSubscribeLatch.assertClosed("onSubscribe should be called prior to onError always"); +515 onErrorLatch.assertOpen("Only one onError call expected"); +516 onErrorLatch.close(); +517 } +518 +519 @Override +520 public void onSubscribe(Subscription subs) { +521 onSubscribeLatch.assertOpen("Only one onSubscribe call expected"); +522 onSubscribeLatch.close(); +523 } +524 }; +525 pub.subscribe(sub); +526 onSubscribeLatch.expectClose("Should have received onSubscribe"); +527 onErrorLatch.expectClose("Should have received onError"); +528 +529 env.verifyNoAsyncErrorsNoDelay(); +530 } +531 }); +532 } +533 +534 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.10 +535 @Override @Test +536 public void untested_spec110_rejectASubscriptionRequestIfTheSameSubscriberSubscribesTwice() throws Throwable { +537 notVerified(); // can we meaningfully test this? +538 } +539 +540 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.11 +541 @Override @Test +542 public void optional_spec111_maySupportMultiSubscribe() throws Throwable { +543 optionalActivePublisherTest(1, false, new PublisherTestRun<T>() { +544 @Override +545 public void run(Publisher<T> pub) throws Throwable { +546 ManualSubscriber<T> sub1 = env.newManualSubscriber(pub); +547 ManualSubscriber<T> sub2 = env.newManualSubscriber(pub); +548 +549 env.verifyNoAsyncErrors(); +550 } +551 }); +552 } +553 +554 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.11 +555 @Override @Test +556 public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingOneByOne() throws Throwable { +557 optionalActivePublisherTest(5, true, new PublisherTestRun<T>() { // This test is skipped if the publisher is unbounded (never sends onComplete) +558 @Override +559 public void run(Publisher<T> pub) throws InterruptedException { +560 ManualSubscriber<T> sub1 = env.newManualSubscriber(pub); +561 ManualSubscriber<T> sub2 = env.newManualSubscriber(pub); +562 ManualSubscriber<T> sub3 = env.newManualSubscriber(pub); +563 +564 sub1.request(1); +565 T x1 = sub1.nextElement(String.format("Publisher %s did not produce the requested 1 element on 1st subscriber", pub)); +566 sub2.request(2); +567 List<T> y1 = sub2.nextElements(2, String.format("Publisher %s did not produce the requested 2 elements on 2nd subscriber", pub)); +568 sub1.request(1); +569 T x2 = sub1.nextElement(String.format("Publisher %s did not produce the requested 1 element on 1st subscriber", pub)); +570 sub3.request(3); +571 List<T> z1 = sub3.nextElements(3, String.format("Publisher %s did not produce the requested 3 elements on 3rd subscriber", pub)); +572 sub3.request(1); +573 T z2 = sub3.nextElement(String.format("Publisher %s did not produce the requested 1 element on 3rd subscriber", pub)); +574 sub3.request(1); +575 T z3 = sub3.nextElement(String.format("Publisher %s did not produce the requested 1 element on 3rd subscriber", pub)); +576 sub3.requestEndOfStream(String.format("Publisher %s did not complete the stream as expected on 3rd subscriber", pub)); +577 sub2.request(3); +578 List<T> y2 = sub2.nextElements(3, String.format("Publisher %s did not produce the requested 3 elements on 2nd subscriber", pub)); +579 sub2.requestEndOfStream(String.format("Publisher %s did not complete the stream as expected on 2nd subscriber", pub)); +580 sub1.request(2); +581 List<T> x3 = sub1.nextElements(2, String.format("Publisher %s did not produce the requested 2 elements on 1st subscriber", pub)); +582 sub1.request(1); +583 T x4 = sub1.nextElement(String.format("Publisher %s did not produce the requested 1 element on 1st subscriber", pub)); +584 sub1.requestEndOfStream(String.format("Publisher %s did not complete the stream as expected on 1st subscriber", pub)); +585 +586 @SuppressWarnings("unchecked") +587 List<T> r = new ArrayList<T>(Arrays.asList(x1, x2)); +588 r.addAll(x3); +589 r.addAll(Collections.singleton(x4)); +590 +591 List<T> check1 = new ArrayList<T>(y1); +592 check1.addAll(y2); +593 +594 //noinspection unchecked +595 List<T> check2 = new ArrayList<T>(z1); +596 check2.add(z2); +597 check2.add(z3); +598 +599 assertEquals(r, check1, String.format("Publisher %s did not produce the same element sequence for subscribers 1 and 2", pub)); +600 assertEquals(r, check2, String.format("Publisher %s did not produce the same element sequence for subscribers 1 and 3", pub)); +601 } +602 }); +603 } +604 +605 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.11 +606 @Override @Test +607 public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfront() throws Throwable { +608 optionalActivePublisherTest(3, false, new PublisherTestRun<T>() { // This test is skipped if the publisher cannot produce enough elements +609 @Override +610 public void run(Publisher<T> pub) throws Throwable { +611 ManualSubscriber<T> sub1 = env.newManualSubscriber(pub); +612 ManualSubscriber<T> sub2 = env.newManualSubscriber(pub); +613 ManualSubscriber<T> sub3 = env.newManualSubscriber(pub); +614 +615 List<T> received1 = new ArrayList<T>(); +616 List<T> received2 = new ArrayList<T>(); +617 List<T> received3 = new ArrayList<T>(); +618 +619 // if the publisher must touch it's source to notice it's been drained, the OnComplete won't come until we ask for more than it actually contains... +620 // edgy edge case? +621 sub1.request(4); +622 sub2.request(4); +623 sub3.request(4); +624 +625 received1.addAll(sub1.nextElements(3)); +626 received2.addAll(sub2.nextElements(3)); +627 received3.addAll(sub3.nextElements(3)); +628 +629 // NOTE: can't check completion, the Publisher may not be able to signal it +630 // a similar test *with* completion checking is implemented +631 +632 assertEquals(received1, received2, String.format("Expected elements to be signaled in the same sequence to 1st and 2nd subscribers")); +633 assertEquals(received2, received3, String.format("Expected elements to be signaled in the same sequence to 2nd and 3rd subscribers")); +634 } +635 }); +636 } +637 +638 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.11 +639 @Override @Test +640 public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfrontAndCompleteAsExpected() throws Throwable { +641 optionalActivePublisherTest(3, true, new PublisherTestRun<T>() { // This test is skipped if the publisher is unbounded (never sends onComplete) +642 @Override +643 public void run(Publisher<T> pub) throws Throwable { +644 ManualSubscriber<T> sub1 = env.newManualSubscriber(pub); +645 ManualSubscriber<T> sub2 = env.newManualSubscriber(pub); +646 ManualSubscriber<T> sub3 = env.newManualSubscriber(pub); +647 +648 List<T> received1 = new ArrayList<T>(); +649 List<T> received2 = new ArrayList<T>(); +650 List<T> received3 = new ArrayList<T>(); +651 +652 // if the publisher must touch it's source to notice it's been drained, the OnComplete won't come until we ask for more than it actually contains... +653 // edgy edge case? +654 sub1.request(4); +655 sub2.request(4); +656 sub3.request(4); +657 +658 received1.addAll(sub1.nextElements(3)); +659 received2.addAll(sub2.nextElements(3)); +660 received3.addAll(sub3.nextElements(3)); +661 +662 sub1.expectCompletion(); +663 sub2.expectCompletion(); +664 sub3.expectCompletion(); +665 +666 assertEquals(received1, received2, String.format("Expected elements to be signaled in the same sequence to 1st and 2nd subscribers")); +667 assertEquals(received2, received3, String.format("Expected elements to be signaled in the same sequence to 2nd and 3rd subscribers")); +668 } +669 }); +670 } +671 +672 ///////////////////// SUBSCRIPTION TESTS ////////////////////////////////// +673 +674 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.2 +675 @Override @Test +676 public void required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe() throws Throwable { +677 activePublisherTest(6, false, new PublisherTestRun<T>() { +678 @Override +679 public void run(Publisher<T> pub) throws Throwable { +680 ManualSubscriber<T> sub = new ManualSubscriber<T>(env) { +681 @Override +682 public void onSubscribe(Subscription subs) { +683 this.subscription.completeImmediatly(subs); +684 +685 subs.request(1); +686 subs.request(1); +687 subs.request(1); +688 } +689 +690 @Override +691 public void onNext(T element) { +692 Subscription subs = this.subscription.value(); +693 subs.request(1); +694 } +695 }; +696 +697 env.subscribe(pub, sub); +698 +699 long delay = env.defaultTimeoutMillis(); +700 env.verifyNoAsyncErrors(delay); +701 } +702 }); +703 } +704 +705 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.3 +706 @Override @Test +707 public void required_spec303_mustNotAllowUnboundedRecursion() throws Throwable { +708 final long oneMoreThanBoundedLimit = boundedDepthOfOnNextAndRequestRecursion() + 1; +709 +710 activePublisherTest(oneMoreThanBoundedLimit, false, new PublisherTestRun<T>() { +711 @Override +712 public void run(Publisher<T> pub) throws Throwable { +713 final ThreadLocal<Long> stackDepthCounter = new ThreadLocal<Long>() { +714 @Override +715 protected Long initialValue() { +716 return 0L; +717 } +718 }; +719 +720 final Latch runCompleted = new Latch(env); +721 +722 final ManualSubscriber<T> sub = new ManualSubscriberWithSubscriptionSupport<T>(env) { +723 // counts the number of signals received, used to break out from possibly infinite request/onNext loops +724 long signalsReceived = 0L; +725 +726 @Override +727 public void onNext(T element) { +728 // NOT calling super.onNext as this test only cares about stack depths, not the actual values of elements +729 // which also simplifies this test as we do not have to drain the test buffer, which would otherwise be in danger of overflowing +730 +731 signalsReceived += 1; +732 stackDepthCounter.set(stackDepthCounter.get() + 1); +733 env.debug(String.format("%s(recursion depth: %d)::onNext(%s)", this, stackDepthCounter.get(), element)); +734 +735 final long callsUntilNow = stackDepthCounter.get(); +736 if (callsUntilNow > boundedDepthOfOnNextAndRequestRecursion()) { +737 env.flop(String.format("Got %d onNext calls within thread: %s, yet expected recursive bound was %d", +738 callsUntilNow, Thread.currentThread(), boundedDepthOfOnNextAndRequestRecursion())); +739 +740 // stop the recursive call chain +741 runCompleted.close(); +742 return; +743 } else if (signalsReceived >= oneMoreThanBoundedLimit) { +744 // since max number of signals reached, and recursion depth not exceeded, we judge this as a success and +745 // stop the recursive call chain +746 runCompleted.close(); +747 return; +748 } +749 +750 // request more right away, the Publisher must break the recursion +751 subscription.value().request(1); +752 +753 stackDepthCounter.set(stackDepthCounter.get() - 1); +754 } +755 +756 @Override +757 public void onComplete() { +758 super.onComplete(); +759 runCompleted.close(); +760 } +761 +762 @Override +763 public void onError(Throwable cause) { +764 super.onError(cause); +765 runCompleted.close(); +766 } +767 }; +768 +769 try { +770 env.subscribe(pub, sub); +771 +772 sub.request(1); // kick-off the `request -> onNext -> request -> onNext -> ...` +773 +774 final String msg = String.format("Unable to validate call stack depth safety, " + +775 "awaited at-most %s signals (`maxOnNextSignalsInRecursionTest()`) or completion", +776 oneMoreThanBoundedLimit); +777 runCompleted.expectClose(env.defaultTimeoutMillis(), msg); +778 env.verifyNoAsyncErrorsNoDelay(); +779 } finally { +780 // since the request/onNext recursive calls may keep the publisher running "forever", +781 // we MUST cancel it manually before exiting this test case +782 sub.cancel(); +783 } +784 } +785 }); +786 } +787 +788 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.4 +789 @Override @Test +790 public void untested_spec304_requestShouldNotPerformHeavyComputations() throws Exception { +791 notVerified(); // cannot be meaningfully tested, or can it? +792 } +793 +794 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.5 +795 @Override @Test +796 public void untested_spec305_cancelMustNotSynchronouslyPerformHeavyCompuatation() throws Exception { +797 notVerified(); // cannot be meaningfully tested, or can it? +798 } +799 +800 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.6 +801 @Override @Test +802 public void required_spec306_afterSubscriptionIsCancelledRequestMustBeNops() throws Throwable { +803 activePublisherTest(3, false, new PublisherTestRun<T>() { +804 @Override +805 public void run(Publisher<T> pub) throws Throwable { +806 +807 // override ManualSubscriberWithSubscriptionSupport#cancel because by default a ManualSubscriber will drop the +808 // subscription once it's cancelled (as expected). +809 // In this test however it must keep the cancelled Subscription and keep issuing `request(long)` to it. +810 ManualSubscriber<T> sub = new ManualSubscriberWithSubscriptionSupport<T>(env) { +811 @Override +812 public void cancel() { +813 if (subscription.isCompleted()) { +814 subscription.value().cancel(); +815 } else { +816 env.flop("Cannot cancel a subscription before having received it"); +817 } +818 } +819 }; +820 +821 env.subscribe(pub, sub); +822 +823 sub.cancel(); +824 sub.request(1); +825 sub.request(1); +826 sub.request(1); +827 +828 sub.expectNone(); +829 env.verifyNoAsyncErrorsNoDelay(); +830 } +831 }); +832 } +833 +834 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.7 +835 @Override @Test +836 public void required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops() throws Throwable { +837 activePublisherTest(1, false, new PublisherTestRun<T>() { +838 @Override +839 public void run(Publisher<T> pub) throws Throwable { +840 final ManualSubscriber<T> sub = env.newManualSubscriber(pub); +841 +842 // leak the Subscription +843 final Subscription subs = sub.subscription.value(); +844 +845 subs.cancel(); +846 subs.cancel(); +847 subs.cancel(); +848 +849 sub.expectNone(); +850 env.verifyNoAsyncErrorsNoDelay(); +851 } +852 }); +853 } +854 +855 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.9 +856 @Override @Test +857 public void required_spec309_requestZeroMustSignalIllegalArgumentException() throws Throwable { +858 activePublisherTest(10, false, new PublisherTestRun<T>() { +859 @Override public void run(Publisher<T> pub) throws Throwable { +860 final ManualSubscriber<T> sub = env.newManualSubscriber(pub); +861 sub.request(0); +862 sub.expectErrorWithMessage(IllegalArgumentException.class, "3.9"); // we do require implementations to mention the rule number at the very least +863 } +864 }); +865 } +866 +867 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.9 +868 @Override @Test +869 public void required_spec309_requestNegativeNumberMustSignalIllegalArgumentException() throws Throwable { +870 activePublisherTest(10, false, new PublisherTestRun<T>() { +871 @Override +872 public void run(Publisher<T> pub) throws Throwable { +873 final ManualSubscriber<T> sub = env.newManualSubscriber(pub); +874 final Random r = new Random(); +875 sub.request(-r.nextInt(Integer.MAX_VALUE)); +876 sub.expectErrorWithMessage(IllegalArgumentException.class, "3.9"); // we do require implementations to mention the rule number at the very least +877 } +878 }); +879 } +880 +881 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.12 +882 @Override @Test +883 public void required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling() throws Throwable { +884 // the publisher is able to signal more elements than the subscriber will be requesting in total +885 final int publisherElements = 20; +886 +887 final int demand1 = 10; +888 final int demand2 = 5; +889 final int totalDemand = demand1 + demand2; +890 +891 activePublisherTest(publisherElements, false, new PublisherTestRun<T>() { +892 @Override @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +893 public void run(Publisher<T> pub) throws Throwable { +894 final ManualSubscriber<T> sub = env.newManualSubscriber(pub); +895 +896 sub.request(demand1); +897 sub.request(demand2); +898 +899 /* +900 NOTE: The order of the nextElement/cancel calls below is very important (!) +901 +902 If this ordering was reversed, given an asynchronous publisher, +903 the following scenario would be *legal* and would break this test: +904 +905 > AsyncPublisher receives request(10) - it does not emit data right away, it's asynchronous +906 > AsyncPublisher receives request(5) - demand is now 15 +907 ! AsyncPublisher didn't emit any onNext yet (!) +908 > AsyncPublisher receives cancel() - handles it right away, by "stopping itself" for example +909 ! cancel was handled hefore the AsyncPublisher ever got the chance to emit data +910 ! the subscriber ends up never receiving even one element - the test is stuck (and fails, even on valid Publisher) +911 +912 Which is why we must first expect an element, and then cancel, once the producing is "running". +913 */ +914 sub.nextElement(); +915 sub.cancel(); +916 +917 int onNextsSignalled = 1; +918 +919 boolean stillBeingSignalled; +920 do { +921 // put asyncError if onNext signal received +922 sub.expectNone(); +923 Throwable error = env.dropAsyncError(); +924 +925 if (error == null) { +926 stillBeingSignalled = false; +927 } else { +928 onNextsSignalled += 1; +929 stillBeingSignalled = true; +930 } +931 +932 // if the Publisher tries to emit more elements than was requested (and/or ignores cancellation) this will throw +933 assertTrue(onNextsSignalled <= totalDemand, +934 String.format("Publisher signalled [%d] elements, which is more than the signalled demand: %d", +935 onNextsSignalled, totalDemand)); +936 +937 } while (stillBeingSignalled); +938 } +939 }); +940 +941 env.verifyNoAsyncErrorsNoDelay(); +942 } +943 +944 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.13 +945 @Override @Test +946 public void required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber() throws Throwable { +947 final ReferenceQueue<ManualSubscriber<T>> queue = new ReferenceQueue<ManualSubscriber<T>>(); +948 +949 final Function<Publisher<T>, WeakReference<ManualSubscriber<T>>> run = new Function<Publisher<T>, WeakReference<ManualSubscriber<T>>>() { +950 @Override +951 public WeakReference<ManualSubscriber<T>> apply(Publisher<T> pub) throws Exception { +952 final ManualSubscriber<T> sub = env.newManualSubscriber(pub); +953 final WeakReference<ManualSubscriber<T>> ref = new WeakReference<ManualSubscriber<T>>(sub, queue); +954 +955 sub.request(1); +956 sub.nextElement(); +957 sub.cancel(); +958 +959 return ref; +960 } +961 }; +962 +963 activePublisherTest(3, false, new PublisherTestRun<T>() { +964 @Override +965 public void run(Publisher<T> pub) throws Throwable { +966 final WeakReference<ManualSubscriber<T>> ref = run.apply(pub); +967 +968 // cancel may be run asynchronously so we add a sleep before running the GC +969 // to "resolve" the race +970 Thread.sleep(publisherReferenceGCTimeoutMillis); +971 System.gc(); +972 +973 if (!ref.equals(queue.remove(100))) { +974 env.flop(String.format("Publisher %s did not drop reference to test subscriber after subscription cancellation", pub)); +975 } +976 +977 env.verifyNoAsyncErrorsNoDelay(); +978 } +979 }); +980 } +981 +982 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.17 +983 @Override @Test +984 public void required_spec317_mustSupportAPendingElementCountUpToLongMaxValue() throws Throwable { +985 final int totalElements = 3; +986 +987 activePublisherTest(totalElements, true, new PublisherTestRun<T>() { +988 @Override +989 public void run(Publisher<T> pub) throws Throwable { +990 ManualSubscriber<T> sub = env.newManualSubscriber(pub); +991 sub.request(Long.MAX_VALUE); +992 +993 sub.nextElements(totalElements); +994 sub.expectCompletion(); +995 +996 env.verifyNoAsyncErrorsNoDelay(); +997 } +998 }); +999 } +1000 +1001 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.17 +1002 @Override @Test +1003 public void required_spec317_mustSupportACumulativePendingElementCountUpToLongMaxValue() throws Throwable { +1004 final int totalElements = 3; +1005 +1006 activePublisherTest(totalElements, true, new PublisherTestRun<T>() { +1007 @Override +1008 public void run(Publisher<T> pub) throws Throwable { +1009 final ManualSubscriber<T> sub = env.newManualSubscriber(pub); +1010 sub.request(Long.MAX_VALUE / 2); // pending = Long.MAX_VALUE / 2 +1011 sub.request(Long.MAX_VALUE / 2); // pending = Long.MAX_VALUE - 1 +1012 sub.request(1); // pending = Long.MAX_VALUE +1013 +1014 sub.nextElements(totalElements); +1015 sub.expectCompletion(); +1016 +1017 try { +1018 env.verifyNoAsyncErrorsNoDelay(); +1019 } finally { +1020 sub.cancel(); +1021 } +1022 +1023 } +1024 }); +1025 } +1026 +1027 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.17 +1028 @Override @Test +1029 public void required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue() throws Throwable { +1030 activePublisherTest(Integer.MAX_VALUE, false, new PublisherTestRun<T>() { +1031 @Override public void run(Publisher<T> pub) throws Throwable { +1032 final ManualSubscriberWithSubscriptionSupport<T> sub = new BlackholeSubscriberWithSubscriptionSupport<T>(env) { +1033 // arbitrarily set limit on nuber of request calls signalled, we expect overflow after already 2 calls, +1034 // so 10 is relatively high and safe even if arbitrarily chosen +1035 int callsCounter = 10; +1036 +1037 @Override +1038 public void onNext(T element) { +1039 env.debug(String.format("%s::onNext(%s)", this, element)); +1040 if (subscription.isCompleted()) { +1041 if (callsCounter > 0) { +1042 subscription.value().request(Long.MAX_VALUE - 1); +1043 callsCounter--; +1044 } else { +1045 subscription.value().cancel(); +1046 } +1047 } else { +1048 env.flop(String.format("Subscriber::onNext(%s) called before Subscriber::onSubscribe", element)); +1049 } +1050 } +1051 }; +1052 env.subscribe(pub, sub, env.defaultTimeoutMillis()); +1053 +1054 // eventually triggers `onNext`, which will then trigger up to `callsCounter` times `request(Long.MAX_VALUE - 1)` +1055 // we're pretty sure to overflow from those +1056 sub.request(1); +1057 +1058 // no onError should be signalled +1059 try { +1060 env.verifyNoAsyncErrors(env.defaultTimeoutMillis()); +1061 } finally { +1062 sub.cancel(); +1063 } +1064 } +1065 }); +1066 } +1067 +1068 ///////////////////// ADDITIONAL "COROLLARY" TESTS //////////////////////// +1069 +1070 ///////////////////// TEST INFRASTRUCTURE ///////////////////////////////// +1071 +1072 public interface PublisherTestRun<T> { +1073 public void run(Publisher<T> pub) throws Throwable; +1074 } +1075 +1076 /** +1077 * Test for feature that SHOULD/MUST be implemented, using a live publisher. +1078 * +1079 * @param elements the number of elements the Publisher under test must be able to emit to run this test +1080 * @param completionSignalRequired true if an {@code onComplete} signal is required by this test to run. +1081 * If the tested Publisher is unable to signal completion, tests requireing onComplete signals will be skipped. +1082 * To signal if your Publisher is able to signal completion see {@link PublisherVerification#maxElementsFromPublisher()}. +1083 */ +1084 public void activePublisherTest(long elements, boolean completionSignalRequired, PublisherTestRun<T> body) throws Throwable { +1085 if (elements > maxElementsFromPublisher()) { +1086 throw new SkipException(String.format("Unable to run this test, as required elements nr: %d is higher than supported by given producer: %d", elements, maxElementsFromPublisher())); +1087 } else if (completionSignalRequired && maxElementsFromPublisher() == Long.MAX_VALUE) { +1088 throw new SkipException("Unable to run this test, as it requires an onComplete signal, " + +1089 "which this Publisher is unable to provide (as signalled by returning Long.MAX_VALUE from `maxElementsFromPublisher()`)"); +1090 } else { +1091 Publisher<T> pub = createPublisher(elements); +1092 body.run(pub); +1093 env.verifyNoAsyncErrorsNoDelay(); +1094 } +1095 } +1096 +1097 /** +1098 * Test for feature that MAY be implemented. This test will be marked as SKIPPED if it fails. +1099 * +1100 * @param elements the number of elements the Publisher under test must be able to emit to run this test +1101 * @param completionSignalRequired true if an {@code onComplete} signal is required by this test to run. +1102 * If the tested Publisher is unable to signal completion, tests requireing onComplete signals will be skipped. +1103 * To signal if your Publisher is able to signal completion see {@link PublisherVerification#maxElementsFromPublisher()}. +1104 */ +1105 public void optionalActivePublisherTest(long elements, boolean completionSignalRequired, PublisherTestRun<T> body) throws Throwable { +1106 if (elements > maxElementsFromPublisher()) { +1107 throw new SkipException(String.format("Unable to run this test, as required elements nr: %d is higher than supported by given producer: %d", elements, maxElementsFromPublisher())); +1108 } else if (completionSignalRequired && maxElementsFromPublisher() == Long.MAX_VALUE) { +1109 throw new SkipException("Unable to run this test, as it requires an onComplete signal, " + +1110 "which this Publisher is unable to provide (as signalled by returning Long.MAX_VALUE from `maxElementsFromPublisher()`)"); +1111 } else { +1112 +1113 final Publisher<T> pub = createPublisher(elements); +1114 final String skipMessage = "Skipped because tested publisher does NOT implement this OPTIONAL requirement."; +1115 +1116 try { +1117 potentiallyPendingTest(pub, body); +1118 } catch (Exception ex) { +1119 notVerified(skipMessage); +1120 } catch (AssertionError ex) { +1121 notVerified(skipMessage + " Reason for skipping was: " + ex.getMessage()); +1122 } +1123 } +1124 } +1125 +1126 public static final String SKIPPING_NO_ERROR_PUBLISHER_AVAILABLE = +1127 "Skipping because no error state Publisher provided, and the test requires it. " + +1128 "Please implement PublisherVerification#createFailedPublisher to run this test."; +1129 +1130 public static final String SKIPPING_OPTIONAL_TEST_FAILED = +1131 "Skipping, because provided Publisher does not pass this *additional* verification."; +1132 /** +1133 * Additional test for Publisher in error state +1134 */ +1135 public void whenHasErrorPublisherTest(PublisherTestRun<T> body) throws Throwable { +1136 potentiallyPendingTest(createFailedPublisher(), body, SKIPPING_NO_ERROR_PUBLISHER_AVAILABLE); +1137 } +1138 +1139 public void potentiallyPendingTest(Publisher<T> pub, PublisherTestRun<T> body) throws Throwable { +1140 potentiallyPendingTest(pub, body, SKIPPING_OPTIONAL_TEST_FAILED); +1141 } +1142 +1143 public void potentiallyPendingTest(Publisher<T> pub, PublisherTestRun<T> body, String message) throws Throwable { +1144 if (pub != null) { +1145 body.run(pub); +1146 } else { +1147 throw new SkipException(message); +1148 } +1149 } +1150 +1151 /** +1152 * Executes a given test body {@code n} times. +1153 * All the test runs must pass in order for the stochastic test to pass. +1154 */ +1155 public void stochasticTest(int n, Function<Integer, Void> body) throws Throwable { +1156 if (skipStochasticTests()) { +1157 notVerified("Skipping @Stochastic test because `skipStochasticTests()` returned `true`!"); +1158 } +1159 +1160 for (int i = 0; i < n; i++) { +1161 body.apply(i); +1162 } +1163 } +1164 +1165 public void notVerified() { +1166 throw new SkipException("Not verified by this TCK."); +1167 } +1168 +1169 /** +1170 * Return this value from {@link PublisherVerification#maxElementsFromPublisher()} to mark that the given {@link org.reactivestreams.Publisher}, +1171 * is not able to signal completion. For example it is strictly a time-bound or unbounded source of data. +1172 * +1173 * <b>Returning this value from {@link PublisherVerification#maxElementsFromPublisher()} will result in skipping all TCK tests which require onComplete signals!</b> +1174 */ +1175 public long publisherUnableToSignalOnComplete() { +1176 return Long.MAX_VALUE; +1177 } +1178 +1179 public void notVerified(String message) { +1180 throw new SkipException(message); +1181 } +1182 +1183} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck; +002 +003import org.reactivestreams.Publisher; +004import org.reactivestreams.Subscriber; +005import org.reactivestreams.Subscription; +006import org.reactivestreams.tck.TestEnvironment.ManualPublisher; +007import org.reactivestreams.tck.TestEnvironment.ManualSubscriber; +008import org.reactivestreams.tck.support.Optional; +009import org.reactivestreams.tck.support.SubscriberBlackboxVerificationRules; +010import org.reactivestreams.tck.support.TestException; +011import org.testng.SkipException; +012import org.testng.annotations.AfterClass; +013import org.testng.annotations.BeforeClass; +014import org.testng.annotations.BeforeMethod; +015import org.testng.annotations.Test; +016 +017import java.util.concurrent.ExecutorService; +018import java.util.concurrent.Executors; +019 +020import static org.reactivestreams.tck.SubscriberWhiteboxVerification.BlackboxSubscriberProxy; +021import static org.testng.Assert.assertTrue; +022 +023/** +024 * Provides tests for verifying {@link org.reactivestreams.Subscriber} and {@link org.reactivestreams.Subscription} +025 * specification rules, without any modifications to the tested implementation (also known as "Black Box" testing). +026 * +027 * This verification is NOT able to check many of the rules of the spec, and if you want more +028 * verification of your implementation you'll have to implement {@code org.reactivestreams.tck.SubscriberWhiteboxVerification} +029 * instead. +030 * +031 * @see org.reactivestreams.Subscriber +032 * @see org.reactivestreams.Subscription +033 */ +034public abstract class SubscriberBlackboxVerification<T> extends WithHelperPublisher<T> +035 implements SubscriberBlackboxVerificationRules { +036 +037 protected final TestEnvironment env; +038 +039 protected SubscriberBlackboxVerification(TestEnvironment env) { +040 this.env = env; +041 } +042 +043 // USER API +044 +045 /** +046 * This is the main method you must implement in your test incarnation. +047 * It must create a new {@link org.reactivestreams.Subscriber} instance to be subjected to the testing logic. +048 */ +049 public abstract Subscriber<T> createSubscriber(); +050 +051 /** +052 * Override this method if the Subscriber implementation you are verifying +053 * needs an external signal before it signals demand to its Publisher. +054 * +055 * By default this method does nothing. +056 */ +057 public void triggerRequest(final Subscriber<? super T> subscriber) { +058 +059 } +060 +061 // ENV SETUP +062 +063 /** +064 * Executor service used by the default provided asynchronous Publisher. +065 * @see #createHelperPublisher(long) +066 */ +067 private ExecutorService publisherExecutor; +068 @BeforeClass public void startPublisherExecutorService() { publisherExecutor = Executors.newFixedThreadPool(4); } +069 @AfterClass public void shutdownPublisherExecutorService() { if (publisherExecutor != null) publisherExecutor.shutdown(); } +070 @Override public ExecutorService publisherExecutorService() { return publisherExecutor; } +071 +072 ////////////////////// TEST ENV CLEANUP ///////////////////////////////////// +073 +074 @BeforeMethod +075 public void setUp() throws Exception { +076 env.clearAsyncErrors(); +077 } +078 +079 ////////////////////// SPEC RULE VERIFICATION /////////////////////////////// +080 +081 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.1 +082 @Override @Test +083 public void required_spec201_blackbox_mustSignalDemandViaSubscriptionRequest() throws Throwable { +084 blackboxSubscriberTest(new BlackboxTestStageTestRun() { +085 @Override +086 public void run(BlackboxTestStage stage) throws InterruptedException { +087 triggerRequest(stage.subProxy().sub()); +088 final long n = stage.expectRequest();// assuming subscriber wants to consume elements... +089 +090 // should cope with up to requested number of elements +091 for (int i = 0; i < n; i++) +092 stage.signalNext(); +093 } +094 }); +095 } +096 +097 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.2 +098 @Override @Test +099 public void untested_spec202_blackbox_shouldAsynchronouslyDispatch() throws Exception { +100 notVerified(); // cannot be meaningfully tested, or can it? +101 } +102 +103 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.3 +104 @Override @Test +105 public void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() throws Throwable { +106 blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() { +107 @Override +108 public void run(BlackboxTestStage stage) throws Throwable { +109 final Subscription subs = new Subscription() { +110 @Override +111 public void request(long n) { +112 final Optional<StackTraceElement> onCompleteStackTraceElement = env.findCallerMethodInStackTrace("onComplete"); +113 if (onCompleteStackTraceElement.isDefined()) { +114 final StackTraceElement stackElem = onCompleteStackTraceElement.get(); +115 env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)", +116 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +117 } +118 } +119 +120 @Override +121 public void cancel() { +122 final Optional<StackTraceElement> onCompleteStackElement = env.findCallerMethodInStackTrace("onComplete"); +123 if (onCompleteStackElement.isDefined()) { +124 final StackTraceElement stackElem = onCompleteStackElement.get(); +125 env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)", +126 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +127 } +128 } +129 }; +130 +131 final Subscriber<T> sub = createSubscriber(); +132 sub.onSubscribe(subs); +133 sub.onComplete(); +134 +135 env.verifyNoAsyncErrorsNoDelay(); +136 } +137 }); +138 } +139 +140 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.3 +141 @Override @Test +142 public void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() throws Throwable { +143 blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() { +144 @Override +145 public void run(BlackboxTestStage stage) throws Throwable { +146 final Subscription subs = new Subscription() { +147 @Override +148 public void request(long n) { +149 Throwable thr = new Throwable(); +150 for (StackTraceElement stackElem : thr.getStackTrace()) { +151 if (stackElem.getMethodName().equals("onError")) { +152 env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)", +153 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +154 } +155 } +156 } +157 +158 @Override +159 public void cancel() { +160 Throwable thr = new Throwable(); +161 for (StackTraceElement stackElem : thr.getStackTrace()) { +162 if (stackElem.getMethodName().equals("onError")) { +163 env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)", +164 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +165 } +166 } +167 } +168 }; +169 +170 final Subscriber<T> sub = createSubscriber(); +171 sub.onSubscribe(subs); +172 sub.onError(new TestException()); +173 +174 env.verifyNoAsyncErrorsNoDelay(); +175 } +176 }); +177 } +178 +179 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.4 +180 @Override @Test +181 public void untested_spec204_blackbox_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() throws Exception { +182 notVerified(); // cannot be meaningfully tested, or can it? +183 } +184 +185 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.5 +186 @Override @Test +187 public void required_spec205_blackbox_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() throws Exception { +188 new BlackboxTestStage(env) {{ +189 // try to subscribe another time, if the subscriber calls `probe.registerOnSubscribe` the test will fail +190 final TestEnvironment.Latch secondSubscriptionCancelled = new TestEnvironment.Latch(env); +191 sub().onSubscribe( +192 new Subscription() { +193 @Override +194 public void request(long elements) { +195 env.flop(String.format("Subscriber %s illegally called `subscription.request(%s)`!", sub(), elements)); +196 } +197 +198 @Override +199 public void cancel() { +200 secondSubscriptionCancelled.close(); +201 } +202 +203 @Override +204 public String toString() { +205 return "SecondSubscription(should get cancelled)"; +206 } +207 }); +208 +209 secondSubscriptionCancelled.expectClose("Expected SecondSubscription given to subscriber to be cancelled, but `Subscription.cancel()` was not called."); +210 env.verifyNoAsyncErrorsNoDelay(); +211 }}; +212 } +213 +214 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.6 +215 @Override @Test +216 public void untested_spec206_blackbox_mustCallSubscriptionCancelIfItIsNoLongerValid() throws Exception { +217 notVerified(); // cannot be meaningfully tested, or can it? +218 } +219 +220 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.7 +221 @Override @Test +222 public void untested_spec207_blackbox_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() throws Exception { +223 notVerified(); // cannot be meaningfully tested, or can it? +224 // the same thread part of the clause can be verified but that is not very useful, or is it? +225 } +226 +227 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.8 +228 @Override @Test +229 public void untested_spec208_blackbox_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() throws Throwable { +230 notVerified(); // cannot be meaningfully tested as black box, or can it? +231 } +232 +233 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.9 +234 @Override @Test +235 public void required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() throws Throwable { +236 blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() { +237 @Override +238 public void run(BlackboxTestStage stage) throws Throwable { +239 final Publisher<T> pub = new Publisher<T>() { +240 @Override public void subscribe(final Subscriber<? super T> s) { +241 s.onSubscribe(new Subscription() { +242 private boolean completed = false; +243 +244 @Override public void request(long n) { +245 if (!completed) { +246 completed = true; +247 s.onComplete(); // Publisher now realises that it is in fact already completed +248 } +249 } +250 +251 @Override public void cancel() { +252 // noop, ignore +253 } +254 }); +255 } +256 }; +257 +258 final Subscriber<T> sub = createSubscriber(); +259 final BlackboxSubscriberProxy<T> probe = stage.createBlackboxSubscriberProxy(env, sub); +260 +261 pub.subscribe(probe); +262 triggerRequest(sub); +263 probe.expectCompletion(); +264 probe.expectNone(); +265 +266 env.verifyNoAsyncErrorsNoDelay(); +267 } +268 }); +269 } +270 +271 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.9 +272 @Override @Test +273 public void required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() throws Throwable { +274 blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() { +275 @Override +276 public void run(BlackboxTestStage stage) throws Throwable { +277 final Publisher<T> pub = new Publisher<T>() { +278 @Override +279 public void subscribe(Subscriber<? super T> s) { +280 s.onComplete(); +281 } +282 }; +283 +284 final Subscriber<T> sub = createSubscriber(); +285 final BlackboxSubscriberProxy<T> probe = stage.createBlackboxSubscriberProxy(env, sub); +286 +287 pub.subscribe(probe); +288 probe.expectCompletion(); +289 +290 env.verifyNoAsyncErrorsNoDelay(); +291 } +292 }); +293 } +294 +295 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.10 +296 @Override @Test +297 public void required_spec210_blackbox_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() throws Throwable { +298 blackboxSubscriberTest(new BlackboxTestStageTestRun() { +299 @Override +300 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +301 public void run(BlackboxTestStage stage) throws Throwable { +302 stage.sub().onError(new TestException()); +303 stage.subProxy().expectError(Throwable.class); +304 } +305 }); +306 } +307 +308 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.11 +309 @Override @Test +310 public void untested_spec211_blackbox_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() throws Exception { +311 notVerified(); // cannot be meaningfully tested, or can it? +312 } +313 +314 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.12 +315 @Override @Test +316 public void untested_spec212_blackbox_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality() throws Throwable { +317 notVerified(); // cannot be meaningfully tested as black box, or can it? +318 } +319 +320 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +321 @Override @Test +322 public void untested_spec213_blackbox_failingOnSignalInvocation() throws Exception { +323 notVerified(); // cannot be meaningfully tested, or can it? +324 } +325 +326 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +327 @Override @Test +328 public void required_spec213_blackbox_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +329 blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() { +330 @Override +331 public void run(BlackboxTestStage stage) throws Throwable { +332 +333 { +334 final Subscriber<T> sub = createSubscriber(); +335 boolean gotNPE = false; +336 try { +337 sub.onSubscribe(null); +338 } catch(final NullPointerException expected) { +339 gotNPE = true; +340 } +341 assertTrue(gotNPE, "onSubscribe(null) did not throw NullPointerException"); +342 } +343 +344 env.verifyNoAsyncErrorsNoDelay(); +345 } +346 }); +347 } +348 +349 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +350 @Override @Test +351 public void required_spec213_blackbox_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +352 blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() { +353 @Override +354 public void run(BlackboxTestStage stage) throws Throwable { +355 final Subscription subscription = new Subscription() { +356 @Override public void request(final long elements) {} +357 @Override public void cancel() {} +358 }; +359 +360 { +361 final Subscriber<T> sub = createSubscriber(); +362 boolean gotNPE = false; +363 sub.onSubscribe(subscription); +364 try { +365 sub.onNext(null); +366 } catch(final NullPointerException expected) { +367 gotNPE = true; +368 } +369 assertTrue(gotNPE, "onNext(null) did not throw NullPointerException"); +370 } +371 +372 env.verifyNoAsyncErrorsNoDelay(); +373 } +374 }); +375 } +376 +377 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +378 @Override @Test +379 public void required_spec213_blackbox_onError_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +380 blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() { +381 @Override +382 public void run(BlackboxTestStage stage) throws Throwable { +383 final Subscription subscription = new Subscription() { +384 @Override public void request(final long elements) {} +385 @Override public void cancel() {} +386 }; +387 +388 { +389 final Subscriber<T> sub = createSubscriber(); +390 boolean gotNPE = false; +391 sub.onSubscribe(subscription); +392 try { +393 sub.onError(null); +394 } catch(final NullPointerException expected) { +395 gotNPE = true; +396 } +397 assertTrue(gotNPE, "onError(null) did not throw NullPointerException"); +398 } +399 +400 env.verifyNoAsyncErrorsNoDelay(); +401 } +402 }); +403 } +404 +405 ////////////////////// SUBSCRIPTION SPEC RULE VERIFICATION ////////////////// +406 +407 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.1 +408 @Override @Test +409 public void untested_spec301_blackbox_mustNotBeCalledOutsideSubscriberContext() throws Exception { +410 notVerified(); // cannot be meaningfully tested, or can it? +411 } +412 +413 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.8 +414 @Override @Test +415 public void untested_spec308_blackbox_requestMustRegisterGivenNumberElementsToBeProduced() throws Throwable { +416 notVerified(); // cannot be meaningfully tested as black box, or can it? +417 } +418 +419 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.10 +420 @Override @Test +421 public void untested_spec310_blackbox_requestMaySynchronouslyCallOnNextOnSubscriber() throws Exception { +422 notVerified(); // cannot be meaningfully tested, or can it? +423 } +424 +425 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.11 +426 @Override @Test +427 public void untested_spec311_blackbox_requestMaySynchronouslyCallOnCompleteOrOnError() throws Exception { +428 notVerified(); // cannot be meaningfully tested, or can it? +429 } +430 +431 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.14 +432 @Override @Test +433 public void untested_spec314_blackbox_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() throws Exception { +434 notVerified(); // cannot be meaningfully tested, or can it? +435 } +436 +437 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.15 +438 @Override @Test +439 public void untested_spec315_blackbox_cancelMustNotThrowExceptionAndMustSignalOnError() throws Exception { +440 notVerified(); // cannot be meaningfully tested, or can it? +441 } +442 +443 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.16 +444 @Override @Test +445 public void untested_spec316_blackbox_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() throws Exception { +446 notVerified(); // cannot be meaningfully tested, or can it? +447 } +448 +449 /////////////////////// ADDITIONAL "COROLLARY" TESTS //////////////////////// +450 +451 /////////////////////// TEST INFRASTRUCTURE ///////////////////////////////// +452 +453 abstract class BlackboxTestStageTestRun { +454 public abstract void run(BlackboxTestStage stage) throws Throwable; +455 } +456 +457 public void blackboxSubscriberTest(BlackboxTestStageTestRun body) throws Throwable { +458 BlackboxTestStage stage = new BlackboxTestStage(env, true); +459 body.run(stage); +460 } +461 +462 public void blackboxSubscriberWithoutSetupTest(BlackboxTestStageTestRun body) throws Throwable { +463 BlackboxTestStage stage = new BlackboxTestStage(env, false); +464 body.run(stage); +465 } +466 +467 public class BlackboxTestStage extends ManualPublisher<T> { +468 public Publisher<T> pub; +469 public ManualSubscriber<T> tees; // gives us access to an infinite stream of T values +470 +471 public T lastT = null; +472 private Optional<BlackboxSubscriberProxy<T>> subProxy = Optional.empty(); +473 +474 public BlackboxTestStage(TestEnvironment env) throws InterruptedException { +475 this(env, true); +476 } +477 +478 public BlackboxTestStage(TestEnvironment env, boolean runDefaultInit) throws InterruptedException { +479 super(env); +480 if (runDefaultInit) { +481 pub = this.createHelperPublisher(Long.MAX_VALUE); +482 tees = env.newManualSubscriber(pub); +483 Subscriber<T> sub = createSubscriber(); +484 subProxy = Optional.of(createBlackboxSubscriberProxy(env, sub)); +485 subscribe(subProxy.get()); +486 } +487 } +488 +489 public Subscriber<? super T> sub() { +490 return subscriber.value(); +491 } +492 +493 /** +494 * Proxy for the {@link #sub()} {@code Subscriber}, providing certain assertions on methods being called on the Subscriber. +495 */ +496 public BlackboxSubscriberProxy<T> subProxy() { +497 return subProxy.get(); +498 } +499 +500 public Publisher<T> createHelperPublisher(long elements) { +501 return SubscriberBlackboxVerification.this.createHelperPublisher(elements); +502 } +503 +504 public BlackboxSubscriberProxy<T> createBlackboxSubscriberProxy(TestEnvironment env, Subscriber<T> sub) { +505 return new BlackboxSubscriberProxy<T>(env, sub); +506 } +507 +508 public T signalNext() throws InterruptedException { +509 T element = nextT(); +510 sendNext(element); +511 return element; +512 } +513 +514 public T nextT() throws InterruptedException { +515 lastT = tees.requestNextElement(); +516 return lastT; +517 } +518 +519 } +520 +521 public void notVerified() { +522 throw new SkipException("Not verified using this TCK."); +523 } +524} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck; +002 +003import org.reactivestreams.Publisher; +004import org.reactivestreams.Subscriber; +005import org.reactivestreams.Subscription; +006import org.reactivestreams.tck.TestEnvironment.ManualPublisher; +007import org.reactivestreams.tck.TestEnvironment.ManualSubscriber; +008import org.reactivestreams.tck.support.Optional; +009import org.reactivestreams.tck.support.SubscriberBlackboxVerificationRules; +010import org.reactivestreams.tck.support.TestException; +011import org.testng.SkipException; +012import org.testng.annotations.AfterClass; +013import org.testng.annotations.BeforeClass; +014import org.testng.annotations.BeforeMethod; +015import org.testng.annotations.Test; +016 +017import java.util.concurrent.ExecutorService; +018import java.util.concurrent.Executors; +019 +020import static org.reactivestreams.tck.SubscriberWhiteboxVerification.BlackboxSubscriberProxy; +021import static org.testng.Assert.assertTrue; +022 +023/** +024 * Provides tests for verifying {@link org.reactivestreams.Subscriber} and {@link org.reactivestreams.Subscription} +025 * specification rules, without any modifications to the tested implementation (also known as "Black Box" testing). +026 * +027 * This verification is NOT able to check many of the rules of the spec, and if you want more +028 * verification of your implementation you'll have to implement {@code org.reactivestreams.tck.SubscriberWhiteboxVerification} +029 * instead. +030 * +031 * @see org.reactivestreams.Subscriber +032 * @see org.reactivestreams.Subscription +033 */ +034public abstract class SubscriberBlackboxVerification<T> extends WithHelperPublisher<T> +035 implements SubscriberBlackboxVerificationRules { +036 +037 protected final TestEnvironment env; +038 +039 protected SubscriberBlackboxVerification(TestEnvironment env) { +040 this.env = env; +041 } +042 +043 // USER API +044 +045 /** +046 * This is the main method you must implement in your test incarnation. +047 * It must create a new {@link org.reactivestreams.Subscriber} instance to be subjected to the testing logic. +048 */ +049 public abstract Subscriber<T> createSubscriber(); +050 +051 /** +052 * Override this method if the Subscriber implementation you are verifying +053 * needs an external signal before it signals demand to its Publisher. +054 * +055 * By default this method does nothing. +056 */ +057 public void triggerRequest(final Subscriber<? super T> subscriber) { +058 +059 } +060 +061 // ENV SETUP +062 +063 /** +064 * Executor service used by the default provided asynchronous Publisher. +065 * @see #createHelperPublisher(long) +066 */ +067 private ExecutorService publisherExecutor; +068 @BeforeClass public void startPublisherExecutorService() { publisherExecutor = Executors.newFixedThreadPool(4); } +069 @AfterClass public void shutdownPublisherExecutorService() { if (publisherExecutor != null) publisherExecutor.shutdown(); } +070 @Override public ExecutorService publisherExecutorService() { return publisherExecutor; } +071 +072 ////////////////////// TEST ENV CLEANUP ///////////////////////////////////// +073 +074 @BeforeMethod +075 public void setUp() throws Exception { +076 env.clearAsyncErrors(); +077 } +078 +079 ////////////////////// SPEC RULE VERIFICATION /////////////////////////////// +080 +081 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.1 +082 @Override @Test +083 public void required_spec201_blackbox_mustSignalDemandViaSubscriptionRequest() throws Throwable { +084 blackboxSubscriberTest(new BlackboxTestStageTestRun() { +085 @Override +086 public void run(BlackboxTestStage stage) throws InterruptedException { +087 triggerRequest(stage.subProxy().sub()); +088 final long n = stage.expectRequest();// assuming subscriber wants to consume elements... +089 +090 // should cope with up to requested number of elements +091 for (int i = 0; i < n; i++) +092 stage.signalNext(); +093 } +094 }); +095 } +096 +097 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.2 +098 @Override @Test +099 public void untested_spec202_blackbox_shouldAsynchronouslyDispatch() throws Exception { +100 notVerified(); // cannot be meaningfully tested, or can it? +101 } +102 +103 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.3 +104 @Override @Test +105 public void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() throws Throwable { +106 blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() { +107 @Override +108 public void run(BlackboxTestStage stage) throws Throwable { +109 final Subscription subs = new Subscription() { +110 @Override +111 public void request(long n) { +112 final Optional<StackTraceElement> onCompleteStackTraceElement = env.findCallerMethodInStackTrace("onComplete"); +113 if (onCompleteStackTraceElement.isDefined()) { +114 final StackTraceElement stackElem = onCompleteStackTraceElement.get(); +115 env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)", +116 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +117 } +118 } +119 +120 @Override +121 public void cancel() { +122 final Optional<StackTraceElement> onCompleteStackElement = env.findCallerMethodInStackTrace("onComplete"); +123 if (onCompleteStackElement.isDefined()) { +124 final StackTraceElement stackElem = onCompleteStackElement.get(); +125 env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)", +126 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +127 } +128 } +129 }; +130 +131 final Subscriber<T> sub = createSubscriber(); +132 sub.onSubscribe(subs); +133 sub.onComplete(); +134 +135 env.verifyNoAsyncErrorsNoDelay(); +136 } +137 }); +138 } +139 +140 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.3 +141 @Override @Test +142 public void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() throws Throwable { +143 blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() { +144 @Override +145 public void run(BlackboxTestStage stage) throws Throwable { +146 final Subscription subs = new Subscription() { +147 @Override +148 public void request(long n) { +149 Throwable thr = new Throwable(); +150 for (StackTraceElement stackElem : thr.getStackTrace()) { +151 if (stackElem.getMethodName().equals("onError")) { +152 env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)", +153 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +154 } +155 } +156 } +157 +158 @Override +159 public void cancel() { +160 Throwable thr = new Throwable(); +161 for (StackTraceElement stackElem : thr.getStackTrace()) { +162 if (stackElem.getMethodName().equals("onError")) { +163 env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)", +164 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +165 } +166 } +167 } +168 }; +169 +170 final Subscriber<T> sub = createSubscriber(); +171 sub.onSubscribe(subs); +172 sub.onError(new TestException()); +173 +174 env.verifyNoAsyncErrorsNoDelay(); +175 } +176 }); +177 } +178 +179 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.4 +180 @Override @Test +181 public void untested_spec204_blackbox_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() throws Exception { +182 notVerified(); // cannot be meaningfully tested, or can it? +183 } +184 +185 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.5 +186 @Override @Test +187 public void required_spec205_blackbox_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() throws Exception { +188 new BlackboxTestStage(env) {{ +189 // try to subscribe another time, if the subscriber calls `probe.registerOnSubscribe` the test will fail +190 final TestEnvironment.Latch secondSubscriptionCancelled = new TestEnvironment.Latch(env); +191 sub().onSubscribe( +192 new Subscription() { +193 @Override +194 public void request(long elements) { +195 env.flop(String.format("Subscriber %s illegally called `subscription.request(%s)`!", sub(), elements)); +196 } +197 +198 @Override +199 public void cancel() { +200 secondSubscriptionCancelled.close(); +201 } +202 +203 @Override +204 public String toString() { +205 return "SecondSubscription(should get cancelled)"; +206 } +207 }); +208 +209 secondSubscriptionCancelled.expectClose("Expected SecondSubscription given to subscriber to be cancelled, but `Subscription.cancel()` was not called."); +210 env.verifyNoAsyncErrorsNoDelay(); +211 }}; +212 } +213 +214 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.6 +215 @Override @Test +216 public void untested_spec206_blackbox_mustCallSubscriptionCancelIfItIsNoLongerValid() throws Exception { +217 notVerified(); // cannot be meaningfully tested, or can it? +218 } +219 +220 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.7 +221 @Override @Test +222 public void untested_spec207_blackbox_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() throws Exception { +223 notVerified(); // cannot be meaningfully tested, or can it? +224 // the same thread part of the clause can be verified but that is not very useful, or is it? +225 } +226 +227 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.8 +228 @Override @Test +229 public void untested_spec208_blackbox_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() throws Throwable { +230 notVerified(); // cannot be meaningfully tested as black box, or can it? +231 } +232 +233 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.9 +234 @Override @Test +235 public void required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() throws Throwable { +236 blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() { +237 @Override +238 public void run(BlackboxTestStage stage) throws Throwable { +239 final Publisher<T> pub = new Publisher<T>() { +240 @Override public void subscribe(final Subscriber<? super T> s) { +241 s.onSubscribe(new Subscription() { +242 private boolean completed = false; +243 +244 @Override public void request(long n) { +245 if (!completed) { +246 completed = true; +247 s.onComplete(); // Publisher now realises that it is in fact already completed +248 } +249 } +250 +251 @Override public void cancel() { +252 // noop, ignore +253 } +254 }); +255 } +256 }; +257 +258 final Subscriber<T> sub = createSubscriber(); +259 final BlackboxSubscriberProxy<T> probe = stage.createBlackboxSubscriberProxy(env, sub); +260 +261 pub.subscribe(probe); +262 triggerRequest(sub); +263 probe.expectCompletion(); +264 probe.expectNone(); +265 +266 env.verifyNoAsyncErrorsNoDelay(); +267 } +268 }); +269 } +270 +271 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.9 +272 @Override @Test +273 public void required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() throws Throwable { +274 blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() { +275 @Override +276 public void run(BlackboxTestStage stage) throws Throwable { +277 final Publisher<T> pub = new Publisher<T>() { +278 @Override +279 public void subscribe(Subscriber<? super T> s) { +280 s.onComplete(); +281 } +282 }; +283 +284 final Subscriber<T> sub = createSubscriber(); +285 final BlackboxSubscriberProxy<T> probe = stage.createBlackboxSubscriberProxy(env, sub); +286 +287 pub.subscribe(probe); +288 probe.expectCompletion(); +289 +290 env.verifyNoAsyncErrorsNoDelay(); +291 } +292 }); +293 } +294 +295 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.10 +296 @Override @Test +297 public void required_spec210_blackbox_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() throws Throwable { +298 blackboxSubscriberTest(new BlackboxTestStageTestRun() { +299 @Override +300 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +301 public void run(BlackboxTestStage stage) throws Throwable { +302 stage.sub().onError(new TestException()); +303 stage.subProxy().expectError(Throwable.class); +304 } +305 }); +306 } +307 +308 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.11 +309 @Override @Test +310 public void untested_spec211_blackbox_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() throws Exception { +311 notVerified(); // cannot be meaningfully tested, or can it? +312 } +313 +314 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.12 +315 @Override @Test +316 public void untested_spec212_blackbox_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality() throws Throwable { +317 notVerified(); // cannot be meaningfully tested as black box, or can it? +318 } +319 +320 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +321 @Override @Test +322 public void untested_spec213_blackbox_failingOnSignalInvocation() throws Exception { +323 notVerified(); // cannot be meaningfully tested, or can it? +324 } +325 +326 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +327 @Override @Test +328 public void required_spec213_blackbox_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +329 blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() { +330 @Override +331 public void run(BlackboxTestStage stage) throws Throwable { +332 +333 { +334 final Subscriber<T> sub = createSubscriber(); +335 boolean gotNPE = false; +336 try { +337 sub.onSubscribe(null); +338 } catch(final NullPointerException expected) { +339 gotNPE = true; +340 } +341 assertTrue(gotNPE, "onSubscribe(null) did not throw NullPointerException"); +342 } +343 +344 env.verifyNoAsyncErrorsNoDelay(); +345 } +346 }); +347 } +348 +349 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +350 @Override @Test +351 public void required_spec213_blackbox_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +352 blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() { +353 @Override +354 public void run(BlackboxTestStage stage) throws Throwable { +355 final Subscription subscription = new Subscription() { +356 @Override public void request(final long elements) {} +357 @Override public void cancel() {} +358 }; +359 +360 { +361 final Subscriber<T> sub = createSubscriber(); +362 boolean gotNPE = false; +363 sub.onSubscribe(subscription); +364 try { +365 sub.onNext(null); +366 } catch(final NullPointerException expected) { +367 gotNPE = true; +368 } +369 assertTrue(gotNPE, "onNext(null) did not throw NullPointerException"); +370 } +371 +372 env.verifyNoAsyncErrorsNoDelay(); +373 } +374 }); +375 } +376 +377 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +378 @Override @Test +379 public void required_spec213_blackbox_onError_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +380 blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() { +381 @Override +382 public void run(BlackboxTestStage stage) throws Throwable { +383 final Subscription subscription = new Subscription() { +384 @Override public void request(final long elements) {} +385 @Override public void cancel() {} +386 }; +387 +388 { +389 final Subscriber<T> sub = createSubscriber(); +390 boolean gotNPE = false; +391 sub.onSubscribe(subscription); +392 try { +393 sub.onError(null); +394 } catch(final NullPointerException expected) { +395 gotNPE = true; +396 } +397 assertTrue(gotNPE, "onError(null) did not throw NullPointerException"); +398 } +399 +400 env.verifyNoAsyncErrorsNoDelay(); +401 } +402 }); +403 } +404 +405 ////////////////////// SUBSCRIPTION SPEC RULE VERIFICATION ////////////////// +406 +407 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.1 +408 @Override @Test +409 public void untested_spec301_blackbox_mustNotBeCalledOutsideSubscriberContext() throws Exception { +410 notVerified(); // cannot be meaningfully tested, or can it? +411 } +412 +413 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.8 +414 @Override @Test +415 public void untested_spec308_blackbox_requestMustRegisterGivenNumberElementsToBeProduced() throws Throwable { +416 notVerified(); // cannot be meaningfully tested as black box, or can it? +417 } +418 +419 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.10 +420 @Override @Test +421 public void untested_spec310_blackbox_requestMaySynchronouslyCallOnNextOnSubscriber() throws Exception { +422 notVerified(); // cannot be meaningfully tested, or can it? +423 } +424 +425 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.11 +426 @Override @Test +427 public void untested_spec311_blackbox_requestMaySynchronouslyCallOnCompleteOrOnError() throws Exception { +428 notVerified(); // cannot be meaningfully tested, or can it? +429 } +430 +431 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.14 +432 @Override @Test +433 public void untested_spec314_blackbox_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() throws Exception { +434 notVerified(); // cannot be meaningfully tested, or can it? +435 } +436 +437 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.15 +438 @Override @Test +439 public void untested_spec315_blackbox_cancelMustNotThrowExceptionAndMustSignalOnError() throws Exception { +440 notVerified(); // cannot be meaningfully tested, or can it? +441 } +442 +443 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.16 +444 @Override @Test +445 public void untested_spec316_blackbox_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() throws Exception { +446 notVerified(); // cannot be meaningfully tested, or can it? +447 } +448 +449 /////////////////////// ADDITIONAL "COROLLARY" TESTS //////////////////////// +450 +451 /////////////////////// TEST INFRASTRUCTURE ///////////////////////////////// +452 +453 abstract class BlackboxTestStageTestRun { +454 public abstract void run(BlackboxTestStage stage) throws Throwable; +455 } +456 +457 public void blackboxSubscriberTest(BlackboxTestStageTestRun body) throws Throwable { +458 BlackboxTestStage stage = new BlackboxTestStage(env, true); +459 body.run(stage); +460 } +461 +462 public void blackboxSubscriberWithoutSetupTest(BlackboxTestStageTestRun body) throws Throwable { +463 BlackboxTestStage stage = new BlackboxTestStage(env, false); +464 body.run(stage); +465 } +466 +467 public class BlackboxTestStage extends ManualPublisher<T> { +468 public Publisher<T> pub; +469 public ManualSubscriber<T> tees; // gives us access to an infinite stream of T values +470 +471 public T lastT = null; +472 private Optional<BlackboxSubscriberProxy<T>> subProxy = Optional.empty(); +473 +474 public BlackboxTestStage(TestEnvironment env) throws InterruptedException { +475 this(env, true); +476 } +477 +478 public BlackboxTestStage(TestEnvironment env, boolean runDefaultInit) throws InterruptedException { +479 super(env); +480 if (runDefaultInit) { +481 pub = this.createHelperPublisher(Long.MAX_VALUE); +482 tees = env.newManualSubscriber(pub); +483 Subscriber<T> sub = createSubscriber(); +484 subProxy = Optional.of(createBlackboxSubscriberProxy(env, sub)); +485 subscribe(subProxy.get()); +486 } +487 } +488 +489 public Subscriber<? super T> sub() { +490 return subscriber.value(); +491 } +492 +493 /** +494 * Proxy for the {@link #sub()} {@code Subscriber}, providing certain assertions on methods being called on the Subscriber. +495 */ +496 public BlackboxSubscriberProxy<T> subProxy() { +497 return subProxy.get(); +498 } +499 +500 public Publisher<T> createHelperPublisher(long elements) { +501 return SubscriberBlackboxVerification.this.createHelperPublisher(elements); +502 } +503 +504 public BlackboxSubscriberProxy<T> createBlackboxSubscriberProxy(TestEnvironment env, Subscriber<T> sub) { +505 return new BlackboxSubscriberProxy<T>(env, sub); +506 } +507 +508 public T signalNext() throws InterruptedException { +509 T element = nextT(); +510 sendNext(element); +511 return element; +512 } +513 +514 public T nextT() throws InterruptedException { +515 lastT = tees.requestNextElement(); +516 return lastT; +517 } +518 +519 } +520 +521 public void notVerified() { +522 throw new SkipException("Not verified using this TCK."); +523 } +524} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck; +002 +003import org.reactivestreams.Publisher; +004import org.reactivestreams.Subscriber; +005import org.reactivestreams.Subscription; +006import org.reactivestreams.tck.TestEnvironment.*; +007import org.reactivestreams.tck.support.Optional; +008import org.reactivestreams.tck.support.SubscriberWhiteboxVerificationRules; +009import org.reactivestreams.tck.support.TestException; +010import org.testng.SkipException; +011import org.testng.annotations.AfterClass; +012import org.testng.annotations.BeforeClass; +013import org.testng.annotations.BeforeMethod; +014import org.testng.annotations.Test; +015 +016import java.util.concurrent.ExecutorService; +017import java.util.concurrent.Executors; +018 +019import static org.testng.Assert.assertTrue; +020 +021/** +022 * Provides tests for verifying {@link org.reactivestreams.Subscriber} and {@link org.reactivestreams.Subscription} specification rules. +023 * +024 * @see org.reactivestreams.Subscriber +025 * @see org.reactivestreams.Subscription +026 */ +027public abstract class SubscriberWhiteboxVerification<T> extends WithHelperPublisher<T> +028 implements SubscriberWhiteboxVerificationRules { +029 +030 private final TestEnvironment env; +031 +032 protected SubscriberWhiteboxVerification(TestEnvironment env) { +033 this.env = env; +034 } +035 +036 // USER API +037 +038 /** +039 * This is the main method you must implement in your test incarnation. +040 * It must create a new {@link org.reactivestreams.Subscriber} instance to be subjected to the testing logic. +041 * +042 * In order to be meaningfully testable your Subscriber must inform the given +043 * `WhiteboxSubscriberProbe` of the respective events having been received. +044 */ +045 public abstract Subscriber<T> createSubscriber(WhiteboxSubscriberProbe<T> probe); +046 +047 // ENV SETUP +048 +049 /** +050 * Executor service used by the default provided asynchronous Publisher. +051 * @see #createHelperPublisher(long) +052 */ +053 private ExecutorService publisherExecutor; +054 @BeforeClass public void startPublisherExecutorService() { publisherExecutor = Executors.newFixedThreadPool(4); } +055 @AfterClass public void shutdownPublisherExecutorService() { if (publisherExecutor != null) publisherExecutor.shutdown(); } +056 @Override public ExecutorService publisherExecutorService() { return publisherExecutor; } +057 +058 ////////////////////// TEST ENV CLEANUP ///////////////////////////////////// +059 +060 @BeforeMethod +061 public void setUp() throws Exception { +062 env.clearAsyncErrors(); +063 } +064 +065 ////////////////////// TEST SETUP VERIFICATION ////////////////////////////// +066 +067 @Test +068 public void required_exerciseWhiteboxHappyPath() throws Throwable { +069 subscriberTest(new TestStageTestRun() { +070 @Override +071 public void run(WhiteboxTestStage stage) throws InterruptedException { +072 stage.puppet().triggerRequest(1); +073 stage.puppet().triggerRequest(1); +074 +075 long receivedRequests = stage.expectRequest(); +076 +077 stage.signalNext(); +078 stage.probe.expectNext(stage.lastT); +079 +080 stage.puppet().triggerRequest(1); +081 if (receivedRequests == 1) { +082 stage.expectRequest(); +083 } +084 +085 stage.signalNext(); +086 stage.probe.expectNext(stage.lastT); +087 +088 stage.puppet().signalCancel(); +089 stage.expectCancelling(); +090 +091 stage.verifyNoAsyncErrors(); +092 } +093 }); +094 } +095 +096 ////////////////////// SPEC RULE VERIFICATION /////////////////////////////// +097 +098 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.1 +099 @Override @Test +100 public void required_spec201_mustSignalDemandViaSubscriptionRequest() throws Throwable { +101 subscriberTest(new TestStageTestRun() { +102 @Override +103 public void run(WhiteboxTestStage stage) throws InterruptedException { +104 stage.puppet().triggerRequest(1); +105 stage.expectRequest(); +106 +107 stage.signalNext(); +108 } +109 }); +110 } +111 +112 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.2 +113 @Override @Test +114 public void untested_spec202_shouldAsynchronouslyDispatch() throws Exception { +115 notVerified(); // cannot be meaningfully tested, or can it? +116 } +117 +118 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.3 +119 @Override @Test +120 public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() throws Throwable { +121 subscriberTestWithoutSetup(new TestStageTestRun() { +122 @Override +123 public void run(WhiteboxTestStage stage) throws Throwable { +124 final Subscription subs = new Subscription() { +125 @Override +126 public void request(long n) { +127 final Optional<StackTraceElement> onCompleteStackTraceElement = env.findCallerMethodInStackTrace("onComplete"); +128 if (onCompleteStackTraceElement.isDefined()) { +129 final StackTraceElement stackElem = onCompleteStackTraceElement.get(); +130 env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)", +131 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +132 } +133 } +134 +135 @Override +136 public void cancel() { +137 final Optional<StackTraceElement> onCompleteStackElement = env.findCallerMethodInStackTrace("onComplete"); +138 if (onCompleteStackElement.isDefined()) { +139 final StackTraceElement stackElem = onCompleteStackElement.get(); +140 env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)", +141 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +142 } +143 } +144 }; +145 +146 stage.probe = stage.createWhiteboxSubscriberProbe(env); +147 final Subscriber<T> sub = createSubscriber(stage.probe); +148 +149 sub.onSubscribe(subs); +150 sub.onComplete(); +151 +152 env.verifyNoAsyncErrorsNoDelay(); +153 } +154 }); +155 } +156 +157 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.3 +158 @Override @Test +159 public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() throws Throwable { +160 subscriberTestWithoutSetup(new TestStageTestRun() { +161 @Override +162 public void run(WhiteboxTestStage stage) throws Throwable { +163 final Subscription subs = new Subscription() { +164 @Override +165 public void request(long n) { +166 Throwable thr = new Throwable(); +167 for (StackTraceElement stackElem : thr.getStackTrace()) { +168 if (stackElem.getMethodName().equals("onError")) { +169 env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)", +170 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +171 } +172 } +173 } +174 +175 @Override +176 public void cancel() { +177 Throwable thr = new Throwable(); +178 for (StackTraceElement stackElem : thr.getStackTrace()) { +179 if (stackElem.getMethodName().equals("onError")) { +180 env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)", +181 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +182 } +183 } +184 } +185 }; +186 +187 stage.probe = stage.createWhiteboxSubscriberProbe(env); +188 final Subscriber<T> sub = createSubscriber(stage.probe); +189 +190 sub.onSubscribe(subs); +191 sub.onError(new TestException()); +192 +193 env.verifyNoAsyncErrorsNoDelay(); +194 } +195 }); +196 } +197 +198 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.4 +199 @Override @Test +200 public void untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() throws Exception { +201 notVerified(); // cannot be meaningfully tested, or can it? +202 } +203 +204 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.5 +205 @Override @Test +206 public void required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() throws Throwable { +207 subscriberTest(new TestStageTestRun() { +208 @Override +209 public void run(WhiteboxTestStage stage) throws Throwable { +210 // try to subscribe another time, if the subscriber calls `probe.registerOnSubscribe` the test will fail +211 final Latch secondSubscriptionCancelled = new Latch(env); +212 final Subscriber<? super T> sub = stage.sub(); +213 final Subscription subscription = new Subscription() { +214 @Override +215 public void request(long elements) { +216 // ignore... +217 } +218 +219 @Override +220 public void cancel() { +221 secondSubscriptionCancelled.close(); +222 } +223 +224 @Override +225 public String toString() { +226 return "SecondSubscription(should get cancelled)"; +227 } +228 }; +229 sub.onSubscribe(subscription); +230 +231 secondSubscriptionCancelled.expectClose("Expected 2nd Subscription given to subscriber to be cancelled, but `Subscription.cancel()` was not called"); +232 env.verifyNoAsyncErrors(); +233 } +234 }); +235 } +236 +237 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.6 +238 @Override @Test +239 public void untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid() throws Exception { +240 notVerified(); // cannot be meaningfully tested, or can it? +241 } +242 +243 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.7 +244 @Override @Test +245 public void untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() throws Exception { +246 notVerified(); // cannot be meaningfully tested, or can it? +247 // the same thread part of the clause can be verified but that is not very useful, or is it? +248 } +249 +250 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.8 +251 @Override @Test +252 public void required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() throws Throwable { +253 subscriberTest(new TestStageTestRun() { +254 @Override +255 public void run(WhiteboxTestStage stage) throws InterruptedException { +256 stage.puppet().triggerRequest(1); +257 stage.puppet().signalCancel(); +258 stage.signalNext(); +259 +260 stage.puppet().triggerRequest(1); +261 stage.puppet().triggerRequest(1); +262 +263 stage.verifyNoAsyncErrors(); +264 } +265 }); +266 } +267 +268 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.9 +269 @Override @Test +270 public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() throws Throwable { +271 subscriberTest(new TestStageTestRun() { +272 @Override +273 public void run(WhiteboxTestStage stage) throws InterruptedException { +274 stage.puppet().triggerRequest(1); +275 stage.sendCompletion(); +276 stage.probe.expectCompletion(); +277 +278 stage.verifyNoAsyncErrors(); +279 } +280 }); +281 } +282 +283 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.9 +284 @Override @Test +285 public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() throws Throwable { +286 subscriberTest(new TestStageTestRun() { +287 @Override +288 public void run(WhiteboxTestStage stage) throws InterruptedException { +289 stage.sendCompletion(); +290 stage.probe.expectCompletion(); +291 +292 stage.verifyNoAsyncErrors(); +293 } +294 }); +295 } +296 +297 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.10 +298 @Override @Test +299 public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() throws Throwable { +300 subscriberTest(new TestStageTestRun() { +301 @Override +302 public void run(WhiteboxTestStage stage) throws InterruptedException { +303 stage.puppet().triggerRequest(1); +304 stage.puppet().triggerRequest(1); +305 +306 Exception ex = new TestException(); +307 stage.sendError(ex); +308 stage.probe.expectError(ex); +309 +310 env.verifyNoAsyncErrorsNoDelay(); +311 } +312 }); +313 } +314 +315 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.10 +316 @Override @Test +317 public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall() throws Throwable { +318 subscriberTest(new TestStageTestRun() { +319 @Override +320 public void run(WhiteboxTestStage stage) throws InterruptedException { +321 Exception ex = new TestException(); +322 stage.sendError(ex); +323 stage.probe.expectError(ex); +324 +325 env.verifyNoAsyncErrorsNoDelay(); +326 } +327 }); +328 } +329 +330 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.11 +331 @Override @Test +332 public void untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() throws Exception { +333 notVerified(); // cannot be meaningfully tested, or can it? +334 } +335 +336 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.12 +337 @Override @Test +338 public void untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation() throws Throwable { +339 notVerified(); // cannot be meaningfully tested, or can it? +340 } +341 +342 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +343 @Override @Test +344 public void untested_spec213_failingOnSignalInvocation() throws Exception { +345 notVerified(); // cannot be meaningfully tested, or can it? +346 } +347 +348 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +349 @Override @Test +350 public void required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +351 subscriberTest(new TestStageTestRun() { +352 @Override +353 public void run(WhiteboxTestStage stage) throws Throwable { +354 +355 final Subscriber<? super T> sub = stage.sub(); +356 boolean gotNPE = false; +357 try { +358 sub.onSubscribe(null); +359 } catch (final NullPointerException expected) { +360 gotNPE = true; +361 } +362 +363 assertTrue(gotNPE, "onSubscribe(null) did not throw NullPointerException"); +364 env.verifyNoAsyncErrorsNoDelay(); +365 } +366 }); +367 } +368 +369 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +370 @Override @Test +371 public void required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +372 subscriberTest(new TestStageTestRun() { +373 @Override +374 public void run(WhiteboxTestStage stage) throws Throwable { +375 +376 final Subscriber<? super T> sub = stage.sub(); +377 boolean gotNPE = false; +378 try { +379 sub.onNext(null); +380 } catch (final NullPointerException expected) { +381 gotNPE = true; +382 } +383 +384 assertTrue(gotNPE, "onNext(null) did not throw NullPointerException"); +385 env.verifyNoAsyncErrorsNoDelay(); +386 } +387 }); +388 } +389 +390 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +391 @Override @Test +392 public void required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +393 subscriberTest(new TestStageTestRun() { +394 @Override +395 public void run(WhiteboxTestStage stage) throws Throwable { +396 +397 final Subscriber<? super T> sub = stage.sub(); +398 boolean gotNPE = false; +399 try { +400 sub.onError(null); +401 } catch (final NullPointerException expected) { +402 gotNPE = true; +403 } finally { +404 assertTrue(gotNPE, "onError(null) did not throw NullPointerException"); +405 } +406 +407 env.verifyNoAsyncErrorsNoDelay(); +408 } +409 }); +410 } +411 +412 +413 ////////////////////// SUBSCRIPTION SPEC RULE VERIFICATION ////////////////// +414 +415 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.1 +416 @Override @Test +417 public void untested_spec301_mustNotBeCalledOutsideSubscriberContext() throws Exception { +418 notVerified(); // cannot be meaningfully tested, or can it? +419 } +420 +421 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.8 +422 @Override @Test +423 public void required_spec308_requestMustRegisterGivenNumberElementsToBeProduced() throws Throwable { +424 subscriberTest(new TestStageTestRun() { +425 @Override +426 public void run(WhiteboxTestStage stage) throws InterruptedException { +427 stage.puppet().triggerRequest(2); +428 stage.probe.expectNext(stage.signalNext()); +429 stage.probe.expectNext(stage.signalNext()); +430 +431 stage.probe.expectNone(); +432 stage.puppet().triggerRequest(3); +433 +434 stage.verifyNoAsyncErrors(); +435 } +436 }); +437 } +438 +439 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.10 +440 @Override @Test +441 public void untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber() throws Exception { +442 notVerified(); // cannot be meaningfully tested, or can it? +443 } +444 +445 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.11 +446 @Override @Test +447 public void untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError() throws Exception { +448 notVerified(); // cannot be meaningfully tested, or can it? +449 } +450 +451 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.14 +452 @Override @Test +453 public void untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() throws Exception { +454 notVerified(); // cannot be meaningfully tested, or can it? +455 } +456 +457 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.15 +458 @Override @Test +459 public void untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError() throws Exception { +460 notVerified(); // cannot be meaningfully tested, or can it? +461 } +462 +463 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.16 +464 @Override @Test +465 public void untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() throws Exception { +466 notVerified(); // cannot be meaningfully tested, or can it? +467 } +468 +469 /////////////////////// ADDITIONAL "COROLLARY" TESTS //////////////////////// +470 +471 /////////////////////// TEST INFRASTRUCTURE ///////////////////////////////// +472 +473 abstract class TestStageTestRun { +474 public abstract void run(WhiteboxTestStage stage) throws Throwable; +475 } +476 +477 /** +478 * Prepares subscriber and publisher pair (by subscribing the first to the latter), +479 * and then hands over the tests {@link WhiteboxTestStage} over to the test. +480 * +481 * The test stage is, like in a puppet show, used to orchestrate what each participant should do. +482 * Since this is a whitebox test, this allows the stage to completely control when and how to signal / expect signals. +483 */ +484 public void subscriberTest(TestStageTestRun body) throws Throwable { +485 WhiteboxTestStage stage = new WhiteboxTestStage(env, true); +486 body.run(stage); +487 } +488 +489 /** +490 * Provides a {@link WhiteboxTestStage} without performing any additional setup, +491 * like the {@link #subscriberTest(SubscriberWhiteboxVerification.TestStageTestRun)} would. +492 * +493 * Use this method to write tests in which you need full control over when and how the initial {@code subscribe} is signalled. +494 */ +495 public void subscriberTestWithoutSetup(TestStageTestRun body) throws Throwable { +496 WhiteboxTestStage stage = new WhiteboxTestStage(env, false); +497 body.run(stage); +498 } +499 +500 /** +501 * Test for feature that MAY be implemented. This test will be marked as SKIPPED if it fails. +502 */ +503 public void optionalSubscriberTestWithoutSetup(TestStageTestRun body) throws Throwable { +504 try { +505 subscriberTestWithoutSetup(body); +506 } catch (Exception ex) { +507 notVerified("Skipped because tested publisher does NOT implement this OPTIONAL requirement."); +508 } +509 } +510 +511 public class WhiteboxTestStage extends ManualPublisher<T> { +512 public Publisher<T> pub; +513 public ManualSubscriber<T> tees; // gives us access to a stream T values +514 public WhiteboxSubscriberProbe<T> probe; +515 +516 public T lastT = null; +517 +518 public WhiteboxTestStage(TestEnvironment env) throws InterruptedException { +519 this(env, true); +520 } +521 +522 public WhiteboxTestStage(TestEnvironment env, boolean runDefaultInit) throws InterruptedException { +523 super(env); +524 if (runDefaultInit) { +525 pub = this.createHelperPublisher(Long.MAX_VALUE); +526 tees = env.newManualSubscriber(pub); +527 probe = new WhiteboxSubscriberProbe<T>(env, subscriber); +528 subscribe(createSubscriber(probe)); +529 probe.puppet.expectCompletion(env.defaultTimeoutMillis(), String.format("Subscriber %s did not `registerOnSubscribe`", sub())); +530 } +531 } +532 +533 public Subscriber<? super T> sub() { +534 return subscriber.value(); +535 } +536 +537 public SubscriberPuppet puppet() { +538 return probe.puppet(); +539 } +540 +541 public WhiteboxSubscriberProbe<T> probe() { +542 return probe; +543 } +544 +545 public Publisher<T> createHelperPublisher(long elements) { +546 return SubscriberWhiteboxVerification.this.createHelperPublisher(elements); +547 } +548 +549 public WhiteboxSubscriberProbe<T> createWhiteboxSubscriberProbe(TestEnvironment env) { +550 return new WhiteboxSubscriberProbe<T>(env, subscriber); +551 } +552 +553 public T signalNext() throws InterruptedException { +554 return signalNext(nextT()); +555 } +556 +557 private T signalNext(T element) throws InterruptedException { +558 sendNext(element); +559 return element; +560 } +561 +562 public T nextT() throws InterruptedException { +563 lastT = tees.requestNextElement(); +564 return lastT; +565 } +566 +567 public void verifyNoAsyncErrors() { +568 env.verifyNoAsyncErrors(); +569 } +570 } +571 +572 /** +573 * This class is intented to be used as {@code Subscriber} decorator and should be used in {@code pub.subscriber(...)} calls, +574 * in order to allow intercepting calls on the underlying {@code Subscriber}. +575 * This delegation allows the proxy to implement {@link BlackboxProbe} assertions. +576 */ +577 public static class BlackboxSubscriberProxy<T> extends BlackboxProbe<T> implements Subscriber<T> { +578 +579 public BlackboxSubscriberProxy(TestEnvironment env, Subscriber<T> subscriber) { +580 super(env, Promise.<Subscriber<? super T>>completed(env, subscriber)); +581 } +582 +583 @Override +584 public void onSubscribe(Subscription s) { +585 sub().onSubscribe(s); +586 } +587 +588 @Override +589 public void onNext(T t) { +590 registerOnNext(t); +591 sub().onNext(t); +592 } +593 +594 @Override +595 public void onError(Throwable cause) { +596 registerOnError(cause); +597 sub().onError(cause); +598 } +599 +600 @Override +601 public void onComplete() { +602 registerOnComplete(); +603 sub().onComplete(); +604 } +605 } +606 +607 public static class BlackboxProbe<T> implements SubscriberProbe<T> { +608 protected final TestEnvironment env; +609 protected final Promise<Subscriber<? super T>> subscriber; +610 +611 protected final Receptacle<T> elements; +612 protected final Promise<Throwable> error; +613 +614 public BlackboxProbe(TestEnvironment env, Promise<Subscriber<? super T>> subscriber) { +615 this.env = env; +616 this.subscriber = subscriber; +617 elements = new Receptacle<T>(env); +618 error = new Promise<Throwable>(env); +619 } +620 +621 @Override +622 public void registerOnNext(T element) { +623 elements.add(element); +624 } +625 +626 @Override +627 public void registerOnComplete() { +628 try { +629 elements.complete(); +630 } catch (IllegalStateException ex) { +631 // "Queue full", onComplete was already called +632 env.flop("subscriber::onComplete was called a second time, which is illegal according to Rule 1.7"); +633 } +634 } +635 +636 @Override +637 public void registerOnError(Throwable cause) { +638 try { +639 error.complete(cause); +640 } catch (IllegalStateException ex) { +641 // "Queue full", onError was already called +642 env.flop("subscriber::onError was called a second time, which is illegal according to Rule 1.7"); +643 } +644 } +645 +646 public T expectNext() throws InterruptedException { +647 return elements.next(env.defaultTimeoutMillis(), String.format("Subscriber %s did not call `registerOnNext(_)`", sub())); +648 } +649 +650 public void expectNext(T expected) throws InterruptedException { +651 expectNext(expected, env.defaultTimeoutMillis()); +652 } +653 +654 public void expectNext(T expected, long timeoutMillis) throws InterruptedException { +655 T received = elements.next(timeoutMillis, String.format("Subscriber %s did not call `registerOnNext(%s)`", sub(), expected)); +656 if (!received.equals(expected)) { +657 env.flop(String.format("Subscriber %s called `registerOnNext(%s)` rather than `registerOnNext(%s)`", sub(), received, expected)); +658 } +659 } +660 +661 public Subscriber<? super T> sub() { +662 return subscriber.value(); +663 } +664 +665 public void expectCompletion() throws InterruptedException { +666 expectCompletion(env.defaultTimeoutMillis()); +667 } +668 +669 public void expectCompletion(long timeoutMillis) throws InterruptedException { +670 expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnComplete()`", sub())); +671 } +672 +673 public void expectCompletion(long timeoutMillis, String msg) throws InterruptedException { +674 elements.expectCompletion(timeoutMillis, msg); +675 } +676 +677 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +678 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart) throws InterruptedException { +679 final E err = expectError(expected); +680 String message = err.getMessage(); +681 assertTrue(message.contains(requiredMessagePart), +682 String.format("Got expected exception %s but missing message [%s], was: %s", err.getClass(), requiredMessagePart, expected)); +683 } +684 +685 public <E extends Throwable> E expectError(Class<E> expected) throws InterruptedException { +686 return expectError(expected, env.defaultTimeoutMillis()); +687 } +688 +689 @SuppressWarnings({"unchecked", "ThrowableResultOfMethodCallIgnored"}) +690 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis) throws InterruptedException { +691 error.expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected)); +692 if (error.value() == null) { +693 return env.flopAndFail(String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected)); +694 } else if (expected.isInstance(error.value())) { +695 return (E) error.value(); +696 } else { +697 return env.flopAndFail(String.format("Subscriber %s called `registerOnError(%s)` rather than `registerOnError(%s)`", sub(), error.value(), expected)); +698 } +699 } +700 +701 public void expectError(Throwable expected) throws InterruptedException { +702 expectError(expected, env.defaultTimeoutMillis()); +703 } +704 +705 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +706 public void expectError(Throwable expected, long timeoutMillis) throws InterruptedException { +707 error.expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected)); +708 if (error.value() != expected) { +709 env.flop(String.format("Subscriber %s called `registerOnError(%s)` rather than `registerOnError(%s)`", sub(), error.value(), expected)); +710 } +711 } +712 +713 public void expectNone() throws InterruptedException { +714 expectNone(env.defaultTimeoutMillis()); +715 } +716 +717 public void expectNone(long withinMillis) throws InterruptedException { +718 elements.expectNone(withinMillis, "Expected nothing"); +719 } +720 +721 } +722 +723 public static class WhiteboxSubscriberProbe<T> extends BlackboxProbe<T> implements SubscriberPuppeteer { +724 protected Promise<SubscriberPuppet> puppet; +725 +726 public WhiteboxSubscriberProbe(TestEnvironment env, Promise<Subscriber<? super T>> subscriber) { +727 super(env, subscriber); +728 puppet = new Promise<SubscriberPuppet>(env); +729 } +730 +731 private SubscriberPuppet puppet() { +732 return puppet.value(); +733 } +734 +735 @Override +736 public void registerOnSubscribe(SubscriberPuppet p) { +737 if (!puppet.isCompleted()) { +738 puppet.complete(p); +739 } +740 } +741 +742 } +743 +744 public interface SubscriberPuppeteer { +745 +746 /** +747 * Must be called by the test subscriber when it has successfully registered a subscription +748 * inside the `onSubscribe` method. +749 */ +750 void registerOnSubscribe(SubscriberPuppet puppet); +751 } +752 +753 public interface SubscriberProbe<T> { +754 +755 /** +756 * Must be called by the test subscriber when it has received an`onNext` event. +757 */ +758 void registerOnNext(T element); +759 +760 /** +761 * Must be called by the test subscriber when it has received an `onComplete` event. +762 */ +763 void registerOnComplete(); +764 +765 /** +766 * Must be called by the test subscriber when it has received an `onError` event. +767 */ +768 void registerOnError(Throwable cause); +769 +770 } +771 +772 public interface SubscriberPuppet { +773 void triggerRequest(long elements); +774 +775 void signalCancel(); +776 } +777 +778 public void notVerified() { +779 throw new SkipException("Not verified using this TCK."); +780 } +781 +782 public void notVerified(String msg) { +783 throw new SkipException(msg); +784 } +785} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck; +002 +003import org.reactivestreams.Publisher; +004import org.reactivestreams.Subscriber; +005import org.reactivestreams.Subscription; +006import org.reactivestreams.tck.TestEnvironment.*; +007import org.reactivestreams.tck.support.Optional; +008import org.reactivestreams.tck.support.SubscriberWhiteboxVerificationRules; +009import org.reactivestreams.tck.support.TestException; +010import org.testng.SkipException; +011import org.testng.annotations.AfterClass; +012import org.testng.annotations.BeforeClass; +013import org.testng.annotations.BeforeMethod; +014import org.testng.annotations.Test; +015 +016import java.util.concurrent.ExecutorService; +017import java.util.concurrent.Executors; +018 +019import static org.testng.Assert.assertTrue; +020 +021/** +022 * Provides tests for verifying {@link org.reactivestreams.Subscriber} and {@link org.reactivestreams.Subscription} specification rules. +023 * +024 * @see org.reactivestreams.Subscriber +025 * @see org.reactivestreams.Subscription +026 */ +027public abstract class SubscriberWhiteboxVerification<T> extends WithHelperPublisher<T> +028 implements SubscriberWhiteboxVerificationRules { +029 +030 private final TestEnvironment env; +031 +032 protected SubscriberWhiteboxVerification(TestEnvironment env) { +033 this.env = env; +034 } +035 +036 // USER API +037 +038 /** +039 * This is the main method you must implement in your test incarnation. +040 * It must create a new {@link org.reactivestreams.Subscriber} instance to be subjected to the testing logic. +041 * +042 * In order to be meaningfully testable your Subscriber must inform the given +043 * `WhiteboxSubscriberProbe` of the respective events having been received. +044 */ +045 public abstract Subscriber<T> createSubscriber(WhiteboxSubscriberProbe<T> probe); +046 +047 // ENV SETUP +048 +049 /** +050 * Executor service used by the default provided asynchronous Publisher. +051 * @see #createHelperPublisher(long) +052 */ +053 private ExecutorService publisherExecutor; +054 @BeforeClass public void startPublisherExecutorService() { publisherExecutor = Executors.newFixedThreadPool(4); } +055 @AfterClass public void shutdownPublisherExecutorService() { if (publisherExecutor != null) publisherExecutor.shutdown(); } +056 @Override public ExecutorService publisherExecutorService() { return publisherExecutor; } +057 +058 ////////////////////// TEST ENV CLEANUP ///////////////////////////////////// +059 +060 @BeforeMethod +061 public void setUp() throws Exception { +062 env.clearAsyncErrors(); +063 } +064 +065 ////////////////////// TEST SETUP VERIFICATION ////////////////////////////// +066 +067 @Test +068 public void required_exerciseWhiteboxHappyPath() throws Throwable { +069 subscriberTest(new TestStageTestRun() { +070 @Override +071 public void run(WhiteboxTestStage stage) throws InterruptedException { +072 stage.puppet().triggerRequest(1); +073 stage.puppet().triggerRequest(1); +074 +075 long receivedRequests = stage.expectRequest(); +076 +077 stage.signalNext(); +078 stage.probe.expectNext(stage.lastT); +079 +080 stage.puppet().triggerRequest(1); +081 if (receivedRequests == 1) { +082 stage.expectRequest(); +083 } +084 +085 stage.signalNext(); +086 stage.probe.expectNext(stage.lastT); +087 +088 stage.puppet().signalCancel(); +089 stage.expectCancelling(); +090 +091 stage.verifyNoAsyncErrors(); +092 } +093 }); +094 } +095 +096 ////////////////////// SPEC RULE VERIFICATION /////////////////////////////// +097 +098 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.1 +099 @Override @Test +100 public void required_spec201_mustSignalDemandViaSubscriptionRequest() throws Throwable { +101 subscriberTest(new TestStageTestRun() { +102 @Override +103 public void run(WhiteboxTestStage stage) throws InterruptedException { +104 stage.puppet().triggerRequest(1); +105 stage.expectRequest(); +106 +107 stage.signalNext(); +108 } +109 }); +110 } +111 +112 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.2 +113 @Override @Test +114 public void untested_spec202_shouldAsynchronouslyDispatch() throws Exception { +115 notVerified(); // cannot be meaningfully tested, or can it? +116 } +117 +118 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.3 +119 @Override @Test +120 public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() throws Throwable { +121 subscriberTestWithoutSetup(new TestStageTestRun() { +122 @Override +123 public void run(WhiteboxTestStage stage) throws Throwable { +124 final Subscription subs = new Subscription() { +125 @Override +126 public void request(long n) { +127 final Optional<StackTraceElement> onCompleteStackTraceElement = env.findCallerMethodInStackTrace("onComplete"); +128 if (onCompleteStackTraceElement.isDefined()) { +129 final StackTraceElement stackElem = onCompleteStackTraceElement.get(); +130 env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)", +131 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +132 } +133 } +134 +135 @Override +136 public void cancel() { +137 final Optional<StackTraceElement> onCompleteStackElement = env.findCallerMethodInStackTrace("onComplete"); +138 if (onCompleteStackElement.isDefined()) { +139 final StackTraceElement stackElem = onCompleteStackElement.get(); +140 env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)", +141 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +142 } +143 } +144 }; +145 +146 stage.probe = stage.createWhiteboxSubscriberProbe(env); +147 final Subscriber<T> sub = createSubscriber(stage.probe); +148 +149 sub.onSubscribe(subs); +150 sub.onComplete(); +151 +152 env.verifyNoAsyncErrorsNoDelay(); +153 } +154 }); +155 } +156 +157 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.3 +158 @Override @Test +159 public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() throws Throwable { +160 subscriberTestWithoutSetup(new TestStageTestRun() { +161 @Override +162 public void run(WhiteboxTestStage stage) throws Throwable { +163 final Subscription subs = new Subscription() { +164 @Override +165 public void request(long n) { +166 Throwable thr = new Throwable(); +167 for (StackTraceElement stackElem : thr.getStackTrace()) { +168 if (stackElem.getMethodName().equals("onError")) { +169 env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)", +170 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +171 } +172 } +173 } +174 +175 @Override +176 public void cancel() { +177 Throwable thr = new Throwable(); +178 for (StackTraceElement stackElem : thr.getStackTrace()) { +179 if (stackElem.getMethodName().equals("onError")) { +180 env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)", +181 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +182 } +183 } +184 } +185 }; +186 +187 stage.probe = stage.createWhiteboxSubscriberProbe(env); +188 final Subscriber<T> sub = createSubscriber(stage.probe); +189 +190 sub.onSubscribe(subs); +191 sub.onError(new TestException()); +192 +193 env.verifyNoAsyncErrorsNoDelay(); +194 } +195 }); +196 } +197 +198 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.4 +199 @Override @Test +200 public void untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() throws Exception { +201 notVerified(); // cannot be meaningfully tested, or can it? +202 } +203 +204 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.5 +205 @Override @Test +206 public void required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() throws Throwable { +207 subscriberTest(new TestStageTestRun() { +208 @Override +209 public void run(WhiteboxTestStage stage) throws Throwable { +210 // try to subscribe another time, if the subscriber calls `probe.registerOnSubscribe` the test will fail +211 final Latch secondSubscriptionCancelled = new Latch(env); +212 final Subscriber<? super T> sub = stage.sub(); +213 final Subscription subscription = new Subscription() { +214 @Override +215 public void request(long elements) { +216 // ignore... +217 } +218 +219 @Override +220 public void cancel() { +221 secondSubscriptionCancelled.close(); +222 } +223 +224 @Override +225 public String toString() { +226 return "SecondSubscription(should get cancelled)"; +227 } +228 }; +229 sub.onSubscribe(subscription); +230 +231 secondSubscriptionCancelled.expectClose("Expected 2nd Subscription given to subscriber to be cancelled, but `Subscription.cancel()` was not called"); +232 env.verifyNoAsyncErrors(); +233 } +234 }); +235 } +236 +237 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.6 +238 @Override @Test +239 public void untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid() throws Exception { +240 notVerified(); // cannot be meaningfully tested, or can it? +241 } +242 +243 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.7 +244 @Override @Test +245 public void untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() throws Exception { +246 notVerified(); // cannot be meaningfully tested, or can it? +247 // the same thread part of the clause can be verified but that is not very useful, or is it? +248 } +249 +250 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.8 +251 @Override @Test +252 public void required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() throws Throwable { +253 subscriberTest(new TestStageTestRun() { +254 @Override +255 public void run(WhiteboxTestStage stage) throws InterruptedException { +256 stage.puppet().triggerRequest(1); +257 stage.puppet().signalCancel(); +258 stage.signalNext(); +259 +260 stage.puppet().triggerRequest(1); +261 stage.puppet().triggerRequest(1); +262 +263 stage.verifyNoAsyncErrors(); +264 } +265 }); +266 } +267 +268 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.9 +269 @Override @Test +270 public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() throws Throwable { +271 subscriberTest(new TestStageTestRun() { +272 @Override +273 public void run(WhiteboxTestStage stage) throws InterruptedException { +274 stage.puppet().triggerRequest(1); +275 stage.sendCompletion(); +276 stage.probe.expectCompletion(); +277 +278 stage.verifyNoAsyncErrors(); +279 } +280 }); +281 } +282 +283 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.9 +284 @Override @Test +285 public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() throws Throwable { +286 subscriberTest(new TestStageTestRun() { +287 @Override +288 public void run(WhiteboxTestStage stage) throws InterruptedException { +289 stage.sendCompletion(); +290 stage.probe.expectCompletion(); +291 +292 stage.verifyNoAsyncErrors(); +293 } +294 }); +295 } +296 +297 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.10 +298 @Override @Test +299 public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() throws Throwable { +300 subscriberTest(new TestStageTestRun() { +301 @Override +302 public void run(WhiteboxTestStage stage) throws InterruptedException { +303 stage.puppet().triggerRequest(1); +304 stage.puppet().triggerRequest(1); +305 +306 Exception ex = new TestException(); +307 stage.sendError(ex); +308 stage.probe.expectError(ex); +309 +310 env.verifyNoAsyncErrorsNoDelay(); +311 } +312 }); +313 } +314 +315 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.10 +316 @Override @Test +317 public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall() throws Throwable { +318 subscriberTest(new TestStageTestRun() { +319 @Override +320 public void run(WhiteboxTestStage stage) throws InterruptedException { +321 Exception ex = new TestException(); +322 stage.sendError(ex); +323 stage.probe.expectError(ex); +324 +325 env.verifyNoAsyncErrorsNoDelay(); +326 } +327 }); +328 } +329 +330 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.11 +331 @Override @Test +332 public void untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() throws Exception { +333 notVerified(); // cannot be meaningfully tested, or can it? +334 } +335 +336 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.12 +337 @Override @Test +338 public void untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation() throws Throwable { +339 notVerified(); // cannot be meaningfully tested, or can it? +340 } +341 +342 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +343 @Override @Test +344 public void untested_spec213_failingOnSignalInvocation() throws Exception { +345 notVerified(); // cannot be meaningfully tested, or can it? +346 } +347 +348 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +349 @Override @Test +350 public void required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +351 subscriberTest(new TestStageTestRun() { +352 @Override +353 public void run(WhiteboxTestStage stage) throws Throwable { +354 +355 final Subscriber<? super T> sub = stage.sub(); +356 boolean gotNPE = false; +357 try { +358 sub.onSubscribe(null); +359 } catch (final NullPointerException expected) { +360 gotNPE = true; +361 } +362 +363 assertTrue(gotNPE, "onSubscribe(null) did not throw NullPointerException"); +364 env.verifyNoAsyncErrorsNoDelay(); +365 } +366 }); +367 } +368 +369 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +370 @Override @Test +371 public void required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +372 subscriberTest(new TestStageTestRun() { +373 @Override +374 public void run(WhiteboxTestStage stage) throws Throwable { +375 +376 final Subscriber<? super T> sub = stage.sub(); +377 boolean gotNPE = false; +378 try { +379 sub.onNext(null); +380 } catch (final NullPointerException expected) { +381 gotNPE = true; +382 } +383 +384 assertTrue(gotNPE, "onNext(null) did not throw NullPointerException"); +385 env.verifyNoAsyncErrorsNoDelay(); +386 } +387 }); +388 } +389 +390 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +391 @Override @Test +392 public void required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +393 subscriberTest(new TestStageTestRun() { +394 @Override +395 public void run(WhiteboxTestStage stage) throws Throwable { +396 +397 final Subscriber<? super T> sub = stage.sub(); +398 boolean gotNPE = false; +399 try { +400 sub.onError(null); +401 } catch (final NullPointerException expected) { +402 gotNPE = true; +403 } finally { +404 assertTrue(gotNPE, "onError(null) did not throw NullPointerException"); +405 } +406 +407 env.verifyNoAsyncErrorsNoDelay(); +408 } +409 }); +410 } +411 +412 +413 ////////////////////// SUBSCRIPTION SPEC RULE VERIFICATION ////////////////// +414 +415 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.1 +416 @Override @Test +417 public void untested_spec301_mustNotBeCalledOutsideSubscriberContext() throws Exception { +418 notVerified(); // cannot be meaningfully tested, or can it? +419 } +420 +421 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.8 +422 @Override @Test +423 public void required_spec308_requestMustRegisterGivenNumberElementsToBeProduced() throws Throwable { +424 subscriberTest(new TestStageTestRun() { +425 @Override +426 public void run(WhiteboxTestStage stage) throws InterruptedException { +427 stage.puppet().triggerRequest(2); +428 stage.probe.expectNext(stage.signalNext()); +429 stage.probe.expectNext(stage.signalNext()); +430 +431 stage.probe.expectNone(); +432 stage.puppet().triggerRequest(3); +433 +434 stage.verifyNoAsyncErrors(); +435 } +436 }); +437 } +438 +439 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.10 +440 @Override @Test +441 public void untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber() throws Exception { +442 notVerified(); // cannot be meaningfully tested, or can it? +443 } +444 +445 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.11 +446 @Override @Test +447 public void untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError() throws Exception { +448 notVerified(); // cannot be meaningfully tested, or can it? +449 } +450 +451 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.14 +452 @Override @Test +453 public void untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() throws Exception { +454 notVerified(); // cannot be meaningfully tested, or can it? +455 } +456 +457 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.15 +458 @Override @Test +459 public void untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError() throws Exception { +460 notVerified(); // cannot be meaningfully tested, or can it? +461 } +462 +463 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.16 +464 @Override @Test +465 public void untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() throws Exception { +466 notVerified(); // cannot be meaningfully tested, or can it? +467 } +468 +469 /////////////////////// ADDITIONAL "COROLLARY" TESTS //////////////////////// +470 +471 /////////////////////// TEST INFRASTRUCTURE ///////////////////////////////// +472 +473 abstract class TestStageTestRun { +474 public abstract void run(WhiteboxTestStage stage) throws Throwable; +475 } +476 +477 /** +478 * Prepares subscriber and publisher pair (by subscribing the first to the latter), +479 * and then hands over the tests {@link WhiteboxTestStage} over to the test. +480 * +481 * The test stage is, like in a puppet show, used to orchestrate what each participant should do. +482 * Since this is a whitebox test, this allows the stage to completely control when and how to signal / expect signals. +483 */ +484 public void subscriberTest(TestStageTestRun body) throws Throwable { +485 WhiteboxTestStage stage = new WhiteboxTestStage(env, true); +486 body.run(stage); +487 } +488 +489 /** +490 * Provides a {@link WhiteboxTestStage} without performing any additional setup, +491 * like the {@link #subscriberTest(SubscriberWhiteboxVerification.TestStageTestRun)} would. +492 * +493 * Use this method to write tests in which you need full control over when and how the initial {@code subscribe} is signalled. +494 */ +495 public void subscriberTestWithoutSetup(TestStageTestRun body) throws Throwable { +496 WhiteboxTestStage stage = new WhiteboxTestStage(env, false); +497 body.run(stage); +498 } +499 +500 /** +501 * Test for feature that MAY be implemented. This test will be marked as SKIPPED if it fails. +502 */ +503 public void optionalSubscriberTestWithoutSetup(TestStageTestRun body) throws Throwable { +504 try { +505 subscriberTestWithoutSetup(body); +506 } catch (Exception ex) { +507 notVerified("Skipped because tested publisher does NOT implement this OPTIONAL requirement."); +508 } +509 } +510 +511 public class WhiteboxTestStage extends ManualPublisher<T> { +512 public Publisher<T> pub; +513 public ManualSubscriber<T> tees; // gives us access to a stream T values +514 public WhiteboxSubscriberProbe<T> probe; +515 +516 public T lastT = null; +517 +518 public WhiteboxTestStage(TestEnvironment env) throws InterruptedException { +519 this(env, true); +520 } +521 +522 public WhiteboxTestStage(TestEnvironment env, boolean runDefaultInit) throws InterruptedException { +523 super(env); +524 if (runDefaultInit) { +525 pub = this.createHelperPublisher(Long.MAX_VALUE); +526 tees = env.newManualSubscriber(pub); +527 probe = new WhiteboxSubscriberProbe<T>(env, subscriber); +528 subscribe(createSubscriber(probe)); +529 probe.puppet.expectCompletion(env.defaultTimeoutMillis(), String.format("Subscriber %s did not `registerOnSubscribe`", sub())); +530 } +531 } +532 +533 public Subscriber<? super T> sub() { +534 return subscriber.value(); +535 } +536 +537 public SubscriberPuppet puppet() { +538 return probe.puppet(); +539 } +540 +541 public WhiteboxSubscriberProbe<T> probe() { +542 return probe; +543 } +544 +545 public Publisher<T> createHelperPublisher(long elements) { +546 return SubscriberWhiteboxVerification.this.createHelperPublisher(elements); +547 } +548 +549 public WhiteboxSubscriberProbe<T> createWhiteboxSubscriberProbe(TestEnvironment env) { +550 return new WhiteboxSubscriberProbe<T>(env, subscriber); +551 } +552 +553 public T signalNext() throws InterruptedException { +554 return signalNext(nextT()); +555 } +556 +557 private T signalNext(T element) throws InterruptedException { +558 sendNext(element); +559 return element; +560 } +561 +562 public T nextT() throws InterruptedException { +563 lastT = tees.requestNextElement(); +564 return lastT; +565 } +566 +567 public void verifyNoAsyncErrors() { +568 env.verifyNoAsyncErrors(); +569 } +570 } +571 +572 /** +573 * This class is intented to be used as {@code Subscriber} decorator and should be used in {@code pub.subscriber(...)} calls, +574 * in order to allow intercepting calls on the underlying {@code Subscriber}. +575 * This delegation allows the proxy to implement {@link BlackboxProbe} assertions. +576 */ +577 public static class BlackboxSubscriberProxy<T> extends BlackboxProbe<T> implements Subscriber<T> { +578 +579 public BlackboxSubscriberProxy(TestEnvironment env, Subscriber<T> subscriber) { +580 super(env, Promise.<Subscriber<? super T>>completed(env, subscriber)); +581 } +582 +583 @Override +584 public void onSubscribe(Subscription s) { +585 sub().onSubscribe(s); +586 } +587 +588 @Override +589 public void onNext(T t) { +590 registerOnNext(t); +591 sub().onNext(t); +592 } +593 +594 @Override +595 public void onError(Throwable cause) { +596 registerOnError(cause); +597 sub().onError(cause); +598 } +599 +600 @Override +601 public void onComplete() { +602 registerOnComplete(); +603 sub().onComplete(); +604 } +605 } +606 +607 public static class BlackboxProbe<T> implements SubscriberProbe<T> { +608 protected final TestEnvironment env; +609 protected final Promise<Subscriber<? super T>> subscriber; +610 +611 protected final Receptacle<T> elements; +612 protected final Promise<Throwable> error; +613 +614 public BlackboxProbe(TestEnvironment env, Promise<Subscriber<? super T>> subscriber) { +615 this.env = env; +616 this.subscriber = subscriber; +617 elements = new Receptacle<T>(env); +618 error = new Promise<Throwable>(env); +619 } +620 +621 @Override +622 public void registerOnNext(T element) { +623 elements.add(element); +624 } +625 +626 @Override +627 public void registerOnComplete() { +628 try { +629 elements.complete(); +630 } catch (IllegalStateException ex) { +631 // "Queue full", onComplete was already called +632 env.flop("subscriber::onComplete was called a second time, which is illegal according to Rule 1.7"); +633 } +634 } +635 +636 @Override +637 public void registerOnError(Throwable cause) { +638 try { +639 error.complete(cause); +640 } catch (IllegalStateException ex) { +641 // "Queue full", onError was already called +642 env.flop("subscriber::onError was called a second time, which is illegal according to Rule 1.7"); +643 } +644 } +645 +646 public T expectNext() throws InterruptedException { +647 return elements.next(env.defaultTimeoutMillis(), String.format("Subscriber %s did not call `registerOnNext(_)`", sub())); +648 } +649 +650 public void expectNext(T expected) throws InterruptedException { +651 expectNext(expected, env.defaultTimeoutMillis()); +652 } +653 +654 public void expectNext(T expected, long timeoutMillis) throws InterruptedException { +655 T received = elements.next(timeoutMillis, String.format("Subscriber %s did not call `registerOnNext(%s)`", sub(), expected)); +656 if (!received.equals(expected)) { +657 env.flop(String.format("Subscriber %s called `registerOnNext(%s)` rather than `registerOnNext(%s)`", sub(), received, expected)); +658 } +659 } +660 +661 public Subscriber<? super T> sub() { +662 return subscriber.value(); +663 } +664 +665 public void expectCompletion() throws InterruptedException { +666 expectCompletion(env.defaultTimeoutMillis()); +667 } +668 +669 public void expectCompletion(long timeoutMillis) throws InterruptedException { +670 expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnComplete()`", sub())); +671 } +672 +673 public void expectCompletion(long timeoutMillis, String msg) throws InterruptedException { +674 elements.expectCompletion(timeoutMillis, msg); +675 } +676 +677 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +678 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart) throws InterruptedException { +679 final E err = expectError(expected); +680 String message = err.getMessage(); +681 assertTrue(message.contains(requiredMessagePart), +682 String.format("Got expected exception %s but missing message [%s], was: %s", err.getClass(), requiredMessagePart, expected)); +683 } +684 +685 public <E extends Throwable> E expectError(Class<E> expected) throws InterruptedException { +686 return expectError(expected, env.defaultTimeoutMillis()); +687 } +688 +689 @SuppressWarnings({"unchecked", "ThrowableResultOfMethodCallIgnored"}) +690 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis) throws InterruptedException { +691 error.expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected)); +692 if (error.value() == null) { +693 return env.flopAndFail(String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected)); +694 } else if (expected.isInstance(error.value())) { +695 return (E) error.value(); +696 } else { +697 return env.flopAndFail(String.format("Subscriber %s called `registerOnError(%s)` rather than `registerOnError(%s)`", sub(), error.value(), expected)); +698 } +699 } +700 +701 public void expectError(Throwable expected) throws InterruptedException { +702 expectError(expected, env.defaultTimeoutMillis()); +703 } +704 +705 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +706 public void expectError(Throwable expected, long timeoutMillis) throws InterruptedException { +707 error.expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected)); +708 if (error.value() != expected) { +709 env.flop(String.format("Subscriber %s called `registerOnError(%s)` rather than `registerOnError(%s)`", sub(), error.value(), expected)); +710 } +711 } +712 +713 public void expectNone() throws InterruptedException { +714 expectNone(env.defaultTimeoutMillis()); +715 } +716 +717 public void expectNone(long withinMillis) throws InterruptedException { +718 elements.expectNone(withinMillis, "Expected nothing"); +719 } +720 +721 } +722 +723 public static class WhiteboxSubscriberProbe<T> extends BlackboxProbe<T> implements SubscriberPuppeteer { +724 protected Promise<SubscriberPuppet> puppet; +725 +726 public WhiteboxSubscriberProbe(TestEnvironment env, Promise<Subscriber<? super T>> subscriber) { +727 super(env, subscriber); +728 puppet = new Promise<SubscriberPuppet>(env); +729 } +730 +731 private SubscriberPuppet puppet() { +732 return puppet.value(); +733 } +734 +735 @Override +736 public void registerOnSubscribe(SubscriberPuppet p) { +737 if (!puppet.isCompleted()) { +738 puppet.complete(p); +739 } +740 } +741 +742 } +743 +744 public interface SubscriberPuppeteer { +745 +746 /** +747 * Must be called by the test subscriber when it has successfully registered a subscription +748 * inside the `onSubscribe` method. +749 */ +750 void registerOnSubscribe(SubscriberPuppet puppet); +751 } +752 +753 public interface SubscriberProbe<T> { +754 +755 /** +756 * Must be called by the test subscriber when it has received an`onNext` event. +757 */ +758 void registerOnNext(T element); +759 +760 /** +761 * Must be called by the test subscriber when it has received an `onComplete` event. +762 */ +763 void registerOnComplete(); +764 +765 /** +766 * Must be called by the test subscriber when it has received an `onError` event. +767 */ +768 void registerOnError(Throwable cause); +769 +770 } +771 +772 public interface SubscriberPuppet { +773 void triggerRequest(long elements); +774 +775 void signalCancel(); +776 } +777 +778 public void notVerified() { +779 throw new SkipException("Not verified using this TCK."); +780 } +781 +782 public void notVerified(String msg) { +783 throw new SkipException(msg); +784 } +785} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck; +002 +003import org.reactivestreams.Publisher; +004import org.reactivestreams.Subscriber; +005import org.reactivestreams.Subscription; +006import org.reactivestreams.tck.TestEnvironment.*; +007import org.reactivestreams.tck.support.Optional; +008import org.reactivestreams.tck.support.SubscriberWhiteboxVerificationRules; +009import org.reactivestreams.tck.support.TestException; +010import org.testng.SkipException; +011import org.testng.annotations.AfterClass; +012import org.testng.annotations.BeforeClass; +013import org.testng.annotations.BeforeMethod; +014import org.testng.annotations.Test; +015 +016import java.util.concurrent.ExecutorService; +017import java.util.concurrent.Executors; +018 +019import static org.testng.Assert.assertTrue; +020 +021/** +022 * Provides tests for verifying {@link org.reactivestreams.Subscriber} and {@link org.reactivestreams.Subscription} specification rules. +023 * +024 * @see org.reactivestreams.Subscriber +025 * @see org.reactivestreams.Subscription +026 */ +027public abstract class SubscriberWhiteboxVerification<T> extends WithHelperPublisher<T> +028 implements SubscriberWhiteboxVerificationRules { +029 +030 private final TestEnvironment env; +031 +032 protected SubscriberWhiteboxVerification(TestEnvironment env) { +033 this.env = env; +034 } +035 +036 // USER API +037 +038 /** +039 * This is the main method you must implement in your test incarnation. +040 * It must create a new {@link org.reactivestreams.Subscriber} instance to be subjected to the testing logic. +041 * +042 * In order to be meaningfully testable your Subscriber must inform the given +043 * `WhiteboxSubscriberProbe` of the respective events having been received. +044 */ +045 public abstract Subscriber<T> createSubscriber(WhiteboxSubscriberProbe<T> probe); +046 +047 // ENV SETUP +048 +049 /** +050 * Executor service used by the default provided asynchronous Publisher. +051 * @see #createHelperPublisher(long) +052 */ +053 private ExecutorService publisherExecutor; +054 @BeforeClass public void startPublisherExecutorService() { publisherExecutor = Executors.newFixedThreadPool(4); } +055 @AfterClass public void shutdownPublisherExecutorService() { if (publisherExecutor != null) publisherExecutor.shutdown(); } +056 @Override public ExecutorService publisherExecutorService() { return publisherExecutor; } +057 +058 ////////////////////// TEST ENV CLEANUP ///////////////////////////////////// +059 +060 @BeforeMethod +061 public void setUp() throws Exception { +062 env.clearAsyncErrors(); +063 } +064 +065 ////////////////////// TEST SETUP VERIFICATION ////////////////////////////// +066 +067 @Test +068 public void required_exerciseWhiteboxHappyPath() throws Throwable { +069 subscriberTest(new TestStageTestRun() { +070 @Override +071 public void run(WhiteboxTestStage stage) throws InterruptedException { +072 stage.puppet().triggerRequest(1); +073 stage.puppet().triggerRequest(1); +074 +075 long receivedRequests = stage.expectRequest(); +076 +077 stage.signalNext(); +078 stage.probe.expectNext(stage.lastT); +079 +080 stage.puppet().triggerRequest(1); +081 if (receivedRequests == 1) { +082 stage.expectRequest(); +083 } +084 +085 stage.signalNext(); +086 stage.probe.expectNext(stage.lastT); +087 +088 stage.puppet().signalCancel(); +089 stage.expectCancelling(); +090 +091 stage.verifyNoAsyncErrors(); +092 } +093 }); +094 } +095 +096 ////////////////////// SPEC RULE VERIFICATION /////////////////////////////// +097 +098 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.1 +099 @Override @Test +100 public void required_spec201_mustSignalDemandViaSubscriptionRequest() throws Throwable { +101 subscriberTest(new TestStageTestRun() { +102 @Override +103 public void run(WhiteboxTestStage stage) throws InterruptedException { +104 stage.puppet().triggerRequest(1); +105 stage.expectRequest(); +106 +107 stage.signalNext(); +108 } +109 }); +110 } +111 +112 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.2 +113 @Override @Test +114 public void untested_spec202_shouldAsynchronouslyDispatch() throws Exception { +115 notVerified(); // cannot be meaningfully tested, or can it? +116 } +117 +118 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.3 +119 @Override @Test +120 public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() throws Throwable { +121 subscriberTestWithoutSetup(new TestStageTestRun() { +122 @Override +123 public void run(WhiteboxTestStage stage) throws Throwable { +124 final Subscription subs = new Subscription() { +125 @Override +126 public void request(long n) { +127 final Optional<StackTraceElement> onCompleteStackTraceElement = env.findCallerMethodInStackTrace("onComplete"); +128 if (onCompleteStackTraceElement.isDefined()) { +129 final StackTraceElement stackElem = onCompleteStackTraceElement.get(); +130 env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)", +131 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +132 } +133 } +134 +135 @Override +136 public void cancel() { +137 final Optional<StackTraceElement> onCompleteStackElement = env.findCallerMethodInStackTrace("onComplete"); +138 if (onCompleteStackElement.isDefined()) { +139 final StackTraceElement stackElem = onCompleteStackElement.get(); +140 env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)", +141 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +142 } +143 } +144 }; +145 +146 stage.probe = stage.createWhiteboxSubscriberProbe(env); +147 final Subscriber<T> sub = createSubscriber(stage.probe); +148 +149 sub.onSubscribe(subs); +150 sub.onComplete(); +151 +152 env.verifyNoAsyncErrorsNoDelay(); +153 } +154 }); +155 } +156 +157 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.3 +158 @Override @Test +159 public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() throws Throwable { +160 subscriberTestWithoutSetup(new TestStageTestRun() { +161 @Override +162 public void run(WhiteboxTestStage stage) throws Throwable { +163 final Subscription subs = new Subscription() { +164 @Override +165 public void request(long n) { +166 Throwable thr = new Throwable(); +167 for (StackTraceElement stackElem : thr.getStackTrace()) { +168 if (stackElem.getMethodName().equals("onError")) { +169 env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)", +170 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +171 } +172 } +173 } +174 +175 @Override +176 public void cancel() { +177 Throwable thr = new Throwable(); +178 for (StackTraceElement stackElem : thr.getStackTrace()) { +179 if (stackElem.getMethodName().equals("onError")) { +180 env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)", +181 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +182 } +183 } +184 } +185 }; +186 +187 stage.probe = stage.createWhiteboxSubscriberProbe(env); +188 final Subscriber<T> sub = createSubscriber(stage.probe); +189 +190 sub.onSubscribe(subs); +191 sub.onError(new TestException()); +192 +193 env.verifyNoAsyncErrorsNoDelay(); +194 } +195 }); +196 } +197 +198 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.4 +199 @Override @Test +200 public void untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() throws Exception { +201 notVerified(); // cannot be meaningfully tested, or can it? +202 } +203 +204 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.5 +205 @Override @Test +206 public void required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() throws Throwable { +207 subscriberTest(new TestStageTestRun() { +208 @Override +209 public void run(WhiteboxTestStage stage) throws Throwable { +210 // try to subscribe another time, if the subscriber calls `probe.registerOnSubscribe` the test will fail +211 final Latch secondSubscriptionCancelled = new Latch(env); +212 final Subscriber<? super T> sub = stage.sub(); +213 final Subscription subscription = new Subscription() { +214 @Override +215 public void request(long elements) { +216 // ignore... +217 } +218 +219 @Override +220 public void cancel() { +221 secondSubscriptionCancelled.close(); +222 } +223 +224 @Override +225 public String toString() { +226 return "SecondSubscription(should get cancelled)"; +227 } +228 }; +229 sub.onSubscribe(subscription); +230 +231 secondSubscriptionCancelled.expectClose("Expected 2nd Subscription given to subscriber to be cancelled, but `Subscription.cancel()` was not called"); +232 env.verifyNoAsyncErrors(); +233 } +234 }); +235 } +236 +237 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.6 +238 @Override @Test +239 public void untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid() throws Exception { +240 notVerified(); // cannot be meaningfully tested, or can it? +241 } +242 +243 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.7 +244 @Override @Test +245 public void untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() throws Exception { +246 notVerified(); // cannot be meaningfully tested, or can it? +247 // the same thread part of the clause can be verified but that is not very useful, or is it? +248 } +249 +250 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.8 +251 @Override @Test +252 public void required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() throws Throwable { +253 subscriberTest(new TestStageTestRun() { +254 @Override +255 public void run(WhiteboxTestStage stage) throws InterruptedException { +256 stage.puppet().triggerRequest(1); +257 stage.puppet().signalCancel(); +258 stage.signalNext(); +259 +260 stage.puppet().triggerRequest(1); +261 stage.puppet().triggerRequest(1); +262 +263 stage.verifyNoAsyncErrors(); +264 } +265 }); +266 } +267 +268 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.9 +269 @Override @Test +270 public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() throws Throwable { +271 subscriberTest(new TestStageTestRun() { +272 @Override +273 public void run(WhiteboxTestStage stage) throws InterruptedException { +274 stage.puppet().triggerRequest(1); +275 stage.sendCompletion(); +276 stage.probe.expectCompletion(); +277 +278 stage.verifyNoAsyncErrors(); +279 } +280 }); +281 } +282 +283 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.9 +284 @Override @Test +285 public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() throws Throwable { +286 subscriberTest(new TestStageTestRun() { +287 @Override +288 public void run(WhiteboxTestStage stage) throws InterruptedException { +289 stage.sendCompletion(); +290 stage.probe.expectCompletion(); +291 +292 stage.verifyNoAsyncErrors(); +293 } +294 }); +295 } +296 +297 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.10 +298 @Override @Test +299 public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() throws Throwable { +300 subscriberTest(new TestStageTestRun() { +301 @Override +302 public void run(WhiteboxTestStage stage) throws InterruptedException { +303 stage.puppet().triggerRequest(1); +304 stage.puppet().triggerRequest(1); +305 +306 Exception ex = new TestException(); +307 stage.sendError(ex); +308 stage.probe.expectError(ex); +309 +310 env.verifyNoAsyncErrorsNoDelay(); +311 } +312 }); +313 } +314 +315 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.10 +316 @Override @Test +317 public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall() throws Throwable { +318 subscriberTest(new TestStageTestRun() { +319 @Override +320 public void run(WhiteboxTestStage stage) throws InterruptedException { +321 Exception ex = new TestException(); +322 stage.sendError(ex); +323 stage.probe.expectError(ex); +324 +325 env.verifyNoAsyncErrorsNoDelay(); +326 } +327 }); +328 } +329 +330 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.11 +331 @Override @Test +332 public void untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() throws Exception { +333 notVerified(); // cannot be meaningfully tested, or can it? +334 } +335 +336 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.12 +337 @Override @Test +338 public void untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation() throws Throwable { +339 notVerified(); // cannot be meaningfully tested, or can it? +340 } +341 +342 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +343 @Override @Test +344 public void untested_spec213_failingOnSignalInvocation() throws Exception { +345 notVerified(); // cannot be meaningfully tested, or can it? +346 } +347 +348 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +349 @Override @Test +350 public void required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +351 subscriberTest(new TestStageTestRun() { +352 @Override +353 public void run(WhiteboxTestStage stage) throws Throwable { +354 +355 final Subscriber<? super T> sub = stage.sub(); +356 boolean gotNPE = false; +357 try { +358 sub.onSubscribe(null); +359 } catch (final NullPointerException expected) { +360 gotNPE = true; +361 } +362 +363 assertTrue(gotNPE, "onSubscribe(null) did not throw NullPointerException"); +364 env.verifyNoAsyncErrorsNoDelay(); +365 } +366 }); +367 } +368 +369 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +370 @Override @Test +371 public void required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +372 subscriberTest(new TestStageTestRun() { +373 @Override +374 public void run(WhiteboxTestStage stage) throws Throwable { +375 +376 final Subscriber<? super T> sub = stage.sub(); +377 boolean gotNPE = false; +378 try { +379 sub.onNext(null); +380 } catch (final NullPointerException expected) { +381 gotNPE = true; +382 } +383 +384 assertTrue(gotNPE, "onNext(null) did not throw NullPointerException"); +385 env.verifyNoAsyncErrorsNoDelay(); +386 } +387 }); +388 } +389 +390 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +391 @Override @Test +392 public void required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +393 subscriberTest(new TestStageTestRun() { +394 @Override +395 public void run(WhiteboxTestStage stage) throws Throwable { +396 +397 final Subscriber<? super T> sub = stage.sub(); +398 boolean gotNPE = false; +399 try { +400 sub.onError(null); +401 } catch (final NullPointerException expected) { +402 gotNPE = true; +403 } finally { +404 assertTrue(gotNPE, "onError(null) did not throw NullPointerException"); +405 } +406 +407 env.verifyNoAsyncErrorsNoDelay(); +408 } +409 }); +410 } +411 +412 +413 ////////////////////// SUBSCRIPTION SPEC RULE VERIFICATION ////////////////// +414 +415 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.1 +416 @Override @Test +417 public void untested_spec301_mustNotBeCalledOutsideSubscriberContext() throws Exception { +418 notVerified(); // cannot be meaningfully tested, or can it? +419 } +420 +421 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.8 +422 @Override @Test +423 public void required_spec308_requestMustRegisterGivenNumberElementsToBeProduced() throws Throwable { +424 subscriberTest(new TestStageTestRun() { +425 @Override +426 public void run(WhiteboxTestStage stage) throws InterruptedException { +427 stage.puppet().triggerRequest(2); +428 stage.probe.expectNext(stage.signalNext()); +429 stage.probe.expectNext(stage.signalNext()); +430 +431 stage.probe.expectNone(); +432 stage.puppet().triggerRequest(3); +433 +434 stage.verifyNoAsyncErrors(); +435 } +436 }); +437 } +438 +439 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.10 +440 @Override @Test +441 public void untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber() throws Exception { +442 notVerified(); // cannot be meaningfully tested, or can it? +443 } +444 +445 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.11 +446 @Override @Test +447 public void untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError() throws Exception { +448 notVerified(); // cannot be meaningfully tested, or can it? +449 } +450 +451 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.14 +452 @Override @Test +453 public void untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() throws Exception { +454 notVerified(); // cannot be meaningfully tested, or can it? +455 } +456 +457 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.15 +458 @Override @Test +459 public void untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError() throws Exception { +460 notVerified(); // cannot be meaningfully tested, or can it? +461 } +462 +463 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.16 +464 @Override @Test +465 public void untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() throws Exception { +466 notVerified(); // cannot be meaningfully tested, or can it? +467 } +468 +469 /////////////////////// ADDITIONAL "COROLLARY" TESTS //////////////////////// +470 +471 /////////////////////// TEST INFRASTRUCTURE ///////////////////////////////// +472 +473 abstract class TestStageTestRun { +474 public abstract void run(WhiteboxTestStage stage) throws Throwable; +475 } +476 +477 /** +478 * Prepares subscriber and publisher pair (by subscribing the first to the latter), +479 * and then hands over the tests {@link WhiteboxTestStage} over to the test. +480 * +481 * The test stage is, like in a puppet show, used to orchestrate what each participant should do. +482 * Since this is a whitebox test, this allows the stage to completely control when and how to signal / expect signals. +483 */ +484 public void subscriberTest(TestStageTestRun body) throws Throwable { +485 WhiteboxTestStage stage = new WhiteboxTestStage(env, true); +486 body.run(stage); +487 } +488 +489 /** +490 * Provides a {@link WhiteboxTestStage} without performing any additional setup, +491 * like the {@link #subscriberTest(SubscriberWhiteboxVerification.TestStageTestRun)} would. +492 * +493 * Use this method to write tests in which you need full control over when and how the initial {@code subscribe} is signalled. +494 */ +495 public void subscriberTestWithoutSetup(TestStageTestRun body) throws Throwable { +496 WhiteboxTestStage stage = new WhiteboxTestStage(env, false); +497 body.run(stage); +498 } +499 +500 /** +501 * Test for feature that MAY be implemented. This test will be marked as SKIPPED if it fails. +502 */ +503 public void optionalSubscriberTestWithoutSetup(TestStageTestRun body) throws Throwable { +504 try { +505 subscriberTestWithoutSetup(body); +506 } catch (Exception ex) { +507 notVerified("Skipped because tested publisher does NOT implement this OPTIONAL requirement."); +508 } +509 } +510 +511 public class WhiteboxTestStage extends ManualPublisher<T> { +512 public Publisher<T> pub; +513 public ManualSubscriber<T> tees; // gives us access to a stream T values +514 public WhiteboxSubscriberProbe<T> probe; +515 +516 public T lastT = null; +517 +518 public WhiteboxTestStage(TestEnvironment env) throws InterruptedException { +519 this(env, true); +520 } +521 +522 public WhiteboxTestStage(TestEnvironment env, boolean runDefaultInit) throws InterruptedException { +523 super(env); +524 if (runDefaultInit) { +525 pub = this.createHelperPublisher(Long.MAX_VALUE); +526 tees = env.newManualSubscriber(pub); +527 probe = new WhiteboxSubscriberProbe<T>(env, subscriber); +528 subscribe(createSubscriber(probe)); +529 probe.puppet.expectCompletion(env.defaultTimeoutMillis(), String.format("Subscriber %s did not `registerOnSubscribe`", sub())); +530 } +531 } +532 +533 public Subscriber<? super T> sub() { +534 return subscriber.value(); +535 } +536 +537 public SubscriberPuppet puppet() { +538 return probe.puppet(); +539 } +540 +541 public WhiteboxSubscriberProbe<T> probe() { +542 return probe; +543 } +544 +545 public Publisher<T> createHelperPublisher(long elements) { +546 return SubscriberWhiteboxVerification.this.createHelperPublisher(elements); +547 } +548 +549 public WhiteboxSubscriberProbe<T> createWhiteboxSubscriberProbe(TestEnvironment env) { +550 return new WhiteboxSubscriberProbe<T>(env, subscriber); +551 } +552 +553 public T signalNext() throws InterruptedException { +554 return signalNext(nextT()); +555 } +556 +557 private T signalNext(T element) throws InterruptedException { +558 sendNext(element); +559 return element; +560 } +561 +562 public T nextT() throws InterruptedException { +563 lastT = tees.requestNextElement(); +564 return lastT; +565 } +566 +567 public void verifyNoAsyncErrors() { +568 env.verifyNoAsyncErrors(); +569 } +570 } +571 +572 /** +573 * This class is intented to be used as {@code Subscriber} decorator and should be used in {@code pub.subscriber(...)} calls, +574 * in order to allow intercepting calls on the underlying {@code Subscriber}. +575 * This delegation allows the proxy to implement {@link BlackboxProbe} assertions. +576 */ +577 public static class BlackboxSubscriberProxy<T> extends BlackboxProbe<T> implements Subscriber<T> { +578 +579 public BlackboxSubscriberProxy(TestEnvironment env, Subscriber<T> subscriber) { +580 super(env, Promise.<Subscriber<? super T>>completed(env, subscriber)); +581 } +582 +583 @Override +584 public void onSubscribe(Subscription s) { +585 sub().onSubscribe(s); +586 } +587 +588 @Override +589 public void onNext(T t) { +590 registerOnNext(t); +591 sub().onNext(t); +592 } +593 +594 @Override +595 public void onError(Throwable cause) { +596 registerOnError(cause); +597 sub().onError(cause); +598 } +599 +600 @Override +601 public void onComplete() { +602 registerOnComplete(); +603 sub().onComplete(); +604 } +605 } +606 +607 public static class BlackboxProbe<T> implements SubscriberProbe<T> { +608 protected final TestEnvironment env; +609 protected final Promise<Subscriber<? super T>> subscriber; +610 +611 protected final Receptacle<T> elements; +612 protected final Promise<Throwable> error; +613 +614 public BlackboxProbe(TestEnvironment env, Promise<Subscriber<? super T>> subscriber) { +615 this.env = env; +616 this.subscriber = subscriber; +617 elements = new Receptacle<T>(env); +618 error = new Promise<Throwable>(env); +619 } +620 +621 @Override +622 public void registerOnNext(T element) { +623 elements.add(element); +624 } +625 +626 @Override +627 public void registerOnComplete() { +628 try { +629 elements.complete(); +630 } catch (IllegalStateException ex) { +631 // "Queue full", onComplete was already called +632 env.flop("subscriber::onComplete was called a second time, which is illegal according to Rule 1.7"); +633 } +634 } +635 +636 @Override +637 public void registerOnError(Throwable cause) { +638 try { +639 error.complete(cause); +640 } catch (IllegalStateException ex) { +641 // "Queue full", onError was already called +642 env.flop("subscriber::onError was called a second time, which is illegal according to Rule 1.7"); +643 } +644 } +645 +646 public T expectNext() throws InterruptedException { +647 return elements.next(env.defaultTimeoutMillis(), String.format("Subscriber %s did not call `registerOnNext(_)`", sub())); +648 } +649 +650 public void expectNext(T expected) throws InterruptedException { +651 expectNext(expected, env.defaultTimeoutMillis()); +652 } +653 +654 public void expectNext(T expected, long timeoutMillis) throws InterruptedException { +655 T received = elements.next(timeoutMillis, String.format("Subscriber %s did not call `registerOnNext(%s)`", sub(), expected)); +656 if (!received.equals(expected)) { +657 env.flop(String.format("Subscriber %s called `registerOnNext(%s)` rather than `registerOnNext(%s)`", sub(), received, expected)); +658 } +659 } +660 +661 public Subscriber<? super T> sub() { +662 return subscriber.value(); +663 } +664 +665 public void expectCompletion() throws InterruptedException { +666 expectCompletion(env.defaultTimeoutMillis()); +667 } +668 +669 public void expectCompletion(long timeoutMillis) throws InterruptedException { +670 expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnComplete()`", sub())); +671 } +672 +673 public void expectCompletion(long timeoutMillis, String msg) throws InterruptedException { +674 elements.expectCompletion(timeoutMillis, msg); +675 } +676 +677 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +678 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart) throws InterruptedException { +679 final E err = expectError(expected); +680 String message = err.getMessage(); +681 assertTrue(message.contains(requiredMessagePart), +682 String.format("Got expected exception %s but missing message [%s], was: %s", err.getClass(), requiredMessagePart, expected)); +683 } +684 +685 public <E extends Throwable> E expectError(Class<E> expected) throws InterruptedException { +686 return expectError(expected, env.defaultTimeoutMillis()); +687 } +688 +689 @SuppressWarnings({"unchecked", "ThrowableResultOfMethodCallIgnored"}) +690 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis) throws InterruptedException { +691 error.expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected)); +692 if (error.value() == null) { +693 return env.flopAndFail(String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected)); +694 } else if (expected.isInstance(error.value())) { +695 return (E) error.value(); +696 } else { +697 return env.flopAndFail(String.format("Subscriber %s called `registerOnError(%s)` rather than `registerOnError(%s)`", sub(), error.value(), expected)); +698 } +699 } +700 +701 public void expectError(Throwable expected) throws InterruptedException { +702 expectError(expected, env.defaultTimeoutMillis()); +703 } +704 +705 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +706 public void expectError(Throwable expected, long timeoutMillis) throws InterruptedException { +707 error.expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected)); +708 if (error.value() != expected) { +709 env.flop(String.format("Subscriber %s called `registerOnError(%s)` rather than `registerOnError(%s)`", sub(), error.value(), expected)); +710 } +711 } +712 +713 public void expectNone() throws InterruptedException { +714 expectNone(env.defaultTimeoutMillis()); +715 } +716 +717 public void expectNone(long withinMillis) throws InterruptedException { +718 elements.expectNone(withinMillis, "Expected nothing"); +719 } +720 +721 } +722 +723 public static class WhiteboxSubscriberProbe<T> extends BlackboxProbe<T> implements SubscriberPuppeteer { +724 protected Promise<SubscriberPuppet> puppet; +725 +726 public WhiteboxSubscriberProbe(TestEnvironment env, Promise<Subscriber<? super T>> subscriber) { +727 super(env, subscriber); +728 puppet = new Promise<SubscriberPuppet>(env); +729 } +730 +731 private SubscriberPuppet puppet() { +732 return puppet.value(); +733 } +734 +735 @Override +736 public void registerOnSubscribe(SubscriberPuppet p) { +737 if (!puppet.isCompleted()) { +738 puppet.complete(p); +739 } +740 } +741 +742 } +743 +744 public interface SubscriberPuppeteer { +745 +746 /** +747 * Must be called by the test subscriber when it has successfully registered a subscription +748 * inside the `onSubscribe` method. +749 */ +750 void registerOnSubscribe(SubscriberPuppet puppet); +751 } +752 +753 public interface SubscriberProbe<T> { +754 +755 /** +756 * Must be called by the test subscriber when it has received an`onNext` event. +757 */ +758 void registerOnNext(T element); +759 +760 /** +761 * Must be called by the test subscriber when it has received an `onComplete` event. +762 */ +763 void registerOnComplete(); +764 +765 /** +766 * Must be called by the test subscriber when it has received an `onError` event. +767 */ +768 void registerOnError(Throwable cause); +769 +770 } +771 +772 public interface SubscriberPuppet { +773 void triggerRequest(long elements); +774 +775 void signalCancel(); +776 } +777 +778 public void notVerified() { +779 throw new SkipException("Not verified using this TCK."); +780 } +781 +782 public void notVerified(String msg) { +783 throw new SkipException(msg); +784 } +785} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck; +002 +003import org.reactivestreams.Publisher; +004import org.reactivestreams.Subscriber; +005import org.reactivestreams.Subscription; +006import org.reactivestreams.tck.TestEnvironment.*; +007import org.reactivestreams.tck.support.Optional; +008import org.reactivestreams.tck.support.SubscriberWhiteboxVerificationRules; +009import org.reactivestreams.tck.support.TestException; +010import org.testng.SkipException; +011import org.testng.annotations.AfterClass; +012import org.testng.annotations.BeforeClass; +013import org.testng.annotations.BeforeMethod; +014import org.testng.annotations.Test; +015 +016import java.util.concurrent.ExecutorService; +017import java.util.concurrent.Executors; +018 +019import static org.testng.Assert.assertTrue; +020 +021/** +022 * Provides tests for verifying {@link org.reactivestreams.Subscriber} and {@link org.reactivestreams.Subscription} specification rules. +023 * +024 * @see org.reactivestreams.Subscriber +025 * @see org.reactivestreams.Subscription +026 */ +027public abstract class SubscriberWhiteboxVerification<T> extends WithHelperPublisher<T> +028 implements SubscriberWhiteboxVerificationRules { +029 +030 private final TestEnvironment env; +031 +032 protected SubscriberWhiteboxVerification(TestEnvironment env) { +033 this.env = env; +034 } +035 +036 // USER API +037 +038 /** +039 * This is the main method you must implement in your test incarnation. +040 * It must create a new {@link org.reactivestreams.Subscriber} instance to be subjected to the testing logic. +041 * +042 * In order to be meaningfully testable your Subscriber must inform the given +043 * `WhiteboxSubscriberProbe` of the respective events having been received. +044 */ +045 public abstract Subscriber<T> createSubscriber(WhiteboxSubscriberProbe<T> probe); +046 +047 // ENV SETUP +048 +049 /** +050 * Executor service used by the default provided asynchronous Publisher. +051 * @see #createHelperPublisher(long) +052 */ +053 private ExecutorService publisherExecutor; +054 @BeforeClass public void startPublisherExecutorService() { publisherExecutor = Executors.newFixedThreadPool(4); } +055 @AfterClass public void shutdownPublisherExecutorService() { if (publisherExecutor != null) publisherExecutor.shutdown(); } +056 @Override public ExecutorService publisherExecutorService() { return publisherExecutor; } +057 +058 ////////////////////// TEST ENV CLEANUP ///////////////////////////////////// +059 +060 @BeforeMethod +061 public void setUp() throws Exception { +062 env.clearAsyncErrors(); +063 } +064 +065 ////////////////////// TEST SETUP VERIFICATION ////////////////////////////// +066 +067 @Test +068 public void required_exerciseWhiteboxHappyPath() throws Throwable { +069 subscriberTest(new TestStageTestRun() { +070 @Override +071 public void run(WhiteboxTestStage stage) throws InterruptedException { +072 stage.puppet().triggerRequest(1); +073 stage.puppet().triggerRequest(1); +074 +075 long receivedRequests = stage.expectRequest(); +076 +077 stage.signalNext(); +078 stage.probe.expectNext(stage.lastT); +079 +080 stage.puppet().triggerRequest(1); +081 if (receivedRequests == 1) { +082 stage.expectRequest(); +083 } +084 +085 stage.signalNext(); +086 stage.probe.expectNext(stage.lastT); +087 +088 stage.puppet().signalCancel(); +089 stage.expectCancelling(); +090 +091 stage.verifyNoAsyncErrors(); +092 } +093 }); +094 } +095 +096 ////////////////////// SPEC RULE VERIFICATION /////////////////////////////// +097 +098 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.1 +099 @Override @Test +100 public void required_spec201_mustSignalDemandViaSubscriptionRequest() throws Throwable { +101 subscriberTest(new TestStageTestRun() { +102 @Override +103 public void run(WhiteboxTestStage stage) throws InterruptedException { +104 stage.puppet().triggerRequest(1); +105 stage.expectRequest(); +106 +107 stage.signalNext(); +108 } +109 }); +110 } +111 +112 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.2 +113 @Override @Test +114 public void untested_spec202_shouldAsynchronouslyDispatch() throws Exception { +115 notVerified(); // cannot be meaningfully tested, or can it? +116 } +117 +118 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.3 +119 @Override @Test +120 public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() throws Throwable { +121 subscriberTestWithoutSetup(new TestStageTestRun() { +122 @Override +123 public void run(WhiteboxTestStage stage) throws Throwable { +124 final Subscription subs = new Subscription() { +125 @Override +126 public void request(long n) { +127 final Optional<StackTraceElement> onCompleteStackTraceElement = env.findCallerMethodInStackTrace("onComplete"); +128 if (onCompleteStackTraceElement.isDefined()) { +129 final StackTraceElement stackElem = onCompleteStackTraceElement.get(); +130 env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)", +131 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +132 } +133 } +134 +135 @Override +136 public void cancel() { +137 final Optional<StackTraceElement> onCompleteStackElement = env.findCallerMethodInStackTrace("onComplete"); +138 if (onCompleteStackElement.isDefined()) { +139 final StackTraceElement stackElem = onCompleteStackElement.get(); +140 env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)", +141 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +142 } +143 } +144 }; +145 +146 stage.probe = stage.createWhiteboxSubscriberProbe(env); +147 final Subscriber<T> sub = createSubscriber(stage.probe); +148 +149 sub.onSubscribe(subs); +150 sub.onComplete(); +151 +152 env.verifyNoAsyncErrorsNoDelay(); +153 } +154 }); +155 } +156 +157 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.3 +158 @Override @Test +159 public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() throws Throwable { +160 subscriberTestWithoutSetup(new TestStageTestRun() { +161 @Override +162 public void run(WhiteboxTestStage stage) throws Throwable { +163 final Subscription subs = new Subscription() { +164 @Override +165 public void request(long n) { +166 Throwable thr = new Throwable(); +167 for (StackTraceElement stackElem : thr.getStackTrace()) { +168 if (stackElem.getMethodName().equals("onError")) { +169 env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)", +170 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +171 } +172 } +173 } +174 +175 @Override +176 public void cancel() { +177 Throwable thr = new Throwable(); +178 for (StackTraceElement stackElem : thr.getStackTrace()) { +179 if (stackElem.getMethodName().equals("onError")) { +180 env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)", +181 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +182 } +183 } +184 } +185 }; +186 +187 stage.probe = stage.createWhiteboxSubscriberProbe(env); +188 final Subscriber<T> sub = createSubscriber(stage.probe); +189 +190 sub.onSubscribe(subs); +191 sub.onError(new TestException()); +192 +193 env.verifyNoAsyncErrorsNoDelay(); +194 } +195 }); +196 } +197 +198 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.4 +199 @Override @Test +200 public void untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() throws Exception { +201 notVerified(); // cannot be meaningfully tested, or can it? +202 } +203 +204 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.5 +205 @Override @Test +206 public void required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() throws Throwable { +207 subscriberTest(new TestStageTestRun() { +208 @Override +209 public void run(WhiteboxTestStage stage) throws Throwable { +210 // try to subscribe another time, if the subscriber calls `probe.registerOnSubscribe` the test will fail +211 final Latch secondSubscriptionCancelled = new Latch(env); +212 final Subscriber<? super T> sub = stage.sub(); +213 final Subscription subscription = new Subscription() { +214 @Override +215 public void request(long elements) { +216 // ignore... +217 } +218 +219 @Override +220 public void cancel() { +221 secondSubscriptionCancelled.close(); +222 } +223 +224 @Override +225 public String toString() { +226 return "SecondSubscription(should get cancelled)"; +227 } +228 }; +229 sub.onSubscribe(subscription); +230 +231 secondSubscriptionCancelled.expectClose("Expected 2nd Subscription given to subscriber to be cancelled, but `Subscription.cancel()` was not called"); +232 env.verifyNoAsyncErrors(); +233 } +234 }); +235 } +236 +237 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.6 +238 @Override @Test +239 public void untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid() throws Exception { +240 notVerified(); // cannot be meaningfully tested, or can it? +241 } +242 +243 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.7 +244 @Override @Test +245 public void untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() throws Exception { +246 notVerified(); // cannot be meaningfully tested, or can it? +247 // the same thread part of the clause can be verified but that is not very useful, or is it? +248 } +249 +250 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.8 +251 @Override @Test +252 public void required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() throws Throwable { +253 subscriberTest(new TestStageTestRun() { +254 @Override +255 public void run(WhiteboxTestStage stage) throws InterruptedException { +256 stage.puppet().triggerRequest(1); +257 stage.puppet().signalCancel(); +258 stage.signalNext(); +259 +260 stage.puppet().triggerRequest(1); +261 stage.puppet().triggerRequest(1); +262 +263 stage.verifyNoAsyncErrors(); +264 } +265 }); +266 } +267 +268 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.9 +269 @Override @Test +270 public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() throws Throwable { +271 subscriberTest(new TestStageTestRun() { +272 @Override +273 public void run(WhiteboxTestStage stage) throws InterruptedException { +274 stage.puppet().triggerRequest(1); +275 stage.sendCompletion(); +276 stage.probe.expectCompletion(); +277 +278 stage.verifyNoAsyncErrors(); +279 } +280 }); +281 } +282 +283 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.9 +284 @Override @Test +285 public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() throws Throwable { +286 subscriberTest(new TestStageTestRun() { +287 @Override +288 public void run(WhiteboxTestStage stage) throws InterruptedException { +289 stage.sendCompletion(); +290 stage.probe.expectCompletion(); +291 +292 stage.verifyNoAsyncErrors(); +293 } +294 }); +295 } +296 +297 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.10 +298 @Override @Test +299 public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() throws Throwable { +300 subscriberTest(new TestStageTestRun() { +301 @Override +302 public void run(WhiteboxTestStage stage) throws InterruptedException { +303 stage.puppet().triggerRequest(1); +304 stage.puppet().triggerRequest(1); +305 +306 Exception ex = new TestException(); +307 stage.sendError(ex); +308 stage.probe.expectError(ex); +309 +310 env.verifyNoAsyncErrorsNoDelay(); +311 } +312 }); +313 } +314 +315 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.10 +316 @Override @Test +317 public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall() throws Throwable { +318 subscriberTest(new TestStageTestRun() { +319 @Override +320 public void run(WhiteboxTestStage stage) throws InterruptedException { +321 Exception ex = new TestException(); +322 stage.sendError(ex); +323 stage.probe.expectError(ex); +324 +325 env.verifyNoAsyncErrorsNoDelay(); +326 } +327 }); +328 } +329 +330 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.11 +331 @Override @Test +332 public void untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() throws Exception { +333 notVerified(); // cannot be meaningfully tested, or can it? +334 } +335 +336 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.12 +337 @Override @Test +338 public void untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation() throws Throwable { +339 notVerified(); // cannot be meaningfully tested, or can it? +340 } +341 +342 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +343 @Override @Test +344 public void untested_spec213_failingOnSignalInvocation() throws Exception { +345 notVerified(); // cannot be meaningfully tested, or can it? +346 } +347 +348 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +349 @Override @Test +350 public void required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +351 subscriberTest(new TestStageTestRun() { +352 @Override +353 public void run(WhiteboxTestStage stage) throws Throwable { +354 +355 final Subscriber<? super T> sub = stage.sub(); +356 boolean gotNPE = false; +357 try { +358 sub.onSubscribe(null); +359 } catch (final NullPointerException expected) { +360 gotNPE = true; +361 } +362 +363 assertTrue(gotNPE, "onSubscribe(null) did not throw NullPointerException"); +364 env.verifyNoAsyncErrorsNoDelay(); +365 } +366 }); +367 } +368 +369 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +370 @Override @Test +371 public void required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +372 subscriberTest(new TestStageTestRun() { +373 @Override +374 public void run(WhiteboxTestStage stage) throws Throwable { +375 +376 final Subscriber<? super T> sub = stage.sub(); +377 boolean gotNPE = false; +378 try { +379 sub.onNext(null); +380 } catch (final NullPointerException expected) { +381 gotNPE = true; +382 } +383 +384 assertTrue(gotNPE, "onNext(null) did not throw NullPointerException"); +385 env.verifyNoAsyncErrorsNoDelay(); +386 } +387 }); +388 } +389 +390 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +391 @Override @Test +392 public void required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +393 subscriberTest(new TestStageTestRun() { +394 @Override +395 public void run(WhiteboxTestStage stage) throws Throwable { +396 +397 final Subscriber<? super T> sub = stage.sub(); +398 boolean gotNPE = false; +399 try { +400 sub.onError(null); +401 } catch (final NullPointerException expected) { +402 gotNPE = true; +403 } finally { +404 assertTrue(gotNPE, "onError(null) did not throw NullPointerException"); +405 } +406 +407 env.verifyNoAsyncErrorsNoDelay(); +408 } +409 }); +410 } +411 +412 +413 ////////////////////// SUBSCRIPTION SPEC RULE VERIFICATION ////////////////// +414 +415 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.1 +416 @Override @Test +417 public void untested_spec301_mustNotBeCalledOutsideSubscriberContext() throws Exception { +418 notVerified(); // cannot be meaningfully tested, or can it? +419 } +420 +421 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.8 +422 @Override @Test +423 public void required_spec308_requestMustRegisterGivenNumberElementsToBeProduced() throws Throwable { +424 subscriberTest(new TestStageTestRun() { +425 @Override +426 public void run(WhiteboxTestStage stage) throws InterruptedException { +427 stage.puppet().triggerRequest(2); +428 stage.probe.expectNext(stage.signalNext()); +429 stage.probe.expectNext(stage.signalNext()); +430 +431 stage.probe.expectNone(); +432 stage.puppet().triggerRequest(3); +433 +434 stage.verifyNoAsyncErrors(); +435 } +436 }); +437 } +438 +439 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.10 +440 @Override @Test +441 public void untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber() throws Exception { +442 notVerified(); // cannot be meaningfully tested, or can it? +443 } +444 +445 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.11 +446 @Override @Test +447 public void untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError() throws Exception { +448 notVerified(); // cannot be meaningfully tested, or can it? +449 } +450 +451 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.14 +452 @Override @Test +453 public void untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() throws Exception { +454 notVerified(); // cannot be meaningfully tested, or can it? +455 } +456 +457 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.15 +458 @Override @Test +459 public void untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError() throws Exception { +460 notVerified(); // cannot be meaningfully tested, or can it? +461 } +462 +463 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.16 +464 @Override @Test +465 public void untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() throws Exception { +466 notVerified(); // cannot be meaningfully tested, or can it? +467 } +468 +469 /////////////////////// ADDITIONAL "COROLLARY" TESTS //////////////////////// +470 +471 /////////////////////// TEST INFRASTRUCTURE ///////////////////////////////// +472 +473 abstract class TestStageTestRun { +474 public abstract void run(WhiteboxTestStage stage) throws Throwable; +475 } +476 +477 /** +478 * Prepares subscriber and publisher pair (by subscribing the first to the latter), +479 * and then hands over the tests {@link WhiteboxTestStage} over to the test. +480 * +481 * The test stage is, like in a puppet show, used to orchestrate what each participant should do. +482 * Since this is a whitebox test, this allows the stage to completely control when and how to signal / expect signals. +483 */ +484 public void subscriberTest(TestStageTestRun body) throws Throwable { +485 WhiteboxTestStage stage = new WhiteboxTestStage(env, true); +486 body.run(stage); +487 } +488 +489 /** +490 * Provides a {@link WhiteboxTestStage} without performing any additional setup, +491 * like the {@link #subscriberTest(SubscriberWhiteboxVerification.TestStageTestRun)} would. +492 * +493 * Use this method to write tests in which you need full control over when and how the initial {@code subscribe} is signalled. +494 */ +495 public void subscriberTestWithoutSetup(TestStageTestRun body) throws Throwable { +496 WhiteboxTestStage stage = new WhiteboxTestStage(env, false); +497 body.run(stage); +498 } +499 +500 /** +501 * Test for feature that MAY be implemented. This test will be marked as SKIPPED if it fails. +502 */ +503 public void optionalSubscriberTestWithoutSetup(TestStageTestRun body) throws Throwable { +504 try { +505 subscriberTestWithoutSetup(body); +506 } catch (Exception ex) { +507 notVerified("Skipped because tested publisher does NOT implement this OPTIONAL requirement."); +508 } +509 } +510 +511 public class WhiteboxTestStage extends ManualPublisher<T> { +512 public Publisher<T> pub; +513 public ManualSubscriber<T> tees; // gives us access to a stream T values +514 public WhiteboxSubscriberProbe<T> probe; +515 +516 public T lastT = null; +517 +518 public WhiteboxTestStage(TestEnvironment env) throws InterruptedException { +519 this(env, true); +520 } +521 +522 public WhiteboxTestStage(TestEnvironment env, boolean runDefaultInit) throws InterruptedException { +523 super(env); +524 if (runDefaultInit) { +525 pub = this.createHelperPublisher(Long.MAX_VALUE); +526 tees = env.newManualSubscriber(pub); +527 probe = new WhiteboxSubscriberProbe<T>(env, subscriber); +528 subscribe(createSubscriber(probe)); +529 probe.puppet.expectCompletion(env.defaultTimeoutMillis(), String.format("Subscriber %s did not `registerOnSubscribe`", sub())); +530 } +531 } +532 +533 public Subscriber<? super T> sub() { +534 return subscriber.value(); +535 } +536 +537 public SubscriberPuppet puppet() { +538 return probe.puppet(); +539 } +540 +541 public WhiteboxSubscriberProbe<T> probe() { +542 return probe; +543 } +544 +545 public Publisher<T> createHelperPublisher(long elements) { +546 return SubscriberWhiteboxVerification.this.createHelperPublisher(elements); +547 } +548 +549 public WhiteboxSubscriberProbe<T> createWhiteboxSubscriberProbe(TestEnvironment env) { +550 return new WhiteboxSubscriberProbe<T>(env, subscriber); +551 } +552 +553 public T signalNext() throws InterruptedException { +554 return signalNext(nextT()); +555 } +556 +557 private T signalNext(T element) throws InterruptedException { +558 sendNext(element); +559 return element; +560 } +561 +562 public T nextT() throws InterruptedException { +563 lastT = tees.requestNextElement(); +564 return lastT; +565 } +566 +567 public void verifyNoAsyncErrors() { +568 env.verifyNoAsyncErrors(); +569 } +570 } +571 +572 /** +573 * This class is intented to be used as {@code Subscriber} decorator and should be used in {@code pub.subscriber(...)} calls, +574 * in order to allow intercepting calls on the underlying {@code Subscriber}. +575 * This delegation allows the proxy to implement {@link BlackboxProbe} assertions. +576 */ +577 public static class BlackboxSubscriberProxy<T> extends BlackboxProbe<T> implements Subscriber<T> { +578 +579 public BlackboxSubscriberProxy(TestEnvironment env, Subscriber<T> subscriber) { +580 super(env, Promise.<Subscriber<? super T>>completed(env, subscriber)); +581 } +582 +583 @Override +584 public void onSubscribe(Subscription s) { +585 sub().onSubscribe(s); +586 } +587 +588 @Override +589 public void onNext(T t) { +590 registerOnNext(t); +591 sub().onNext(t); +592 } +593 +594 @Override +595 public void onError(Throwable cause) { +596 registerOnError(cause); +597 sub().onError(cause); +598 } +599 +600 @Override +601 public void onComplete() { +602 registerOnComplete(); +603 sub().onComplete(); +604 } +605 } +606 +607 public static class BlackboxProbe<T> implements SubscriberProbe<T> { +608 protected final TestEnvironment env; +609 protected final Promise<Subscriber<? super T>> subscriber; +610 +611 protected final Receptacle<T> elements; +612 protected final Promise<Throwable> error; +613 +614 public BlackboxProbe(TestEnvironment env, Promise<Subscriber<? super T>> subscriber) { +615 this.env = env; +616 this.subscriber = subscriber; +617 elements = new Receptacle<T>(env); +618 error = new Promise<Throwable>(env); +619 } +620 +621 @Override +622 public void registerOnNext(T element) { +623 elements.add(element); +624 } +625 +626 @Override +627 public void registerOnComplete() { +628 try { +629 elements.complete(); +630 } catch (IllegalStateException ex) { +631 // "Queue full", onComplete was already called +632 env.flop("subscriber::onComplete was called a second time, which is illegal according to Rule 1.7"); +633 } +634 } +635 +636 @Override +637 public void registerOnError(Throwable cause) { +638 try { +639 error.complete(cause); +640 } catch (IllegalStateException ex) { +641 // "Queue full", onError was already called +642 env.flop("subscriber::onError was called a second time, which is illegal according to Rule 1.7"); +643 } +644 } +645 +646 public T expectNext() throws InterruptedException { +647 return elements.next(env.defaultTimeoutMillis(), String.format("Subscriber %s did not call `registerOnNext(_)`", sub())); +648 } +649 +650 public void expectNext(T expected) throws InterruptedException { +651 expectNext(expected, env.defaultTimeoutMillis()); +652 } +653 +654 public void expectNext(T expected, long timeoutMillis) throws InterruptedException { +655 T received = elements.next(timeoutMillis, String.format("Subscriber %s did not call `registerOnNext(%s)`", sub(), expected)); +656 if (!received.equals(expected)) { +657 env.flop(String.format("Subscriber %s called `registerOnNext(%s)` rather than `registerOnNext(%s)`", sub(), received, expected)); +658 } +659 } +660 +661 public Subscriber<? super T> sub() { +662 return subscriber.value(); +663 } +664 +665 public void expectCompletion() throws InterruptedException { +666 expectCompletion(env.defaultTimeoutMillis()); +667 } +668 +669 public void expectCompletion(long timeoutMillis) throws InterruptedException { +670 expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnComplete()`", sub())); +671 } +672 +673 public void expectCompletion(long timeoutMillis, String msg) throws InterruptedException { +674 elements.expectCompletion(timeoutMillis, msg); +675 } +676 +677 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +678 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart) throws InterruptedException { +679 final E err = expectError(expected); +680 String message = err.getMessage(); +681 assertTrue(message.contains(requiredMessagePart), +682 String.format("Got expected exception %s but missing message [%s], was: %s", err.getClass(), requiredMessagePart, expected)); +683 } +684 +685 public <E extends Throwable> E expectError(Class<E> expected) throws InterruptedException { +686 return expectError(expected, env.defaultTimeoutMillis()); +687 } +688 +689 @SuppressWarnings({"unchecked", "ThrowableResultOfMethodCallIgnored"}) +690 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis) throws InterruptedException { +691 error.expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected)); +692 if (error.value() == null) { +693 return env.flopAndFail(String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected)); +694 } else if (expected.isInstance(error.value())) { +695 return (E) error.value(); +696 } else { +697 return env.flopAndFail(String.format("Subscriber %s called `registerOnError(%s)` rather than `registerOnError(%s)`", sub(), error.value(), expected)); +698 } +699 } +700 +701 public void expectError(Throwable expected) throws InterruptedException { +702 expectError(expected, env.defaultTimeoutMillis()); +703 } +704 +705 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +706 public void expectError(Throwable expected, long timeoutMillis) throws InterruptedException { +707 error.expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected)); +708 if (error.value() != expected) { +709 env.flop(String.format("Subscriber %s called `registerOnError(%s)` rather than `registerOnError(%s)`", sub(), error.value(), expected)); +710 } +711 } +712 +713 public void expectNone() throws InterruptedException { +714 expectNone(env.defaultTimeoutMillis()); +715 } +716 +717 public void expectNone(long withinMillis) throws InterruptedException { +718 elements.expectNone(withinMillis, "Expected nothing"); +719 } +720 +721 } +722 +723 public static class WhiteboxSubscriberProbe<T> extends BlackboxProbe<T> implements SubscriberPuppeteer { +724 protected Promise<SubscriberPuppet> puppet; +725 +726 public WhiteboxSubscriberProbe(TestEnvironment env, Promise<Subscriber<? super T>> subscriber) { +727 super(env, subscriber); +728 puppet = new Promise<SubscriberPuppet>(env); +729 } +730 +731 private SubscriberPuppet puppet() { +732 return puppet.value(); +733 } +734 +735 @Override +736 public void registerOnSubscribe(SubscriberPuppet p) { +737 if (!puppet.isCompleted()) { +738 puppet.complete(p); +739 } +740 } +741 +742 } +743 +744 public interface SubscriberPuppeteer { +745 +746 /** +747 * Must be called by the test subscriber when it has successfully registered a subscription +748 * inside the `onSubscribe` method. +749 */ +750 void registerOnSubscribe(SubscriberPuppet puppet); +751 } +752 +753 public interface SubscriberProbe<T> { +754 +755 /** +756 * Must be called by the test subscriber when it has received an`onNext` event. +757 */ +758 void registerOnNext(T element); +759 +760 /** +761 * Must be called by the test subscriber when it has received an `onComplete` event. +762 */ +763 void registerOnComplete(); +764 +765 /** +766 * Must be called by the test subscriber when it has received an `onError` event. +767 */ +768 void registerOnError(Throwable cause); +769 +770 } +771 +772 public interface SubscriberPuppet { +773 void triggerRequest(long elements); +774 +775 void signalCancel(); +776 } +777 +778 public void notVerified() { +779 throw new SkipException("Not verified using this TCK."); +780 } +781 +782 public void notVerified(String msg) { +783 throw new SkipException(msg); +784 } +785} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck; +002 +003import org.reactivestreams.Publisher; +004import org.reactivestreams.Subscriber; +005import org.reactivestreams.Subscription; +006import org.reactivestreams.tck.TestEnvironment.*; +007import org.reactivestreams.tck.support.Optional; +008import org.reactivestreams.tck.support.SubscriberWhiteboxVerificationRules; +009import org.reactivestreams.tck.support.TestException; +010import org.testng.SkipException; +011import org.testng.annotations.AfterClass; +012import org.testng.annotations.BeforeClass; +013import org.testng.annotations.BeforeMethod; +014import org.testng.annotations.Test; +015 +016import java.util.concurrent.ExecutorService; +017import java.util.concurrent.Executors; +018 +019import static org.testng.Assert.assertTrue; +020 +021/** +022 * Provides tests for verifying {@link org.reactivestreams.Subscriber} and {@link org.reactivestreams.Subscription} specification rules. +023 * +024 * @see org.reactivestreams.Subscriber +025 * @see org.reactivestreams.Subscription +026 */ +027public abstract class SubscriberWhiteboxVerification<T> extends WithHelperPublisher<T> +028 implements SubscriberWhiteboxVerificationRules { +029 +030 private final TestEnvironment env; +031 +032 protected SubscriberWhiteboxVerification(TestEnvironment env) { +033 this.env = env; +034 } +035 +036 // USER API +037 +038 /** +039 * This is the main method you must implement in your test incarnation. +040 * It must create a new {@link org.reactivestreams.Subscriber} instance to be subjected to the testing logic. +041 * +042 * In order to be meaningfully testable your Subscriber must inform the given +043 * `WhiteboxSubscriberProbe` of the respective events having been received. +044 */ +045 public abstract Subscriber<T> createSubscriber(WhiteboxSubscriberProbe<T> probe); +046 +047 // ENV SETUP +048 +049 /** +050 * Executor service used by the default provided asynchronous Publisher. +051 * @see #createHelperPublisher(long) +052 */ +053 private ExecutorService publisherExecutor; +054 @BeforeClass public void startPublisherExecutorService() { publisherExecutor = Executors.newFixedThreadPool(4); } +055 @AfterClass public void shutdownPublisherExecutorService() { if (publisherExecutor != null) publisherExecutor.shutdown(); } +056 @Override public ExecutorService publisherExecutorService() { return publisherExecutor; } +057 +058 ////////////////////// TEST ENV CLEANUP ///////////////////////////////////// +059 +060 @BeforeMethod +061 public void setUp() throws Exception { +062 env.clearAsyncErrors(); +063 } +064 +065 ////////////////////// TEST SETUP VERIFICATION ////////////////////////////// +066 +067 @Test +068 public void required_exerciseWhiteboxHappyPath() throws Throwable { +069 subscriberTest(new TestStageTestRun() { +070 @Override +071 public void run(WhiteboxTestStage stage) throws InterruptedException { +072 stage.puppet().triggerRequest(1); +073 stage.puppet().triggerRequest(1); +074 +075 long receivedRequests = stage.expectRequest(); +076 +077 stage.signalNext(); +078 stage.probe.expectNext(stage.lastT); +079 +080 stage.puppet().triggerRequest(1); +081 if (receivedRequests == 1) { +082 stage.expectRequest(); +083 } +084 +085 stage.signalNext(); +086 stage.probe.expectNext(stage.lastT); +087 +088 stage.puppet().signalCancel(); +089 stage.expectCancelling(); +090 +091 stage.verifyNoAsyncErrors(); +092 } +093 }); +094 } +095 +096 ////////////////////// SPEC RULE VERIFICATION /////////////////////////////// +097 +098 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.1 +099 @Override @Test +100 public void required_spec201_mustSignalDemandViaSubscriptionRequest() throws Throwable { +101 subscriberTest(new TestStageTestRun() { +102 @Override +103 public void run(WhiteboxTestStage stage) throws InterruptedException { +104 stage.puppet().triggerRequest(1); +105 stage.expectRequest(); +106 +107 stage.signalNext(); +108 } +109 }); +110 } +111 +112 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.2 +113 @Override @Test +114 public void untested_spec202_shouldAsynchronouslyDispatch() throws Exception { +115 notVerified(); // cannot be meaningfully tested, or can it? +116 } +117 +118 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.3 +119 @Override @Test +120 public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() throws Throwable { +121 subscriberTestWithoutSetup(new TestStageTestRun() { +122 @Override +123 public void run(WhiteboxTestStage stage) throws Throwable { +124 final Subscription subs = new Subscription() { +125 @Override +126 public void request(long n) { +127 final Optional<StackTraceElement> onCompleteStackTraceElement = env.findCallerMethodInStackTrace("onComplete"); +128 if (onCompleteStackTraceElement.isDefined()) { +129 final StackTraceElement stackElem = onCompleteStackTraceElement.get(); +130 env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)", +131 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +132 } +133 } +134 +135 @Override +136 public void cancel() { +137 final Optional<StackTraceElement> onCompleteStackElement = env.findCallerMethodInStackTrace("onComplete"); +138 if (onCompleteStackElement.isDefined()) { +139 final StackTraceElement stackElem = onCompleteStackElement.get(); +140 env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)", +141 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +142 } +143 } +144 }; +145 +146 stage.probe = stage.createWhiteboxSubscriberProbe(env); +147 final Subscriber<T> sub = createSubscriber(stage.probe); +148 +149 sub.onSubscribe(subs); +150 sub.onComplete(); +151 +152 env.verifyNoAsyncErrorsNoDelay(); +153 } +154 }); +155 } +156 +157 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.3 +158 @Override @Test +159 public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() throws Throwable { +160 subscriberTestWithoutSetup(new TestStageTestRun() { +161 @Override +162 public void run(WhiteboxTestStage stage) throws Throwable { +163 final Subscription subs = new Subscription() { +164 @Override +165 public void request(long n) { +166 Throwable thr = new Throwable(); +167 for (StackTraceElement stackElem : thr.getStackTrace()) { +168 if (stackElem.getMethodName().equals("onError")) { +169 env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)", +170 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +171 } +172 } +173 } +174 +175 @Override +176 public void cancel() { +177 Throwable thr = new Throwable(); +178 for (StackTraceElement stackElem : thr.getStackTrace()) { +179 if (stackElem.getMethodName().equals("onError")) { +180 env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)", +181 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +182 } +183 } +184 } +185 }; +186 +187 stage.probe = stage.createWhiteboxSubscriberProbe(env); +188 final Subscriber<T> sub = createSubscriber(stage.probe); +189 +190 sub.onSubscribe(subs); +191 sub.onError(new TestException()); +192 +193 env.verifyNoAsyncErrorsNoDelay(); +194 } +195 }); +196 } +197 +198 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.4 +199 @Override @Test +200 public void untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() throws Exception { +201 notVerified(); // cannot be meaningfully tested, or can it? +202 } +203 +204 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.5 +205 @Override @Test +206 public void required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() throws Throwable { +207 subscriberTest(new TestStageTestRun() { +208 @Override +209 public void run(WhiteboxTestStage stage) throws Throwable { +210 // try to subscribe another time, if the subscriber calls `probe.registerOnSubscribe` the test will fail +211 final Latch secondSubscriptionCancelled = new Latch(env); +212 final Subscriber<? super T> sub = stage.sub(); +213 final Subscription subscription = new Subscription() { +214 @Override +215 public void request(long elements) { +216 // ignore... +217 } +218 +219 @Override +220 public void cancel() { +221 secondSubscriptionCancelled.close(); +222 } +223 +224 @Override +225 public String toString() { +226 return "SecondSubscription(should get cancelled)"; +227 } +228 }; +229 sub.onSubscribe(subscription); +230 +231 secondSubscriptionCancelled.expectClose("Expected 2nd Subscription given to subscriber to be cancelled, but `Subscription.cancel()` was not called"); +232 env.verifyNoAsyncErrors(); +233 } +234 }); +235 } +236 +237 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.6 +238 @Override @Test +239 public void untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid() throws Exception { +240 notVerified(); // cannot be meaningfully tested, or can it? +241 } +242 +243 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.7 +244 @Override @Test +245 public void untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() throws Exception { +246 notVerified(); // cannot be meaningfully tested, or can it? +247 // the same thread part of the clause can be verified but that is not very useful, or is it? +248 } +249 +250 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.8 +251 @Override @Test +252 public void required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() throws Throwable { +253 subscriberTest(new TestStageTestRun() { +254 @Override +255 public void run(WhiteboxTestStage stage) throws InterruptedException { +256 stage.puppet().triggerRequest(1); +257 stage.puppet().signalCancel(); +258 stage.signalNext(); +259 +260 stage.puppet().triggerRequest(1); +261 stage.puppet().triggerRequest(1); +262 +263 stage.verifyNoAsyncErrors(); +264 } +265 }); +266 } +267 +268 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.9 +269 @Override @Test +270 public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() throws Throwable { +271 subscriberTest(new TestStageTestRun() { +272 @Override +273 public void run(WhiteboxTestStage stage) throws InterruptedException { +274 stage.puppet().triggerRequest(1); +275 stage.sendCompletion(); +276 stage.probe.expectCompletion(); +277 +278 stage.verifyNoAsyncErrors(); +279 } +280 }); +281 } +282 +283 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.9 +284 @Override @Test +285 public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() throws Throwable { +286 subscriberTest(new TestStageTestRun() { +287 @Override +288 public void run(WhiteboxTestStage stage) throws InterruptedException { +289 stage.sendCompletion(); +290 stage.probe.expectCompletion(); +291 +292 stage.verifyNoAsyncErrors(); +293 } +294 }); +295 } +296 +297 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.10 +298 @Override @Test +299 public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() throws Throwable { +300 subscriberTest(new TestStageTestRun() { +301 @Override +302 public void run(WhiteboxTestStage stage) throws InterruptedException { +303 stage.puppet().triggerRequest(1); +304 stage.puppet().triggerRequest(1); +305 +306 Exception ex = new TestException(); +307 stage.sendError(ex); +308 stage.probe.expectError(ex); +309 +310 env.verifyNoAsyncErrorsNoDelay(); +311 } +312 }); +313 } +314 +315 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.10 +316 @Override @Test +317 public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall() throws Throwable { +318 subscriberTest(new TestStageTestRun() { +319 @Override +320 public void run(WhiteboxTestStage stage) throws InterruptedException { +321 Exception ex = new TestException(); +322 stage.sendError(ex); +323 stage.probe.expectError(ex); +324 +325 env.verifyNoAsyncErrorsNoDelay(); +326 } +327 }); +328 } +329 +330 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.11 +331 @Override @Test +332 public void untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() throws Exception { +333 notVerified(); // cannot be meaningfully tested, or can it? +334 } +335 +336 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.12 +337 @Override @Test +338 public void untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation() throws Throwable { +339 notVerified(); // cannot be meaningfully tested, or can it? +340 } +341 +342 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +343 @Override @Test +344 public void untested_spec213_failingOnSignalInvocation() throws Exception { +345 notVerified(); // cannot be meaningfully tested, or can it? +346 } +347 +348 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +349 @Override @Test +350 public void required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +351 subscriberTest(new TestStageTestRun() { +352 @Override +353 public void run(WhiteboxTestStage stage) throws Throwable { +354 +355 final Subscriber<? super T> sub = stage.sub(); +356 boolean gotNPE = false; +357 try { +358 sub.onSubscribe(null); +359 } catch (final NullPointerException expected) { +360 gotNPE = true; +361 } +362 +363 assertTrue(gotNPE, "onSubscribe(null) did not throw NullPointerException"); +364 env.verifyNoAsyncErrorsNoDelay(); +365 } +366 }); +367 } +368 +369 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +370 @Override @Test +371 public void required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +372 subscriberTest(new TestStageTestRun() { +373 @Override +374 public void run(WhiteboxTestStage stage) throws Throwable { +375 +376 final Subscriber<? super T> sub = stage.sub(); +377 boolean gotNPE = false; +378 try { +379 sub.onNext(null); +380 } catch (final NullPointerException expected) { +381 gotNPE = true; +382 } +383 +384 assertTrue(gotNPE, "onNext(null) did not throw NullPointerException"); +385 env.verifyNoAsyncErrorsNoDelay(); +386 } +387 }); +388 } +389 +390 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +391 @Override @Test +392 public void required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +393 subscriberTest(new TestStageTestRun() { +394 @Override +395 public void run(WhiteboxTestStage stage) throws Throwable { +396 +397 final Subscriber<? super T> sub = stage.sub(); +398 boolean gotNPE = false; +399 try { +400 sub.onError(null); +401 } catch (final NullPointerException expected) { +402 gotNPE = true; +403 } finally { +404 assertTrue(gotNPE, "onError(null) did not throw NullPointerException"); +405 } +406 +407 env.verifyNoAsyncErrorsNoDelay(); +408 } +409 }); +410 } +411 +412 +413 ////////////////////// SUBSCRIPTION SPEC RULE VERIFICATION ////////////////// +414 +415 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.1 +416 @Override @Test +417 public void untested_spec301_mustNotBeCalledOutsideSubscriberContext() throws Exception { +418 notVerified(); // cannot be meaningfully tested, or can it? +419 } +420 +421 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.8 +422 @Override @Test +423 public void required_spec308_requestMustRegisterGivenNumberElementsToBeProduced() throws Throwable { +424 subscriberTest(new TestStageTestRun() { +425 @Override +426 public void run(WhiteboxTestStage stage) throws InterruptedException { +427 stage.puppet().triggerRequest(2); +428 stage.probe.expectNext(stage.signalNext()); +429 stage.probe.expectNext(stage.signalNext()); +430 +431 stage.probe.expectNone(); +432 stage.puppet().triggerRequest(3); +433 +434 stage.verifyNoAsyncErrors(); +435 } +436 }); +437 } +438 +439 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.10 +440 @Override @Test +441 public void untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber() throws Exception { +442 notVerified(); // cannot be meaningfully tested, or can it? +443 } +444 +445 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.11 +446 @Override @Test +447 public void untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError() throws Exception { +448 notVerified(); // cannot be meaningfully tested, or can it? +449 } +450 +451 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.14 +452 @Override @Test +453 public void untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() throws Exception { +454 notVerified(); // cannot be meaningfully tested, or can it? +455 } +456 +457 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.15 +458 @Override @Test +459 public void untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError() throws Exception { +460 notVerified(); // cannot be meaningfully tested, or can it? +461 } +462 +463 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.16 +464 @Override @Test +465 public void untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() throws Exception { +466 notVerified(); // cannot be meaningfully tested, or can it? +467 } +468 +469 /////////////////////// ADDITIONAL "COROLLARY" TESTS //////////////////////// +470 +471 /////////////////////// TEST INFRASTRUCTURE ///////////////////////////////// +472 +473 abstract class TestStageTestRun { +474 public abstract void run(WhiteboxTestStage stage) throws Throwable; +475 } +476 +477 /** +478 * Prepares subscriber and publisher pair (by subscribing the first to the latter), +479 * and then hands over the tests {@link WhiteboxTestStage} over to the test. +480 * +481 * The test stage is, like in a puppet show, used to orchestrate what each participant should do. +482 * Since this is a whitebox test, this allows the stage to completely control when and how to signal / expect signals. +483 */ +484 public void subscriberTest(TestStageTestRun body) throws Throwable { +485 WhiteboxTestStage stage = new WhiteboxTestStage(env, true); +486 body.run(stage); +487 } +488 +489 /** +490 * Provides a {@link WhiteboxTestStage} without performing any additional setup, +491 * like the {@link #subscriberTest(SubscriberWhiteboxVerification.TestStageTestRun)} would. +492 * +493 * Use this method to write tests in which you need full control over when and how the initial {@code subscribe} is signalled. +494 */ +495 public void subscriberTestWithoutSetup(TestStageTestRun body) throws Throwable { +496 WhiteboxTestStage stage = new WhiteboxTestStage(env, false); +497 body.run(stage); +498 } +499 +500 /** +501 * Test for feature that MAY be implemented. This test will be marked as SKIPPED if it fails. +502 */ +503 public void optionalSubscriberTestWithoutSetup(TestStageTestRun body) throws Throwable { +504 try { +505 subscriberTestWithoutSetup(body); +506 } catch (Exception ex) { +507 notVerified("Skipped because tested publisher does NOT implement this OPTIONAL requirement."); +508 } +509 } +510 +511 public class WhiteboxTestStage extends ManualPublisher<T> { +512 public Publisher<T> pub; +513 public ManualSubscriber<T> tees; // gives us access to a stream T values +514 public WhiteboxSubscriberProbe<T> probe; +515 +516 public T lastT = null; +517 +518 public WhiteboxTestStage(TestEnvironment env) throws InterruptedException { +519 this(env, true); +520 } +521 +522 public WhiteboxTestStage(TestEnvironment env, boolean runDefaultInit) throws InterruptedException { +523 super(env); +524 if (runDefaultInit) { +525 pub = this.createHelperPublisher(Long.MAX_VALUE); +526 tees = env.newManualSubscriber(pub); +527 probe = new WhiteboxSubscriberProbe<T>(env, subscriber); +528 subscribe(createSubscriber(probe)); +529 probe.puppet.expectCompletion(env.defaultTimeoutMillis(), String.format("Subscriber %s did not `registerOnSubscribe`", sub())); +530 } +531 } +532 +533 public Subscriber<? super T> sub() { +534 return subscriber.value(); +535 } +536 +537 public SubscriberPuppet puppet() { +538 return probe.puppet(); +539 } +540 +541 public WhiteboxSubscriberProbe<T> probe() { +542 return probe; +543 } +544 +545 public Publisher<T> createHelperPublisher(long elements) { +546 return SubscriberWhiteboxVerification.this.createHelperPublisher(elements); +547 } +548 +549 public WhiteboxSubscriberProbe<T> createWhiteboxSubscriberProbe(TestEnvironment env) { +550 return new WhiteboxSubscriberProbe<T>(env, subscriber); +551 } +552 +553 public T signalNext() throws InterruptedException { +554 return signalNext(nextT()); +555 } +556 +557 private T signalNext(T element) throws InterruptedException { +558 sendNext(element); +559 return element; +560 } +561 +562 public T nextT() throws InterruptedException { +563 lastT = tees.requestNextElement(); +564 return lastT; +565 } +566 +567 public void verifyNoAsyncErrors() { +568 env.verifyNoAsyncErrors(); +569 } +570 } +571 +572 /** +573 * This class is intented to be used as {@code Subscriber} decorator and should be used in {@code pub.subscriber(...)} calls, +574 * in order to allow intercepting calls on the underlying {@code Subscriber}. +575 * This delegation allows the proxy to implement {@link BlackboxProbe} assertions. +576 */ +577 public static class BlackboxSubscriberProxy<T> extends BlackboxProbe<T> implements Subscriber<T> { +578 +579 public BlackboxSubscriberProxy(TestEnvironment env, Subscriber<T> subscriber) { +580 super(env, Promise.<Subscriber<? super T>>completed(env, subscriber)); +581 } +582 +583 @Override +584 public void onSubscribe(Subscription s) { +585 sub().onSubscribe(s); +586 } +587 +588 @Override +589 public void onNext(T t) { +590 registerOnNext(t); +591 sub().onNext(t); +592 } +593 +594 @Override +595 public void onError(Throwable cause) { +596 registerOnError(cause); +597 sub().onError(cause); +598 } +599 +600 @Override +601 public void onComplete() { +602 registerOnComplete(); +603 sub().onComplete(); +604 } +605 } +606 +607 public static class BlackboxProbe<T> implements SubscriberProbe<T> { +608 protected final TestEnvironment env; +609 protected final Promise<Subscriber<? super T>> subscriber; +610 +611 protected final Receptacle<T> elements; +612 protected final Promise<Throwable> error; +613 +614 public BlackboxProbe(TestEnvironment env, Promise<Subscriber<? super T>> subscriber) { +615 this.env = env; +616 this.subscriber = subscriber; +617 elements = new Receptacle<T>(env); +618 error = new Promise<Throwable>(env); +619 } +620 +621 @Override +622 public void registerOnNext(T element) { +623 elements.add(element); +624 } +625 +626 @Override +627 public void registerOnComplete() { +628 try { +629 elements.complete(); +630 } catch (IllegalStateException ex) { +631 // "Queue full", onComplete was already called +632 env.flop("subscriber::onComplete was called a second time, which is illegal according to Rule 1.7"); +633 } +634 } +635 +636 @Override +637 public void registerOnError(Throwable cause) { +638 try { +639 error.complete(cause); +640 } catch (IllegalStateException ex) { +641 // "Queue full", onError was already called +642 env.flop("subscriber::onError was called a second time, which is illegal according to Rule 1.7"); +643 } +644 } +645 +646 public T expectNext() throws InterruptedException { +647 return elements.next(env.defaultTimeoutMillis(), String.format("Subscriber %s did not call `registerOnNext(_)`", sub())); +648 } +649 +650 public void expectNext(T expected) throws InterruptedException { +651 expectNext(expected, env.defaultTimeoutMillis()); +652 } +653 +654 public void expectNext(T expected, long timeoutMillis) throws InterruptedException { +655 T received = elements.next(timeoutMillis, String.format("Subscriber %s did not call `registerOnNext(%s)`", sub(), expected)); +656 if (!received.equals(expected)) { +657 env.flop(String.format("Subscriber %s called `registerOnNext(%s)` rather than `registerOnNext(%s)`", sub(), received, expected)); +658 } +659 } +660 +661 public Subscriber<? super T> sub() { +662 return subscriber.value(); +663 } +664 +665 public void expectCompletion() throws InterruptedException { +666 expectCompletion(env.defaultTimeoutMillis()); +667 } +668 +669 public void expectCompletion(long timeoutMillis) throws InterruptedException { +670 expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnComplete()`", sub())); +671 } +672 +673 public void expectCompletion(long timeoutMillis, String msg) throws InterruptedException { +674 elements.expectCompletion(timeoutMillis, msg); +675 } +676 +677 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +678 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart) throws InterruptedException { +679 final E err = expectError(expected); +680 String message = err.getMessage(); +681 assertTrue(message.contains(requiredMessagePart), +682 String.format("Got expected exception %s but missing message [%s], was: %s", err.getClass(), requiredMessagePart, expected)); +683 } +684 +685 public <E extends Throwable> E expectError(Class<E> expected) throws InterruptedException { +686 return expectError(expected, env.defaultTimeoutMillis()); +687 } +688 +689 @SuppressWarnings({"unchecked", "ThrowableResultOfMethodCallIgnored"}) +690 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis) throws InterruptedException { +691 error.expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected)); +692 if (error.value() == null) { +693 return env.flopAndFail(String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected)); +694 } else if (expected.isInstance(error.value())) { +695 return (E) error.value(); +696 } else { +697 return env.flopAndFail(String.format("Subscriber %s called `registerOnError(%s)` rather than `registerOnError(%s)`", sub(), error.value(), expected)); +698 } +699 } +700 +701 public void expectError(Throwable expected) throws InterruptedException { +702 expectError(expected, env.defaultTimeoutMillis()); +703 } +704 +705 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +706 public void expectError(Throwable expected, long timeoutMillis) throws InterruptedException { +707 error.expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected)); +708 if (error.value() != expected) { +709 env.flop(String.format("Subscriber %s called `registerOnError(%s)` rather than `registerOnError(%s)`", sub(), error.value(), expected)); +710 } +711 } +712 +713 public void expectNone() throws InterruptedException { +714 expectNone(env.defaultTimeoutMillis()); +715 } +716 +717 public void expectNone(long withinMillis) throws InterruptedException { +718 elements.expectNone(withinMillis, "Expected nothing"); +719 } +720 +721 } +722 +723 public static class WhiteboxSubscriberProbe<T> extends BlackboxProbe<T> implements SubscriberPuppeteer { +724 protected Promise<SubscriberPuppet> puppet; +725 +726 public WhiteboxSubscriberProbe(TestEnvironment env, Promise<Subscriber<? super T>> subscriber) { +727 super(env, subscriber); +728 puppet = new Promise<SubscriberPuppet>(env); +729 } +730 +731 private SubscriberPuppet puppet() { +732 return puppet.value(); +733 } +734 +735 @Override +736 public void registerOnSubscribe(SubscriberPuppet p) { +737 if (!puppet.isCompleted()) { +738 puppet.complete(p); +739 } +740 } +741 +742 } +743 +744 public interface SubscriberPuppeteer { +745 +746 /** +747 * Must be called by the test subscriber when it has successfully registered a subscription +748 * inside the `onSubscribe` method. +749 */ +750 void registerOnSubscribe(SubscriberPuppet puppet); +751 } +752 +753 public interface SubscriberProbe<T> { +754 +755 /** +756 * Must be called by the test subscriber when it has received an`onNext` event. +757 */ +758 void registerOnNext(T element); +759 +760 /** +761 * Must be called by the test subscriber when it has received an `onComplete` event. +762 */ +763 void registerOnComplete(); +764 +765 /** +766 * Must be called by the test subscriber when it has received an `onError` event. +767 */ +768 void registerOnError(Throwable cause); +769 +770 } +771 +772 public interface SubscriberPuppet { +773 void triggerRequest(long elements); +774 +775 void signalCancel(); +776 } +777 +778 public void notVerified() { +779 throw new SkipException("Not verified using this TCK."); +780 } +781 +782 public void notVerified(String msg) { +783 throw new SkipException(msg); +784 } +785} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck; +002 +003import org.reactivestreams.Publisher; +004import org.reactivestreams.Subscriber; +005import org.reactivestreams.Subscription; +006import org.reactivestreams.tck.TestEnvironment.*; +007import org.reactivestreams.tck.support.Optional; +008import org.reactivestreams.tck.support.SubscriberWhiteboxVerificationRules; +009import org.reactivestreams.tck.support.TestException; +010import org.testng.SkipException; +011import org.testng.annotations.AfterClass; +012import org.testng.annotations.BeforeClass; +013import org.testng.annotations.BeforeMethod; +014import org.testng.annotations.Test; +015 +016import java.util.concurrent.ExecutorService; +017import java.util.concurrent.Executors; +018 +019import static org.testng.Assert.assertTrue; +020 +021/** +022 * Provides tests for verifying {@link org.reactivestreams.Subscriber} and {@link org.reactivestreams.Subscription} specification rules. +023 * +024 * @see org.reactivestreams.Subscriber +025 * @see org.reactivestreams.Subscription +026 */ +027public abstract class SubscriberWhiteboxVerification<T> extends WithHelperPublisher<T> +028 implements SubscriberWhiteboxVerificationRules { +029 +030 private final TestEnvironment env; +031 +032 protected SubscriberWhiteboxVerification(TestEnvironment env) { +033 this.env = env; +034 } +035 +036 // USER API +037 +038 /** +039 * This is the main method you must implement in your test incarnation. +040 * It must create a new {@link org.reactivestreams.Subscriber} instance to be subjected to the testing logic. +041 * +042 * In order to be meaningfully testable your Subscriber must inform the given +043 * `WhiteboxSubscriberProbe` of the respective events having been received. +044 */ +045 public abstract Subscriber<T> createSubscriber(WhiteboxSubscriberProbe<T> probe); +046 +047 // ENV SETUP +048 +049 /** +050 * Executor service used by the default provided asynchronous Publisher. +051 * @see #createHelperPublisher(long) +052 */ +053 private ExecutorService publisherExecutor; +054 @BeforeClass public void startPublisherExecutorService() { publisherExecutor = Executors.newFixedThreadPool(4); } +055 @AfterClass public void shutdownPublisherExecutorService() { if (publisherExecutor != null) publisherExecutor.shutdown(); } +056 @Override public ExecutorService publisherExecutorService() { return publisherExecutor; } +057 +058 ////////////////////// TEST ENV CLEANUP ///////////////////////////////////// +059 +060 @BeforeMethod +061 public void setUp() throws Exception { +062 env.clearAsyncErrors(); +063 } +064 +065 ////////////////////// TEST SETUP VERIFICATION ////////////////////////////// +066 +067 @Test +068 public void required_exerciseWhiteboxHappyPath() throws Throwable { +069 subscriberTest(new TestStageTestRun() { +070 @Override +071 public void run(WhiteboxTestStage stage) throws InterruptedException { +072 stage.puppet().triggerRequest(1); +073 stage.puppet().triggerRequest(1); +074 +075 long receivedRequests = stage.expectRequest(); +076 +077 stage.signalNext(); +078 stage.probe.expectNext(stage.lastT); +079 +080 stage.puppet().triggerRequest(1); +081 if (receivedRequests == 1) { +082 stage.expectRequest(); +083 } +084 +085 stage.signalNext(); +086 stage.probe.expectNext(stage.lastT); +087 +088 stage.puppet().signalCancel(); +089 stage.expectCancelling(); +090 +091 stage.verifyNoAsyncErrors(); +092 } +093 }); +094 } +095 +096 ////////////////////// SPEC RULE VERIFICATION /////////////////////////////// +097 +098 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.1 +099 @Override @Test +100 public void required_spec201_mustSignalDemandViaSubscriptionRequest() throws Throwable { +101 subscriberTest(new TestStageTestRun() { +102 @Override +103 public void run(WhiteboxTestStage stage) throws InterruptedException { +104 stage.puppet().triggerRequest(1); +105 stage.expectRequest(); +106 +107 stage.signalNext(); +108 } +109 }); +110 } +111 +112 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.2 +113 @Override @Test +114 public void untested_spec202_shouldAsynchronouslyDispatch() throws Exception { +115 notVerified(); // cannot be meaningfully tested, or can it? +116 } +117 +118 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.3 +119 @Override @Test +120 public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() throws Throwable { +121 subscriberTestWithoutSetup(new TestStageTestRun() { +122 @Override +123 public void run(WhiteboxTestStage stage) throws Throwable { +124 final Subscription subs = new Subscription() { +125 @Override +126 public void request(long n) { +127 final Optional<StackTraceElement> onCompleteStackTraceElement = env.findCallerMethodInStackTrace("onComplete"); +128 if (onCompleteStackTraceElement.isDefined()) { +129 final StackTraceElement stackElem = onCompleteStackTraceElement.get(); +130 env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)", +131 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +132 } +133 } +134 +135 @Override +136 public void cancel() { +137 final Optional<StackTraceElement> onCompleteStackElement = env.findCallerMethodInStackTrace("onComplete"); +138 if (onCompleteStackElement.isDefined()) { +139 final StackTraceElement stackElem = onCompleteStackElement.get(); +140 env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)", +141 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +142 } +143 } +144 }; +145 +146 stage.probe = stage.createWhiteboxSubscriberProbe(env); +147 final Subscriber<T> sub = createSubscriber(stage.probe); +148 +149 sub.onSubscribe(subs); +150 sub.onComplete(); +151 +152 env.verifyNoAsyncErrorsNoDelay(); +153 } +154 }); +155 } +156 +157 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.3 +158 @Override @Test +159 public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() throws Throwable { +160 subscriberTestWithoutSetup(new TestStageTestRun() { +161 @Override +162 public void run(WhiteboxTestStage stage) throws Throwable { +163 final Subscription subs = new Subscription() { +164 @Override +165 public void request(long n) { +166 Throwable thr = new Throwable(); +167 for (StackTraceElement stackElem : thr.getStackTrace()) { +168 if (stackElem.getMethodName().equals("onError")) { +169 env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)", +170 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +171 } +172 } +173 } +174 +175 @Override +176 public void cancel() { +177 Throwable thr = new Throwable(); +178 for (StackTraceElement stackElem : thr.getStackTrace()) { +179 if (stackElem.getMethodName().equals("onError")) { +180 env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)", +181 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +182 } +183 } +184 } +185 }; +186 +187 stage.probe = stage.createWhiteboxSubscriberProbe(env); +188 final Subscriber<T> sub = createSubscriber(stage.probe); +189 +190 sub.onSubscribe(subs); +191 sub.onError(new TestException()); +192 +193 env.verifyNoAsyncErrorsNoDelay(); +194 } +195 }); +196 } +197 +198 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.4 +199 @Override @Test +200 public void untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() throws Exception { +201 notVerified(); // cannot be meaningfully tested, or can it? +202 } +203 +204 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.5 +205 @Override @Test +206 public void required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() throws Throwable { +207 subscriberTest(new TestStageTestRun() { +208 @Override +209 public void run(WhiteboxTestStage stage) throws Throwable { +210 // try to subscribe another time, if the subscriber calls `probe.registerOnSubscribe` the test will fail +211 final Latch secondSubscriptionCancelled = new Latch(env); +212 final Subscriber<? super T> sub = stage.sub(); +213 final Subscription subscription = new Subscription() { +214 @Override +215 public void request(long elements) { +216 // ignore... +217 } +218 +219 @Override +220 public void cancel() { +221 secondSubscriptionCancelled.close(); +222 } +223 +224 @Override +225 public String toString() { +226 return "SecondSubscription(should get cancelled)"; +227 } +228 }; +229 sub.onSubscribe(subscription); +230 +231 secondSubscriptionCancelled.expectClose("Expected 2nd Subscription given to subscriber to be cancelled, but `Subscription.cancel()` was not called"); +232 env.verifyNoAsyncErrors(); +233 } +234 }); +235 } +236 +237 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.6 +238 @Override @Test +239 public void untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid() throws Exception { +240 notVerified(); // cannot be meaningfully tested, or can it? +241 } +242 +243 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.7 +244 @Override @Test +245 public void untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() throws Exception { +246 notVerified(); // cannot be meaningfully tested, or can it? +247 // the same thread part of the clause can be verified but that is not very useful, or is it? +248 } +249 +250 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.8 +251 @Override @Test +252 public void required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() throws Throwable { +253 subscriberTest(new TestStageTestRun() { +254 @Override +255 public void run(WhiteboxTestStage stage) throws InterruptedException { +256 stage.puppet().triggerRequest(1); +257 stage.puppet().signalCancel(); +258 stage.signalNext(); +259 +260 stage.puppet().triggerRequest(1); +261 stage.puppet().triggerRequest(1); +262 +263 stage.verifyNoAsyncErrors(); +264 } +265 }); +266 } +267 +268 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.9 +269 @Override @Test +270 public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() throws Throwable { +271 subscriberTest(new TestStageTestRun() { +272 @Override +273 public void run(WhiteboxTestStage stage) throws InterruptedException { +274 stage.puppet().triggerRequest(1); +275 stage.sendCompletion(); +276 stage.probe.expectCompletion(); +277 +278 stage.verifyNoAsyncErrors(); +279 } +280 }); +281 } +282 +283 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.9 +284 @Override @Test +285 public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() throws Throwable { +286 subscriberTest(new TestStageTestRun() { +287 @Override +288 public void run(WhiteboxTestStage stage) throws InterruptedException { +289 stage.sendCompletion(); +290 stage.probe.expectCompletion(); +291 +292 stage.verifyNoAsyncErrors(); +293 } +294 }); +295 } +296 +297 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.10 +298 @Override @Test +299 public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() throws Throwable { +300 subscriberTest(new TestStageTestRun() { +301 @Override +302 public void run(WhiteboxTestStage stage) throws InterruptedException { +303 stage.puppet().triggerRequest(1); +304 stage.puppet().triggerRequest(1); +305 +306 Exception ex = new TestException(); +307 stage.sendError(ex); +308 stage.probe.expectError(ex); +309 +310 env.verifyNoAsyncErrorsNoDelay(); +311 } +312 }); +313 } +314 +315 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.10 +316 @Override @Test +317 public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall() throws Throwable { +318 subscriberTest(new TestStageTestRun() { +319 @Override +320 public void run(WhiteboxTestStage stage) throws InterruptedException { +321 Exception ex = new TestException(); +322 stage.sendError(ex); +323 stage.probe.expectError(ex); +324 +325 env.verifyNoAsyncErrorsNoDelay(); +326 } +327 }); +328 } +329 +330 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.11 +331 @Override @Test +332 public void untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() throws Exception { +333 notVerified(); // cannot be meaningfully tested, or can it? +334 } +335 +336 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.12 +337 @Override @Test +338 public void untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation() throws Throwable { +339 notVerified(); // cannot be meaningfully tested, or can it? +340 } +341 +342 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +343 @Override @Test +344 public void untested_spec213_failingOnSignalInvocation() throws Exception { +345 notVerified(); // cannot be meaningfully tested, or can it? +346 } +347 +348 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +349 @Override @Test +350 public void required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +351 subscriberTest(new TestStageTestRun() { +352 @Override +353 public void run(WhiteboxTestStage stage) throws Throwable { +354 +355 final Subscriber<? super T> sub = stage.sub(); +356 boolean gotNPE = false; +357 try { +358 sub.onSubscribe(null); +359 } catch (final NullPointerException expected) { +360 gotNPE = true; +361 } +362 +363 assertTrue(gotNPE, "onSubscribe(null) did not throw NullPointerException"); +364 env.verifyNoAsyncErrorsNoDelay(); +365 } +366 }); +367 } +368 +369 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +370 @Override @Test +371 public void required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +372 subscriberTest(new TestStageTestRun() { +373 @Override +374 public void run(WhiteboxTestStage stage) throws Throwable { +375 +376 final Subscriber<? super T> sub = stage.sub(); +377 boolean gotNPE = false; +378 try { +379 sub.onNext(null); +380 } catch (final NullPointerException expected) { +381 gotNPE = true; +382 } +383 +384 assertTrue(gotNPE, "onNext(null) did not throw NullPointerException"); +385 env.verifyNoAsyncErrorsNoDelay(); +386 } +387 }); +388 } +389 +390 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +391 @Override @Test +392 public void required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +393 subscriberTest(new TestStageTestRun() { +394 @Override +395 public void run(WhiteboxTestStage stage) throws Throwable { +396 +397 final Subscriber<? super T> sub = stage.sub(); +398 boolean gotNPE = false; +399 try { +400 sub.onError(null); +401 } catch (final NullPointerException expected) { +402 gotNPE = true; +403 } finally { +404 assertTrue(gotNPE, "onError(null) did not throw NullPointerException"); +405 } +406 +407 env.verifyNoAsyncErrorsNoDelay(); +408 } +409 }); +410 } +411 +412 +413 ////////////////////// SUBSCRIPTION SPEC RULE VERIFICATION ////////////////// +414 +415 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.1 +416 @Override @Test +417 public void untested_spec301_mustNotBeCalledOutsideSubscriberContext() throws Exception { +418 notVerified(); // cannot be meaningfully tested, or can it? +419 } +420 +421 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.8 +422 @Override @Test +423 public void required_spec308_requestMustRegisterGivenNumberElementsToBeProduced() throws Throwable { +424 subscriberTest(new TestStageTestRun() { +425 @Override +426 public void run(WhiteboxTestStage stage) throws InterruptedException { +427 stage.puppet().triggerRequest(2); +428 stage.probe.expectNext(stage.signalNext()); +429 stage.probe.expectNext(stage.signalNext()); +430 +431 stage.probe.expectNone(); +432 stage.puppet().triggerRequest(3); +433 +434 stage.verifyNoAsyncErrors(); +435 } +436 }); +437 } +438 +439 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.10 +440 @Override @Test +441 public void untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber() throws Exception { +442 notVerified(); // cannot be meaningfully tested, or can it? +443 } +444 +445 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.11 +446 @Override @Test +447 public void untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError() throws Exception { +448 notVerified(); // cannot be meaningfully tested, or can it? +449 } +450 +451 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.14 +452 @Override @Test +453 public void untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() throws Exception { +454 notVerified(); // cannot be meaningfully tested, or can it? +455 } +456 +457 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.15 +458 @Override @Test +459 public void untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError() throws Exception { +460 notVerified(); // cannot be meaningfully tested, or can it? +461 } +462 +463 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.16 +464 @Override @Test +465 public void untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() throws Exception { +466 notVerified(); // cannot be meaningfully tested, or can it? +467 } +468 +469 /////////////////////// ADDITIONAL "COROLLARY" TESTS //////////////////////// +470 +471 /////////////////////// TEST INFRASTRUCTURE ///////////////////////////////// +472 +473 abstract class TestStageTestRun { +474 public abstract void run(WhiteboxTestStage stage) throws Throwable; +475 } +476 +477 /** +478 * Prepares subscriber and publisher pair (by subscribing the first to the latter), +479 * and then hands over the tests {@link WhiteboxTestStage} over to the test. +480 * +481 * The test stage is, like in a puppet show, used to orchestrate what each participant should do. +482 * Since this is a whitebox test, this allows the stage to completely control when and how to signal / expect signals. +483 */ +484 public void subscriberTest(TestStageTestRun body) throws Throwable { +485 WhiteboxTestStage stage = new WhiteboxTestStage(env, true); +486 body.run(stage); +487 } +488 +489 /** +490 * Provides a {@link WhiteboxTestStage} without performing any additional setup, +491 * like the {@link #subscriberTest(SubscriberWhiteboxVerification.TestStageTestRun)} would. +492 * +493 * Use this method to write tests in which you need full control over when and how the initial {@code subscribe} is signalled. +494 */ +495 public void subscriberTestWithoutSetup(TestStageTestRun body) throws Throwable { +496 WhiteboxTestStage stage = new WhiteboxTestStage(env, false); +497 body.run(stage); +498 } +499 +500 /** +501 * Test for feature that MAY be implemented. This test will be marked as SKIPPED if it fails. +502 */ +503 public void optionalSubscriberTestWithoutSetup(TestStageTestRun body) throws Throwable { +504 try { +505 subscriberTestWithoutSetup(body); +506 } catch (Exception ex) { +507 notVerified("Skipped because tested publisher does NOT implement this OPTIONAL requirement."); +508 } +509 } +510 +511 public class WhiteboxTestStage extends ManualPublisher<T> { +512 public Publisher<T> pub; +513 public ManualSubscriber<T> tees; // gives us access to a stream T values +514 public WhiteboxSubscriberProbe<T> probe; +515 +516 public T lastT = null; +517 +518 public WhiteboxTestStage(TestEnvironment env) throws InterruptedException { +519 this(env, true); +520 } +521 +522 public WhiteboxTestStage(TestEnvironment env, boolean runDefaultInit) throws InterruptedException { +523 super(env); +524 if (runDefaultInit) { +525 pub = this.createHelperPublisher(Long.MAX_VALUE); +526 tees = env.newManualSubscriber(pub); +527 probe = new WhiteboxSubscriberProbe<T>(env, subscriber); +528 subscribe(createSubscriber(probe)); +529 probe.puppet.expectCompletion(env.defaultTimeoutMillis(), String.format("Subscriber %s did not `registerOnSubscribe`", sub())); +530 } +531 } +532 +533 public Subscriber<? super T> sub() { +534 return subscriber.value(); +535 } +536 +537 public SubscriberPuppet puppet() { +538 return probe.puppet(); +539 } +540 +541 public WhiteboxSubscriberProbe<T> probe() { +542 return probe; +543 } +544 +545 public Publisher<T> createHelperPublisher(long elements) { +546 return SubscriberWhiteboxVerification.this.createHelperPublisher(elements); +547 } +548 +549 public WhiteboxSubscriberProbe<T> createWhiteboxSubscriberProbe(TestEnvironment env) { +550 return new WhiteboxSubscriberProbe<T>(env, subscriber); +551 } +552 +553 public T signalNext() throws InterruptedException { +554 return signalNext(nextT()); +555 } +556 +557 private T signalNext(T element) throws InterruptedException { +558 sendNext(element); +559 return element; +560 } +561 +562 public T nextT() throws InterruptedException { +563 lastT = tees.requestNextElement(); +564 return lastT; +565 } +566 +567 public void verifyNoAsyncErrors() { +568 env.verifyNoAsyncErrors(); +569 } +570 } +571 +572 /** +573 * This class is intented to be used as {@code Subscriber} decorator and should be used in {@code pub.subscriber(...)} calls, +574 * in order to allow intercepting calls on the underlying {@code Subscriber}. +575 * This delegation allows the proxy to implement {@link BlackboxProbe} assertions. +576 */ +577 public static class BlackboxSubscriberProxy<T> extends BlackboxProbe<T> implements Subscriber<T> { +578 +579 public BlackboxSubscriberProxy(TestEnvironment env, Subscriber<T> subscriber) { +580 super(env, Promise.<Subscriber<? super T>>completed(env, subscriber)); +581 } +582 +583 @Override +584 public void onSubscribe(Subscription s) { +585 sub().onSubscribe(s); +586 } +587 +588 @Override +589 public void onNext(T t) { +590 registerOnNext(t); +591 sub().onNext(t); +592 } +593 +594 @Override +595 public void onError(Throwable cause) { +596 registerOnError(cause); +597 sub().onError(cause); +598 } +599 +600 @Override +601 public void onComplete() { +602 registerOnComplete(); +603 sub().onComplete(); +604 } +605 } +606 +607 public static class BlackboxProbe<T> implements SubscriberProbe<T> { +608 protected final TestEnvironment env; +609 protected final Promise<Subscriber<? super T>> subscriber; +610 +611 protected final Receptacle<T> elements; +612 protected final Promise<Throwable> error; +613 +614 public BlackboxProbe(TestEnvironment env, Promise<Subscriber<? super T>> subscriber) { +615 this.env = env; +616 this.subscriber = subscriber; +617 elements = new Receptacle<T>(env); +618 error = new Promise<Throwable>(env); +619 } +620 +621 @Override +622 public void registerOnNext(T element) { +623 elements.add(element); +624 } +625 +626 @Override +627 public void registerOnComplete() { +628 try { +629 elements.complete(); +630 } catch (IllegalStateException ex) { +631 // "Queue full", onComplete was already called +632 env.flop("subscriber::onComplete was called a second time, which is illegal according to Rule 1.7"); +633 } +634 } +635 +636 @Override +637 public void registerOnError(Throwable cause) { +638 try { +639 error.complete(cause); +640 } catch (IllegalStateException ex) { +641 // "Queue full", onError was already called +642 env.flop("subscriber::onError was called a second time, which is illegal according to Rule 1.7"); +643 } +644 } +645 +646 public T expectNext() throws InterruptedException { +647 return elements.next(env.defaultTimeoutMillis(), String.format("Subscriber %s did not call `registerOnNext(_)`", sub())); +648 } +649 +650 public void expectNext(T expected) throws InterruptedException { +651 expectNext(expected, env.defaultTimeoutMillis()); +652 } +653 +654 public void expectNext(T expected, long timeoutMillis) throws InterruptedException { +655 T received = elements.next(timeoutMillis, String.format("Subscriber %s did not call `registerOnNext(%s)`", sub(), expected)); +656 if (!received.equals(expected)) { +657 env.flop(String.format("Subscriber %s called `registerOnNext(%s)` rather than `registerOnNext(%s)`", sub(), received, expected)); +658 } +659 } +660 +661 public Subscriber<? super T> sub() { +662 return subscriber.value(); +663 } +664 +665 public void expectCompletion() throws InterruptedException { +666 expectCompletion(env.defaultTimeoutMillis()); +667 } +668 +669 public void expectCompletion(long timeoutMillis) throws InterruptedException { +670 expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnComplete()`", sub())); +671 } +672 +673 public void expectCompletion(long timeoutMillis, String msg) throws InterruptedException { +674 elements.expectCompletion(timeoutMillis, msg); +675 } +676 +677 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +678 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart) throws InterruptedException { +679 final E err = expectError(expected); +680 String message = err.getMessage(); +681 assertTrue(message.contains(requiredMessagePart), +682 String.format("Got expected exception %s but missing message [%s], was: %s", err.getClass(), requiredMessagePart, expected)); +683 } +684 +685 public <E extends Throwable> E expectError(Class<E> expected) throws InterruptedException { +686 return expectError(expected, env.defaultTimeoutMillis()); +687 } +688 +689 @SuppressWarnings({"unchecked", "ThrowableResultOfMethodCallIgnored"}) +690 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis) throws InterruptedException { +691 error.expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected)); +692 if (error.value() == null) { +693 return env.flopAndFail(String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected)); +694 } else if (expected.isInstance(error.value())) { +695 return (E) error.value(); +696 } else { +697 return env.flopAndFail(String.format("Subscriber %s called `registerOnError(%s)` rather than `registerOnError(%s)`", sub(), error.value(), expected)); +698 } +699 } +700 +701 public void expectError(Throwable expected) throws InterruptedException { +702 expectError(expected, env.defaultTimeoutMillis()); +703 } +704 +705 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +706 public void expectError(Throwable expected, long timeoutMillis) throws InterruptedException { +707 error.expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected)); +708 if (error.value() != expected) { +709 env.flop(String.format("Subscriber %s called `registerOnError(%s)` rather than `registerOnError(%s)`", sub(), error.value(), expected)); +710 } +711 } +712 +713 public void expectNone() throws InterruptedException { +714 expectNone(env.defaultTimeoutMillis()); +715 } +716 +717 public void expectNone(long withinMillis) throws InterruptedException { +718 elements.expectNone(withinMillis, "Expected nothing"); +719 } +720 +721 } +722 +723 public static class WhiteboxSubscriberProbe<T> extends BlackboxProbe<T> implements SubscriberPuppeteer { +724 protected Promise<SubscriberPuppet> puppet; +725 +726 public WhiteboxSubscriberProbe(TestEnvironment env, Promise<Subscriber<? super T>> subscriber) { +727 super(env, subscriber); +728 puppet = new Promise<SubscriberPuppet>(env); +729 } +730 +731 private SubscriberPuppet puppet() { +732 return puppet.value(); +733 } +734 +735 @Override +736 public void registerOnSubscribe(SubscriberPuppet p) { +737 if (!puppet.isCompleted()) { +738 puppet.complete(p); +739 } +740 } +741 +742 } +743 +744 public interface SubscriberPuppeteer { +745 +746 /** +747 * Must be called by the test subscriber when it has successfully registered a subscription +748 * inside the `onSubscribe` method. +749 */ +750 void registerOnSubscribe(SubscriberPuppet puppet); +751 } +752 +753 public interface SubscriberProbe<T> { +754 +755 /** +756 * Must be called by the test subscriber when it has received an`onNext` event. +757 */ +758 void registerOnNext(T element); +759 +760 /** +761 * Must be called by the test subscriber when it has received an `onComplete` event. +762 */ +763 void registerOnComplete(); +764 +765 /** +766 * Must be called by the test subscriber when it has received an `onError` event. +767 */ +768 void registerOnError(Throwable cause); +769 +770 } +771 +772 public interface SubscriberPuppet { +773 void triggerRequest(long elements); +774 +775 void signalCancel(); +776 } +777 +778 public void notVerified() { +779 throw new SkipException("Not verified using this TCK."); +780 } +781 +782 public void notVerified(String msg) { +783 throw new SkipException(msg); +784 } +785} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck; +002 +003import org.reactivestreams.Publisher; +004import org.reactivestreams.Subscriber; +005import org.reactivestreams.Subscription; +006import org.reactivestreams.tck.TestEnvironment.*; +007import org.reactivestreams.tck.support.Optional; +008import org.reactivestreams.tck.support.SubscriberWhiteboxVerificationRules; +009import org.reactivestreams.tck.support.TestException; +010import org.testng.SkipException; +011import org.testng.annotations.AfterClass; +012import org.testng.annotations.BeforeClass; +013import org.testng.annotations.BeforeMethod; +014import org.testng.annotations.Test; +015 +016import java.util.concurrent.ExecutorService; +017import java.util.concurrent.Executors; +018 +019import static org.testng.Assert.assertTrue; +020 +021/** +022 * Provides tests for verifying {@link org.reactivestreams.Subscriber} and {@link org.reactivestreams.Subscription} specification rules. +023 * +024 * @see org.reactivestreams.Subscriber +025 * @see org.reactivestreams.Subscription +026 */ +027public abstract class SubscriberWhiteboxVerification<T> extends WithHelperPublisher<T> +028 implements SubscriberWhiteboxVerificationRules { +029 +030 private final TestEnvironment env; +031 +032 protected SubscriberWhiteboxVerification(TestEnvironment env) { +033 this.env = env; +034 } +035 +036 // USER API +037 +038 /** +039 * This is the main method you must implement in your test incarnation. +040 * It must create a new {@link org.reactivestreams.Subscriber} instance to be subjected to the testing logic. +041 * +042 * In order to be meaningfully testable your Subscriber must inform the given +043 * `WhiteboxSubscriberProbe` of the respective events having been received. +044 */ +045 public abstract Subscriber<T> createSubscriber(WhiteboxSubscriberProbe<T> probe); +046 +047 // ENV SETUP +048 +049 /** +050 * Executor service used by the default provided asynchronous Publisher. +051 * @see #createHelperPublisher(long) +052 */ +053 private ExecutorService publisherExecutor; +054 @BeforeClass public void startPublisherExecutorService() { publisherExecutor = Executors.newFixedThreadPool(4); } +055 @AfterClass public void shutdownPublisherExecutorService() { if (publisherExecutor != null) publisherExecutor.shutdown(); } +056 @Override public ExecutorService publisherExecutorService() { return publisherExecutor; } +057 +058 ////////////////////// TEST ENV CLEANUP ///////////////////////////////////// +059 +060 @BeforeMethod +061 public void setUp() throws Exception { +062 env.clearAsyncErrors(); +063 } +064 +065 ////////////////////// TEST SETUP VERIFICATION ////////////////////////////// +066 +067 @Test +068 public void required_exerciseWhiteboxHappyPath() throws Throwable { +069 subscriberTest(new TestStageTestRun() { +070 @Override +071 public void run(WhiteboxTestStage stage) throws InterruptedException { +072 stage.puppet().triggerRequest(1); +073 stage.puppet().triggerRequest(1); +074 +075 long receivedRequests = stage.expectRequest(); +076 +077 stage.signalNext(); +078 stage.probe.expectNext(stage.lastT); +079 +080 stage.puppet().triggerRequest(1); +081 if (receivedRequests == 1) { +082 stage.expectRequest(); +083 } +084 +085 stage.signalNext(); +086 stage.probe.expectNext(stage.lastT); +087 +088 stage.puppet().signalCancel(); +089 stage.expectCancelling(); +090 +091 stage.verifyNoAsyncErrors(); +092 } +093 }); +094 } +095 +096 ////////////////////// SPEC RULE VERIFICATION /////////////////////////////// +097 +098 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.1 +099 @Override @Test +100 public void required_spec201_mustSignalDemandViaSubscriptionRequest() throws Throwable { +101 subscriberTest(new TestStageTestRun() { +102 @Override +103 public void run(WhiteboxTestStage stage) throws InterruptedException { +104 stage.puppet().triggerRequest(1); +105 stage.expectRequest(); +106 +107 stage.signalNext(); +108 } +109 }); +110 } +111 +112 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.2 +113 @Override @Test +114 public void untested_spec202_shouldAsynchronouslyDispatch() throws Exception { +115 notVerified(); // cannot be meaningfully tested, or can it? +116 } +117 +118 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.3 +119 @Override @Test +120 public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() throws Throwable { +121 subscriberTestWithoutSetup(new TestStageTestRun() { +122 @Override +123 public void run(WhiteboxTestStage stage) throws Throwable { +124 final Subscription subs = new Subscription() { +125 @Override +126 public void request(long n) { +127 final Optional<StackTraceElement> onCompleteStackTraceElement = env.findCallerMethodInStackTrace("onComplete"); +128 if (onCompleteStackTraceElement.isDefined()) { +129 final StackTraceElement stackElem = onCompleteStackTraceElement.get(); +130 env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)", +131 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +132 } +133 } +134 +135 @Override +136 public void cancel() { +137 final Optional<StackTraceElement> onCompleteStackElement = env.findCallerMethodInStackTrace("onComplete"); +138 if (onCompleteStackElement.isDefined()) { +139 final StackTraceElement stackElem = onCompleteStackElement.get(); +140 env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)", +141 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +142 } +143 } +144 }; +145 +146 stage.probe = stage.createWhiteboxSubscriberProbe(env); +147 final Subscriber<T> sub = createSubscriber(stage.probe); +148 +149 sub.onSubscribe(subs); +150 sub.onComplete(); +151 +152 env.verifyNoAsyncErrorsNoDelay(); +153 } +154 }); +155 } +156 +157 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.3 +158 @Override @Test +159 public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() throws Throwable { +160 subscriberTestWithoutSetup(new TestStageTestRun() { +161 @Override +162 public void run(WhiteboxTestStage stage) throws Throwable { +163 final Subscription subs = new Subscription() { +164 @Override +165 public void request(long n) { +166 Throwable thr = new Throwable(); +167 for (StackTraceElement stackElem : thr.getStackTrace()) { +168 if (stackElem.getMethodName().equals("onError")) { +169 env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)", +170 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +171 } +172 } +173 } +174 +175 @Override +176 public void cancel() { +177 Throwable thr = new Throwable(); +178 for (StackTraceElement stackElem : thr.getStackTrace()) { +179 if (stackElem.getMethodName().equals("onError")) { +180 env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)", +181 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +182 } +183 } +184 } +185 }; +186 +187 stage.probe = stage.createWhiteboxSubscriberProbe(env); +188 final Subscriber<T> sub = createSubscriber(stage.probe); +189 +190 sub.onSubscribe(subs); +191 sub.onError(new TestException()); +192 +193 env.verifyNoAsyncErrorsNoDelay(); +194 } +195 }); +196 } +197 +198 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.4 +199 @Override @Test +200 public void untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() throws Exception { +201 notVerified(); // cannot be meaningfully tested, or can it? +202 } +203 +204 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.5 +205 @Override @Test +206 public void required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() throws Throwable { +207 subscriberTest(new TestStageTestRun() { +208 @Override +209 public void run(WhiteboxTestStage stage) throws Throwable { +210 // try to subscribe another time, if the subscriber calls `probe.registerOnSubscribe` the test will fail +211 final Latch secondSubscriptionCancelled = new Latch(env); +212 final Subscriber<? super T> sub = stage.sub(); +213 final Subscription subscription = new Subscription() { +214 @Override +215 public void request(long elements) { +216 // ignore... +217 } +218 +219 @Override +220 public void cancel() { +221 secondSubscriptionCancelled.close(); +222 } +223 +224 @Override +225 public String toString() { +226 return "SecondSubscription(should get cancelled)"; +227 } +228 }; +229 sub.onSubscribe(subscription); +230 +231 secondSubscriptionCancelled.expectClose("Expected 2nd Subscription given to subscriber to be cancelled, but `Subscription.cancel()` was not called"); +232 env.verifyNoAsyncErrors(); +233 } +234 }); +235 } +236 +237 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.6 +238 @Override @Test +239 public void untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid() throws Exception { +240 notVerified(); // cannot be meaningfully tested, or can it? +241 } +242 +243 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.7 +244 @Override @Test +245 public void untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() throws Exception { +246 notVerified(); // cannot be meaningfully tested, or can it? +247 // the same thread part of the clause can be verified but that is not very useful, or is it? +248 } +249 +250 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.8 +251 @Override @Test +252 public void required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() throws Throwable { +253 subscriberTest(new TestStageTestRun() { +254 @Override +255 public void run(WhiteboxTestStage stage) throws InterruptedException { +256 stage.puppet().triggerRequest(1); +257 stage.puppet().signalCancel(); +258 stage.signalNext(); +259 +260 stage.puppet().triggerRequest(1); +261 stage.puppet().triggerRequest(1); +262 +263 stage.verifyNoAsyncErrors(); +264 } +265 }); +266 } +267 +268 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.9 +269 @Override @Test +270 public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() throws Throwable { +271 subscriberTest(new TestStageTestRun() { +272 @Override +273 public void run(WhiteboxTestStage stage) throws InterruptedException { +274 stage.puppet().triggerRequest(1); +275 stage.sendCompletion(); +276 stage.probe.expectCompletion(); +277 +278 stage.verifyNoAsyncErrors(); +279 } +280 }); +281 } +282 +283 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.9 +284 @Override @Test +285 public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() throws Throwable { +286 subscriberTest(new TestStageTestRun() { +287 @Override +288 public void run(WhiteboxTestStage stage) throws InterruptedException { +289 stage.sendCompletion(); +290 stage.probe.expectCompletion(); +291 +292 stage.verifyNoAsyncErrors(); +293 } +294 }); +295 } +296 +297 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.10 +298 @Override @Test +299 public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() throws Throwable { +300 subscriberTest(new TestStageTestRun() { +301 @Override +302 public void run(WhiteboxTestStage stage) throws InterruptedException { +303 stage.puppet().triggerRequest(1); +304 stage.puppet().triggerRequest(1); +305 +306 Exception ex = new TestException(); +307 stage.sendError(ex); +308 stage.probe.expectError(ex); +309 +310 env.verifyNoAsyncErrorsNoDelay(); +311 } +312 }); +313 } +314 +315 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.10 +316 @Override @Test +317 public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall() throws Throwable { +318 subscriberTest(new TestStageTestRun() { +319 @Override +320 public void run(WhiteboxTestStage stage) throws InterruptedException { +321 Exception ex = new TestException(); +322 stage.sendError(ex); +323 stage.probe.expectError(ex); +324 +325 env.verifyNoAsyncErrorsNoDelay(); +326 } +327 }); +328 } +329 +330 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.11 +331 @Override @Test +332 public void untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() throws Exception { +333 notVerified(); // cannot be meaningfully tested, or can it? +334 } +335 +336 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.12 +337 @Override @Test +338 public void untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation() throws Throwable { +339 notVerified(); // cannot be meaningfully tested, or can it? +340 } +341 +342 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +343 @Override @Test +344 public void untested_spec213_failingOnSignalInvocation() throws Exception { +345 notVerified(); // cannot be meaningfully tested, or can it? +346 } +347 +348 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +349 @Override @Test +350 public void required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +351 subscriberTest(new TestStageTestRun() { +352 @Override +353 public void run(WhiteboxTestStage stage) throws Throwable { +354 +355 final Subscriber<? super T> sub = stage.sub(); +356 boolean gotNPE = false; +357 try { +358 sub.onSubscribe(null); +359 } catch (final NullPointerException expected) { +360 gotNPE = true; +361 } +362 +363 assertTrue(gotNPE, "onSubscribe(null) did not throw NullPointerException"); +364 env.verifyNoAsyncErrorsNoDelay(); +365 } +366 }); +367 } +368 +369 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +370 @Override @Test +371 public void required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +372 subscriberTest(new TestStageTestRun() { +373 @Override +374 public void run(WhiteboxTestStage stage) throws Throwable { +375 +376 final Subscriber<? super T> sub = stage.sub(); +377 boolean gotNPE = false; +378 try { +379 sub.onNext(null); +380 } catch (final NullPointerException expected) { +381 gotNPE = true; +382 } +383 +384 assertTrue(gotNPE, "onNext(null) did not throw NullPointerException"); +385 env.verifyNoAsyncErrorsNoDelay(); +386 } +387 }); +388 } +389 +390 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +391 @Override @Test +392 public void required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +393 subscriberTest(new TestStageTestRun() { +394 @Override +395 public void run(WhiteboxTestStage stage) throws Throwable { +396 +397 final Subscriber<? super T> sub = stage.sub(); +398 boolean gotNPE = false; +399 try { +400 sub.onError(null); +401 } catch (final NullPointerException expected) { +402 gotNPE = true; +403 } finally { +404 assertTrue(gotNPE, "onError(null) did not throw NullPointerException"); +405 } +406 +407 env.verifyNoAsyncErrorsNoDelay(); +408 } +409 }); +410 } +411 +412 +413 ////////////////////// SUBSCRIPTION SPEC RULE VERIFICATION ////////////////// +414 +415 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.1 +416 @Override @Test +417 public void untested_spec301_mustNotBeCalledOutsideSubscriberContext() throws Exception { +418 notVerified(); // cannot be meaningfully tested, or can it? +419 } +420 +421 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.8 +422 @Override @Test +423 public void required_spec308_requestMustRegisterGivenNumberElementsToBeProduced() throws Throwable { +424 subscriberTest(new TestStageTestRun() { +425 @Override +426 public void run(WhiteboxTestStage stage) throws InterruptedException { +427 stage.puppet().triggerRequest(2); +428 stage.probe.expectNext(stage.signalNext()); +429 stage.probe.expectNext(stage.signalNext()); +430 +431 stage.probe.expectNone(); +432 stage.puppet().triggerRequest(3); +433 +434 stage.verifyNoAsyncErrors(); +435 } +436 }); +437 } +438 +439 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.10 +440 @Override @Test +441 public void untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber() throws Exception { +442 notVerified(); // cannot be meaningfully tested, or can it? +443 } +444 +445 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.11 +446 @Override @Test +447 public void untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError() throws Exception { +448 notVerified(); // cannot be meaningfully tested, or can it? +449 } +450 +451 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.14 +452 @Override @Test +453 public void untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() throws Exception { +454 notVerified(); // cannot be meaningfully tested, or can it? +455 } +456 +457 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.15 +458 @Override @Test +459 public void untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError() throws Exception { +460 notVerified(); // cannot be meaningfully tested, or can it? +461 } +462 +463 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.16 +464 @Override @Test +465 public void untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() throws Exception { +466 notVerified(); // cannot be meaningfully tested, or can it? +467 } +468 +469 /////////////////////// ADDITIONAL "COROLLARY" TESTS //////////////////////// +470 +471 /////////////////////// TEST INFRASTRUCTURE ///////////////////////////////// +472 +473 abstract class TestStageTestRun { +474 public abstract void run(WhiteboxTestStage stage) throws Throwable; +475 } +476 +477 /** +478 * Prepares subscriber and publisher pair (by subscribing the first to the latter), +479 * and then hands over the tests {@link WhiteboxTestStage} over to the test. +480 * +481 * The test stage is, like in a puppet show, used to orchestrate what each participant should do. +482 * Since this is a whitebox test, this allows the stage to completely control when and how to signal / expect signals. +483 */ +484 public void subscriberTest(TestStageTestRun body) throws Throwable { +485 WhiteboxTestStage stage = new WhiteboxTestStage(env, true); +486 body.run(stage); +487 } +488 +489 /** +490 * Provides a {@link WhiteboxTestStage} without performing any additional setup, +491 * like the {@link #subscriberTest(SubscriberWhiteboxVerification.TestStageTestRun)} would. +492 * +493 * Use this method to write tests in which you need full control over when and how the initial {@code subscribe} is signalled. +494 */ +495 public void subscriberTestWithoutSetup(TestStageTestRun body) throws Throwable { +496 WhiteboxTestStage stage = new WhiteboxTestStage(env, false); +497 body.run(stage); +498 } +499 +500 /** +501 * Test for feature that MAY be implemented. This test will be marked as SKIPPED if it fails. +502 */ +503 public void optionalSubscriberTestWithoutSetup(TestStageTestRun body) throws Throwable { +504 try { +505 subscriberTestWithoutSetup(body); +506 } catch (Exception ex) { +507 notVerified("Skipped because tested publisher does NOT implement this OPTIONAL requirement."); +508 } +509 } +510 +511 public class WhiteboxTestStage extends ManualPublisher<T> { +512 public Publisher<T> pub; +513 public ManualSubscriber<T> tees; // gives us access to a stream T values +514 public WhiteboxSubscriberProbe<T> probe; +515 +516 public T lastT = null; +517 +518 public WhiteboxTestStage(TestEnvironment env) throws InterruptedException { +519 this(env, true); +520 } +521 +522 public WhiteboxTestStage(TestEnvironment env, boolean runDefaultInit) throws InterruptedException { +523 super(env); +524 if (runDefaultInit) { +525 pub = this.createHelperPublisher(Long.MAX_VALUE); +526 tees = env.newManualSubscriber(pub); +527 probe = new WhiteboxSubscriberProbe<T>(env, subscriber); +528 subscribe(createSubscriber(probe)); +529 probe.puppet.expectCompletion(env.defaultTimeoutMillis(), String.format("Subscriber %s did not `registerOnSubscribe`", sub())); +530 } +531 } +532 +533 public Subscriber<? super T> sub() { +534 return subscriber.value(); +535 } +536 +537 public SubscriberPuppet puppet() { +538 return probe.puppet(); +539 } +540 +541 public WhiteboxSubscriberProbe<T> probe() { +542 return probe; +543 } +544 +545 public Publisher<T> createHelperPublisher(long elements) { +546 return SubscriberWhiteboxVerification.this.createHelperPublisher(elements); +547 } +548 +549 public WhiteboxSubscriberProbe<T> createWhiteboxSubscriberProbe(TestEnvironment env) { +550 return new WhiteboxSubscriberProbe<T>(env, subscriber); +551 } +552 +553 public T signalNext() throws InterruptedException { +554 return signalNext(nextT()); +555 } +556 +557 private T signalNext(T element) throws InterruptedException { +558 sendNext(element); +559 return element; +560 } +561 +562 public T nextT() throws InterruptedException { +563 lastT = tees.requestNextElement(); +564 return lastT; +565 } +566 +567 public void verifyNoAsyncErrors() { +568 env.verifyNoAsyncErrors(); +569 } +570 } +571 +572 /** +573 * This class is intented to be used as {@code Subscriber} decorator and should be used in {@code pub.subscriber(...)} calls, +574 * in order to allow intercepting calls on the underlying {@code Subscriber}. +575 * This delegation allows the proxy to implement {@link BlackboxProbe} assertions. +576 */ +577 public static class BlackboxSubscriberProxy<T> extends BlackboxProbe<T> implements Subscriber<T> { +578 +579 public BlackboxSubscriberProxy(TestEnvironment env, Subscriber<T> subscriber) { +580 super(env, Promise.<Subscriber<? super T>>completed(env, subscriber)); +581 } +582 +583 @Override +584 public void onSubscribe(Subscription s) { +585 sub().onSubscribe(s); +586 } +587 +588 @Override +589 public void onNext(T t) { +590 registerOnNext(t); +591 sub().onNext(t); +592 } +593 +594 @Override +595 public void onError(Throwable cause) { +596 registerOnError(cause); +597 sub().onError(cause); +598 } +599 +600 @Override +601 public void onComplete() { +602 registerOnComplete(); +603 sub().onComplete(); +604 } +605 } +606 +607 public static class BlackboxProbe<T> implements SubscriberProbe<T> { +608 protected final TestEnvironment env; +609 protected final Promise<Subscriber<? super T>> subscriber; +610 +611 protected final Receptacle<T> elements; +612 protected final Promise<Throwable> error; +613 +614 public BlackboxProbe(TestEnvironment env, Promise<Subscriber<? super T>> subscriber) { +615 this.env = env; +616 this.subscriber = subscriber; +617 elements = new Receptacle<T>(env); +618 error = new Promise<Throwable>(env); +619 } +620 +621 @Override +622 public void registerOnNext(T element) { +623 elements.add(element); +624 } +625 +626 @Override +627 public void registerOnComplete() { +628 try { +629 elements.complete(); +630 } catch (IllegalStateException ex) { +631 // "Queue full", onComplete was already called +632 env.flop("subscriber::onComplete was called a second time, which is illegal according to Rule 1.7"); +633 } +634 } +635 +636 @Override +637 public void registerOnError(Throwable cause) { +638 try { +639 error.complete(cause); +640 } catch (IllegalStateException ex) { +641 // "Queue full", onError was already called +642 env.flop("subscriber::onError was called a second time, which is illegal according to Rule 1.7"); +643 } +644 } +645 +646 public T expectNext() throws InterruptedException { +647 return elements.next(env.defaultTimeoutMillis(), String.format("Subscriber %s did not call `registerOnNext(_)`", sub())); +648 } +649 +650 public void expectNext(T expected) throws InterruptedException { +651 expectNext(expected, env.defaultTimeoutMillis()); +652 } +653 +654 public void expectNext(T expected, long timeoutMillis) throws InterruptedException { +655 T received = elements.next(timeoutMillis, String.format("Subscriber %s did not call `registerOnNext(%s)`", sub(), expected)); +656 if (!received.equals(expected)) { +657 env.flop(String.format("Subscriber %s called `registerOnNext(%s)` rather than `registerOnNext(%s)`", sub(), received, expected)); +658 } +659 } +660 +661 public Subscriber<? super T> sub() { +662 return subscriber.value(); +663 } +664 +665 public void expectCompletion() throws InterruptedException { +666 expectCompletion(env.defaultTimeoutMillis()); +667 } +668 +669 public void expectCompletion(long timeoutMillis) throws InterruptedException { +670 expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnComplete()`", sub())); +671 } +672 +673 public void expectCompletion(long timeoutMillis, String msg) throws InterruptedException { +674 elements.expectCompletion(timeoutMillis, msg); +675 } +676 +677 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +678 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart) throws InterruptedException { +679 final E err = expectError(expected); +680 String message = err.getMessage(); +681 assertTrue(message.contains(requiredMessagePart), +682 String.format("Got expected exception %s but missing message [%s], was: %s", err.getClass(), requiredMessagePart, expected)); +683 } +684 +685 public <E extends Throwable> E expectError(Class<E> expected) throws InterruptedException { +686 return expectError(expected, env.defaultTimeoutMillis()); +687 } +688 +689 @SuppressWarnings({"unchecked", "ThrowableResultOfMethodCallIgnored"}) +690 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis) throws InterruptedException { +691 error.expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected)); +692 if (error.value() == null) { +693 return env.flopAndFail(String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected)); +694 } else if (expected.isInstance(error.value())) { +695 return (E) error.value(); +696 } else { +697 return env.flopAndFail(String.format("Subscriber %s called `registerOnError(%s)` rather than `registerOnError(%s)`", sub(), error.value(), expected)); +698 } +699 } +700 +701 public void expectError(Throwable expected) throws InterruptedException { +702 expectError(expected, env.defaultTimeoutMillis()); +703 } +704 +705 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +706 public void expectError(Throwable expected, long timeoutMillis) throws InterruptedException { +707 error.expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected)); +708 if (error.value() != expected) { +709 env.flop(String.format("Subscriber %s called `registerOnError(%s)` rather than `registerOnError(%s)`", sub(), error.value(), expected)); +710 } +711 } +712 +713 public void expectNone() throws InterruptedException { +714 expectNone(env.defaultTimeoutMillis()); +715 } +716 +717 public void expectNone(long withinMillis) throws InterruptedException { +718 elements.expectNone(withinMillis, "Expected nothing"); +719 } +720 +721 } +722 +723 public static class WhiteboxSubscriberProbe<T> extends BlackboxProbe<T> implements SubscriberPuppeteer { +724 protected Promise<SubscriberPuppet> puppet; +725 +726 public WhiteboxSubscriberProbe(TestEnvironment env, Promise<Subscriber<? super T>> subscriber) { +727 super(env, subscriber); +728 puppet = new Promise<SubscriberPuppet>(env); +729 } +730 +731 private SubscriberPuppet puppet() { +732 return puppet.value(); +733 } +734 +735 @Override +736 public void registerOnSubscribe(SubscriberPuppet p) { +737 if (!puppet.isCompleted()) { +738 puppet.complete(p); +739 } +740 } +741 +742 } +743 +744 public interface SubscriberPuppeteer { +745 +746 /** +747 * Must be called by the test subscriber when it has successfully registered a subscription +748 * inside the `onSubscribe` method. +749 */ +750 void registerOnSubscribe(SubscriberPuppet puppet); +751 } +752 +753 public interface SubscriberProbe<T> { +754 +755 /** +756 * Must be called by the test subscriber when it has received an`onNext` event. +757 */ +758 void registerOnNext(T element); +759 +760 /** +761 * Must be called by the test subscriber when it has received an `onComplete` event. +762 */ +763 void registerOnComplete(); +764 +765 /** +766 * Must be called by the test subscriber when it has received an `onError` event. +767 */ +768 void registerOnError(Throwable cause); +769 +770 } +771 +772 public interface SubscriberPuppet { +773 void triggerRequest(long elements); +774 +775 void signalCancel(); +776 } +777 +778 public void notVerified() { +779 throw new SkipException("Not verified using this TCK."); +780 } +781 +782 public void notVerified(String msg) { +783 throw new SkipException(msg); +784 } +785} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck; +002 +003import org.reactivestreams.Publisher; +004import org.reactivestreams.Subscriber; +005import org.reactivestreams.Subscription; +006import org.reactivestreams.tck.TestEnvironment.*; +007import org.reactivestreams.tck.support.Optional; +008import org.reactivestreams.tck.support.SubscriberWhiteboxVerificationRules; +009import org.reactivestreams.tck.support.TestException; +010import org.testng.SkipException; +011import org.testng.annotations.AfterClass; +012import org.testng.annotations.BeforeClass; +013import org.testng.annotations.BeforeMethod; +014import org.testng.annotations.Test; +015 +016import java.util.concurrent.ExecutorService; +017import java.util.concurrent.Executors; +018 +019import static org.testng.Assert.assertTrue; +020 +021/** +022 * Provides tests for verifying {@link org.reactivestreams.Subscriber} and {@link org.reactivestreams.Subscription} specification rules. +023 * +024 * @see org.reactivestreams.Subscriber +025 * @see org.reactivestreams.Subscription +026 */ +027public abstract class SubscriberWhiteboxVerification<T> extends WithHelperPublisher<T> +028 implements SubscriberWhiteboxVerificationRules { +029 +030 private final TestEnvironment env; +031 +032 protected SubscriberWhiteboxVerification(TestEnvironment env) { +033 this.env = env; +034 } +035 +036 // USER API +037 +038 /** +039 * This is the main method you must implement in your test incarnation. +040 * It must create a new {@link org.reactivestreams.Subscriber} instance to be subjected to the testing logic. +041 * +042 * In order to be meaningfully testable your Subscriber must inform the given +043 * `WhiteboxSubscriberProbe` of the respective events having been received. +044 */ +045 public abstract Subscriber<T> createSubscriber(WhiteboxSubscriberProbe<T> probe); +046 +047 // ENV SETUP +048 +049 /** +050 * Executor service used by the default provided asynchronous Publisher. +051 * @see #createHelperPublisher(long) +052 */ +053 private ExecutorService publisherExecutor; +054 @BeforeClass public void startPublisherExecutorService() { publisherExecutor = Executors.newFixedThreadPool(4); } +055 @AfterClass public void shutdownPublisherExecutorService() { if (publisherExecutor != null) publisherExecutor.shutdown(); } +056 @Override public ExecutorService publisherExecutorService() { return publisherExecutor; } +057 +058 ////////////////////// TEST ENV CLEANUP ///////////////////////////////////// +059 +060 @BeforeMethod +061 public void setUp() throws Exception { +062 env.clearAsyncErrors(); +063 } +064 +065 ////////////////////// TEST SETUP VERIFICATION ////////////////////////////// +066 +067 @Test +068 public void required_exerciseWhiteboxHappyPath() throws Throwable { +069 subscriberTest(new TestStageTestRun() { +070 @Override +071 public void run(WhiteboxTestStage stage) throws InterruptedException { +072 stage.puppet().triggerRequest(1); +073 stage.puppet().triggerRequest(1); +074 +075 long receivedRequests = stage.expectRequest(); +076 +077 stage.signalNext(); +078 stage.probe.expectNext(stage.lastT); +079 +080 stage.puppet().triggerRequest(1); +081 if (receivedRequests == 1) { +082 stage.expectRequest(); +083 } +084 +085 stage.signalNext(); +086 stage.probe.expectNext(stage.lastT); +087 +088 stage.puppet().signalCancel(); +089 stage.expectCancelling(); +090 +091 stage.verifyNoAsyncErrors(); +092 } +093 }); +094 } +095 +096 ////////////////////// SPEC RULE VERIFICATION /////////////////////////////// +097 +098 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.1 +099 @Override @Test +100 public void required_spec201_mustSignalDemandViaSubscriptionRequest() throws Throwable { +101 subscriberTest(new TestStageTestRun() { +102 @Override +103 public void run(WhiteboxTestStage stage) throws InterruptedException { +104 stage.puppet().triggerRequest(1); +105 stage.expectRequest(); +106 +107 stage.signalNext(); +108 } +109 }); +110 } +111 +112 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.2 +113 @Override @Test +114 public void untested_spec202_shouldAsynchronouslyDispatch() throws Exception { +115 notVerified(); // cannot be meaningfully tested, or can it? +116 } +117 +118 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.3 +119 @Override @Test +120 public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() throws Throwable { +121 subscriberTestWithoutSetup(new TestStageTestRun() { +122 @Override +123 public void run(WhiteboxTestStage stage) throws Throwable { +124 final Subscription subs = new Subscription() { +125 @Override +126 public void request(long n) { +127 final Optional<StackTraceElement> onCompleteStackTraceElement = env.findCallerMethodInStackTrace("onComplete"); +128 if (onCompleteStackTraceElement.isDefined()) { +129 final StackTraceElement stackElem = onCompleteStackTraceElement.get(); +130 env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)", +131 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +132 } +133 } +134 +135 @Override +136 public void cancel() { +137 final Optional<StackTraceElement> onCompleteStackElement = env.findCallerMethodInStackTrace("onComplete"); +138 if (onCompleteStackElement.isDefined()) { +139 final StackTraceElement stackElem = onCompleteStackElement.get(); +140 env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)", +141 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +142 } +143 } +144 }; +145 +146 stage.probe = stage.createWhiteboxSubscriberProbe(env); +147 final Subscriber<T> sub = createSubscriber(stage.probe); +148 +149 sub.onSubscribe(subs); +150 sub.onComplete(); +151 +152 env.verifyNoAsyncErrorsNoDelay(); +153 } +154 }); +155 } +156 +157 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.3 +158 @Override @Test +159 public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() throws Throwable { +160 subscriberTestWithoutSetup(new TestStageTestRun() { +161 @Override +162 public void run(WhiteboxTestStage stage) throws Throwable { +163 final Subscription subs = new Subscription() { +164 @Override +165 public void request(long n) { +166 Throwable thr = new Throwable(); +167 for (StackTraceElement stackElem : thr.getStackTrace()) { +168 if (stackElem.getMethodName().equals("onError")) { +169 env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)", +170 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +171 } +172 } +173 } +174 +175 @Override +176 public void cancel() { +177 Throwable thr = new Throwable(); +178 for (StackTraceElement stackElem : thr.getStackTrace()) { +179 if (stackElem.getMethodName().equals("onError")) { +180 env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)", +181 stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber())); +182 } +183 } +184 } +185 }; +186 +187 stage.probe = stage.createWhiteboxSubscriberProbe(env); +188 final Subscriber<T> sub = createSubscriber(stage.probe); +189 +190 sub.onSubscribe(subs); +191 sub.onError(new TestException()); +192 +193 env.verifyNoAsyncErrorsNoDelay(); +194 } +195 }); +196 } +197 +198 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.4 +199 @Override @Test +200 public void untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() throws Exception { +201 notVerified(); // cannot be meaningfully tested, or can it? +202 } +203 +204 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.5 +205 @Override @Test +206 public void required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() throws Throwable { +207 subscriberTest(new TestStageTestRun() { +208 @Override +209 public void run(WhiteboxTestStage stage) throws Throwable { +210 // try to subscribe another time, if the subscriber calls `probe.registerOnSubscribe` the test will fail +211 final Latch secondSubscriptionCancelled = new Latch(env); +212 final Subscriber<? super T> sub = stage.sub(); +213 final Subscription subscription = new Subscription() { +214 @Override +215 public void request(long elements) { +216 // ignore... +217 } +218 +219 @Override +220 public void cancel() { +221 secondSubscriptionCancelled.close(); +222 } +223 +224 @Override +225 public String toString() { +226 return "SecondSubscription(should get cancelled)"; +227 } +228 }; +229 sub.onSubscribe(subscription); +230 +231 secondSubscriptionCancelled.expectClose("Expected 2nd Subscription given to subscriber to be cancelled, but `Subscription.cancel()` was not called"); +232 env.verifyNoAsyncErrors(); +233 } +234 }); +235 } +236 +237 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.6 +238 @Override @Test +239 public void untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid() throws Exception { +240 notVerified(); // cannot be meaningfully tested, or can it? +241 } +242 +243 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.7 +244 @Override @Test +245 public void untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() throws Exception { +246 notVerified(); // cannot be meaningfully tested, or can it? +247 // the same thread part of the clause can be verified but that is not very useful, or is it? +248 } +249 +250 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.8 +251 @Override @Test +252 public void required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() throws Throwable { +253 subscriberTest(new TestStageTestRun() { +254 @Override +255 public void run(WhiteboxTestStage stage) throws InterruptedException { +256 stage.puppet().triggerRequest(1); +257 stage.puppet().signalCancel(); +258 stage.signalNext(); +259 +260 stage.puppet().triggerRequest(1); +261 stage.puppet().triggerRequest(1); +262 +263 stage.verifyNoAsyncErrors(); +264 } +265 }); +266 } +267 +268 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.9 +269 @Override @Test +270 public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() throws Throwable { +271 subscriberTest(new TestStageTestRun() { +272 @Override +273 public void run(WhiteboxTestStage stage) throws InterruptedException { +274 stage.puppet().triggerRequest(1); +275 stage.sendCompletion(); +276 stage.probe.expectCompletion(); +277 +278 stage.verifyNoAsyncErrors(); +279 } +280 }); +281 } +282 +283 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.9 +284 @Override @Test +285 public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() throws Throwable { +286 subscriberTest(new TestStageTestRun() { +287 @Override +288 public void run(WhiteboxTestStage stage) throws InterruptedException { +289 stage.sendCompletion(); +290 stage.probe.expectCompletion(); +291 +292 stage.verifyNoAsyncErrors(); +293 } +294 }); +295 } +296 +297 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.10 +298 @Override @Test +299 public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() throws Throwable { +300 subscriberTest(new TestStageTestRun() { +301 @Override +302 public void run(WhiteboxTestStage stage) throws InterruptedException { +303 stage.puppet().triggerRequest(1); +304 stage.puppet().triggerRequest(1); +305 +306 Exception ex = new TestException(); +307 stage.sendError(ex); +308 stage.probe.expectError(ex); +309 +310 env.verifyNoAsyncErrorsNoDelay(); +311 } +312 }); +313 } +314 +315 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.10 +316 @Override @Test +317 public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall() throws Throwable { +318 subscriberTest(new TestStageTestRun() { +319 @Override +320 public void run(WhiteboxTestStage stage) throws InterruptedException { +321 Exception ex = new TestException(); +322 stage.sendError(ex); +323 stage.probe.expectError(ex); +324 +325 env.verifyNoAsyncErrorsNoDelay(); +326 } +327 }); +328 } +329 +330 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.11 +331 @Override @Test +332 public void untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() throws Exception { +333 notVerified(); // cannot be meaningfully tested, or can it? +334 } +335 +336 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.12 +337 @Override @Test +338 public void untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation() throws Throwable { +339 notVerified(); // cannot be meaningfully tested, or can it? +340 } +341 +342 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +343 @Override @Test +344 public void untested_spec213_failingOnSignalInvocation() throws Exception { +345 notVerified(); // cannot be meaningfully tested, or can it? +346 } +347 +348 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +349 @Override @Test +350 public void required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +351 subscriberTest(new TestStageTestRun() { +352 @Override +353 public void run(WhiteboxTestStage stage) throws Throwable { +354 +355 final Subscriber<? super T> sub = stage.sub(); +356 boolean gotNPE = false; +357 try { +358 sub.onSubscribe(null); +359 } catch (final NullPointerException expected) { +360 gotNPE = true; +361 } +362 +363 assertTrue(gotNPE, "onSubscribe(null) did not throw NullPointerException"); +364 env.verifyNoAsyncErrorsNoDelay(); +365 } +366 }); +367 } +368 +369 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +370 @Override @Test +371 public void required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +372 subscriberTest(new TestStageTestRun() { +373 @Override +374 public void run(WhiteboxTestStage stage) throws Throwable { +375 +376 final Subscriber<? super T> sub = stage.sub(); +377 boolean gotNPE = false; +378 try { +379 sub.onNext(null); +380 } catch (final NullPointerException expected) { +381 gotNPE = true; +382 } +383 +384 assertTrue(gotNPE, "onNext(null) did not throw NullPointerException"); +385 env.verifyNoAsyncErrorsNoDelay(); +386 } +387 }); +388 } +389 +390 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13 +391 @Override @Test +392 public void required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable { +393 subscriberTest(new TestStageTestRun() { +394 @Override +395 public void run(WhiteboxTestStage stage) throws Throwable { +396 +397 final Subscriber<? super T> sub = stage.sub(); +398 boolean gotNPE = false; +399 try { +400 sub.onError(null); +401 } catch (final NullPointerException expected) { +402 gotNPE = true; +403 } finally { +404 assertTrue(gotNPE, "onError(null) did not throw NullPointerException"); +405 } +406 +407 env.verifyNoAsyncErrorsNoDelay(); +408 } +409 }); +410 } +411 +412 +413 ////////////////////// SUBSCRIPTION SPEC RULE VERIFICATION ////////////////// +414 +415 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.1 +416 @Override @Test +417 public void untested_spec301_mustNotBeCalledOutsideSubscriberContext() throws Exception { +418 notVerified(); // cannot be meaningfully tested, or can it? +419 } +420 +421 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.8 +422 @Override @Test +423 public void required_spec308_requestMustRegisterGivenNumberElementsToBeProduced() throws Throwable { +424 subscriberTest(new TestStageTestRun() { +425 @Override +426 public void run(WhiteboxTestStage stage) throws InterruptedException { +427 stage.puppet().triggerRequest(2); +428 stage.probe.expectNext(stage.signalNext()); +429 stage.probe.expectNext(stage.signalNext()); +430 +431 stage.probe.expectNone(); +432 stage.puppet().triggerRequest(3); +433 +434 stage.verifyNoAsyncErrors(); +435 } +436 }); +437 } +438 +439 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.10 +440 @Override @Test +441 public void untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber() throws Exception { +442 notVerified(); // cannot be meaningfully tested, or can it? +443 } +444 +445 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.11 +446 @Override @Test +447 public void untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError() throws Exception { +448 notVerified(); // cannot be meaningfully tested, or can it? +449 } +450 +451 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.14 +452 @Override @Test +453 public void untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() throws Exception { +454 notVerified(); // cannot be meaningfully tested, or can it? +455 } +456 +457 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.15 +458 @Override @Test +459 public void untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError() throws Exception { +460 notVerified(); // cannot be meaningfully tested, or can it? +461 } +462 +463 // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.16 +464 @Override @Test +465 public void untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() throws Exception { +466 notVerified(); // cannot be meaningfully tested, or can it? +467 } +468 +469 /////////////////////// ADDITIONAL "COROLLARY" TESTS //////////////////////// +470 +471 /////////////////////// TEST INFRASTRUCTURE ///////////////////////////////// +472 +473 abstract class TestStageTestRun { +474 public abstract void run(WhiteboxTestStage stage) throws Throwable; +475 } +476 +477 /** +478 * Prepares subscriber and publisher pair (by subscribing the first to the latter), +479 * and then hands over the tests {@link WhiteboxTestStage} over to the test. +480 * +481 * The test stage is, like in a puppet show, used to orchestrate what each participant should do. +482 * Since this is a whitebox test, this allows the stage to completely control when and how to signal / expect signals. +483 */ +484 public void subscriberTest(TestStageTestRun body) throws Throwable { +485 WhiteboxTestStage stage = new WhiteboxTestStage(env, true); +486 body.run(stage); +487 } +488 +489 /** +490 * Provides a {@link WhiteboxTestStage} without performing any additional setup, +491 * like the {@link #subscriberTest(SubscriberWhiteboxVerification.TestStageTestRun)} would. +492 * +493 * Use this method to write tests in which you need full control over when and how the initial {@code subscribe} is signalled. +494 */ +495 public void subscriberTestWithoutSetup(TestStageTestRun body) throws Throwable { +496 WhiteboxTestStage stage = new WhiteboxTestStage(env, false); +497 body.run(stage); +498 } +499 +500 /** +501 * Test for feature that MAY be implemented. This test will be marked as SKIPPED if it fails. +502 */ +503 public void optionalSubscriberTestWithoutSetup(TestStageTestRun body) throws Throwable { +504 try { +505 subscriberTestWithoutSetup(body); +506 } catch (Exception ex) { +507 notVerified("Skipped because tested publisher does NOT implement this OPTIONAL requirement."); +508 } +509 } +510 +511 public class WhiteboxTestStage extends ManualPublisher<T> { +512 public Publisher<T> pub; +513 public ManualSubscriber<T> tees; // gives us access to a stream T values +514 public WhiteboxSubscriberProbe<T> probe; +515 +516 public T lastT = null; +517 +518 public WhiteboxTestStage(TestEnvironment env) throws InterruptedException { +519 this(env, true); +520 } +521 +522 public WhiteboxTestStage(TestEnvironment env, boolean runDefaultInit) throws InterruptedException { +523 super(env); +524 if (runDefaultInit) { +525 pub = this.createHelperPublisher(Long.MAX_VALUE); +526 tees = env.newManualSubscriber(pub); +527 probe = new WhiteboxSubscriberProbe<T>(env, subscriber); +528 subscribe(createSubscriber(probe)); +529 probe.puppet.expectCompletion(env.defaultTimeoutMillis(), String.format("Subscriber %s did not `registerOnSubscribe`", sub())); +530 } +531 } +532 +533 public Subscriber<? super T> sub() { +534 return subscriber.value(); +535 } +536 +537 public SubscriberPuppet puppet() { +538 return probe.puppet(); +539 } +540 +541 public WhiteboxSubscriberProbe<T> probe() { +542 return probe; +543 } +544 +545 public Publisher<T> createHelperPublisher(long elements) { +546 return SubscriberWhiteboxVerification.this.createHelperPublisher(elements); +547 } +548 +549 public WhiteboxSubscriberProbe<T> createWhiteboxSubscriberProbe(TestEnvironment env) { +550 return new WhiteboxSubscriberProbe<T>(env, subscriber); +551 } +552 +553 public T signalNext() throws InterruptedException { +554 return signalNext(nextT()); +555 } +556 +557 private T signalNext(T element) throws InterruptedException { +558 sendNext(element); +559 return element; +560 } +561 +562 public T nextT() throws InterruptedException { +563 lastT = tees.requestNextElement(); +564 return lastT; +565 } +566 +567 public void verifyNoAsyncErrors() { +568 env.verifyNoAsyncErrors(); +569 } +570 } +571 +572 /** +573 * This class is intented to be used as {@code Subscriber} decorator and should be used in {@code pub.subscriber(...)} calls, +574 * in order to allow intercepting calls on the underlying {@code Subscriber}. +575 * This delegation allows the proxy to implement {@link BlackboxProbe} assertions. +576 */ +577 public static class BlackboxSubscriberProxy<T> extends BlackboxProbe<T> implements Subscriber<T> { +578 +579 public BlackboxSubscriberProxy(TestEnvironment env, Subscriber<T> subscriber) { +580 super(env, Promise.<Subscriber<? super T>>completed(env, subscriber)); +581 } +582 +583 @Override +584 public void onSubscribe(Subscription s) { +585 sub().onSubscribe(s); +586 } +587 +588 @Override +589 public void onNext(T t) { +590 registerOnNext(t); +591 sub().onNext(t); +592 } +593 +594 @Override +595 public void onError(Throwable cause) { +596 registerOnError(cause); +597 sub().onError(cause); +598 } +599 +600 @Override +601 public void onComplete() { +602 registerOnComplete(); +603 sub().onComplete(); +604 } +605 } +606 +607 public static class BlackboxProbe<T> implements SubscriberProbe<T> { +608 protected final TestEnvironment env; +609 protected final Promise<Subscriber<? super T>> subscriber; +610 +611 protected final Receptacle<T> elements; +612 protected final Promise<Throwable> error; +613 +614 public BlackboxProbe(TestEnvironment env, Promise<Subscriber<? super T>> subscriber) { +615 this.env = env; +616 this.subscriber = subscriber; +617 elements = new Receptacle<T>(env); +618 error = new Promise<Throwable>(env); +619 } +620 +621 @Override +622 public void registerOnNext(T element) { +623 elements.add(element); +624 } +625 +626 @Override +627 public void registerOnComplete() { +628 try { +629 elements.complete(); +630 } catch (IllegalStateException ex) { +631 // "Queue full", onComplete was already called +632 env.flop("subscriber::onComplete was called a second time, which is illegal according to Rule 1.7"); +633 } +634 } +635 +636 @Override +637 public void registerOnError(Throwable cause) { +638 try { +639 error.complete(cause); +640 } catch (IllegalStateException ex) { +641 // "Queue full", onError was already called +642 env.flop("subscriber::onError was called a second time, which is illegal according to Rule 1.7"); +643 } +644 } +645 +646 public T expectNext() throws InterruptedException { +647 return elements.next(env.defaultTimeoutMillis(), String.format("Subscriber %s did not call `registerOnNext(_)`", sub())); +648 } +649 +650 public void expectNext(T expected) throws InterruptedException { +651 expectNext(expected, env.defaultTimeoutMillis()); +652 } +653 +654 public void expectNext(T expected, long timeoutMillis) throws InterruptedException { +655 T received = elements.next(timeoutMillis, String.format("Subscriber %s did not call `registerOnNext(%s)`", sub(), expected)); +656 if (!received.equals(expected)) { +657 env.flop(String.format("Subscriber %s called `registerOnNext(%s)` rather than `registerOnNext(%s)`", sub(), received, expected)); +658 } +659 } +660 +661 public Subscriber<? super T> sub() { +662 return subscriber.value(); +663 } +664 +665 public void expectCompletion() throws InterruptedException { +666 expectCompletion(env.defaultTimeoutMillis()); +667 } +668 +669 public void expectCompletion(long timeoutMillis) throws InterruptedException { +670 expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnComplete()`", sub())); +671 } +672 +673 public void expectCompletion(long timeoutMillis, String msg) throws InterruptedException { +674 elements.expectCompletion(timeoutMillis, msg); +675 } +676 +677 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +678 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart) throws InterruptedException { +679 final E err = expectError(expected); +680 String message = err.getMessage(); +681 assertTrue(message.contains(requiredMessagePart), +682 String.format("Got expected exception %s but missing message [%s], was: %s", err.getClass(), requiredMessagePart, expected)); +683 } +684 +685 public <E extends Throwable> E expectError(Class<E> expected) throws InterruptedException { +686 return expectError(expected, env.defaultTimeoutMillis()); +687 } +688 +689 @SuppressWarnings({"unchecked", "ThrowableResultOfMethodCallIgnored"}) +690 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis) throws InterruptedException { +691 error.expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected)); +692 if (error.value() == null) { +693 return env.flopAndFail(String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected)); +694 } else if (expected.isInstance(error.value())) { +695 return (E) error.value(); +696 } else { +697 return env.flopAndFail(String.format("Subscriber %s called `registerOnError(%s)` rather than `registerOnError(%s)`", sub(), error.value(), expected)); +698 } +699 } +700 +701 public void expectError(Throwable expected) throws InterruptedException { +702 expectError(expected, env.defaultTimeoutMillis()); +703 } +704 +705 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +706 public void expectError(Throwable expected, long timeoutMillis) throws InterruptedException { +707 error.expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected)); +708 if (error.value() != expected) { +709 env.flop(String.format("Subscriber %s called `registerOnError(%s)` rather than `registerOnError(%s)`", sub(), error.value(), expected)); +710 } +711 } +712 +713 public void expectNone() throws InterruptedException { +714 expectNone(env.defaultTimeoutMillis()); +715 } +716 +717 public void expectNone(long withinMillis) throws InterruptedException { +718 elements.expectNone(withinMillis, "Expected nothing"); +719 } +720 +721 } +722 +723 public static class WhiteboxSubscriberProbe<T> extends BlackboxProbe<T> implements SubscriberPuppeteer { +724 protected Promise<SubscriberPuppet> puppet; +725 +726 public WhiteboxSubscriberProbe(TestEnvironment env, Promise<Subscriber<? super T>> subscriber) { +727 super(env, subscriber); +728 puppet = new Promise<SubscriberPuppet>(env); +729 } +730 +731 private SubscriberPuppet puppet() { +732 return puppet.value(); +733 } +734 +735 @Override +736 public void registerOnSubscribe(SubscriberPuppet p) { +737 if (!puppet.isCompleted()) { +738 puppet.complete(p); +739 } +740 } +741 +742 } +743 +744 public interface SubscriberPuppeteer { +745 +746 /** +747 * Must be called by the test subscriber when it has successfully registered a subscription +748 * inside the `onSubscribe` method. +749 */ +750 void registerOnSubscribe(SubscriberPuppet puppet); +751 } +752 +753 public interface SubscriberProbe<T> { +754 +755 /** +756 * Must be called by the test subscriber when it has received an`onNext` event. +757 */ +758 void registerOnNext(T element); +759 +760 /** +761 * Must be called by the test subscriber when it has received an `onComplete` event. +762 */ +763 void registerOnComplete(); +764 +765 /** +766 * Must be called by the test subscriber when it has received an `onError` event. +767 */ +768 void registerOnError(Throwable cause); +769 +770 } +771 +772 public interface SubscriberPuppet { +773 void triggerRequest(long elements); +774 +775 void signalCancel(); +776 } +777 +778 public void notVerified() { +779 throw new SkipException("Not verified using this TCK."); +780 } +781 +782 public void notVerified(String msg) { +783 throw new SkipException(msg); +784 } +785} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck; +002 +003import org.reactivestreams.Publisher; +004import org.reactivestreams.Subscriber; +005import org.reactivestreams.Subscription; +006import org.reactivestreams.tck.support.SubscriberBufferOverflowException; +007import org.reactivestreams.tck.support.Optional; +008 +009import java.util.LinkedList; +010import java.util.List; +011import java.util.concurrent.ArrayBlockingQueue; +012import java.util.concurrent.CopyOnWriteArrayList; +013import java.util.concurrent.CountDownLatch; +014import java.util.concurrent.TimeUnit; +015 +016import static org.testng.Assert.assertTrue; +017import static org.testng.Assert.fail; +018 +019public class TestEnvironment { +020 public static final int TEST_BUFFER_SIZE = 16; +021 +022 private static final String DEFAULT_TIMEOUT_MILLIS_ENV = "DEFAULT_TIMEOUT_MILLIS"; +023 private static final long DEFAULT_TIMEOUT_MILLIS = 100; +024 +025 private final long defaultTimeoutMillis; +026 private final boolean printlnDebug; +027 +028 private CopyOnWriteArrayList<Throwable> asyncErrors = new CopyOnWriteArrayList<Throwable>(); +029 +030 /** +031 * Tests must specify the timeout for expected outcome of asynchronous +032 * interactions. Longer timeout does not invalidate the correctness of +033 * the implementation, but can in some cases result in longer time to +034 * run the tests. +035 * +036 * @param defaultTimeoutMillis default timeout to be used in all expect* methods +037 * @param printlnDebug if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output, +038 * often helpful to pinpoint simple race conditions etc. +039 */ +040 public TestEnvironment(long defaultTimeoutMillis, boolean printlnDebug) { +041 this.defaultTimeoutMillis = defaultTimeoutMillis; +042 this.printlnDebug = printlnDebug; +043 } +044 +045 /** +046 * Tests must specify the timeout for expected outcome of asynchronous +047 * interactions. Longer timeout does not invalidate the correctness of +048 * the implementation, but can in some cases result in longer time to +049 * run the tests. +050 * +051 * @param defaultTimeoutMillis default timeout to be used in all expect* methods +052 */ +053 public TestEnvironment(long defaultTimeoutMillis) { +054 this(defaultTimeoutMillis, false); +055 } +056 +057 /** +058 * Tests must specify the timeout for expected outcome of asynchronous +059 * interactions. Longer timeout does not invalidate the correctness of +060 * the implementation, but can in some cases result in longer time to +061 * run the tests. +062 * +063 * The default timeout for all expect* methods will be obtained by either the env variable {@code DEFAULT_TIMEOUT_MILLIS} +064 * or the default value ({@link TestEnvironment#DEFAULT_TIMEOUT_MILLIS}) will be used. +065 * +066 * @param printlnDebug if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output, +067 * often helpful to pinpoint simple race conditions etc. +068 */ +069 public TestEnvironment(boolean printlnDebug) { +070 this(envDefaultTimeoutMillis(), printlnDebug); +071 } +072 +073 /** +074 * Tests must specify the timeout for expected outcome of asynchronous +075 * interactions. Longer timeout does not invalidate the correctness of +076 * the implementation, but can in some cases result in longer time to +077 * run the tests. +078 * +079 * The default timeout for all expect* methods will be obtained by either the env variable {@code DEFAULT_TIMEOUT_MILLIS} +080 * or the default value ({@link TestEnvironment#DEFAULT_TIMEOUT_MILLIS}) will be used. +081 */ +082 public TestEnvironment() { +083 this(envDefaultTimeoutMillis()); +084 } +085 +086 public long defaultTimeoutMillis() { +087 return defaultTimeoutMillis; +088 } +089 +090 /** +091 * Tries to parse the env variable {@code DEFAULT_TIMEOUT_MILLIS} as long and returns the value if present OR its default value. +092 * +093 * @throws java.lang.IllegalArgumentException when unable to parse the env variable +094 */ +095 public static long envDefaultTimeoutMillis() { +096 final String envMillis = System.getenv(DEFAULT_TIMEOUT_MILLIS_ENV); +097 if (envMillis == null) return DEFAULT_TIMEOUT_MILLIS; +098 else try { +099 return Long.parseLong(envMillis); +100 } catch(NumberFormatException ex) { +101 throw new IllegalArgumentException(String.format("Unable to parse %s env value [%s] as long!", DEFAULT_TIMEOUT_MILLIS_ENV, envMillis), ex); +102 } +103 } +104 +105 /** +106 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +107 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +108 * +109 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +110 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +111 * from the environment using {@code env.dropAsyncError()}. +112 * +113 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +114 */ +115 public void flop(String msg) { +116 try { +117 fail(msg); +118 } catch (Throwable t) { +119 asyncErrors.add(t); +120 } +121 } +122 +123 /** +124 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +125 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +126 * +127 * This overload keeps the passed in throwable as the asyncError, instead of creating an AssertionError for this. +128 * +129 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +130 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +131 * from the environment using {@code env.dropAsyncError()}. +132 * +133 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +134 */ +135 public void flop(Throwable thr, String msg) { +136 try { +137 fail(msg, thr); +138 } catch (Throwable t) { +139 asyncErrors.add(thr); +140 } +141 } +142 +143 /** +144 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +145 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +146 * +147 * This overload keeps the passed in throwable as the asyncError, instead of creating an AssertionError for this. +148 * +149 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +150 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +151 * from the environment using {@code env.dropAsyncError()}. +152 * +153 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +154 */ +155 public void flop(Throwable thr) { +156 try { +157 fail(thr.getMessage(), thr); +158 } catch (Throwable t) { +159 asyncErrors.add(thr); +160 } +161 } +162 +163 /** +164 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +165 * +166 * This method DOES fail the test right away (it tries to, by throwing an AssertionException), +167 * in such it is different from {@link org.reactivestreams.tck.TestEnvironment#flop} which only records the error. +168 * +169 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +170 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +171 * from the environment using {@code env.dropAsyncError()}. +172 * +173 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +174 */ +175 public <T> T flopAndFail(String msg) { +176 try { +177 fail(msg); +178 } catch (Throwable t) { +179 asyncErrors.add(t); +180 fail(msg, t); +181 } +182 return null; // unreachable, the previous block will always exit by throwing +183 } +184 +185 +186 +187 public <T> void subscribe(Publisher<T> pub, TestSubscriber<T> sub) throws InterruptedException { +188 subscribe(pub, sub, defaultTimeoutMillis); +189 } +190 +191 public <T> void subscribe(Publisher<T> pub, TestSubscriber<T> sub, long timeoutMillis) throws InterruptedException { +192 pub.subscribe(sub); +193 sub.subscription.expectCompletion(timeoutMillis, String.format("Could not subscribe %s to Publisher %s", sub, pub)); +194 verifyNoAsyncErrorsNoDelay(); +195 } +196 +197 public <T> ManualSubscriber<T> newBlackholeSubscriber(Publisher<T> pub) throws InterruptedException { +198 ManualSubscriberWithSubscriptionSupport<T> sub = new BlackholeSubscriberWithSubscriptionSupport<T>(this); +199 subscribe(pub, sub, defaultTimeoutMillis()); +200 return sub; +201 } +202 +203 public <T> ManualSubscriber<T> newManualSubscriber(Publisher<T> pub) throws InterruptedException { +204 return newManualSubscriber(pub, defaultTimeoutMillis()); +205 } +206 +207 public <T> ManualSubscriber<T> newManualSubscriber(Publisher<T> pub, long timeoutMillis) throws InterruptedException { +208 ManualSubscriberWithSubscriptionSupport<T> sub = new ManualSubscriberWithSubscriptionSupport<T>(this); +209 subscribe(pub, sub, timeoutMillis); +210 return sub; +211 } +212 +213 public void clearAsyncErrors() { +214 asyncErrors.clear(); +215 } +216 +217 public Throwable dropAsyncError() { +218 try { +219 return asyncErrors.remove(0); +220 } catch (IndexOutOfBoundsException ex) { +221 return null; +222 } +223 } +224 +225 /** +226 * Waits for {@link TestEnvironment#defaultTimeoutMillis()} and then verifies that no asynchronous errors +227 * were signalled pior to, or during that time (by calling {@code flop()}). +228 */ +229 public void verifyNoAsyncErrors() { +230 verifyNoAsyncErrors(defaultTimeoutMillis()); +231 } +232 +233 /** +234 * This version of {@code verifyNoAsyncErrors} should be used when errors still could be signalled +235 * asynchronously during {@link TestEnvironment#defaultTimeoutMillis()} time. +236 * <p></p> +237 * It will immediatly check if any async errors were signaled (using {@link TestEnvironment#flop(String)}, +238 * and if no errors encountered wait for another default timeout as the errors may yet be signalled. +239 * The initial check is performed in order to fail-fast in case of an already failed test. +240 */ +241 public void verifyNoAsyncErrors(long delay) { +242 try { +243 verifyNoAsyncErrorsNoDelay(); +244 +245 Thread.sleep(delay); +246 verifyNoAsyncErrorsNoDelay(); +247 } catch (InterruptedException e) { +248 throw new RuntimeException(e); +249 } +250 } +251 +252 /** +253 * Verifies that no asynchronous errors were signalled pior to calling this method (by calling {@code flop()}). +254 * This version of verifyNoAsyncError <b>does not wait before checking for asynchronous errors</b>, and is to be used +255 * for example in tight loops etc. +256 */ +257 public void verifyNoAsyncErrorsNoDelay() { +258 for (Throwable e : asyncErrors) { +259 if (e instanceof AssertionError) { +260 throw (AssertionError) e; +261 } else { +262 fail(String.format("Async error during test execution: %s", e.getMessage()), e); +263 } +264 } +265 } +266 +267 /** If {@code TestEnvironment#printlnDebug} is true, print debug message to std out. */ +268 public void debug(String msg) { +269 if (printlnDebug) +270 System.out.printf("[TCK-DEBUG] %s%n", msg); +271 } +272 +273 /** +274 * Looks for given {@code method} method in stack trace. +275 * Can be used to answer questions like "was this method called from onComplete?". +276 * +277 * @return the caller's StackTraceElement at which he the looked for method was found in the call stack, EMPTY otherwise +278 */ +279 public Optional<StackTraceElement> findCallerMethodInStackTrace(String method) { +280 final Throwable thr = new Throwable(); // gets the stacktrace +281 +282 for (StackTraceElement stackElement : thr.getStackTrace()) { +283 if (stackElement.getMethodName().equals(method)) { +284 return Optional.of(stackElement); +285 } +286 } +287 return Optional.empty(); +288 } +289 +290 // ---- classes ---- +291 +292 /** +293 * {@link Subscriber} implementation which can be steered by test code and asserted on. +294 */ +295 public static class ManualSubscriber<T> extends TestSubscriber<T> { +296 Receptacle<T> received; +297 +298 public ManualSubscriber(TestEnvironment env) { +299 super(env); +300 received = new Receptacle<T>(this.env); +301 } +302 +303 @Override +304 public void onNext(T element) { +305 try { +306 received.add(element); +307 } catch (IllegalStateException ex) { +308 // error message refinement +309 throw new SubscriberBufferOverflowException( +310 String.format("Received more than bufferSize (%d) onNext signals. " + +311 "The Publisher probably emited more signals than expected!", +312 received.QUEUE_SIZE), ex); +313 } +314 } +315 +316 @Override +317 public void onComplete() { +318 received.complete(); +319 } +320 +321 public void request(long elements) { +322 subscription.value().request(elements); +323 } +324 +325 public T requestNextElement() throws InterruptedException { +326 return requestNextElement(env.defaultTimeoutMillis()); +327 } +328 +329 public T requestNextElement(long timeoutMillis) throws InterruptedException { +330 return requestNextElement(timeoutMillis, "Did not receive expected element"); +331 } +332 +333 public T requestNextElement(String errorMsg) throws InterruptedException { +334 return requestNextElement(env.defaultTimeoutMillis(), errorMsg); +335 } +336 +337 public T requestNextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +338 request(1); +339 return nextElement(timeoutMillis, errorMsg); +340 } +341 +342 public Optional<T> requestNextElementOrEndOfStream(String errorMsg) throws InterruptedException { +343 return requestNextElementOrEndOfStream(env.defaultTimeoutMillis(), errorMsg); +344 } +345 +346 public Optional<T> requestNextElementOrEndOfStream(long timeoutMillis) throws InterruptedException { +347 return requestNextElementOrEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +348 } +349 +350 public Optional<T> requestNextElementOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +351 request(1); +352 return nextElementOrEndOfStream(timeoutMillis, errorMsg); +353 } +354 +355 public void requestEndOfStream() throws InterruptedException { +356 requestEndOfStream(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +357 } +358 +359 public void requestEndOfStream(long timeoutMillis) throws InterruptedException { +360 requestEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +361 } +362 +363 public void requestEndOfStream(String errorMsg) throws InterruptedException { +364 requestEndOfStream(env.defaultTimeoutMillis(), errorMsg); +365 } +366 +367 public void requestEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +368 request(1); +369 expectCompletion(timeoutMillis, errorMsg); +370 } +371 +372 public List<T> requestNextElements(long elements) throws InterruptedException { +373 request(elements); +374 return nextElements(elements, env.defaultTimeoutMillis()); +375 } +376 +377 public List<T> requestNextElements(long elements, long timeoutMillis) throws InterruptedException { +378 request(elements); +379 return nextElements(elements, timeoutMillis, String.format("Did not receive %d expected elements", elements)); +380 } +381 +382 public List<T> requestNextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +383 request(elements); +384 return nextElements(elements, timeoutMillis, errorMsg); +385 } +386 +387 public T nextElement() throws InterruptedException { +388 return nextElement(env.defaultTimeoutMillis()); +389 } +390 +391 public T nextElement(long timeoutMillis) throws InterruptedException { +392 return nextElement(timeoutMillis, "Did not receive expected element"); +393 } +394 +395 public T nextElement(String errorMsg) throws InterruptedException { +396 return nextElement(env.defaultTimeoutMillis(), errorMsg); +397 } +398 +399 public T nextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +400 return received.next(timeoutMillis, errorMsg); +401 } +402 +403 public Optional<T> nextElementOrEndOfStream() throws InterruptedException { +404 return nextElementOrEndOfStream(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +405 } +406 +407 public Optional<T> nextElementOrEndOfStream(long timeoutMillis) throws InterruptedException { +408 return nextElementOrEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +409 } +410 +411 public Optional<T> nextElementOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +412 return received.nextOrEndOfStream(timeoutMillis, errorMsg); +413 } +414 +415 public List<T> nextElements(long elements) throws InterruptedException { +416 return nextElements(elements, env.defaultTimeoutMillis(), "Did not receive expected element or completion"); +417 } +418 +419 public List<T> nextElements(long elements, String errorMsg) throws InterruptedException { +420 return nextElements(elements, env.defaultTimeoutMillis(), errorMsg); +421 } +422 +423 public List<T> nextElements(long elements, long timeoutMillis) throws InterruptedException { +424 return nextElements(elements, timeoutMillis, "Did not receive expected element or completion"); +425 } +426 +427 public List<T> nextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +428 return received.nextN(elements, timeoutMillis, errorMsg); +429 } +430 +431 public void expectNext(T expected) throws InterruptedException { +432 expectNext(expected, env.defaultTimeoutMillis()); +433 } +434 +435 public void expectNext(T expected, long timeoutMillis) throws InterruptedException { +436 T received = nextElement(timeoutMillis, "Did not receive expected element on downstream"); +437 if (!received.equals(expected)) { +438 env.flop(String.format("Expected element %s on downstream but received %s", expected, received)); +439 } +440 } +441 +442 public void expectCompletion() throws InterruptedException { +443 expectCompletion(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +444 } +445 +446 public void expectCompletion(long timeoutMillis) throws InterruptedException { +447 expectCompletion(timeoutMillis, "Did not receive expected stream completion"); +448 } +449 +450 public void expectCompletion(String errorMsg) throws InterruptedException { +451 expectCompletion(env.defaultTimeoutMillis(), errorMsg); +452 } +453 +454 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +455 received.expectCompletion(timeoutMillis, errorMsg); +456 } +457 +458 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart) throws Exception { +459 expectErrorWithMessage(expected, requiredMessagePart, env.defaultTimeoutMillis()); +460 } +461 +462 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +463 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart, long timeoutMillis) throws Exception { +464 final E err = expectError(expected, timeoutMillis); +465 final String message = err.getMessage(); +466 assertTrue(message.contains(requiredMessagePart), +467 String.format("Got expected exception [%s] but missing message part [%s], was: %s", +468 err.getClass(), requiredMessagePart, err.getMessage())); +469 } +470 +471 public <E extends Throwable> E expectError(Class<E> expected) throws Exception { +472 return expectError(expected, env.defaultTimeoutMillis()); +473 } +474 +475 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis) throws Exception { +476 return expectError(expected, timeoutMillis, String.format("Expected onError(%s)", expected.getName())); +477 } +478 +479 public <E extends Throwable> E expectError(Class<E> expected, String errorMsg) throws Exception { +480 return expectError(expected, env.defaultTimeoutMillis(), errorMsg); +481 } +482 +483 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis, String errorMsg) throws Exception { +484 return received.expectError(expected, timeoutMillis, errorMsg); +485 } +486 +487 public void expectNone() throws InterruptedException { +488 expectNone(env.defaultTimeoutMillis()); +489 } +490 +491 public void expectNone(String errMsgPrefix) throws InterruptedException { +492 expectNone(env.defaultTimeoutMillis(), errMsgPrefix); +493 } +494 +495 public void expectNone(long withinMillis) throws InterruptedException { +496 expectNone(withinMillis, "Did not expect an element but got element"); +497 } +498 +499 public void expectNone(long withinMillis, String errMsgPrefix) throws InterruptedException { +500 received.expectNone(withinMillis, errMsgPrefix); +501 } +502 +503 } +504 +505 public static class ManualSubscriberWithSubscriptionSupport<T> extends ManualSubscriber<T> { +506 +507 public ManualSubscriberWithSubscriptionSupport(TestEnvironment env) { +508 super(env); +509 } +510 +511 @Override +512 public void onNext(T element) { +513 env.debug(String.format("%s::onNext(%s)", this, element)); +514 if (subscription.isCompleted()) { +515 super.onNext(element); +516 } else { +517 env.flop(String.format("Subscriber::onNext(%s) called before Subscriber::onSubscribe", element)); +518 } +519 } +520 +521 @Override +522 public void onComplete() { +523 env.debug(this + "::onComplete()"); +524 if (subscription.isCompleted()) { +525 super.onComplete(); +526 } else { +527 env.flop("Subscriber::onComplete() called before Subscriber::onSubscribe"); +528 } +529 } +530 +531 @Override +532 public void onSubscribe(Subscription s) { +533 env.debug(String.format("%s::onSubscribe(%s)", this, s)); +534 if (!subscription.isCompleted()) { +535 subscription.complete(s); +536 } else { +537 env.flop("Subscriber::onSubscribe called on an already-subscribed Subscriber"); +538 } +539 } +540 +541 @Override +542 public void onError(Throwable cause) { +543 env.debug(String.format("%s::onError(%s)", this, cause)); +544 if (subscription.isCompleted()) { +545 super.onError(cause); +546 } else { +547 env.flop(cause, String.format("Subscriber::onError(%s) called before Subscriber::onSubscribe", cause)); +548 } +549 } +550 } +551 +552 /** +553 * Similar to {@link org.reactivestreams.tck.TestEnvironment.ManualSubscriberWithSubscriptionSupport} +554 * but does not accumulate values signalled via <code>onNext</code>, thus it can not be used to assert +555 * values signalled to this subscriber. Instead it may be used to quickly drain a given publisher. +556 */ +557 public static class BlackholeSubscriberWithSubscriptionSupport<T> +558 extends ManualSubscriberWithSubscriptionSupport<T> { +559 +560 public BlackholeSubscriberWithSubscriptionSupport(TestEnvironment env) { +561 super(env); +562 } +563 +564 @Override +565 public void onNext(T element) { +566 env.debug(String.format("%s::onNext(%s)", this, element)); +567 if (!subscription.isCompleted()) { +568 env.flop(String.format("Subscriber::onNext(%s) called before Subscriber::onSubscribe", element)); +569 } +570 } +571 +572 @Override +573 public T nextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +574 throw new RuntimeException("Can not expect elements from BlackholeSubscriber, use ManualSubscriber instead!"); +575 } +576 +577 @Override +578 public List<T> nextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +579 throw new RuntimeException("Can not expect elements from BlackholeSubscriber, use ManualSubscriber instead!"); +580 } +581 } +582 +583 public static class TestSubscriber<T> implements Subscriber<T> { +584 final Promise<Subscription> subscription; +585 +586 protected final TestEnvironment env; +587 +588 public TestSubscriber(TestEnvironment env) { +589 this.env = env; +590 subscription = new Promise<Subscription>(env); +591 } +592 +593 @Override +594 public void onError(Throwable cause) { +595 env.flop(cause, String.format("Unexpected Subscriber::onError(%s)", cause)); +596 } +597 +598 @Override +599 public void onComplete() { +600 env.flop("Unexpected Subscriber::onComplete()"); +601 } +602 +603 @Override +604 public void onNext(T element) { +605 env.flop(String.format("Unexpected Subscriber::onNext(%s)", element)); +606 } +607 +608 @Override +609 public void onSubscribe(Subscription subscription) { +610 env.flop(String.format("Unexpected Subscriber::onSubscribe(%s)", subscription)); +611 } +612 +613 public void cancel() { +614 if (subscription.isCompleted()) { +615 subscription.value().cancel(); +616 } else { +617 env.flop("Cannot cancel a subscription before having received it"); +618 } +619 } +620 } +621 +622 public static class ManualPublisher<T> implements Publisher<T> { +623 protected final TestEnvironment env; +624 +625 protected long pendingDemand = 0L; +626 protected Promise<Subscriber<? super T>> subscriber; +627 +628 protected final Receptacle<Long> requests; +629 +630 protected final Latch cancelled; +631 +632 public ManualPublisher(TestEnvironment env) { +633 this.env = env; +634 requests = new Receptacle<Long>(env); +635 cancelled = new Latch(env); +636 subscriber = new Promise<Subscriber<? super T>>(this.env); +637 } +638 +639 @Override +640 public void subscribe(Subscriber<? super T> s) { +641 if (!subscriber.isCompleted()) { +642 subscriber.completeImmediatly(s); +643 +644 Subscription subs = new Subscription() { +645 @Override +646 public void request(long elements) { +647 requests.add(elements); +648 } +649 +650 @Override +651 public void cancel() { +652 cancelled.close(); +653 } +654 }; +655 s.onSubscribe(subs); +656 +657 } else { +658 env.flop("TestPublisher doesn't support more than one Subscriber"); +659 } +660 } +661 +662 public void sendNext(T element) { +663 if (subscriber.isCompleted()) { +664 subscriber.value().onNext(element); +665 } else { +666 env.flop("Cannot sendNext before having a Subscriber"); +667 } +668 } +669 +670 public void sendCompletion() { +671 if (subscriber.isCompleted()) { +672 subscriber.value().onComplete(); +673 } else { +674 env.flop("Cannot sendCompletion before having a Subscriber"); +675 } +676 } +677 +678 public void sendError(Throwable cause) { +679 if (subscriber.isCompleted()) { +680 subscriber.value().onError(cause); +681 } else { +682 env.flop("Cannot sendError before having a Subscriber"); +683 } +684 } +685 +686 public long expectRequest() throws InterruptedException { +687 return expectRequest(env.defaultTimeoutMillis()); +688 } +689 +690 public long expectRequest(long timeoutMillis) throws InterruptedException { +691 long requested = requests.next(timeoutMillis, "Did not receive expected `request` call"); +692 if (requested <= 0) { +693 return env.<Long>flopAndFail(String.format("Requests cannot be zero or negative but received request(%s)", requested)); +694 } else { +695 pendingDemand += requested; +696 return requested; +697 } +698 } +699 +700 public void expectExactRequest(long expected) throws InterruptedException { +701 expectExactRequest(expected, env.defaultTimeoutMillis()); +702 } +703 +704 public void expectExactRequest(long expected, long timeoutMillis) throws InterruptedException { +705 long requested = expectRequest(timeoutMillis); +706 if (requested != expected) { +707 env.flop(String.format("Received `request(%d)` on upstream but expected `request(%d)`", requested, expected)); +708 } +709 pendingDemand += requested; +710 } +711 +712 public void expectNoRequest() throws InterruptedException { +713 expectNoRequest(env.defaultTimeoutMillis()); +714 } +715 +716 public void expectNoRequest(long timeoutMillis) throws InterruptedException { +717 requests.expectNone(timeoutMillis, "Received an unexpected call to: request: "); +718 } +719 +720 public void expectCancelling() throws InterruptedException { +721 expectCancelling(env.defaultTimeoutMillis()); +722 } +723 +724 public void expectCancelling(long timeoutMillis) throws InterruptedException { +725 cancelled.expectClose(timeoutMillis, "Did not receive expected cancelling of upstream subscription"); +726 } +727 } +728 +729 /** +730 * Like a CountDownLatch, but resettable and with some convenience methods +731 */ +732 public static class Latch { +733 private final TestEnvironment env; +734 volatile private CountDownLatch countDownLatch = new CountDownLatch(1); +735 +736 public Latch(TestEnvironment env) { +737 this.env = env; +738 } +739 +740 public void reOpen() { +741 countDownLatch = new CountDownLatch(1); +742 } +743 +744 public boolean isClosed() { +745 return countDownLatch.getCount() == 0; +746 } +747 +748 public void close() { +749 countDownLatch.countDown(); +750 } +751 +752 public void assertClosed(String openErrorMsg) { +753 if (!isClosed()) { +754 env.flop(new ExpectedClosedLatchException(openErrorMsg)); +755 } +756 } +757 +758 public void assertOpen(String closedErrorMsg) { +759 if (isClosed()) { +760 env.flop(new ExpectedOpenLatchException(closedErrorMsg)); +761 } +762 } +763 +764 public void expectClose(String notClosedErrorMsg) throws InterruptedException { +765 expectClose(env.defaultTimeoutMillis(), notClosedErrorMsg); +766 } +767 +768 public void expectClose(long timeoutMillis, String notClosedErrorMsg) throws InterruptedException { +769 countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS); +770 if (countDownLatch.getCount() > 0) { +771 env.flop(String.format("%s within %d ms", notClosedErrorMsg, timeoutMillis)); +772 } +773 } +774 +775 static final class ExpectedOpenLatchException extends RuntimeException { +776 public ExpectedOpenLatchException(String message) { +777 super(message); +778 } +779 } +780 +781 static final class ExpectedClosedLatchException extends RuntimeException { +782 public ExpectedClosedLatchException(String message) { +783 super(message); +784 } +785 } +786 +787 } +788 +789 // simple promise for *one* value, which cannot be reset +790 public static class Promise<T> { +791 private final TestEnvironment env; +792 +793 public static <T> Promise<T> completed(TestEnvironment env, T value) { +794 Promise<T> promise = new Promise<T>(env); +795 promise.completeImmediatly(value); +796 return promise; +797 } +798 +799 public Promise(TestEnvironment env) { +800 this.env = env; +801 } +802 +803 private ArrayBlockingQueue<T> abq = new ArrayBlockingQueue<T>(1); +804 private volatile T _value = null; +805 +806 public T value() { +807 if (isCompleted()) { +808 return _value; +809 } else { +810 env.flop("Cannot access promise value before completion"); +811 return null; +812 } +813 } +814 +815 public boolean isCompleted() { +816 return _value != null; +817 } +818 +819 /** +820 * Allows using expectCompletion to await for completion of the value and complete it _then_ +821 */ +822 public void complete(T value) { +823 abq.add(value); +824 } +825 +826 /** +827 * Completes the promise right away, it is not possible to expectCompletion on a Promise completed this way +828 */ +829 public void completeImmediatly(T value) { +830 complete(value); // complete! +831 _value = value; // immediatly! +832 } +833 +834 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +835 if (!isCompleted()) { +836 T val = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +837 +838 if (val == null) { +839 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +840 } else { +841 _value = val; +842 } +843 } +844 } +845 } +846 +847 // a "Promise" for multiple values, which also supports "end-of-stream reached" +848 public static class Receptacle<T> { +849 final int QUEUE_SIZE = 2 * TEST_BUFFER_SIZE; +850 private final TestEnvironment env; +851 +852 private final ArrayBlockingQueue<Optional<T>> abq = new ArrayBlockingQueue<Optional<T>>(QUEUE_SIZE); +853 +854 private final Latch completedLatch; +855 +856 Receptacle(TestEnvironment env) { +857 this.env = env; +858 this.completedLatch = new Latch(env); +859 } +860 +861 public void add(T value) { +862 completedLatch.assertOpen(String.format("Unexpected element %s received after stream completed", value)); +863 +864 abq.add(Optional.of(value)); +865 } +866 +867 public void complete() { +868 completedLatch.assertOpen("Unexpected additional complete signal received!"); +869 completedLatch.close(); +870 +871 abq.add(Optional.<T>empty()); +872 } +873 +874 public T next(long timeoutMillis, String errorMsg) throws InterruptedException { +875 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +876 +877 if (value == null) { +878 return env.flopAndFail(String.format("%s within %d ms", errorMsg, timeoutMillis)); +879 } else if (value.isDefined()) { +880 return value.get(); +881 } else { +882 return env.flopAndFail("Expected element but got end-of-stream"); +883 } +884 } +885 +886 public Optional<T> nextOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +887 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +888 +889 if (value == null) { +890 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +891 return Optional.empty(); +892 } +893 +894 return value; +895 } +896 +897 /** +898 * @param timeoutMillis total timeout time for awaiting all {@code elements} number of elements +899 */ +900 public List<T> nextN(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +901 List<T> result = new LinkedList<T>(); +902 long remaining = elements; +903 long deadline = System.currentTimeMillis() + timeoutMillis; +904 while (remaining > 0) { +905 long remainingMillis = deadline - System.currentTimeMillis(); +906 +907 result.add(next(remainingMillis, errorMsg)); +908 remaining--; +909 } +910 +911 return result; +912 } +913 +914 +915 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +916 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +917 +918 if (value == null) { +919 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +920 } else if (value.isDefined()) { +921 env.flop(String.format("Expected end-of-stream but got element [%s]", value.get())); +922 } // else, ok +923 } +924 +925 @SuppressWarnings("unchecked") +926 public <E extends Throwable> E expectError(Class<E> clazz, long timeoutMillis, String errorMsg) throws Exception { +927 Thread.sleep(timeoutMillis); +928 +929 if (env.asyncErrors.isEmpty()) { +930 return env.flopAndFail(String.format("%s within %d ms", errorMsg, timeoutMillis)); +931 } else { +932 // ok, there was an expected error +933 Throwable thrown = env.asyncErrors.remove(0); +934 +935 if (clazz.isInstance(thrown)) { +936 return (E) thrown; +937 } else { +938 +939 return env.flopAndFail(String.format("%s within %d ms; Got %s but expected %s", +940 errorMsg, timeoutMillis, thrown.getClass().getCanonicalName(), clazz.getCanonicalName())); +941 } +942 } +943 } +944 +945 public void expectNone(long withinMillis, String errorMsgPrefix) throws InterruptedException { +946 Thread.sleep(withinMillis); +947 Optional<T> value = abq.poll(); +948 +949 if (value == null) { +950 // ok +951 } else if (value.isDefined()) { +952 env.flop(String.format("%s [%s]", errorMsgPrefix, value.get())); +953 } else { +954 env.flop("Expected no element but got end-of-stream"); +955 } +956 } +957 } +958} +959 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck; +002 +003import org.reactivestreams.Publisher; +004import org.reactivestreams.Subscriber; +005import org.reactivestreams.Subscription; +006import org.reactivestreams.tck.support.SubscriberBufferOverflowException; +007import org.reactivestreams.tck.support.Optional; +008 +009import java.util.LinkedList; +010import java.util.List; +011import java.util.concurrent.ArrayBlockingQueue; +012import java.util.concurrent.CopyOnWriteArrayList; +013import java.util.concurrent.CountDownLatch; +014import java.util.concurrent.TimeUnit; +015 +016import static org.testng.Assert.assertTrue; +017import static org.testng.Assert.fail; +018 +019public class TestEnvironment { +020 public static final int TEST_BUFFER_SIZE = 16; +021 +022 private static final String DEFAULT_TIMEOUT_MILLIS_ENV = "DEFAULT_TIMEOUT_MILLIS"; +023 private static final long DEFAULT_TIMEOUT_MILLIS = 100; +024 +025 private final long defaultTimeoutMillis; +026 private final boolean printlnDebug; +027 +028 private CopyOnWriteArrayList<Throwable> asyncErrors = new CopyOnWriteArrayList<Throwable>(); +029 +030 /** +031 * Tests must specify the timeout for expected outcome of asynchronous +032 * interactions. Longer timeout does not invalidate the correctness of +033 * the implementation, but can in some cases result in longer time to +034 * run the tests. +035 * +036 * @param defaultTimeoutMillis default timeout to be used in all expect* methods +037 * @param printlnDebug if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output, +038 * often helpful to pinpoint simple race conditions etc. +039 */ +040 public TestEnvironment(long defaultTimeoutMillis, boolean printlnDebug) { +041 this.defaultTimeoutMillis = defaultTimeoutMillis; +042 this.printlnDebug = printlnDebug; +043 } +044 +045 /** +046 * Tests must specify the timeout for expected outcome of asynchronous +047 * interactions. Longer timeout does not invalidate the correctness of +048 * the implementation, but can in some cases result in longer time to +049 * run the tests. +050 * +051 * @param defaultTimeoutMillis default timeout to be used in all expect* methods +052 */ +053 public TestEnvironment(long defaultTimeoutMillis) { +054 this(defaultTimeoutMillis, false); +055 } +056 +057 /** +058 * Tests must specify the timeout for expected outcome of asynchronous +059 * interactions. Longer timeout does not invalidate the correctness of +060 * the implementation, but can in some cases result in longer time to +061 * run the tests. +062 * +063 * The default timeout for all expect* methods will be obtained by either the env variable {@code DEFAULT_TIMEOUT_MILLIS} +064 * or the default value ({@link TestEnvironment#DEFAULT_TIMEOUT_MILLIS}) will be used. +065 * +066 * @param printlnDebug if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output, +067 * often helpful to pinpoint simple race conditions etc. +068 */ +069 public TestEnvironment(boolean printlnDebug) { +070 this(envDefaultTimeoutMillis(), printlnDebug); +071 } +072 +073 /** +074 * Tests must specify the timeout for expected outcome of asynchronous +075 * interactions. Longer timeout does not invalidate the correctness of +076 * the implementation, but can in some cases result in longer time to +077 * run the tests. +078 * +079 * The default timeout for all expect* methods will be obtained by either the env variable {@code DEFAULT_TIMEOUT_MILLIS} +080 * or the default value ({@link TestEnvironment#DEFAULT_TIMEOUT_MILLIS}) will be used. +081 */ +082 public TestEnvironment() { +083 this(envDefaultTimeoutMillis()); +084 } +085 +086 public long defaultTimeoutMillis() { +087 return defaultTimeoutMillis; +088 } +089 +090 /** +091 * Tries to parse the env variable {@code DEFAULT_TIMEOUT_MILLIS} as long and returns the value if present OR its default value. +092 * +093 * @throws java.lang.IllegalArgumentException when unable to parse the env variable +094 */ +095 public static long envDefaultTimeoutMillis() { +096 final String envMillis = System.getenv(DEFAULT_TIMEOUT_MILLIS_ENV); +097 if (envMillis == null) return DEFAULT_TIMEOUT_MILLIS; +098 else try { +099 return Long.parseLong(envMillis); +100 } catch(NumberFormatException ex) { +101 throw new IllegalArgumentException(String.format("Unable to parse %s env value [%s] as long!", DEFAULT_TIMEOUT_MILLIS_ENV, envMillis), ex); +102 } +103 } +104 +105 /** +106 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +107 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +108 * +109 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +110 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +111 * from the environment using {@code env.dropAsyncError()}. +112 * +113 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +114 */ +115 public void flop(String msg) { +116 try { +117 fail(msg); +118 } catch (Throwable t) { +119 asyncErrors.add(t); +120 } +121 } +122 +123 /** +124 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +125 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +126 * +127 * This overload keeps the passed in throwable as the asyncError, instead of creating an AssertionError for this. +128 * +129 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +130 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +131 * from the environment using {@code env.dropAsyncError()}. +132 * +133 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +134 */ +135 public void flop(Throwable thr, String msg) { +136 try { +137 fail(msg, thr); +138 } catch (Throwable t) { +139 asyncErrors.add(thr); +140 } +141 } +142 +143 /** +144 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +145 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +146 * +147 * This overload keeps the passed in throwable as the asyncError, instead of creating an AssertionError for this. +148 * +149 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +150 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +151 * from the environment using {@code env.dropAsyncError()}. +152 * +153 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +154 */ +155 public void flop(Throwable thr) { +156 try { +157 fail(thr.getMessage(), thr); +158 } catch (Throwable t) { +159 asyncErrors.add(thr); +160 } +161 } +162 +163 /** +164 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +165 * +166 * This method DOES fail the test right away (it tries to, by throwing an AssertionException), +167 * in such it is different from {@link org.reactivestreams.tck.TestEnvironment#flop} which only records the error. +168 * +169 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +170 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +171 * from the environment using {@code env.dropAsyncError()}. +172 * +173 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +174 */ +175 public <T> T flopAndFail(String msg) { +176 try { +177 fail(msg); +178 } catch (Throwable t) { +179 asyncErrors.add(t); +180 fail(msg, t); +181 } +182 return null; // unreachable, the previous block will always exit by throwing +183 } +184 +185 +186 +187 public <T> void subscribe(Publisher<T> pub, TestSubscriber<T> sub) throws InterruptedException { +188 subscribe(pub, sub, defaultTimeoutMillis); +189 } +190 +191 public <T> void subscribe(Publisher<T> pub, TestSubscriber<T> sub, long timeoutMillis) throws InterruptedException { +192 pub.subscribe(sub); +193 sub.subscription.expectCompletion(timeoutMillis, String.format("Could not subscribe %s to Publisher %s", sub, pub)); +194 verifyNoAsyncErrorsNoDelay(); +195 } +196 +197 public <T> ManualSubscriber<T> newBlackholeSubscriber(Publisher<T> pub) throws InterruptedException { +198 ManualSubscriberWithSubscriptionSupport<T> sub = new BlackholeSubscriberWithSubscriptionSupport<T>(this); +199 subscribe(pub, sub, defaultTimeoutMillis()); +200 return sub; +201 } +202 +203 public <T> ManualSubscriber<T> newManualSubscriber(Publisher<T> pub) throws InterruptedException { +204 return newManualSubscriber(pub, defaultTimeoutMillis()); +205 } +206 +207 public <T> ManualSubscriber<T> newManualSubscriber(Publisher<T> pub, long timeoutMillis) throws InterruptedException { +208 ManualSubscriberWithSubscriptionSupport<T> sub = new ManualSubscriberWithSubscriptionSupport<T>(this); +209 subscribe(pub, sub, timeoutMillis); +210 return sub; +211 } +212 +213 public void clearAsyncErrors() { +214 asyncErrors.clear(); +215 } +216 +217 public Throwable dropAsyncError() { +218 try { +219 return asyncErrors.remove(0); +220 } catch (IndexOutOfBoundsException ex) { +221 return null; +222 } +223 } +224 +225 /** +226 * Waits for {@link TestEnvironment#defaultTimeoutMillis()} and then verifies that no asynchronous errors +227 * were signalled pior to, or during that time (by calling {@code flop()}). +228 */ +229 public void verifyNoAsyncErrors() { +230 verifyNoAsyncErrors(defaultTimeoutMillis()); +231 } +232 +233 /** +234 * This version of {@code verifyNoAsyncErrors} should be used when errors still could be signalled +235 * asynchronously during {@link TestEnvironment#defaultTimeoutMillis()} time. +236 * <p></p> +237 * It will immediatly check if any async errors were signaled (using {@link TestEnvironment#flop(String)}, +238 * and if no errors encountered wait for another default timeout as the errors may yet be signalled. +239 * The initial check is performed in order to fail-fast in case of an already failed test. +240 */ +241 public void verifyNoAsyncErrors(long delay) { +242 try { +243 verifyNoAsyncErrorsNoDelay(); +244 +245 Thread.sleep(delay); +246 verifyNoAsyncErrorsNoDelay(); +247 } catch (InterruptedException e) { +248 throw new RuntimeException(e); +249 } +250 } +251 +252 /** +253 * Verifies that no asynchronous errors were signalled pior to calling this method (by calling {@code flop()}). +254 * This version of verifyNoAsyncError <b>does not wait before checking for asynchronous errors</b>, and is to be used +255 * for example in tight loops etc. +256 */ +257 public void verifyNoAsyncErrorsNoDelay() { +258 for (Throwable e : asyncErrors) { +259 if (e instanceof AssertionError) { +260 throw (AssertionError) e; +261 } else { +262 fail(String.format("Async error during test execution: %s", e.getMessage()), e); +263 } +264 } +265 } +266 +267 /** If {@code TestEnvironment#printlnDebug} is true, print debug message to std out. */ +268 public void debug(String msg) { +269 if (printlnDebug) +270 System.out.printf("[TCK-DEBUG] %s%n", msg); +271 } +272 +273 /** +274 * Looks for given {@code method} method in stack trace. +275 * Can be used to answer questions like "was this method called from onComplete?". +276 * +277 * @return the caller's StackTraceElement at which he the looked for method was found in the call stack, EMPTY otherwise +278 */ +279 public Optional<StackTraceElement> findCallerMethodInStackTrace(String method) { +280 final Throwable thr = new Throwable(); // gets the stacktrace +281 +282 for (StackTraceElement stackElement : thr.getStackTrace()) { +283 if (stackElement.getMethodName().equals(method)) { +284 return Optional.of(stackElement); +285 } +286 } +287 return Optional.empty(); +288 } +289 +290 // ---- classes ---- +291 +292 /** +293 * {@link Subscriber} implementation which can be steered by test code and asserted on. +294 */ +295 public static class ManualSubscriber<T> extends TestSubscriber<T> { +296 Receptacle<T> received; +297 +298 public ManualSubscriber(TestEnvironment env) { +299 super(env); +300 received = new Receptacle<T>(this.env); +301 } +302 +303 @Override +304 public void onNext(T element) { +305 try { +306 received.add(element); +307 } catch (IllegalStateException ex) { +308 // error message refinement +309 throw new SubscriberBufferOverflowException( +310 String.format("Received more than bufferSize (%d) onNext signals. " + +311 "The Publisher probably emited more signals than expected!", +312 received.QUEUE_SIZE), ex); +313 } +314 } +315 +316 @Override +317 public void onComplete() { +318 received.complete(); +319 } +320 +321 public void request(long elements) { +322 subscription.value().request(elements); +323 } +324 +325 public T requestNextElement() throws InterruptedException { +326 return requestNextElement(env.defaultTimeoutMillis()); +327 } +328 +329 public T requestNextElement(long timeoutMillis) throws InterruptedException { +330 return requestNextElement(timeoutMillis, "Did not receive expected element"); +331 } +332 +333 public T requestNextElement(String errorMsg) throws InterruptedException { +334 return requestNextElement(env.defaultTimeoutMillis(), errorMsg); +335 } +336 +337 public T requestNextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +338 request(1); +339 return nextElement(timeoutMillis, errorMsg); +340 } +341 +342 public Optional<T> requestNextElementOrEndOfStream(String errorMsg) throws InterruptedException { +343 return requestNextElementOrEndOfStream(env.defaultTimeoutMillis(), errorMsg); +344 } +345 +346 public Optional<T> requestNextElementOrEndOfStream(long timeoutMillis) throws InterruptedException { +347 return requestNextElementOrEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +348 } +349 +350 public Optional<T> requestNextElementOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +351 request(1); +352 return nextElementOrEndOfStream(timeoutMillis, errorMsg); +353 } +354 +355 public void requestEndOfStream() throws InterruptedException { +356 requestEndOfStream(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +357 } +358 +359 public void requestEndOfStream(long timeoutMillis) throws InterruptedException { +360 requestEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +361 } +362 +363 public void requestEndOfStream(String errorMsg) throws InterruptedException { +364 requestEndOfStream(env.defaultTimeoutMillis(), errorMsg); +365 } +366 +367 public void requestEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +368 request(1); +369 expectCompletion(timeoutMillis, errorMsg); +370 } +371 +372 public List<T> requestNextElements(long elements) throws InterruptedException { +373 request(elements); +374 return nextElements(elements, env.defaultTimeoutMillis()); +375 } +376 +377 public List<T> requestNextElements(long elements, long timeoutMillis) throws InterruptedException { +378 request(elements); +379 return nextElements(elements, timeoutMillis, String.format("Did not receive %d expected elements", elements)); +380 } +381 +382 public List<T> requestNextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +383 request(elements); +384 return nextElements(elements, timeoutMillis, errorMsg); +385 } +386 +387 public T nextElement() throws InterruptedException { +388 return nextElement(env.defaultTimeoutMillis()); +389 } +390 +391 public T nextElement(long timeoutMillis) throws InterruptedException { +392 return nextElement(timeoutMillis, "Did not receive expected element"); +393 } +394 +395 public T nextElement(String errorMsg) throws InterruptedException { +396 return nextElement(env.defaultTimeoutMillis(), errorMsg); +397 } +398 +399 public T nextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +400 return received.next(timeoutMillis, errorMsg); +401 } +402 +403 public Optional<T> nextElementOrEndOfStream() throws InterruptedException { +404 return nextElementOrEndOfStream(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +405 } +406 +407 public Optional<T> nextElementOrEndOfStream(long timeoutMillis) throws InterruptedException { +408 return nextElementOrEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +409 } +410 +411 public Optional<T> nextElementOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +412 return received.nextOrEndOfStream(timeoutMillis, errorMsg); +413 } +414 +415 public List<T> nextElements(long elements) throws InterruptedException { +416 return nextElements(elements, env.defaultTimeoutMillis(), "Did not receive expected element or completion"); +417 } +418 +419 public List<T> nextElements(long elements, String errorMsg) throws InterruptedException { +420 return nextElements(elements, env.defaultTimeoutMillis(), errorMsg); +421 } +422 +423 public List<T> nextElements(long elements, long timeoutMillis) throws InterruptedException { +424 return nextElements(elements, timeoutMillis, "Did not receive expected element or completion"); +425 } +426 +427 public List<T> nextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +428 return received.nextN(elements, timeoutMillis, errorMsg); +429 } +430 +431 public void expectNext(T expected) throws InterruptedException { +432 expectNext(expected, env.defaultTimeoutMillis()); +433 } +434 +435 public void expectNext(T expected, long timeoutMillis) throws InterruptedException { +436 T received = nextElement(timeoutMillis, "Did not receive expected element on downstream"); +437 if (!received.equals(expected)) { +438 env.flop(String.format("Expected element %s on downstream but received %s", expected, received)); +439 } +440 } +441 +442 public void expectCompletion() throws InterruptedException { +443 expectCompletion(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +444 } +445 +446 public void expectCompletion(long timeoutMillis) throws InterruptedException { +447 expectCompletion(timeoutMillis, "Did not receive expected stream completion"); +448 } +449 +450 public void expectCompletion(String errorMsg) throws InterruptedException { +451 expectCompletion(env.defaultTimeoutMillis(), errorMsg); +452 } +453 +454 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +455 received.expectCompletion(timeoutMillis, errorMsg); +456 } +457 +458 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart) throws Exception { +459 expectErrorWithMessage(expected, requiredMessagePart, env.defaultTimeoutMillis()); +460 } +461 +462 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +463 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart, long timeoutMillis) throws Exception { +464 final E err = expectError(expected, timeoutMillis); +465 final String message = err.getMessage(); +466 assertTrue(message.contains(requiredMessagePart), +467 String.format("Got expected exception [%s] but missing message part [%s], was: %s", +468 err.getClass(), requiredMessagePart, err.getMessage())); +469 } +470 +471 public <E extends Throwable> E expectError(Class<E> expected) throws Exception { +472 return expectError(expected, env.defaultTimeoutMillis()); +473 } +474 +475 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis) throws Exception { +476 return expectError(expected, timeoutMillis, String.format("Expected onError(%s)", expected.getName())); +477 } +478 +479 public <E extends Throwable> E expectError(Class<E> expected, String errorMsg) throws Exception { +480 return expectError(expected, env.defaultTimeoutMillis(), errorMsg); +481 } +482 +483 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis, String errorMsg) throws Exception { +484 return received.expectError(expected, timeoutMillis, errorMsg); +485 } +486 +487 public void expectNone() throws InterruptedException { +488 expectNone(env.defaultTimeoutMillis()); +489 } +490 +491 public void expectNone(String errMsgPrefix) throws InterruptedException { +492 expectNone(env.defaultTimeoutMillis(), errMsgPrefix); +493 } +494 +495 public void expectNone(long withinMillis) throws InterruptedException { +496 expectNone(withinMillis, "Did not expect an element but got element"); +497 } +498 +499 public void expectNone(long withinMillis, String errMsgPrefix) throws InterruptedException { +500 received.expectNone(withinMillis, errMsgPrefix); +501 } +502 +503 } +504 +505 public static class ManualSubscriberWithSubscriptionSupport<T> extends ManualSubscriber<T> { +506 +507 public ManualSubscriberWithSubscriptionSupport(TestEnvironment env) { +508 super(env); +509 } +510 +511 @Override +512 public void onNext(T element) { +513 env.debug(String.format("%s::onNext(%s)", this, element)); +514 if (subscription.isCompleted()) { +515 super.onNext(element); +516 } else { +517 env.flop(String.format("Subscriber::onNext(%s) called before Subscriber::onSubscribe", element)); +518 } +519 } +520 +521 @Override +522 public void onComplete() { +523 env.debug(this + "::onComplete()"); +524 if (subscription.isCompleted()) { +525 super.onComplete(); +526 } else { +527 env.flop("Subscriber::onComplete() called before Subscriber::onSubscribe"); +528 } +529 } +530 +531 @Override +532 public void onSubscribe(Subscription s) { +533 env.debug(String.format("%s::onSubscribe(%s)", this, s)); +534 if (!subscription.isCompleted()) { +535 subscription.complete(s); +536 } else { +537 env.flop("Subscriber::onSubscribe called on an already-subscribed Subscriber"); +538 } +539 } +540 +541 @Override +542 public void onError(Throwable cause) { +543 env.debug(String.format("%s::onError(%s)", this, cause)); +544 if (subscription.isCompleted()) { +545 super.onError(cause); +546 } else { +547 env.flop(cause, String.format("Subscriber::onError(%s) called before Subscriber::onSubscribe", cause)); +548 } +549 } +550 } +551 +552 /** +553 * Similar to {@link org.reactivestreams.tck.TestEnvironment.ManualSubscriberWithSubscriptionSupport} +554 * but does not accumulate values signalled via <code>onNext</code>, thus it can not be used to assert +555 * values signalled to this subscriber. Instead it may be used to quickly drain a given publisher. +556 */ +557 public static class BlackholeSubscriberWithSubscriptionSupport<T> +558 extends ManualSubscriberWithSubscriptionSupport<T> { +559 +560 public BlackholeSubscriberWithSubscriptionSupport(TestEnvironment env) { +561 super(env); +562 } +563 +564 @Override +565 public void onNext(T element) { +566 env.debug(String.format("%s::onNext(%s)", this, element)); +567 if (!subscription.isCompleted()) { +568 env.flop(String.format("Subscriber::onNext(%s) called before Subscriber::onSubscribe", element)); +569 } +570 } +571 +572 @Override +573 public T nextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +574 throw new RuntimeException("Can not expect elements from BlackholeSubscriber, use ManualSubscriber instead!"); +575 } +576 +577 @Override +578 public List<T> nextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +579 throw new RuntimeException("Can not expect elements from BlackholeSubscriber, use ManualSubscriber instead!"); +580 } +581 } +582 +583 public static class TestSubscriber<T> implements Subscriber<T> { +584 final Promise<Subscription> subscription; +585 +586 protected final TestEnvironment env; +587 +588 public TestSubscriber(TestEnvironment env) { +589 this.env = env; +590 subscription = new Promise<Subscription>(env); +591 } +592 +593 @Override +594 public void onError(Throwable cause) { +595 env.flop(cause, String.format("Unexpected Subscriber::onError(%s)", cause)); +596 } +597 +598 @Override +599 public void onComplete() { +600 env.flop("Unexpected Subscriber::onComplete()"); +601 } +602 +603 @Override +604 public void onNext(T element) { +605 env.flop(String.format("Unexpected Subscriber::onNext(%s)", element)); +606 } +607 +608 @Override +609 public void onSubscribe(Subscription subscription) { +610 env.flop(String.format("Unexpected Subscriber::onSubscribe(%s)", subscription)); +611 } +612 +613 public void cancel() { +614 if (subscription.isCompleted()) { +615 subscription.value().cancel(); +616 } else { +617 env.flop("Cannot cancel a subscription before having received it"); +618 } +619 } +620 } +621 +622 public static class ManualPublisher<T> implements Publisher<T> { +623 protected final TestEnvironment env; +624 +625 protected long pendingDemand = 0L; +626 protected Promise<Subscriber<? super T>> subscriber; +627 +628 protected final Receptacle<Long> requests; +629 +630 protected final Latch cancelled; +631 +632 public ManualPublisher(TestEnvironment env) { +633 this.env = env; +634 requests = new Receptacle<Long>(env); +635 cancelled = new Latch(env); +636 subscriber = new Promise<Subscriber<? super T>>(this.env); +637 } +638 +639 @Override +640 public void subscribe(Subscriber<? super T> s) { +641 if (!subscriber.isCompleted()) { +642 subscriber.completeImmediatly(s); +643 +644 Subscription subs = new Subscription() { +645 @Override +646 public void request(long elements) { +647 requests.add(elements); +648 } +649 +650 @Override +651 public void cancel() { +652 cancelled.close(); +653 } +654 }; +655 s.onSubscribe(subs); +656 +657 } else { +658 env.flop("TestPublisher doesn't support more than one Subscriber"); +659 } +660 } +661 +662 public void sendNext(T element) { +663 if (subscriber.isCompleted()) { +664 subscriber.value().onNext(element); +665 } else { +666 env.flop("Cannot sendNext before having a Subscriber"); +667 } +668 } +669 +670 public void sendCompletion() { +671 if (subscriber.isCompleted()) { +672 subscriber.value().onComplete(); +673 } else { +674 env.flop("Cannot sendCompletion before having a Subscriber"); +675 } +676 } +677 +678 public void sendError(Throwable cause) { +679 if (subscriber.isCompleted()) { +680 subscriber.value().onError(cause); +681 } else { +682 env.flop("Cannot sendError before having a Subscriber"); +683 } +684 } +685 +686 public long expectRequest() throws InterruptedException { +687 return expectRequest(env.defaultTimeoutMillis()); +688 } +689 +690 public long expectRequest(long timeoutMillis) throws InterruptedException { +691 long requested = requests.next(timeoutMillis, "Did not receive expected `request` call"); +692 if (requested <= 0) { +693 return env.<Long>flopAndFail(String.format("Requests cannot be zero or negative but received request(%s)", requested)); +694 } else { +695 pendingDemand += requested; +696 return requested; +697 } +698 } +699 +700 public void expectExactRequest(long expected) throws InterruptedException { +701 expectExactRequest(expected, env.defaultTimeoutMillis()); +702 } +703 +704 public void expectExactRequest(long expected, long timeoutMillis) throws InterruptedException { +705 long requested = expectRequest(timeoutMillis); +706 if (requested != expected) { +707 env.flop(String.format("Received `request(%d)` on upstream but expected `request(%d)`", requested, expected)); +708 } +709 pendingDemand += requested; +710 } +711 +712 public void expectNoRequest() throws InterruptedException { +713 expectNoRequest(env.defaultTimeoutMillis()); +714 } +715 +716 public void expectNoRequest(long timeoutMillis) throws InterruptedException { +717 requests.expectNone(timeoutMillis, "Received an unexpected call to: request: "); +718 } +719 +720 public void expectCancelling() throws InterruptedException { +721 expectCancelling(env.defaultTimeoutMillis()); +722 } +723 +724 public void expectCancelling(long timeoutMillis) throws InterruptedException { +725 cancelled.expectClose(timeoutMillis, "Did not receive expected cancelling of upstream subscription"); +726 } +727 } +728 +729 /** +730 * Like a CountDownLatch, but resettable and with some convenience methods +731 */ +732 public static class Latch { +733 private final TestEnvironment env; +734 volatile private CountDownLatch countDownLatch = new CountDownLatch(1); +735 +736 public Latch(TestEnvironment env) { +737 this.env = env; +738 } +739 +740 public void reOpen() { +741 countDownLatch = new CountDownLatch(1); +742 } +743 +744 public boolean isClosed() { +745 return countDownLatch.getCount() == 0; +746 } +747 +748 public void close() { +749 countDownLatch.countDown(); +750 } +751 +752 public void assertClosed(String openErrorMsg) { +753 if (!isClosed()) { +754 env.flop(new ExpectedClosedLatchException(openErrorMsg)); +755 } +756 } +757 +758 public void assertOpen(String closedErrorMsg) { +759 if (isClosed()) { +760 env.flop(new ExpectedOpenLatchException(closedErrorMsg)); +761 } +762 } +763 +764 public void expectClose(String notClosedErrorMsg) throws InterruptedException { +765 expectClose(env.defaultTimeoutMillis(), notClosedErrorMsg); +766 } +767 +768 public void expectClose(long timeoutMillis, String notClosedErrorMsg) throws InterruptedException { +769 countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS); +770 if (countDownLatch.getCount() > 0) { +771 env.flop(String.format("%s within %d ms", notClosedErrorMsg, timeoutMillis)); +772 } +773 } +774 +775 static final class ExpectedOpenLatchException extends RuntimeException { +776 public ExpectedOpenLatchException(String message) { +777 super(message); +778 } +779 } +780 +781 static final class ExpectedClosedLatchException extends RuntimeException { +782 public ExpectedClosedLatchException(String message) { +783 super(message); +784 } +785 } +786 +787 } +788 +789 // simple promise for *one* value, which cannot be reset +790 public static class Promise<T> { +791 private final TestEnvironment env; +792 +793 public static <T> Promise<T> completed(TestEnvironment env, T value) { +794 Promise<T> promise = new Promise<T>(env); +795 promise.completeImmediatly(value); +796 return promise; +797 } +798 +799 public Promise(TestEnvironment env) { +800 this.env = env; +801 } +802 +803 private ArrayBlockingQueue<T> abq = new ArrayBlockingQueue<T>(1); +804 private volatile T _value = null; +805 +806 public T value() { +807 if (isCompleted()) { +808 return _value; +809 } else { +810 env.flop("Cannot access promise value before completion"); +811 return null; +812 } +813 } +814 +815 public boolean isCompleted() { +816 return _value != null; +817 } +818 +819 /** +820 * Allows using expectCompletion to await for completion of the value and complete it _then_ +821 */ +822 public void complete(T value) { +823 abq.add(value); +824 } +825 +826 /** +827 * Completes the promise right away, it is not possible to expectCompletion on a Promise completed this way +828 */ +829 public void completeImmediatly(T value) { +830 complete(value); // complete! +831 _value = value; // immediatly! +832 } +833 +834 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +835 if (!isCompleted()) { +836 T val = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +837 +838 if (val == null) { +839 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +840 } else { +841 _value = val; +842 } +843 } +844 } +845 } +846 +847 // a "Promise" for multiple values, which also supports "end-of-stream reached" +848 public static class Receptacle<T> { +849 final int QUEUE_SIZE = 2 * TEST_BUFFER_SIZE; +850 private final TestEnvironment env; +851 +852 private final ArrayBlockingQueue<Optional<T>> abq = new ArrayBlockingQueue<Optional<T>>(QUEUE_SIZE); +853 +854 private final Latch completedLatch; +855 +856 Receptacle(TestEnvironment env) { +857 this.env = env; +858 this.completedLatch = new Latch(env); +859 } +860 +861 public void add(T value) { +862 completedLatch.assertOpen(String.format("Unexpected element %s received after stream completed", value)); +863 +864 abq.add(Optional.of(value)); +865 } +866 +867 public void complete() { +868 completedLatch.assertOpen("Unexpected additional complete signal received!"); +869 completedLatch.close(); +870 +871 abq.add(Optional.<T>empty()); +872 } +873 +874 public T next(long timeoutMillis, String errorMsg) throws InterruptedException { +875 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +876 +877 if (value == null) { +878 return env.flopAndFail(String.format("%s within %d ms", errorMsg, timeoutMillis)); +879 } else if (value.isDefined()) { +880 return value.get(); +881 } else { +882 return env.flopAndFail("Expected element but got end-of-stream"); +883 } +884 } +885 +886 public Optional<T> nextOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +887 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +888 +889 if (value == null) { +890 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +891 return Optional.empty(); +892 } +893 +894 return value; +895 } +896 +897 /** +898 * @param timeoutMillis total timeout time for awaiting all {@code elements} number of elements +899 */ +900 public List<T> nextN(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +901 List<T> result = new LinkedList<T>(); +902 long remaining = elements; +903 long deadline = System.currentTimeMillis() + timeoutMillis; +904 while (remaining > 0) { +905 long remainingMillis = deadline - System.currentTimeMillis(); +906 +907 result.add(next(remainingMillis, errorMsg)); +908 remaining--; +909 } +910 +911 return result; +912 } +913 +914 +915 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +916 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +917 +918 if (value == null) { +919 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +920 } else if (value.isDefined()) { +921 env.flop(String.format("Expected end-of-stream but got element [%s]", value.get())); +922 } // else, ok +923 } +924 +925 @SuppressWarnings("unchecked") +926 public <E extends Throwable> E expectError(Class<E> clazz, long timeoutMillis, String errorMsg) throws Exception { +927 Thread.sleep(timeoutMillis); +928 +929 if (env.asyncErrors.isEmpty()) { +930 return env.flopAndFail(String.format("%s within %d ms", errorMsg, timeoutMillis)); +931 } else { +932 // ok, there was an expected error +933 Throwable thrown = env.asyncErrors.remove(0); +934 +935 if (clazz.isInstance(thrown)) { +936 return (E) thrown; +937 } else { +938 +939 return env.flopAndFail(String.format("%s within %d ms; Got %s but expected %s", +940 errorMsg, timeoutMillis, thrown.getClass().getCanonicalName(), clazz.getCanonicalName())); +941 } +942 } +943 } +944 +945 public void expectNone(long withinMillis, String errorMsgPrefix) throws InterruptedException { +946 Thread.sleep(withinMillis); +947 Optional<T> value = abq.poll(); +948 +949 if (value == null) { +950 // ok +951 } else if (value.isDefined()) { +952 env.flop(String.format("%s [%s]", errorMsgPrefix, value.get())); +953 } else { +954 env.flop("Expected no element but got end-of-stream"); +955 } +956 } +957 } +958} +959 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck; +002 +003import org.reactivestreams.Publisher; +004import org.reactivestreams.Subscriber; +005import org.reactivestreams.Subscription; +006import org.reactivestreams.tck.support.SubscriberBufferOverflowException; +007import org.reactivestreams.tck.support.Optional; +008 +009import java.util.LinkedList; +010import java.util.List; +011import java.util.concurrent.ArrayBlockingQueue; +012import java.util.concurrent.CopyOnWriteArrayList; +013import java.util.concurrent.CountDownLatch; +014import java.util.concurrent.TimeUnit; +015 +016import static org.testng.Assert.assertTrue; +017import static org.testng.Assert.fail; +018 +019public class TestEnvironment { +020 public static final int TEST_BUFFER_SIZE = 16; +021 +022 private static final String DEFAULT_TIMEOUT_MILLIS_ENV = "DEFAULT_TIMEOUT_MILLIS"; +023 private static final long DEFAULT_TIMEOUT_MILLIS = 100; +024 +025 private final long defaultTimeoutMillis; +026 private final boolean printlnDebug; +027 +028 private CopyOnWriteArrayList<Throwable> asyncErrors = new CopyOnWriteArrayList<Throwable>(); +029 +030 /** +031 * Tests must specify the timeout for expected outcome of asynchronous +032 * interactions. Longer timeout does not invalidate the correctness of +033 * the implementation, but can in some cases result in longer time to +034 * run the tests. +035 * +036 * @param defaultTimeoutMillis default timeout to be used in all expect* methods +037 * @param printlnDebug if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output, +038 * often helpful to pinpoint simple race conditions etc. +039 */ +040 public TestEnvironment(long defaultTimeoutMillis, boolean printlnDebug) { +041 this.defaultTimeoutMillis = defaultTimeoutMillis; +042 this.printlnDebug = printlnDebug; +043 } +044 +045 /** +046 * Tests must specify the timeout for expected outcome of asynchronous +047 * interactions. Longer timeout does not invalidate the correctness of +048 * the implementation, but can in some cases result in longer time to +049 * run the tests. +050 * +051 * @param defaultTimeoutMillis default timeout to be used in all expect* methods +052 */ +053 public TestEnvironment(long defaultTimeoutMillis) { +054 this(defaultTimeoutMillis, false); +055 } +056 +057 /** +058 * Tests must specify the timeout for expected outcome of asynchronous +059 * interactions. Longer timeout does not invalidate the correctness of +060 * the implementation, but can in some cases result in longer time to +061 * run the tests. +062 * +063 * The default timeout for all expect* methods will be obtained by either the env variable {@code DEFAULT_TIMEOUT_MILLIS} +064 * or the default value ({@link TestEnvironment#DEFAULT_TIMEOUT_MILLIS}) will be used. +065 * +066 * @param printlnDebug if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output, +067 * often helpful to pinpoint simple race conditions etc. +068 */ +069 public TestEnvironment(boolean printlnDebug) { +070 this(envDefaultTimeoutMillis(), printlnDebug); +071 } +072 +073 /** +074 * Tests must specify the timeout for expected outcome of asynchronous +075 * interactions. Longer timeout does not invalidate the correctness of +076 * the implementation, but can in some cases result in longer time to +077 * run the tests. +078 * +079 * The default timeout for all expect* methods will be obtained by either the env variable {@code DEFAULT_TIMEOUT_MILLIS} +080 * or the default value ({@link TestEnvironment#DEFAULT_TIMEOUT_MILLIS}) will be used. +081 */ +082 public TestEnvironment() { +083 this(envDefaultTimeoutMillis()); +084 } +085 +086 public long defaultTimeoutMillis() { +087 return defaultTimeoutMillis; +088 } +089 +090 /** +091 * Tries to parse the env variable {@code DEFAULT_TIMEOUT_MILLIS} as long and returns the value if present OR its default value. +092 * +093 * @throws java.lang.IllegalArgumentException when unable to parse the env variable +094 */ +095 public static long envDefaultTimeoutMillis() { +096 final String envMillis = System.getenv(DEFAULT_TIMEOUT_MILLIS_ENV); +097 if (envMillis == null) return DEFAULT_TIMEOUT_MILLIS; +098 else try { +099 return Long.parseLong(envMillis); +100 } catch(NumberFormatException ex) { +101 throw new IllegalArgumentException(String.format("Unable to parse %s env value [%s] as long!", DEFAULT_TIMEOUT_MILLIS_ENV, envMillis), ex); +102 } +103 } +104 +105 /** +106 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +107 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +108 * +109 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +110 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +111 * from the environment using {@code env.dropAsyncError()}. +112 * +113 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +114 */ +115 public void flop(String msg) { +116 try { +117 fail(msg); +118 } catch (Throwable t) { +119 asyncErrors.add(t); +120 } +121 } +122 +123 /** +124 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +125 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +126 * +127 * This overload keeps the passed in throwable as the asyncError, instead of creating an AssertionError for this. +128 * +129 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +130 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +131 * from the environment using {@code env.dropAsyncError()}. +132 * +133 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +134 */ +135 public void flop(Throwable thr, String msg) { +136 try { +137 fail(msg, thr); +138 } catch (Throwable t) { +139 asyncErrors.add(thr); +140 } +141 } +142 +143 /** +144 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +145 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +146 * +147 * This overload keeps the passed in throwable as the asyncError, instead of creating an AssertionError for this. +148 * +149 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +150 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +151 * from the environment using {@code env.dropAsyncError()}. +152 * +153 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +154 */ +155 public void flop(Throwable thr) { +156 try { +157 fail(thr.getMessage(), thr); +158 } catch (Throwable t) { +159 asyncErrors.add(thr); +160 } +161 } +162 +163 /** +164 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +165 * +166 * This method DOES fail the test right away (it tries to, by throwing an AssertionException), +167 * in such it is different from {@link org.reactivestreams.tck.TestEnvironment#flop} which only records the error. +168 * +169 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +170 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +171 * from the environment using {@code env.dropAsyncError()}. +172 * +173 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +174 */ +175 public <T> T flopAndFail(String msg) { +176 try { +177 fail(msg); +178 } catch (Throwable t) { +179 asyncErrors.add(t); +180 fail(msg, t); +181 } +182 return null; // unreachable, the previous block will always exit by throwing +183 } +184 +185 +186 +187 public <T> void subscribe(Publisher<T> pub, TestSubscriber<T> sub) throws InterruptedException { +188 subscribe(pub, sub, defaultTimeoutMillis); +189 } +190 +191 public <T> void subscribe(Publisher<T> pub, TestSubscriber<T> sub, long timeoutMillis) throws InterruptedException { +192 pub.subscribe(sub); +193 sub.subscription.expectCompletion(timeoutMillis, String.format("Could not subscribe %s to Publisher %s", sub, pub)); +194 verifyNoAsyncErrorsNoDelay(); +195 } +196 +197 public <T> ManualSubscriber<T> newBlackholeSubscriber(Publisher<T> pub) throws InterruptedException { +198 ManualSubscriberWithSubscriptionSupport<T> sub = new BlackholeSubscriberWithSubscriptionSupport<T>(this); +199 subscribe(pub, sub, defaultTimeoutMillis()); +200 return sub; +201 } +202 +203 public <T> ManualSubscriber<T> newManualSubscriber(Publisher<T> pub) throws InterruptedException { +204 return newManualSubscriber(pub, defaultTimeoutMillis()); +205 } +206 +207 public <T> ManualSubscriber<T> newManualSubscriber(Publisher<T> pub, long timeoutMillis) throws InterruptedException { +208 ManualSubscriberWithSubscriptionSupport<T> sub = new ManualSubscriberWithSubscriptionSupport<T>(this); +209 subscribe(pub, sub, timeoutMillis); +210 return sub; +211 } +212 +213 public void clearAsyncErrors() { +214 asyncErrors.clear(); +215 } +216 +217 public Throwable dropAsyncError() { +218 try { +219 return asyncErrors.remove(0); +220 } catch (IndexOutOfBoundsException ex) { +221 return null; +222 } +223 } +224 +225 /** +226 * Waits for {@link TestEnvironment#defaultTimeoutMillis()} and then verifies that no asynchronous errors +227 * were signalled pior to, or during that time (by calling {@code flop()}). +228 */ +229 public void verifyNoAsyncErrors() { +230 verifyNoAsyncErrors(defaultTimeoutMillis()); +231 } +232 +233 /** +234 * This version of {@code verifyNoAsyncErrors} should be used when errors still could be signalled +235 * asynchronously during {@link TestEnvironment#defaultTimeoutMillis()} time. +236 * <p></p> +237 * It will immediatly check if any async errors were signaled (using {@link TestEnvironment#flop(String)}, +238 * and if no errors encountered wait for another default timeout as the errors may yet be signalled. +239 * The initial check is performed in order to fail-fast in case of an already failed test. +240 */ +241 public void verifyNoAsyncErrors(long delay) { +242 try { +243 verifyNoAsyncErrorsNoDelay(); +244 +245 Thread.sleep(delay); +246 verifyNoAsyncErrorsNoDelay(); +247 } catch (InterruptedException e) { +248 throw new RuntimeException(e); +249 } +250 } +251 +252 /** +253 * Verifies that no asynchronous errors were signalled pior to calling this method (by calling {@code flop()}). +254 * This version of verifyNoAsyncError <b>does not wait before checking for asynchronous errors</b>, and is to be used +255 * for example in tight loops etc. +256 */ +257 public void verifyNoAsyncErrorsNoDelay() { +258 for (Throwable e : asyncErrors) { +259 if (e instanceof AssertionError) { +260 throw (AssertionError) e; +261 } else { +262 fail(String.format("Async error during test execution: %s", e.getMessage()), e); +263 } +264 } +265 } +266 +267 /** If {@code TestEnvironment#printlnDebug} is true, print debug message to std out. */ +268 public void debug(String msg) { +269 if (printlnDebug) +270 System.out.printf("[TCK-DEBUG] %s%n", msg); +271 } +272 +273 /** +274 * Looks for given {@code method} method in stack trace. +275 * Can be used to answer questions like "was this method called from onComplete?". +276 * +277 * @return the caller's StackTraceElement at which he the looked for method was found in the call stack, EMPTY otherwise +278 */ +279 public Optional<StackTraceElement> findCallerMethodInStackTrace(String method) { +280 final Throwable thr = new Throwable(); // gets the stacktrace +281 +282 for (StackTraceElement stackElement : thr.getStackTrace()) { +283 if (stackElement.getMethodName().equals(method)) { +284 return Optional.of(stackElement); +285 } +286 } +287 return Optional.empty(); +288 } +289 +290 // ---- classes ---- +291 +292 /** +293 * {@link Subscriber} implementation which can be steered by test code and asserted on. +294 */ +295 public static class ManualSubscriber<T> extends TestSubscriber<T> { +296 Receptacle<T> received; +297 +298 public ManualSubscriber(TestEnvironment env) { +299 super(env); +300 received = new Receptacle<T>(this.env); +301 } +302 +303 @Override +304 public void onNext(T element) { +305 try { +306 received.add(element); +307 } catch (IllegalStateException ex) { +308 // error message refinement +309 throw new SubscriberBufferOverflowException( +310 String.format("Received more than bufferSize (%d) onNext signals. " + +311 "The Publisher probably emited more signals than expected!", +312 received.QUEUE_SIZE), ex); +313 } +314 } +315 +316 @Override +317 public void onComplete() { +318 received.complete(); +319 } +320 +321 public void request(long elements) { +322 subscription.value().request(elements); +323 } +324 +325 public T requestNextElement() throws InterruptedException { +326 return requestNextElement(env.defaultTimeoutMillis()); +327 } +328 +329 public T requestNextElement(long timeoutMillis) throws InterruptedException { +330 return requestNextElement(timeoutMillis, "Did not receive expected element"); +331 } +332 +333 public T requestNextElement(String errorMsg) throws InterruptedException { +334 return requestNextElement(env.defaultTimeoutMillis(), errorMsg); +335 } +336 +337 public T requestNextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +338 request(1); +339 return nextElement(timeoutMillis, errorMsg); +340 } +341 +342 public Optional<T> requestNextElementOrEndOfStream(String errorMsg) throws InterruptedException { +343 return requestNextElementOrEndOfStream(env.defaultTimeoutMillis(), errorMsg); +344 } +345 +346 public Optional<T> requestNextElementOrEndOfStream(long timeoutMillis) throws InterruptedException { +347 return requestNextElementOrEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +348 } +349 +350 public Optional<T> requestNextElementOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +351 request(1); +352 return nextElementOrEndOfStream(timeoutMillis, errorMsg); +353 } +354 +355 public void requestEndOfStream() throws InterruptedException { +356 requestEndOfStream(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +357 } +358 +359 public void requestEndOfStream(long timeoutMillis) throws InterruptedException { +360 requestEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +361 } +362 +363 public void requestEndOfStream(String errorMsg) throws InterruptedException { +364 requestEndOfStream(env.defaultTimeoutMillis(), errorMsg); +365 } +366 +367 public void requestEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +368 request(1); +369 expectCompletion(timeoutMillis, errorMsg); +370 } +371 +372 public List<T> requestNextElements(long elements) throws InterruptedException { +373 request(elements); +374 return nextElements(elements, env.defaultTimeoutMillis()); +375 } +376 +377 public List<T> requestNextElements(long elements, long timeoutMillis) throws InterruptedException { +378 request(elements); +379 return nextElements(elements, timeoutMillis, String.format("Did not receive %d expected elements", elements)); +380 } +381 +382 public List<T> requestNextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +383 request(elements); +384 return nextElements(elements, timeoutMillis, errorMsg); +385 } +386 +387 public T nextElement() throws InterruptedException { +388 return nextElement(env.defaultTimeoutMillis()); +389 } +390 +391 public T nextElement(long timeoutMillis) throws InterruptedException { +392 return nextElement(timeoutMillis, "Did not receive expected element"); +393 } +394 +395 public T nextElement(String errorMsg) throws InterruptedException { +396 return nextElement(env.defaultTimeoutMillis(), errorMsg); +397 } +398 +399 public T nextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +400 return received.next(timeoutMillis, errorMsg); +401 } +402 +403 public Optional<T> nextElementOrEndOfStream() throws InterruptedException { +404 return nextElementOrEndOfStream(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +405 } +406 +407 public Optional<T> nextElementOrEndOfStream(long timeoutMillis) throws InterruptedException { +408 return nextElementOrEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +409 } +410 +411 public Optional<T> nextElementOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +412 return received.nextOrEndOfStream(timeoutMillis, errorMsg); +413 } +414 +415 public List<T> nextElements(long elements) throws InterruptedException { +416 return nextElements(elements, env.defaultTimeoutMillis(), "Did not receive expected element or completion"); +417 } +418 +419 public List<T> nextElements(long elements, String errorMsg) throws InterruptedException { +420 return nextElements(elements, env.defaultTimeoutMillis(), errorMsg); +421 } +422 +423 public List<T> nextElements(long elements, long timeoutMillis) throws InterruptedException { +424 return nextElements(elements, timeoutMillis, "Did not receive expected element or completion"); +425 } +426 +427 public List<T> nextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +428 return received.nextN(elements, timeoutMillis, errorMsg); +429 } +430 +431 public void expectNext(T expected) throws InterruptedException { +432 expectNext(expected, env.defaultTimeoutMillis()); +433 } +434 +435 public void expectNext(T expected, long timeoutMillis) throws InterruptedException { +436 T received = nextElement(timeoutMillis, "Did not receive expected element on downstream"); +437 if (!received.equals(expected)) { +438 env.flop(String.format("Expected element %s on downstream but received %s", expected, received)); +439 } +440 } +441 +442 public void expectCompletion() throws InterruptedException { +443 expectCompletion(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +444 } +445 +446 public void expectCompletion(long timeoutMillis) throws InterruptedException { +447 expectCompletion(timeoutMillis, "Did not receive expected stream completion"); +448 } +449 +450 public void expectCompletion(String errorMsg) throws InterruptedException { +451 expectCompletion(env.defaultTimeoutMillis(), errorMsg); +452 } +453 +454 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +455 received.expectCompletion(timeoutMillis, errorMsg); +456 } +457 +458 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart) throws Exception { +459 expectErrorWithMessage(expected, requiredMessagePart, env.defaultTimeoutMillis()); +460 } +461 +462 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +463 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart, long timeoutMillis) throws Exception { +464 final E err = expectError(expected, timeoutMillis); +465 final String message = err.getMessage(); +466 assertTrue(message.contains(requiredMessagePart), +467 String.format("Got expected exception [%s] but missing message part [%s], was: %s", +468 err.getClass(), requiredMessagePart, err.getMessage())); +469 } +470 +471 public <E extends Throwable> E expectError(Class<E> expected) throws Exception { +472 return expectError(expected, env.defaultTimeoutMillis()); +473 } +474 +475 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis) throws Exception { +476 return expectError(expected, timeoutMillis, String.format("Expected onError(%s)", expected.getName())); +477 } +478 +479 public <E extends Throwable> E expectError(Class<E> expected, String errorMsg) throws Exception { +480 return expectError(expected, env.defaultTimeoutMillis(), errorMsg); +481 } +482 +483 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis, String errorMsg) throws Exception { +484 return received.expectError(expected, timeoutMillis, errorMsg); +485 } +486 +487 public void expectNone() throws InterruptedException { +488 expectNone(env.defaultTimeoutMillis()); +489 } +490 +491 public void expectNone(String errMsgPrefix) throws InterruptedException { +492 expectNone(env.defaultTimeoutMillis(), errMsgPrefix); +493 } +494 +495 public void expectNone(long withinMillis) throws InterruptedException { +496 expectNone(withinMillis, "Did not expect an element but got element"); +497 } +498 +499 public void expectNone(long withinMillis, String errMsgPrefix) throws InterruptedException { +500 received.expectNone(withinMillis, errMsgPrefix); +501 } +502 +503 } +504 +505 public static class ManualSubscriberWithSubscriptionSupport<T> extends ManualSubscriber<T> { +506 +507 public ManualSubscriberWithSubscriptionSupport(TestEnvironment env) { +508 super(env); +509 } +510 +511 @Override +512 public void onNext(T element) { +513 env.debug(String.format("%s::onNext(%s)", this, element)); +514 if (subscription.isCompleted()) { +515 super.onNext(element); +516 } else { +517 env.flop(String.format("Subscriber::onNext(%s) called before Subscriber::onSubscribe", element)); +518 } +519 } +520 +521 @Override +522 public void onComplete() { +523 env.debug(this + "::onComplete()"); +524 if (subscription.isCompleted()) { +525 super.onComplete(); +526 } else { +527 env.flop("Subscriber::onComplete() called before Subscriber::onSubscribe"); +528 } +529 } +530 +531 @Override +532 public void onSubscribe(Subscription s) { +533 env.debug(String.format("%s::onSubscribe(%s)", this, s)); +534 if (!subscription.isCompleted()) { +535 subscription.complete(s); +536 } else { +537 env.flop("Subscriber::onSubscribe called on an already-subscribed Subscriber"); +538 } +539 } +540 +541 @Override +542 public void onError(Throwable cause) { +543 env.debug(String.format("%s::onError(%s)", this, cause)); +544 if (subscription.isCompleted()) { +545 super.onError(cause); +546 } else { +547 env.flop(cause, String.format("Subscriber::onError(%s) called before Subscriber::onSubscribe", cause)); +548 } +549 } +550 } +551 +552 /** +553 * Similar to {@link org.reactivestreams.tck.TestEnvironment.ManualSubscriberWithSubscriptionSupport} +554 * but does not accumulate values signalled via <code>onNext</code>, thus it can not be used to assert +555 * values signalled to this subscriber. Instead it may be used to quickly drain a given publisher. +556 */ +557 public static class BlackholeSubscriberWithSubscriptionSupport<T> +558 extends ManualSubscriberWithSubscriptionSupport<T> { +559 +560 public BlackholeSubscriberWithSubscriptionSupport(TestEnvironment env) { +561 super(env); +562 } +563 +564 @Override +565 public void onNext(T element) { +566 env.debug(String.format("%s::onNext(%s)", this, element)); +567 if (!subscription.isCompleted()) { +568 env.flop(String.format("Subscriber::onNext(%s) called before Subscriber::onSubscribe", element)); +569 } +570 } +571 +572 @Override +573 public T nextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +574 throw new RuntimeException("Can not expect elements from BlackholeSubscriber, use ManualSubscriber instead!"); +575 } +576 +577 @Override +578 public List<T> nextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +579 throw new RuntimeException("Can not expect elements from BlackholeSubscriber, use ManualSubscriber instead!"); +580 } +581 } +582 +583 public static class TestSubscriber<T> implements Subscriber<T> { +584 final Promise<Subscription> subscription; +585 +586 protected final TestEnvironment env; +587 +588 public TestSubscriber(TestEnvironment env) { +589 this.env = env; +590 subscription = new Promise<Subscription>(env); +591 } +592 +593 @Override +594 public void onError(Throwable cause) { +595 env.flop(cause, String.format("Unexpected Subscriber::onError(%s)", cause)); +596 } +597 +598 @Override +599 public void onComplete() { +600 env.flop("Unexpected Subscriber::onComplete()"); +601 } +602 +603 @Override +604 public void onNext(T element) { +605 env.flop(String.format("Unexpected Subscriber::onNext(%s)", element)); +606 } +607 +608 @Override +609 public void onSubscribe(Subscription subscription) { +610 env.flop(String.format("Unexpected Subscriber::onSubscribe(%s)", subscription)); +611 } +612 +613 public void cancel() { +614 if (subscription.isCompleted()) { +615 subscription.value().cancel(); +616 } else { +617 env.flop("Cannot cancel a subscription before having received it"); +618 } +619 } +620 } +621 +622 public static class ManualPublisher<T> implements Publisher<T> { +623 protected final TestEnvironment env; +624 +625 protected long pendingDemand = 0L; +626 protected Promise<Subscriber<? super T>> subscriber; +627 +628 protected final Receptacle<Long> requests; +629 +630 protected final Latch cancelled; +631 +632 public ManualPublisher(TestEnvironment env) { +633 this.env = env; +634 requests = new Receptacle<Long>(env); +635 cancelled = new Latch(env); +636 subscriber = new Promise<Subscriber<? super T>>(this.env); +637 } +638 +639 @Override +640 public void subscribe(Subscriber<? super T> s) { +641 if (!subscriber.isCompleted()) { +642 subscriber.completeImmediatly(s); +643 +644 Subscription subs = new Subscription() { +645 @Override +646 public void request(long elements) { +647 requests.add(elements); +648 } +649 +650 @Override +651 public void cancel() { +652 cancelled.close(); +653 } +654 }; +655 s.onSubscribe(subs); +656 +657 } else { +658 env.flop("TestPublisher doesn't support more than one Subscriber"); +659 } +660 } +661 +662 public void sendNext(T element) { +663 if (subscriber.isCompleted()) { +664 subscriber.value().onNext(element); +665 } else { +666 env.flop("Cannot sendNext before having a Subscriber"); +667 } +668 } +669 +670 public void sendCompletion() { +671 if (subscriber.isCompleted()) { +672 subscriber.value().onComplete(); +673 } else { +674 env.flop("Cannot sendCompletion before having a Subscriber"); +675 } +676 } +677 +678 public void sendError(Throwable cause) { +679 if (subscriber.isCompleted()) { +680 subscriber.value().onError(cause); +681 } else { +682 env.flop("Cannot sendError before having a Subscriber"); +683 } +684 } +685 +686 public long expectRequest() throws InterruptedException { +687 return expectRequest(env.defaultTimeoutMillis()); +688 } +689 +690 public long expectRequest(long timeoutMillis) throws InterruptedException { +691 long requested = requests.next(timeoutMillis, "Did not receive expected `request` call"); +692 if (requested <= 0) { +693 return env.<Long>flopAndFail(String.format("Requests cannot be zero or negative but received request(%s)", requested)); +694 } else { +695 pendingDemand += requested; +696 return requested; +697 } +698 } +699 +700 public void expectExactRequest(long expected) throws InterruptedException { +701 expectExactRequest(expected, env.defaultTimeoutMillis()); +702 } +703 +704 public void expectExactRequest(long expected, long timeoutMillis) throws InterruptedException { +705 long requested = expectRequest(timeoutMillis); +706 if (requested != expected) { +707 env.flop(String.format("Received `request(%d)` on upstream but expected `request(%d)`", requested, expected)); +708 } +709 pendingDemand += requested; +710 } +711 +712 public void expectNoRequest() throws InterruptedException { +713 expectNoRequest(env.defaultTimeoutMillis()); +714 } +715 +716 public void expectNoRequest(long timeoutMillis) throws InterruptedException { +717 requests.expectNone(timeoutMillis, "Received an unexpected call to: request: "); +718 } +719 +720 public void expectCancelling() throws InterruptedException { +721 expectCancelling(env.defaultTimeoutMillis()); +722 } +723 +724 public void expectCancelling(long timeoutMillis) throws InterruptedException { +725 cancelled.expectClose(timeoutMillis, "Did not receive expected cancelling of upstream subscription"); +726 } +727 } +728 +729 /** +730 * Like a CountDownLatch, but resettable and with some convenience methods +731 */ +732 public static class Latch { +733 private final TestEnvironment env; +734 volatile private CountDownLatch countDownLatch = new CountDownLatch(1); +735 +736 public Latch(TestEnvironment env) { +737 this.env = env; +738 } +739 +740 public void reOpen() { +741 countDownLatch = new CountDownLatch(1); +742 } +743 +744 public boolean isClosed() { +745 return countDownLatch.getCount() == 0; +746 } +747 +748 public void close() { +749 countDownLatch.countDown(); +750 } +751 +752 public void assertClosed(String openErrorMsg) { +753 if (!isClosed()) { +754 env.flop(new ExpectedClosedLatchException(openErrorMsg)); +755 } +756 } +757 +758 public void assertOpen(String closedErrorMsg) { +759 if (isClosed()) { +760 env.flop(new ExpectedOpenLatchException(closedErrorMsg)); +761 } +762 } +763 +764 public void expectClose(String notClosedErrorMsg) throws InterruptedException { +765 expectClose(env.defaultTimeoutMillis(), notClosedErrorMsg); +766 } +767 +768 public void expectClose(long timeoutMillis, String notClosedErrorMsg) throws InterruptedException { +769 countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS); +770 if (countDownLatch.getCount() > 0) { +771 env.flop(String.format("%s within %d ms", notClosedErrorMsg, timeoutMillis)); +772 } +773 } +774 +775 static final class ExpectedOpenLatchException extends RuntimeException { +776 public ExpectedOpenLatchException(String message) { +777 super(message); +778 } +779 } +780 +781 static final class ExpectedClosedLatchException extends RuntimeException { +782 public ExpectedClosedLatchException(String message) { +783 super(message); +784 } +785 } +786 +787 } +788 +789 // simple promise for *one* value, which cannot be reset +790 public static class Promise<T> { +791 private final TestEnvironment env; +792 +793 public static <T> Promise<T> completed(TestEnvironment env, T value) { +794 Promise<T> promise = new Promise<T>(env); +795 promise.completeImmediatly(value); +796 return promise; +797 } +798 +799 public Promise(TestEnvironment env) { +800 this.env = env; +801 } +802 +803 private ArrayBlockingQueue<T> abq = new ArrayBlockingQueue<T>(1); +804 private volatile T _value = null; +805 +806 public T value() { +807 if (isCompleted()) { +808 return _value; +809 } else { +810 env.flop("Cannot access promise value before completion"); +811 return null; +812 } +813 } +814 +815 public boolean isCompleted() { +816 return _value != null; +817 } +818 +819 /** +820 * Allows using expectCompletion to await for completion of the value and complete it _then_ +821 */ +822 public void complete(T value) { +823 abq.add(value); +824 } +825 +826 /** +827 * Completes the promise right away, it is not possible to expectCompletion on a Promise completed this way +828 */ +829 public void completeImmediatly(T value) { +830 complete(value); // complete! +831 _value = value; // immediatly! +832 } +833 +834 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +835 if (!isCompleted()) { +836 T val = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +837 +838 if (val == null) { +839 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +840 } else { +841 _value = val; +842 } +843 } +844 } +845 } +846 +847 // a "Promise" for multiple values, which also supports "end-of-stream reached" +848 public static class Receptacle<T> { +849 final int QUEUE_SIZE = 2 * TEST_BUFFER_SIZE; +850 private final TestEnvironment env; +851 +852 private final ArrayBlockingQueue<Optional<T>> abq = new ArrayBlockingQueue<Optional<T>>(QUEUE_SIZE); +853 +854 private final Latch completedLatch; +855 +856 Receptacle(TestEnvironment env) { +857 this.env = env; +858 this.completedLatch = new Latch(env); +859 } +860 +861 public void add(T value) { +862 completedLatch.assertOpen(String.format("Unexpected element %s received after stream completed", value)); +863 +864 abq.add(Optional.of(value)); +865 } +866 +867 public void complete() { +868 completedLatch.assertOpen("Unexpected additional complete signal received!"); +869 completedLatch.close(); +870 +871 abq.add(Optional.<T>empty()); +872 } +873 +874 public T next(long timeoutMillis, String errorMsg) throws InterruptedException { +875 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +876 +877 if (value == null) { +878 return env.flopAndFail(String.format("%s within %d ms", errorMsg, timeoutMillis)); +879 } else if (value.isDefined()) { +880 return value.get(); +881 } else { +882 return env.flopAndFail("Expected element but got end-of-stream"); +883 } +884 } +885 +886 public Optional<T> nextOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +887 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +888 +889 if (value == null) { +890 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +891 return Optional.empty(); +892 } +893 +894 return value; +895 } +896 +897 /** +898 * @param timeoutMillis total timeout time for awaiting all {@code elements} number of elements +899 */ +900 public List<T> nextN(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +901 List<T> result = new LinkedList<T>(); +902 long remaining = elements; +903 long deadline = System.currentTimeMillis() + timeoutMillis; +904 while (remaining > 0) { +905 long remainingMillis = deadline - System.currentTimeMillis(); +906 +907 result.add(next(remainingMillis, errorMsg)); +908 remaining--; +909 } +910 +911 return result; +912 } +913 +914 +915 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +916 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +917 +918 if (value == null) { +919 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +920 } else if (value.isDefined()) { +921 env.flop(String.format("Expected end-of-stream but got element [%s]", value.get())); +922 } // else, ok +923 } +924 +925 @SuppressWarnings("unchecked") +926 public <E extends Throwable> E expectError(Class<E> clazz, long timeoutMillis, String errorMsg) throws Exception { +927 Thread.sleep(timeoutMillis); +928 +929 if (env.asyncErrors.isEmpty()) { +930 return env.flopAndFail(String.format("%s within %d ms", errorMsg, timeoutMillis)); +931 } else { +932 // ok, there was an expected error +933 Throwable thrown = env.asyncErrors.remove(0); +934 +935 if (clazz.isInstance(thrown)) { +936 return (E) thrown; +937 } else { +938 +939 return env.flopAndFail(String.format("%s within %d ms; Got %s but expected %s", +940 errorMsg, timeoutMillis, thrown.getClass().getCanonicalName(), clazz.getCanonicalName())); +941 } +942 } +943 } +944 +945 public void expectNone(long withinMillis, String errorMsgPrefix) throws InterruptedException { +946 Thread.sleep(withinMillis); +947 Optional<T> value = abq.poll(); +948 +949 if (value == null) { +950 // ok +951 } else if (value.isDefined()) { +952 env.flop(String.format("%s [%s]", errorMsgPrefix, value.get())); +953 } else { +954 env.flop("Expected no element but got end-of-stream"); +955 } +956 } +957 } +958} +959 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck; +002 +003import org.reactivestreams.Publisher; +004import org.reactivestreams.Subscriber; +005import org.reactivestreams.Subscription; +006import org.reactivestreams.tck.support.SubscriberBufferOverflowException; +007import org.reactivestreams.tck.support.Optional; +008 +009import java.util.LinkedList; +010import java.util.List; +011import java.util.concurrent.ArrayBlockingQueue; +012import java.util.concurrent.CopyOnWriteArrayList; +013import java.util.concurrent.CountDownLatch; +014import java.util.concurrent.TimeUnit; +015 +016import static org.testng.Assert.assertTrue; +017import static org.testng.Assert.fail; +018 +019public class TestEnvironment { +020 public static final int TEST_BUFFER_SIZE = 16; +021 +022 private static final String DEFAULT_TIMEOUT_MILLIS_ENV = "DEFAULT_TIMEOUT_MILLIS"; +023 private static final long DEFAULT_TIMEOUT_MILLIS = 100; +024 +025 private final long defaultTimeoutMillis; +026 private final boolean printlnDebug; +027 +028 private CopyOnWriteArrayList<Throwable> asyncErrors = new CopyOnWriteArrayList<Throwable>(); +029 +030 /** +031 * Tests must specify the timeout for expected outcome of asynchronous +032 * interactions. Longer timeout does not invalidate the correctness of +033 * the implementation, but can in some cases result in longer time to +034 * run the tests. +035 * +036 * @param defaultTimeoutMillis default timeout to be used in all expect* methods +037 * @param printlnDebug if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output, +038 * often helpful to pinpoint simple race conditions etc. +039 */ +040 public TestEnvironment(long defaultTimeoutMillis, boolean printlnDebug) { +041 this.defaultTimeoutMillis = defaultTimeoutMillis; +042 this.printlnDebug = printlnDebug; +043 } +044 +045 /** +046 * Tests must specify the timeout for expected outcome of asynchronous +047 * interactions. Longer timeout does not invalidate the correctness of +048 * the implementation, but can in some cases result in longer time to +049 * run the tests. +050 * +051 * @param defaultTimeoutMillis default timeout to be used in all expect* methods +052 */ +053 public TestEnvironment(long defaultTimeoutMillis) { +054 this(defaultTimeoutMillis, false); +055 } +056 +057 /** +058 * Tests must specify the timeout for expected outcome of asynchronous +059 * interactions. Longer timeout does not invalidate the correctness of +060 * the implementation, but can in some cases result in longer time to +061 * run the tests. +062 * +063 * The default timeout for all expect* methods will be obtained by either the env variable {@code DEFAULT_TIMEOUT_MILLIS} +064 * or the default value ({@link TestEnvironment#DEFAULT_TIMEOUT_MILLIS}) will be used. +065 * +066 * @param printlnDebug if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output, +067 * often helpful to pinpoint simple race conditions etc. +068 */ +069 public TestEnvironment(boolean printlnDebug) { +070 this(envDefaultTimeoutMillis(), printlnDebug); +071 } +072 +073 /** +074 * Tests must specify the timeout for expected outcome of asynchronous +075 * interactions. Longer timeout does not invalidate the correctness of +076 * the implementation, but can in some cases result in longer time to +077 * run the tests. +078 * +079 * The default timeout for all expect* methods will be obtained by either the env variable {@code DEFAULT_TIMEOUT_MILLIS} +080 * or the default value ({@link TestEnvironment#DEFAULT_TIMEOUT_MILLIS}) will be used. +081 */ +082 public TestEnvironment() { +083 this(envDefaultTimeoutMillis()); +084 } +085 +086 public long defaultTimeoutMillis() { +087 return defaultTimeoutMillis; +088 } +089 +090 /** +091 * Tries to parse the env variable {@code DEFAULT_TIMEOUT_MILLIS} as long and returns the value if present OR its default value. +092 * +093 * @throws java.lang.IllegalArgumentException when unable to parse the env variable +094 */ +095 public static long envDefaultTimeoutMillis() { +096 final String envMillis = System.getenv(DEFAULT_TIMEOUT_MILLIS_ENV); +097 if (envMillis == null) return DEFAULT_TIMEOUT_MILLIS; +098 else try { +099 return Long.parseLong(envMillis); +100 } catch(NumberFormatException ex) { +101 throw new IllegalArgumentException(String.format("Unable to parse %s env value [%s] as long!", DEFAULT_TIMEOUT_MILLIS_ENV, envMillis), ex); +102 } +103 } +104 +105 /** +106 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +107 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +108 * +109 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +110 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +111 * from the environment using {@code env.dropAsyncError()}. +112 * +113 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +114 */ +115 public void flop(String msg) { +116 try { +117 fail(msg); +118 } catch (Throwable t) { +119 asyncErrors.add(t); +120 } +121 } +122 +123 /** +124 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +125 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +126 * +127 * This overload keeps the passed in throwable as the asyncError, instead of creating an AssertionError for this. +128 * +129 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +130 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +131 * from the environment using {@code env.dropAsyncError()}. +132 * +133 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +134 */ +135 public void flop(Throwable thr, String msg) { +136 try { +137 fail(msg, thr); +138 } catch (Throwable t) { +139 asyncErrors.add(thr); +140 } +141 } +142 +143 /** +144 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +145 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +146 * +147 * This overload keeps the passed in throwable as the asyncError, instead of creating an AssertionError for this. +148 * +149 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +150 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +151 * from the environment using {@code env.dropAsyncError()}. +152 * +153 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +154 */ +155 public void flop(Throwable thr) { +156 try { +157 fail(thr.getMessage(), thr); +158 } catch (Throwable t) { +159 asyncErrors.add(thr); +160 } +161 } +162 +163 /** +164 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +165 * +166 * This method DOES fail the test right away (it tries to, by throwing an AssertionException), +167 * in such it is different from {@link org.reactivestreams.tck.TestEnvironment#flop} which only records the error. +168 * +169 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +170 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +171 * from the environment using {@code env.dropAsyncError()}. +172 * +173 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +174 */ +175 public <T> T flopAndFail(String msg) { +176 try { +177 fail(msg); +178 } catch (Throwable t) { +179 asyncErrors.add(t); +180 fail(msg, t); +181 } +182 return null; // unreachable, the previous block will always exit by throwing +183 } +184 +185 +186 +187 public <T> void subscribe(Publisher<T> pub, TestSubscriber<T> sub) throws InterruptedException { +188 subscribe(pub, sub, defaultTimeoutMillis); +189 } +190 +191 public <T> void subscribe(Publisher<T> pub, TestSubscriber<T> sub, long timeoutMillis) throws InterruptedException { +192 pub.subscribe(sub); +193 sub.subscription.expectCompletion(timeoutMillis, String.format("Could not subscribe %s to Publisher %s", sub, pub)); +194 verifyNoAsyncErrorsNoDelay(); +195 } +196 +197 public <T> ManualSubscriber<T> newBlackholeSubscriber(Publisher<T> pub) throws InterruptedException { +198 ManualSubscriberWithSubscriptionSupport<T> sub = new BlackholeSubscriberWithSubscriptionSupport<T>(this); +199 subscribe(pub, sub, defaultTimeoutMillis()); +200 return sub; +201 } +202 +203 public <T> ManualSubscriber<T> newManualSubscriber(Publisher<T> pub) throws InterruptedException { +204 return newManualSubscriber(pub, defaultTimeoutMillis()); +205 } +206 +207 public <T> ManualSubscriber<T> newManualSubscriber(Publisher<T> pub, long timeoutMillis) throws InterruptedException { +208 ManualSubscriberWithSubscriptionSupport<T> sub = new ManualSubscriberWithSubscriptionSupport<T>(this); +209 subscribe(pub, sub, timeoutMillis); +210 return sub; +211 } +212 +213 public void clearAsyncErrors() { +214 asyncErrors.clear(); +215 } +216 +217 public Throwable dropAsyncError() { +218 try { +219 return asyncErrors.remove(0); +220 } catch (IndexOutOfBoundsException ex) { +221 return null; +222 } +223 } +224 +225 /** +226 * Waits for {@link TestEnvironment#defaultTimeoutMillis()} and then verifies that no asynchronous errors +227 * were signalled pior to, or during that time (by calling {@code flop()}). +228 */ +229 public void verifyNoAsyncErrors() { +230 verifyNoAsyncErrors(defaultTimeoutMillis()); +231 } +232 +233 /** +234 * This version of {@code verifyNoAsyncErrors} should be used when errors still could be signalled +235 * asynchronously during {@link TestEnvironment#defaultTimeoutMillis()} time. +236 * <p></p> +237 * It will immediatly check if any async errors were signaled (using {@link TestEnvironment#flop(String)}, +238 * and if no errors encountered wait for another default timeout as the errors may yet be signalled. +239 * The initial check is performed in order to fail-fast in case of an already failed test. +240 */ +241 public void verifyNoAsyncErrors(long delay) { +242 try { +243 verifyNoAsyncErrorsNoDelay(); +244 +245 Thread.sleep(delay); +246 verifyNoAsyncErrorsNoDelay(); +247 } catch (InterruptedException e) { +248 throw new RuntimeException(e); +249 } +250 } +251 +252 /** +253 * Verifies that no asynchronous errors were signalled pior to calling this method (by calling {@code flop()}). +254 * This version of verifyNoAsyncError <b>does not wait before checking for asynchronous errors</b>, and is to be used +255 * for example in tight loops etc. +256 */ +257 public void verifyNoAsyncErrorsNoDelay() { +258 for (Throwable e : asyncErrors) { +259 if (e instanceof AssertionError) { +260 throw (AssertionError) e; +261 } else { +262 fail(String.format("Async error during test execution: %s", e.getMessage()), e); +263 } +264 } +265 } +266 +267 /** If {@code TestEnvironment#printlnDebug} is true, print debug message to std out. */ +268 public void debug(String msg) { +269 if (printlnDebug) +270 System.out.printf("[TCK-DEBUG] %s%n", msg); +271 } +272 +273 /** +274 * Looks for given {@code method} method in stack trace. +275 * Can be used to answer questions like "was this method called from onComplete?". +276 * +277 * @return the caller's StackTraceElement at which he the looked for method was found in the call stack, EMPTY otherwise +278 */ +279 public Optional<StackTraceElement> findCallerMethodInStackTrace(String method) { +280 final Throwable thr = new Throwable(); // gets the stacktrace +281 +282 for (StackTraceElement stackElement : thr.getStackTrace()) { +283 if (stackElement.getMethodName().equals(method)) { +284 return Optional.of(stackElement); +285 } +286 } +287 return Optional.empty(); +288 } +289 +290 // ---- classes ---- +291 +292 /** +293 * {@link Subscriber} implementation which can be steered by test code and asserted on. +294 */ +295 public static class ManualSubscriber<T> extends TestSubscriber<T> { +296 Receptacle<T> received; +297 +298 public ManualSubscriber(TestEnvironment env) { +299 super(env); +300 received = new Receptacle<T>(this.env); +301 } +302 +303 @Override +304 public void onNext(T element) { +305 try { +306 received.add(element); +307 } catch (IllegalStateException ex) { +308 // error message refinement +309 throw new SubscriberBufferOverflowException( +310 String.format("Received more than bufferSize (%d) onNext signals. " + +311 "The Publisher probably emited more signals than expected!", +312 received.QUEUE_SIZE), ex); +313 } +314 } +315 +316 @Override +317 public void onComplete() { +318 received.complete(); +319 } +320 +321 public void request(long elements) { +322 subscription.value().request(elements); +323 } +324 +325 public T requestNextElement() throws InterruptedException { +326 return requestNextElement(env.defaultTimeoutMillis()); +327 } +328 +329 public T requestNextElement(long timeoutMillis) throws InterruptedException { +330 return requestNextElement(timeoutMillis, "Did not receive expected element"); +331 } +332 +333 public T requestNextElement(String errorMsg) throws InterruptedException { +334 return requestNextElement(env.defaultTimeoutMillis(), errorMsg); +335 } +336 +337 public T requestNextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +338 request(1); +339 return nextElement(timeoutMillis, errorMsg); +340 } +341 +342 public Optional<T> requestNextElementOrEndOfStream(String errorMsg) throws InterruptedException { +343 return requestNextElementOrEndOfStream(env.defaultTimeoutMillis(), errorMsg); +344 } +345 +346 public Optional<T> requestNextElementOrEndOfStream(long timeoutMillis) throws InterruptedException { +347 return requestNextElementOrEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +348 } +349 +350 public Optional<T> requestNextElementOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +351 request(1); +352 return nextElementOrEndOfStream(timeoutMillis, errorMsg); +353 } +354 +355 public void requestEndOfStream() throws InterruptedException { +356 requestEndOfStream(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +357 } +358 +359 public void requestEndOfStream(long timeoutMillis) throws InterruptedException { +360 requestEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +361 } +362 +363 public void requestEndOfStream(String errorMsg) throws InterruptedException { +364 requestEndOfStream(env.defaultTimeoutMillis(), errorMsg); +365 } +366 +367 public void requestEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +368 request(1); +369 expectCompletion(timeoutMillis, errorMsg); +370 } +371 +372 public List<T> requestNextElements(long elements) throws InterruptedException { +373 request(elements); +374 return nextElements(elements, env.defaultTimeoutMillis()); +375 } +376 +377 public List<T> requestNextElements(long elements, long timeoutMillis) throws InterruptedException { +378 request(elements); +379 return nextElements(elements, timeoutMillis, String.format("Did not receive %d expected elements", elements)); +380 } +381 +382 public List<T> requestNextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +383 request(elements); +384 return nextElements(elements, timeoutMillis, errorMsg); +385 } +386 +387 public T nextElement() throws InterruptedException { +388 return nextElement(env.defaultTimeoutMillis()); +389 } +390 +391 public T nextElement(long timeoutMillis) throws InterruptedException { +392 return nextElement(timeoutMillis, "Did not receive expected element"); +393 } +394 +395 public T nextElement(String errorMsg) throws InterruptedException { +396 return nextElement(env.defaultTimeoutMillis(), errorMsg); +397 } +398 +399 public T nextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +400 return received.next(timeoutMillis, errorMsg); +401 } +402 +403 public Optional<T> nextElementOrEndOfStream() throws InterruptedException { +404 return nextElementOrEndOfStream(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +405 } +406 +407 public Optional<T> nextElementOrEndOfStream(long timeoutMillis) throws InterruptedException { +408 return nextElementOrEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +409 } +410 +411 public Optional<T> nextElementOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +412 return received.nextOrEndOfStream(timeoutMillis, errorMsg); +413 } +414 +415 public List<T> nextElements(long elements) throws InterruptedException { +416 return nextElements(elements, env.defaultTimeoutMillis(), "Did not receive expected element or completion"); +417 } +418 +419 public List<T> nextElements(long elements, String errorMsg) throws InterruptedException { +420 return nextElements(elements, env.defaultTimeoutMillis(), errorMsg); +421 } +422 +423 public List<T> nextElements(long elements, long timeoutMillis) throws InterruptedException { +424 return nextElements(elements, timeoutMillis, "Did not receive expected element or completion"); +425 } +426 +427 public List<T> nextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +428 return received.nextN(elements, timeoutMillis, errorMsg); +429 } +430 +431 public void expectNext(T expected) throws InterruptedException { +432 expectNext(expected, env.defaultTimeoutMillis()); +433 } +434 +435 public void expectNext(T expected, long timeoutMillis) throws InterruptedException { +436 T received = nextElement(timeoutMillis, "Did not receive expected element on downstream"); +437 if (!received.equals(expected)) { +438 env.flop(String.format("Expected element %s on downstream but received %s", expected, received)); +439 } +440 } +441 +442 public void expectCompletion() throws InterruptedException { +443 expectCompletion(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +444 } +445 +446 public void expectCompletion(long timeoutMillis) throws InterruptedException { +447 expectCompletion(timeoutMillis, "Did not receive expected stream completion"); +448 } +449 +450 public void expectCompletion(String errorMsg) throws InterruptedException { +451 expectCompletion(env.defaultTimeoutMillis(), errorMsg); +452 } +453 +454 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +455 received.expectCompletion(timeoutMillis, errorMsg); +456 } +457 +458 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart) throws Exception { +459 expectErrorWithMessage(expected, requiredMessagePart, env.defaultTimeoutMillis()); +460 } +461 +462 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +463 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart, long timeoutMillis) throws Exception { +464 final E err = expectError(expected, timeoutMillis); +465 final String message = err.getMessage(); +466 assertTrue(message.contains(requiredMessagePart), +467 String.format("Got expected exception [%s] but missing message part [%s], was: %s", +468 err.getClass(), requiredMessagePart, err.getMessage())); +469 } +470 +471 public <E extends Throwable> E expectError(Class<E> expected) throws Exception { +472 return expectError(expected, env.defaultTimeoutMillis()); +473 } +474 +475 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis) throws Exception { +476 return expectError(expected, timeoutMillis, String.format("Expected onError(%s)", expected.getName())); +477 } +478 +479 public <E extends Throwable> E expectError(Class<E> expected, String errorMsg) throws Exception { +480 return expectError(expected, env.defaultTimeoutMillis(), errorMsg); +481 } +482 +483 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis, String errorMsg) throws Exception { +484 return received.expectError(expected, timeoutMillis, errorMsg); +485 } +486 +487 public void expectNone() throws InterruptedException { +488 expectNone(env.defaultTimeoutMillis()); +489 } +490 +491 public void expectNone(String errMsgPrefix) throws InterruptedException { +492 expectNone(env.defaultTimeoutMillis(), errMsgPrefix); +493 } +494 +495 public void expectNone(long withinMillis) throws InterruptedException { +496 expectNone(withinMillis, "Did not expect an element but got element"); +497 } +498 +499 public void expectNone(long withinMillis, String errMsgPrefix) throws InterruptedException { +500 received.expectNone(withinMillis, errMsgPrefix); +501 } +502 +503 } +504 +505 public static class ManualSubscriberWithSubscriptionSupport<T> extends ManualSubscriber<T> { +506 +507 public ManualSubscriberWithSubscriptionSupport(TestEnvironment env) { +508 super(env); +509 } +510 +511 @Override +512 public void onNext(T element) { +513 env.debug(String.format("%s::onNext(%s)", this, element)); +514 if (subscription.isCompleted()) { +515 super.onNext(element); +516 } else { +517 env.flop(String.format("Subscriber::onNext(%s) called before Subscriber::onSubscribe", element)); +518 } +519 } +520 +521 @Override +522 public void onComplete() { +523 env.debug(this + "::onComplete()"); +524 if (subscription.isCompleted()) { +525 super.onComplete(); +526 } else { +527 env.flop("Subscriber::onComplete() called before Subscriber::onSubscribe"); +528 } +529 } +530 +531 @Override +532 public void onSubscribe(Subscription s) { +533 env.debug(String.format("%s::onSubscribe(%s)", this, s)); +534 if (!subscription.isCompleted()) { +535 subscription.complete(s); +536 } else { +537 env.flop("Subscriber::onSubscribe called on an already-subscribed Subscriber"); +538 } +539 } +540 +541 @Override +542 public void onError(Throwable cause) { +543 env.debug(String.format("%s::onError(%s)", this, cause)); +544 if (subscription.isCompleted()) { +545 super.onError(cause); +546 } else { +547 env.flop(cause, String.format("Subscriber::onError(%s) called before Subscriber::onSubscribe", cause)); +548 } +549 } +550 } +551 +552 /** +553 * Similar to {@link org.reactivestreams.tck.TestEnvironment.ManualSubscriberWithSubscriptionSupport} +554 * but does not accumulate values signalled via <code>onNext</code>, thus it can not be used to assert +555 * values signalled to this subscriber. Instead it may be used to quickly drain a given publisher. +556 */ +557 public static class BlackholeSubscriberWithSubscriptionSupport<T> +558 extends ManualSubscriberWithSubscriptionSupport<T> { +559 +560 public BlackholeSubscriberWithSubscriptionSupport(TestEnvironment env) { +561 super(env); +562 } +563 +564 @Override +565 public void onNext(T element) { +566 env.debug(String.format("%s::onNext(%s)", this, element)); +567 if (!subscription.isCompleted()) { +568 env.flop(String.format("Subscriber::onNext(%s) called before Subscriber::onSubscribe", element)); +569 } +570 } +571 +572 @Override +573 public T nextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +574 throw new RuntimeException("Can not expect elements from BlackholeSubscriber, use ManualSubscriber instead!"); +575 } +576 +577 @Override +578 public List<T> nextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +579 throw new RuntimeException("Can not expect elements from BlackholeSubscriber, use ManualSubscriber instead!"); +580 } +581 } +582 +583 public static class TestSubscriber<T> implements Subscriber<T> { +584 final Promise<Subscription> subscription; +585 +586 protected final TestEnvironment env; +587 +588 public TestSubscriber(TestEnvironment env) { +589 this.env = env; +590 subscription = new Promise<Subscription>(env); +591 } +592 +593 @Override +594 public void onError(Throwable cause) { +595 env.flop(cause, String.format("Unexpected Subscriber::onError(%s)", cause)); +596 } +597 +598 @Override +599 public void onComplete() { +600 env.flop("Unexpected Subscriber::onComplete()"); +601 } +602 +603 @Override +604 public void onNext(T element) { +605 env.flop(String.format("Unexpected Subscriber::onNext(%s)", element)); +606 } +607 +608 @Override +609 public void onSubscribe(Subscription subscription) { +610 env.flop(String.format("Unexpected Subscriber::onSubscribe(%s)", subscription)); +611 } +612 +613 public void cancel() { +614 if (subscription.isCompleted()) { +615 subscription.value().cancel(); +616 } else { +617 env.flop("Cannot cancel a subscription before having received it"); +618 } +619 } +620 } +621 +622 public static class ManualPublisher<T> implements Publisher<T> { +623 protected final TestEnvironment env; +624 +625 protected long pendingDemand = 0L; +626 protected Promise<Subscriber<? super T>> subscriber; +627 +628 protected final Receptacle<Long> requests; +629 +630 protected final Latch cancelled; +631 +632 public ManualPublisher(TestEnvironment env) { +633 this.env = env; +634 requests = new Receptacle<Long>(env); +635 cancelled = new Latch(env); +636 subscriber = new Promise<Subscriber<? super T>>(this.env); +637 } +638 +639 @Override +640 public void subscribe(Subscriber<? super T> s) { +641 if (!subscriber.isCompleted()) { +642 subscriber.completeImmediatly(s); +643 +644 Subscription subs = new Subscription() { +645 @Override +646 public void request(long elements) { +647 requests.add(elements); +648 } +649 +650 @Override +651 public void cancel() { +652 cancelled.close(); +653 } +654 }; +655 s.onSubscribe(subs); +656 +657 } else { +658 env.flop("TestPublisher doesn't support more than one Subscriber"); +659 } +660 } +661 +662 public void sendNext(T element) { +663 if (subscriber.isCompleted()) { +664 subscriber.value().onNext(element); +665 } else { +666 env.flop("Cannot sendNext before having a Subscriber"); +667 } +668 } +669 +670 public void sendCompletion() { +671 if (subscriber.isCompleted()) { +672 subscriber.value().onComplete(); +673 } else { +674 env.flop("Cannot sendCompletion before having a Subscriber"); +675 } +676 } +677 +678 public void sendError(Throwable cause) { +679 if (subscriber.isCompleted()) { +680 subscriber.value().onError(cause); +681 } else { +682 env.flop("Cannot sendError before having a Subscriber"); +683 } +684 } +685 +686 public long expectRequest() throws InterruptedException { +687 return expectRequest(env.defaultTimeoutMillis()); +688 } +689 +690 public long expectRequest(long timeoutMillis) throws InterruptedException { +691 long requested = requests.next(timeoutMillis, "Did not receive expected `request` call"); +692 if (requested <= 0) { +693 return env.<Long>flopAndFail(String.format("Requests cannot be zero or negative but received request(%s)", requested)); +694 } else { +695 pendingDemand += requested; +696 return requested; +697 } +698 } +699 +700 public void expectExactRequest(long expected) throws InterruptedException { +701 expectExactRequest(expected, env.defaultTimeoutMillis()); +702 } +703 +704 public void expectExactRequest(long expected, long timeoutMillis) throws InterruptedException { +705 long requested = expectRequest(timeoutMillis); +706 if (requested != expected) { +707 env.flop(String.format("Received `request(%d)` on upstream but expected `request(%d)`", requested, expected)); +708 } +709 pendingDemand += requested; +710 } +711 +712 public void expectNoRequest() throws InterruptedException { +713 expectNoRequest(env.defaultTimeoutMillis()); +714 } +715 +716 public void expectNoRequest(long timeoutMillis) throws InterruptedException { +717 requests.expectNone(timeoutMillis, "Received an unexpected call to: request: "); +718 } +719 +720 public void expectCancelling() throws InterruptedException { +721 expectCancelling(env.defaultTimeoutMillis()); +722 } +723 +724 public void expectCancelling(long timeoutMillis) throws InterruptedException { +725 cancelled.expectClose(timeoutMillis, "Did not receive expected cancelling of upstream subscription"); +726 } +727 } +728 +729 /** +730 * Like a CountDownLatch, but resettable and with some convenience methods +731 */ +732 public static class Latch { +733 private final TestEnvironment env; +734 volatile private CountDownLatch countDownLatch = new CountDownLatch(1); +735 +736 public Latch(TestEnvironment env) { +737 this.env = env; +738 } +739 +740 public void reOpen() { +741 countDownLatch = new CountDownLatch(1); +742 } +743 +744 public boolean isClosed() { +745 return countDownLatch.getCount() == 0; +746 } +747 +748 public void close() { +749 countDownLatch.countDown(); +750 } +751 +752 public void assertClosed(String openErrorMsg) { +753 if (!isClosed()) { +754 env.flop(new ExpectedClosedLatchException(openErrorMsg)); +755 } +756 } +757 +758 public void assertOpen(String closedErrorMsg) { +759 if (isClosed()) { +760 env.flop(new ExpectedOpenLatchException(closedErrorMsg)); +761 } +762 } +763 +764 public void expectClose(String notClosedErrorMsg) throws InterruptedException { +765 expectClose(env.defaultTimeoutMillis(), notClosedErrorMsg); +766 } +767 +768 public void expectClose(long timeoutMillis, String notClosedErrorMsg) throws InterruptedException { +769 countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS); +770 if (countDownLatch.getCount() > 0) { +771 env.flop(String.format("%s within %d ms", notClosedErrorMsg, timeoutMillis)); +772 } +773 } +774 +775 static final class ExpectedOpenLatchException extends RuntimeException { +776 public ExpectedOpenLatchException(String message) { +777 super(message); +778 } +779 } +780 +781 static final class ExpectedClosedLatchException extends RuntimeException { +782 public ExpectedClosedLatchException(String message) { +783 super(message); +784 } +785 } +786 +787 } +788 +789 // simple promise for *one* value, which cannot be reset +790 public static class Promise<T> { +791 private final TestEnvironment env; +792 +793 public static <T> Promise<T> completed(TestEnvironment env, T value) { +794 Promise<T> promise = new Promise<T>(env); +795 promise.completeImmediatly(value); +796 return promise; +797 } +798 +799 public Promise(TestEnvironment env) { +800 this.env = env; +801 } +802 +803 private ArrayBlockingQueue<T> abq = new ArrayBlockingQueue<T>(1); +804 private volatile T _value = null; +805 +806 public T value() { +807 if (isCompleted()) { +808 return _value; +809 } else { +810 env.flop("Cannot access promise value before completion"); +811 return null; +812 } +813 } +814 +815 public boolean isCompleted() { +816 return _value != null; +817 } +818 +819 /** +820 * Allows using expectCompletion to await for completion of the value and complete it _then_ +821 */ +822 public void complete(T value) { +823 abq.add(value); +824 } +825 +826 /** +827 * Completes the promise right away, it is not possible to expectCompletion on a Promise completed this way +828 */ +829 public void completeImmediatly(T value) { +830 complete(value); // complete! +831 _value = value; // immediatly! +832 } +833 +834 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +835 if (!isCompleted()) { +836 T val = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +837 +838 if (val == null) { +839 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +840 } else { +841 _value = val; +842 } +843 } +844 } +845 } +846 +847 // a "Promise" for multiple values, which also supports "end-of-stream reached" +848 public static class Receptacle<T> { +849 final int QUEUE_SIZE = 2 * TEST_BUFFER_SIZE; +850 private final TestEnvironment env; +851 +852 private final ArrayBlockingQueue<Optional<T>> abq = new ArrayBlockingQueue<Optional<T>>(QUEUE_SIZE); +853 +854 private final Latch completedLatch; +855 +856 Receptacle(TestEnvironment env) { +857 this.env = env; +858 this.completedLatch = new Latch(env); +859 } +860 +861 public void add(T value) { +862 completedLatch.assertOpen(String.format("Unexpected element %s received after stream completed", value)); +863 +864 abq.add(Optional.of(value)); +865 } +866 +867 public void complete() { +868 completedLatch.assertOpen("Unexpected additional complete signal received!"); +869 completedLatch.close(); +870 +871 abq.add(Optional.<T>empty()); +872 } +873 +874 public T next(long timeoutMillis, String errorMsg) throws InterruptedException { +875 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +876 +877 if (value == null) { +878 return env.flopAndFail(String.format("%s within %d ms", errorMsg, timeoutMillis)); +879 } else if (value.isDefined()) { +880 return value.get(); +881 } else { +882 return env.flopAndFail("Expected element but got end-of-stream"); +883 } +884 } +885 +886 public Optional<T> nextOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +887 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +888 +889 if (value == null) { +890 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +891 return Optional.empty(); +892 } +893 +894 return value; +895 } +896 +897 /** +898 * @param timeoutMillis total timeout time for awaiting all {@code elements} number of elements +899 */ +900 public List<T> nextN(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +901 List<T> result = new LinkedList<T>(); +902 long remaining = elements; +903 long deadline = System.currentTimeMillis() + timeoutMillis; +904 while (remaining > 0) { +905 long remainingMillis = deadline - System.currentTimeMillis(); +906 +907 result.add(next(remainingMillis, errorMsg)); +908 remaining--; +909 } +910 +911 return result; +912 } +913 +914 +915 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +916 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +917 +918 if (value == null) { +919 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +920 } else if (value.isDefined()) { +921 env.flop(String.format("Expected end-of-stream but got element [%s]", value.get())); +922 } // else, ok +923 } +924 +925 @SuppressWarnings("unchecked") +926 public <E extends Throwable> E expectError(Class<E> clazz, long timeoutMillis, String errorMsg) throws Exception { +927 Thread.sleep(timeoutMillis); +928 +929 if (env.asyncErrors.isEmpty()) { +930 return env.flopAndFail(String.format("%s within %d ms", errorMsg, timeoutMillis)); +931 } else { +932 // ok, there was an expected error +933 Throwable thrown = env.asyncErrors.remove(0); +934 +935 if (clazz.isInstance(thrown)) { +936 return (E) thrown; +937 } else { +938 +939 return env.flopAndFail(String.format("%s within %d ms; Got %s but expected %s", +940 errorMsg, timeoutMillis, thrown.getClass().getCanonicalName(), clazz.getCanonicalName())); +941 } +942 } +943 } +944 +945 public void expectNone(long withinMillis, String errorMsgPrefix) throws InterruptedException { +946 Thread.sleep(withinMillis); +947 Optional<T> value = abq.poll(); +948 +949 if (value == null) { +950 // ok +951 } else if (value.isDefined()) { +952 env.flop(String.format("%s [%s]", errorMsgPrefix, value.get())); +953 } else { +954 env.flop("Expected no element but got end-of-stream"); +955 } +956 } +957 } +958} +959 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck; +002 +003import org.reactivestreams.Publisher; +004import org.reactivestreams.Subscriber; +005import org.reactivestreams.Subscription; +006import org.reactivestreams.tck.support.SubscriberBufferOverflowException; +007import org.reactivestreams.tck.support.Optional; +008 +009import java.util.LinkedList; +010import java.util.List; +011import java.util.concurrent.ArrayBlockingQueue; +012import java.util.concurrent.CopyOnWriteArrayList; +013import java.util.concurrent.CountDownLatch; +014import java.util.concurrent.TimeUnit; +015 +016import static org.testng.Assert.assertTrue; +017import static org.testng.Assert.fail; +018 +019public class TestEnvironment { +020 public static final int TEST_BUFFER_SIZE = 16; +021 +022 private static final String DEFAULT_TIMEOUT_MILLIS_ENV = "DEFAULT_TIMEOUT_MILLIS"; +023 private static final long DEFAULT_TIMEOUT_MILLIS = 100; +024 +025 private final long defaultTimeoutMillis; +026 private final boolean printlnDebug; +027 +028 private CopyOnWriteArrayList<Throwable> asyncErrors = new CopyOnWriteArrayList<Throwable>(); +029 +030 /** +031 * Tests must specify the timeout for expected outcome of asynchronous +032 * interactions. Longer timeout does not invalidate the correctness of +033 * the implementation, but can in some cases result in longer time to +034 * run the tests. +035 * +036 * @param defaultTimeoutMillis default timeout to be used in all expect* methods +037 * @param printlnDebug if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output, +038 * often helpful to pinpoint simple race conditions etc. +039 */ +040 public TestEnvironment(long defaultTimeoutMillis, boolean printlnDebug) { +041 this.defaultTimeoutMillis = defaultTimeoutMillis; +042 this.printlnDebug = printlnDebug; +043 } +044 +045 /** +046 * Tests must specify the timeout for expected outcome of asynchronous +047 * interactions. Longer timeout does not invalidate the correctness of +048 * the implementation, but can in some cases result in longer time to +049 * run the tests. +050 * +051 * @param defaultTimeoutMillis default timeout to be used in all expect* methods +052 */ +053 public TestEnvironment(long defaultTimeoutMillis) { +054 this(defaultTimeoutMillis, false); +055 } +056 +057 /** +058 * Tests must specify the timeout for expected outcome of asynchronous +059 * interactions. Longer timeout does not invalidate the correctness of +060 * the implementation, but can in some cases result in longer time to +061 * run the tests. +062 * +063 * The default timeout for all expect* methods will be obtained by either the env variable {@code DEFAULT_TIMEOUT_MILLIS} +064 * or the default value ({@link TestEnvironment#DEFAULT_TIMEOUT_MILLIS}) will be used. +065 * +066 * @param printlnDebug if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output, +067 * often helpful to pinpoint simple race conditions etc. +068 */ +069 public TestEnvironment(boolean printlnDebug) { +070 this(envDefaultTimeoutMillis(), printlnDebug); +071 } +072 +073 /** +074 * Tests must specify the timeout for expected outcome of asynchronous +075 * interactions. Longer timeout does not invalidate the correctness of +076 * the implementation, but can in some cases result in longer time to +077 * run the tests. +078 * +079 * The default timeout for all expect* methods will be obtained by either the env variable {@code DEFAULT_TIMEOUT_MILLIS} +080 * or the default value ({@link TestEnvironment#DEFAULT_TIMEOUT_MILLIS}) will be used. +081 */ +082 public TestEnvironment() { +083 this(envDefaultTimeoutMillis()); +084 } +085 +086 public long defaultTimeoutMillis() { +087 return defaultTimeoutMillis; +088 } +089 +090 /** +091 * Tries to parse the env variable {@code DEFAULT_TIMEOUT_MILLIS} as long and returns the value if present OR its default value. +092 * +093 * @throws java.lang.IllegalArgumentException when unable to parse the env variable +094 */ +095 public static long envDefaultTimeoutMillis() { +096 final String envMillis = System.getenv(DEFAULT_TIMEOUT_MILLIS_ENV); +097 if (envMillis == null) return DEFAULT_TIMEOUT_MILLIS; +098 else try { +099 return Long.parseLong(envMillis); +100 } catch(NumberFormatException ex) { +101 throw new IllegalArgumentException(String.format("Unable to parse %s env value [%s] as long!", DEFAULT_TIMEOUT_MILLIS_ENV, envMillis), ex); +102 } +103 } +104 +105 /** +106 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +107 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +108 * +109 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +110 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +111 * from the environment using {@code env.dropAsyncError()}. +112 * +113 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +114 */ +115 public void flop(String msg) { +116 try { +117 fail(msg); +118 } catch (Throwable t) { +119 asyncErrors.add(t); +120 } +121 } +122 +123 /** +124 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +125 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +126 * +127 * This overload keeps the passed in throwable as the asyncError, instead of creating an AssertionError for this. +128 * +129 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +130 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +131 * from the environment using {@code env.dropAsyncError()}. +132 * +133 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +134 */ +135 public void flop(Throwable thr, String msg) { +136 try { +137 fail(msg, thr); +138 } catch (Throwable t) { +139 asyncErrors.add(thr); +140 } +141 } +142 +143 /** +144 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +145 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +146 * +147 * This overload keeps the passed in throwable as the asyncError, instead of creating an AssertionError for this. +148 * +149 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +150 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +151 * from the environment using {@code env.dropAsyncError()}. +152 * +153 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +154 */ +155 public void flop(Throwable thr) { +156 try { +157 fail(thr.getMessage(), thr); +158 } catch (Throwable t) { +159 asyncErrors.add(thr); +160 } +161 } +162 +163 /** +164 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +165 * +166 * This method DOES fail the test right away (it tries to, by throwing an AssertionException), +167 * in such it is different from {@link org.reactivestreams.tck.TestEnvironment#flop} which only records the error. +168 * +169 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +170 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +171 * from the environment using {@code env.dropAsyncError()}. +172 * +173 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +174 */ +175 public <T> T flopAndFail(String msg) { +176 try { +177 fail(msg); +178 } catch (Throwable t) { +179 asyncErrors.add(t); +180 fail(msg, t); +181 } +182 return null; // unreachable, the previous block will always exit by throwing +183 } +184 +185 +186 +187 public <T> void subscribe(Publisher<T> pub, TestSubscriber<T> sub) throws InterruptedException { +188 subscribe(pub, sub, defaultTimeoutMillis); +189 } +190 +191 public <T> void subscribe(Publisher<T> pub, TestSubscriber<T> sub, long timeoutMillis) throws InterruptedException { +192 pub.subscribe(sub); +193 sub.subscription.expectCompletion(timeoutMillis, String.format("Could not subscribe %s to Publisher %s", sub, pub)); +194 verifyNoAsyncErrorsNoDelay(); +195 } +196 +197 public <T> ManualSubscriber<T> newBlackholeSubscriber(Publisher<T> pub) throws InterruptedException { +198 ManualSubscriberWithSubscriptionSupport<T> sub = new BlackholeSubscriberWithSubscriptionSupport<T>(this); +199 subscribe(pub, sub, defaultTimeoutMillis()); +200 return sub; +201 } +202 +203 public <T> ManualSubscriber<T> newManualSubscriber(Publisher<T> pub) throws InterruptedException { +204 return newManualSubscriber(pub, defaultTimeoutMillis()); +205 } +206 +207 public <T> ManualSubscriber<T> newManualSubscriber(Publisher<T> pub, long timeoutMillis) throws InterruptedException { +208 ManualSubscriberWithSubscriptionSupport<T> sub = new ManualSubscriberWithSubscriptionSupport<T>(this); +209 subscribe(pub, sub, timeoutMillis); +210 return sub; +211 } +212 +213 public void clearAsyncErrors() { +214 asyncErrors.clear(); +215 } +216 +217 public Throwable dropAsyncError() { +218 try { +219 return asyncErrors.remove(0); +220 } catch (IndexOutOfBoundsException ex) { +221 return null; +222 } +223 } +224 +225 /** +226 * Waits for {@link TestEnvironment#defaultTimeoutMillis()} and then verifies that no asynchronous errors +227 * were signalled pior to, or during that time (by calling {@code flop()}). +228 */ +229 public void verifyNoAsyncErrors() { +230 verifyNoAsyncErrors(defaultTimeoutMillis()); +231 } +232 +233 /** +234 * This version of {@code verifyNoAsyncErrors} should be used when errors still could be signalled +235 * asynchronously during {@link TestEnvironment#defaultTimeoutMillis()} time. +236 * <p></p> +237 * It will immediatly check if any async errors were signaled (using {@link TestEnvironment#flop(String)}, +238 * and if no errors encountered wait for another default timeout as the errors may yet be signalled. +239 * The initial check is performed in order to fail-fast in case of an already failed test. +240 */ +241 public void verifyNoAsyncErrors(long delay) { +242 try { +243 verifyNoAsyncErrorsNoDelay(); +244 +245 Thread.sleep(delay); +246 verifyNoAsyncErrorsNoDelay(); +247 } catch (InterruptedException e) { +248 throw new RuntimeException(e); +249 } +250 } +251 +252 /** +253 * Verifies that no asynchronous errors were signalled pior to calling this method (by calling {@code flop()}). +254 * This version of verifyNoAsyncError <b>does not wait before checking for asynchronous errors</b>, and is to be used +255 * for example in tight loops etc. +256 */ +257 public void verifyNoAsyncErrorsNoDelay() { +258 for (Throwable e : asyncErrors) { +259 if (e instanceof AssertionError) { +260 throw (AssertionError) e; +261 } else { +262 fail(String.format("Async error during test execution: %s", e.getMessage()), e); +263 } +264 } +265 } +266 +267 /** If {@code TestEnvironment#printlnDebug} is true, print debug message to std out. */ +268 public void debug(String msg) { +269 if (printlnDebug) +270 System.out.printf("[TCK-DEBUG] %s%n", msg); +271 } +272 +273 /** +274 * Looks for given {@code method} method in stack trace. +275 * Can be used to answer questions like "was this method called from onComplete?". +276 * +277 * @return the caller's StackTraceElement at which he the looked for method was found in the call stack, EMPTY otherwise +278 */ +279 public Optional<StackTraceElement> findCallerMethodInStackTrace(String method) { +280 final Throwable thr = new Throwable(); // gets the stacktrace +281 +282 for (StackTraceElement stackElement : thr.getStackTrace()) { +283 if (stackElement.getMethodName().equals(method)) { +284 return Optional.of(stackElement); +285 } +286 } +287 return Optional.empty(); +288 } +289 +290 // ---- classes ---- +291 +292 /** +293 * {@link Subscriber} implementation which can be steered by test code and asserted on. +294 */ +295 public static class ManualSubscriber<T> extends TestSubscriber<T> { +296 Receptacle<T> received; +297 +298 public ManualSubscriber(TestEnvironment env) { +299 super(env); +300 received = new Receptacle<T>(this.env); +301 } +302 +303 @Override +304 public void onNext(T element) { +305 try { +306 received.add(element); +307 } catch (IllegalStateException ex) { +308 // error message refinement +309 throw new SubscriberBufferOverflowException( +310 String.format("Received more than bufferSize (%d) onNext signals. " + +311 "The Publisher probably emited more signals than expected!", +312 received.QUEUE_SIZE), ex); +313 } +314 } +315 +316 @Override +317 public void onComplete() { +318 received.complete(); +319 } +320 +321 public void request(long elements) { +322 subscription.value().request(elements); +323 } +324 +325 public T requestNextElement() throws InterruptedException { +326 return requestNextElement(env.defaultTimeoutMillis()); +327 } +328 +329 public T requestNextElement(long timeoutMillis) throws InterruptedException { +330 return requestNextElement(timeoutMillis, "Did not receive expected element"); +331 } +332 +333 public T requestNextElement(String errorMsg) throws InterruptedException { +334 return requestNextElement(env.defaultTimeoutMillis(), errorMsg); +335 } +336 +337 public T requestNextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +338 request(1); +339 return nextElement(timeoutMillis, errorMsg); +340 } +341 +342 public Optional<T> requestNextElementOrEndOfStream(String errorMsg) throws InterruptedException { +343 return requestNextElementOrEndOfStream(env.defaultTimeoutMillis(), errorMsg); +344 } +345 +346 public Optional<T> requestNextElementOrEndOfStream(long timeoutMillis) throws InterruptedException { +347 return requestNextElementOrEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +348 } +349 +350 public Optional<T> requestNextElementOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +351 request(1); +352 return nextElementOrEndOfStream(timeoutMillis, errorMsg); +353 } +354 +355 public void requestEndOfStream() throws InterruptedException { +356 requestEndOfStream(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +357 } +358 +359 public void requestEndOfStream(long timeoutMillis) throws InterruptedException { +360 requestEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +361 } +362 +363 public void requestEndOfStream(String errorMsg) throws InterruptedException { +364 requestEndOfStream(env.defaultTimeoutMillis(), errorMsg); +365 } +366 +367 public void requestEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +368 request(1); +369 expectCompletion(timeoutMillis, errorMsg); +370 } +371 +372 public List<T> requestNextElements(long elements) throws InterruptedException { +373 request(elements); +374 return nextElements(elements, env.defaultTimeoutMillis()); +375 } +376 +377 public List<T> requestNextElements(long elements, long timeoutMillis) throws InterruptedException { +378 request(elements); +379 return nextElements(elements, timeoutMillis, String.format("Did not receive %d expected elements", elements)); +380 } +381 +382 public List<T> requestNextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +383 request(elements); +384 return nextElements(elements, timeoutMillis, errorMsg); +385 } +386 +387 public T nextElement() throws InterruptedException { +388 return nextElement(env.defaultTimeoutMillis()); +389 } +390 +391 public T nextElement(long timeoutMillis) throws InterruptedException { +392 return nextElement(timeoutMillis, "Did not receive expected element"); +393 } +394 +395 public T nextElement(String errorMsg) throws InterruptedException { +396 return nextElement(env.defaultTimeoutMillis(), errorMsg); +397 } +398 +399 public T nextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +400 return received.next(timeoutMillis, errorMsg); +401 } +402 +403 public Optional<T> nextElementOrEndOfStream() throws InterruptedException { +404 return nextElementOrEndOfStream(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +405 } +406 +407 public Optional<T> nextElementOrEndOfStream(long timeoutMillis) throws InterruptedException { +408 return nextElementOrEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +409 } +410 +411 public Optional<T> nextElementOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +412 return received.nextOrEndOfStream(timeoutMillis, errorMsg); +413 } +414 +415 public List<T> nextElements(long elements) throws InterruptedException { +416 return nextElements(elements, env.defaultTimeoutMillis(), "Did not receive expected element or completion"); +417 } +418 +419 public List<T> nextElements(long elements, String errorMsg) throws InterruptedException { +420 return nextElements(elements, env.defaultTimeoutMillis(), errorMsg); +421 } +422 +423 public List<T> nextElements(long elements, long timeoutMillis) throws InterruptedException { +424 return nextElements(elements, timeoutMillis, "Did not receive expected element or completion"); +425 } +426 +427 public List<T> nextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +428 return received.nextN(elements, timeoutMillis, errorMsg); +429 } +430 +431 public void expectNext(T expected) throws InterruptedException { +432 expectNext(expected, env.defaultTimeoutMillis()); +433 } +434 +435 public void expectNext(T expected, long timeoutMillis) throws InterruptedException { +436 T received = nextElement(timeoutMillis, "Did not receive expected element on downstream"); +437 if (!received.equals(expected)) { +438 env.flop(String.format("Expected element %s on downstream but received %s", expected, received)); +439 } +440 } +441 +442 public void expectCompletion() throws InterruptedException { +443 expectCompletion(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +444 } +445 +446 public void expectCompletion(long timeoutMillis) throws InterruptedException { +447 expectCompletion(timeoutMillis, "Did not receive expected stream completion"); +448 } +449 +450 public void expectCompletion(String errorMsg) throws InterruptedException { +451 expectCompletion(env.defaultTimeoutMillis(), errorMsg); +452 } +453 +454 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +455 received.expectCompletion(timeoutMillis, errorMsg); +456 } +457 +458 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart) throws Exception { +459 expectErrorWithMessage(expected, requiredMessagePart, env.defaultTimeoutMillis()); +460 } +461 +462 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +463 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart, long timeoutMillis) throws Exception { +464 final E err = expectError(expected, timeoutMillis); +465 final String message = err.getMessage(); +466 assertTrue(message.contains(requiredMessagePart), +467 String.format("Got expected exception [%s] but missing message part [%s], was: %s", +468 err.getClass(), requiredMessagePart, err.getMessage())); +469 } +470 +471 public <E extends Throwable> E expectError(Class<E> expected) throws Exception { +472 return expectError(expected, env.defaultTimeoutMillis()); +473 } +474 +475 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis) throws Exception { +476 return expectError(expected, timeoutMillis, String.format("Expected onError(%s)", expected.getName())); +477 } +478 +479 public <E extends Throwable> E expectError(Class<E> expected, String errorMsg) throws Exception { +480 return expectError(expected, env.defaultTimeoutMillis(), errorMsg); +481 } +482 +483 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis, String errorMsg) throws Exception { +484 return received.expectError(expected, timeoutMillis, errorMsg); +485 } +486 +487 public void expectNone() throws InterruptedException { +488 expectNone(env.defaultTimeoutMillis()); +489 } +490 +491 public void expectNone(String errMsgPrefix) throws InterruptedException { +492 expectNone(env.defaultTimeoutMillis(), errMsgPrefix); +493 } +494 +495 public void expectNone(long withinMillis) throws InterruptedException { +496 expectNone(withinMillis, "Did not expect an element but got element"); +497 } +498 +499 public void expectNone(long withinMillis, String errMsgPrefix) throws InterruptedException { +500 received.expectNone(withinMillis, errMsgPrefix); +501 } +502 +503 } +504 +505 public static class ManualSubscriberWithSubscriptionSupport<T> extends ManualSubscriber<T> { +506 +507 public ManualSubscriberWithSubscriptionSupport(TestEnvironment env) { +508 super(env); +509 } +510 +511 @Override +512 public void onNext(T element) { +513 env.debug(String.format("%s::onNext(%s)", this, element)); +514 if (subscription.isCompleted()) { +515 super.onNext(element); +516 } else { +517 env.flop(String.format("Subscriber::onNext(%s) called before Subscriber::onSubscribe", element)); +518 } +519 } +520 +521 @Override +522 public void onComplete() { +523 env.debug(this + "::onComplete()"); +524 if (subscription.isCompleted()) { +525 super.onComplete(); +526 } else { +527 env.flop("Subscriber::onComplete() called before Subscriber::onSubscribe"); +528 } +529 } +530 +531 @Override +532 public void onSubscribe(Subscription s) { +533 env.debug(String.format("%s::onSubscribe(%s)", this, s)); +534 if (!subscription.isCompleted()) { +535 subscription.complete(s); +536 } else { +537 env.flop("Subscriber::onSubscribe called on an already-subscribed Subscriber"); +538 } +539 } +540 +541 @Override +542 public void onError(Throwable cause) { +543 env.debug(String.format("%s::onError(%s)", this, cause)); +544 if (subscription.isCompleted()) { +545 super.onError(cause); +546 } else { +547 env.flop(cause, String.format("Subscriber::onError(%s) called before Subscriber::onSubscribe", cause)); +548 } +549 } +550 } +551 +552 /** +553 * Similar to {@link org.reactivestreams.tck.TestEnvironment.ManualSubscriberWithSubscriptionSupport} +554 * but does not accumulate values signalled via <code>onNext</code>, thus it can not be used to assert +555 * values signalled to this subscriber. Instead it may be used to quickly drain a given publisher. +556 */ +557 public static class BlackholeSubscriberWithSubscriptionSupport<T> +558 extends ManualSubscriberWithSubscriptionSupport<T> { +559 +560 public BlackholeSubscriberWithSubscriptionSupport(TestEnvironment env) { +561 super(env); +562 } +563 +564 @Override +565 public void onNext(T element) { +566 env.debug(String.format("%s::onNext(%s)", this, element)); +567 if (!subscription.isCompleted()) { +568 env.flop(String.format("Subscriber::onNext(%s) called before Subscriber::onSubscribe", element)); +569 } +570 } +571 +572 @Override +573 public T nextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +574 throw new RuntimeException("Can not expect elements from BlackholeSubscriber, use ManualSubscriber instead!"); +575 } +576 +577 @Override +578 public List<T> nextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +579 throw new RuntimeException("Can not expect elements from BlackholeSubscriber, use ManualSubscriber instead!"); +580 } +581 } +582 +583 public static class TestSubscriber<T> implements Subscriber<T> { +584 final Promise<Subscription> subscription; +585 +586 protected final TestEnvironment env; +587 +588 public TestSubscriber(TestEnvironment env) { +589 this.env = env; +590 subscription = new Promise<Subscription>(env); +591 } +592 +593 @Override +594 public void onError(Throwable cause) { +595 env.flop(cause, String.format("Unexpected Subscriber::onError(%s)", cause)); +596 } +597 +598 @Override +599 public void onComplete() { +600 env.flop("Unexpected Subscriber::onComplete()"); +601 } +602 +603 @Override +604 public void onNext(T element) { +605 env.flop(String.format("Unexpected Subscriber::onNext(%s)", element)); +606 } +607 +608 @Override +609 public void onSubscribe(Subscription subscription) { +610 env.flop(String.format("Unexpected Subscriber::onSubscribe(%s)", subscription)); +611 } +612 +613 public void cancel() { +614 if (subscription.isCompleted()) { +615 subscription.value().cancel(); +616 } else { +617 env.flop("Cannot cancel a subscription before having received it"); +618 } +619 } +620 } +621 +622 public static class ManualPublisher<T> implements Publisher<T> { +623 protected final TestEnvironment env; +624 +625 protected long pendingDemand = 0L; +626 protected Promise<Subscriber<? super T>> subscriber; +627 +628 protected final Receptacle<Long> requests; +629 +630 protected final Latch cancelled; +631 +632 public ManualPublisher(TestEnvironment env) { +633 this.env = env; +634 requests = new Receptacle<Long>(env); +635 cancelled = new Latch(env); +636 subscriber = new Promise<Subscriber<? super T>>(this.env); +637 } +638 +639 @Override +640 public void subscribe(Subscriber<? super T> s) { +641 if (!subscriber.isCompleted()) { +642 subscriber.completeImmediatly(s); +643 +644 Subscription subs = new Subscription() { +645 @Override +646 public void request(long elements) { +647 requests.add(elements); +648 } +649 +650 @Override +651 public void cancel() { +652 cancelled.close(); +653 } +654 }; +655 s.onSubscribe(subs); +656 +657 } else { +658 env.flop("TestPublisher doesn't support more than one Subscriber"); +659 } +660 } +661 +662 public void sendNext(T element) { +663 if (subscriber.isCompleted()) { +664 subscriber.value().onNext(element); +665 } else { +666 env.flop("Cannot sendNext before having a Subscriber"); +667 } +668 } +669 +670 public void sendCompletion() { +671 if (subscriber.isCompleted()) { +672 subscriber.value().onComplete(); +673 } else { +674 env.flop("Cannot sendCompletion before having a Subscriber"); +675 } +676 } +677 +678 public void sendError(Throwable cause) { +679 if (subscriber.isCompleted()) { +680 subscriber.value().onError(cause); +681 } else { +682 env.flop("Cannot sendError before having a Subscriber"); +683 } +684 } +685 +686 public long expectRequest() throws InterruptedException { +687 return expectRequest(env.defaultTimeoutMillis()); +688 } +689 +690 public long expectRequest(long timeoutMillis) throws InterruptedException { +691 long requested = requests.next(timeoutMillis, "Did not receive expected `request` call"); +692 if (requested <= 0) { +693 return env.<Long>flopAndFail(String.format("Requests cannot be zero or negative but received request(%s)", requested)); +694 } else { +695 pendingDemand += requested; +696 return requested; +697 } +698 } +699 +700 public void expectExactRequest(long expected) throws InterruptedException { +701 expectExactRequest(expected, env.defaultTimeoutMillis()); +702 } +703 +704 public void expectExactRequest(long expected, long timeoutMillis) throws InterruptedException { +705 long requested = expectRequest(timeoutMillis); +706 if (requested != expected) { +707 env.flop(String.format("Received `request(%d)` on upstream but expected `request(%d)`", requested, expected)); +708 } +709 pendingDemand += requested; +710 } +711 +712 public void expectNoRequest() throws InterruptedException { +713 expectNoRequest(env.defaultTimeoutMillis()); +714 } +715 +716 public void expectNoRequest(long timeoutMillis) throws InterruptedException { +717 requests.expectNone(timeoutMillis, "Received an unexpected call to: request: "); +718 } +719 +720 public void expectCancelling() throws InterruptedException { +721 expectCancelling(env.defaultTimeoutMillis()); +722 } +723 +724 public void expectCancelling(long timeoutMillis) throws InterruptedException { +725 cancelled.expectClose(timeoutMillis, "Did not receive expected cancelling of upstream subscription"); +726 } +727 } +728 +729 /** +730 * Like a CountDownLatch, but resettable and with some convenience methods +731 */ +732 public static class Latch { +733 private final TestEnvironment env; +734 volatile private CountDownLatch countDownLatch = new CountDownLatch(1); +735 +736 public Latch(TestEnvironment env) { +737 this.env = env; +738 } +739 +740 public void reOpen() { +741 countDownLatch = new CountDownLatch(1); +742 } +743 +744 public boolean isClosed() { +745 return countDownLatch.getCount() == 0; +746 } +747 +748 public void close() { +749 countDownLatch.countDown(); +750 } +751 +752 public void assertClosed(String openErrorMsg) { +753 if (!isClosed()) { +754 env.flop(new ExpectedClosedLatchException(openErrorMsg)); +755 } +756 } +757 +758 public void assertOpen(String closedErrorMsg) { +759 if (isClosed()) { +760 env.flop(new ExpectedOpenLatchException(closedErrorMsg)); +761 } +762 } +763 +764 public void expectClose(String notClosedErrorMsg) throws InterruptedException { +765 expectClose(env.defaultTimeoutMillis(), notClosedErrorMsg); +766 } +767 +768 public void expectClose(long timeoutMillis, String notClosedErrorMsg) throws InterruptedException { +769 countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS); +770 if (countDownLatch.getCount() > 0) { +771 env.flop(String.format("%s within %d ms", notClosedErrorMsg, timeoutMillis)); +772 } +773 } +774 +775 static final class ExpectedOpenLatchException extends RuntimeException { +776 public ExpectedOpenLatchException(String message) { +777 super(message); +778 } +779 } +780 +781 static final class ExpectedClosedLatchException extends RuntimeException { +782 public ExpectedClosedLatchException(String message) { +783 super(message); +784 } +785 } +786 +787 } +788 +789 // simple promise for *one* value, which cannot be reset +790 public static class Promise<T> { +791 private final TestEnvironment env; +792 +793 public static <T> Promise<T> completed(TestEnvironment env, T value) { +794 Promise<T> promise = new Promise<T>(env); +795 promise.completeImmediatly(value); +796 return promise; +797 } +798 +799 public Promise(TestEnvironment env) { +800 this.env = env; +801 } +802 +803 private ArrayBlockingQueue<T> abq = new ArrayBlockingQueue<T>(1); +804 private volatile T _value = null; +805 +806 public T value() { +807 if (isCompleted()) { +808 return _value; +809 } else { +810 env.flop("Cannot access promise value before completion"); +811 return null; +812 } +813 } +814 +815 public boolean isCompleted() { +816 return _value != null; +817 } +818 +819 /** +820 * Allows using expectCompletion to await for completion of the value and complete it _then_ +821 */ +822 public void complete(T value) { +823 abq.add(value); +824 } +825 +826 /** +827 * Completes the promise right away, it is not possible to expectCompletion on a Promise completed this way +828 */ +829 public void completeImmediatly(T value) { +830 complete(value); // complete! +831 _value = value; // immediatly! +832 } +833 +834 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +835 if (!isCompleted()) { +836 T val = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +837 +838 if (val == null) { +839 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +840 } else { +841 _value = val; +842 } +843 } +844 } +845 } +846 +847 // a "Promise" for multiple values, which also supports "end-of-stream reached" +848 public static class Receptacle<T> { +849 final int QUEUE_SIZE = 2 * TEST_BUFFER_SIZE; +850 private final TestEnvironment env; +851 +852 private final ArrayBlockingQueue<Optional<T>> abq = new ArrayBlockingQueue<Optional<T>>(QUEUE_SIZE); +853 +854 private final Latch completedLatch; +855 +856 Receptacle(TestEnvironment env) { +857 this.env = env; +858 this.completedLatch = new Latch(env); +859 } +860 +861 public void add(T value) { +862 completedLatch.assertOpen(String.format("Unexpected element %s received after stream completed", value)); +863 +864 abq.add(Optional.of(value)); +865 } +866 +867 public void complete() { +868 completedLatch.assertOpen("Unexpected additional complete signal received!"); +869 completedLatch.close(); +870 +871 abq.add(Optional.<T>empty()); +872 } +873 +874 public T next(long timeoutMillis, String errorMsg) throws InterruptedException { +875 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +876 +877 if (value == null) { +878 return env.flopAndFail(String.format("%s within %d ms", errorMsg, timeoutMillis)); +879 } else if (value.isDefined()) { +880 return value.get(); +881 } else { +882 return env.flopAndFail("Expected element but got end-of-stream"); +883 } +884 } +885 +886 public Optional<T> nextOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +887 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +888 +889 if (value == null) { +890 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +891 return Optional.empty(); +892 } +893 +894 return value; +895 } +896 +897 /** +898 * @param timeoutMillis total timeout time for awaiting all {@code elements} number of elements +899 */ +900 public List<T> nextN(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +901 List<T> result = new LinkedList<T>(); +902 long remaining = elements; +903 long deadline = System.currentTimeMillis() + timeoutMillis; +904 while (remaining > 0) { +905 long remainingMillis = deadline - System.currentTimeMillis(); +906 +907 result.add(next(remainingMillis, errorMsg)); +908 remaining--; +909 } +910 +911 return result; +912 } +913 +914 +915 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +916 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +917 +918 if (value == null) { +919 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +920 } else if (value.isDefined()) { +921 env.flop(String.format("Expected end-of-stream but got element [%s]", value.get())); +922 } // else, ok +923 } +924 +925 @SuppressWarnings("unchecked") +926 public <E extends Throwable> E expectError(Class<E> clazz, long timeoutMillis, String errorMsg) throws Exception { +927 Thread.sleep(timeoutMillis); +928 +929 if (env.asyncErrors.isEmpty()) { +930 return env.flopAndFail(String.format("%s within %d ms", errorMsg, timeoutMillis)); +931 } else { +932 // ok, there was an expected error +933 Throwable thrown = env.asyncErrors.remove(0); +934 +935 if (clazz.isInstance(thrown)) { +936 return (E) thrown; +937 } else { +938 +939 return env.flopAndFail(String.format("%s within %d ms; Got %s but expected %s", +940 errorMsg, timeoutMillis, thrown.getClass().getCanonicalName(), clazz.getCanonicalName())); +941 } +942 } +943 } +944 +945 public void expectNone(long withinMillis, String errorMsgPrefix) throws InterruptedException { +946 Thread.sleep(withinMillis); +947 Optional<T> value = abq.poll(); +948 +949 if (value == null) { +950 // ok +951 } else if (value.isDefined()) { +952 env.flop(String.format("%s [%s]", errorMsgPrefix, value.get())); +953 } else { +954 env.flop("Expected no element but got end-of-stream"); +955 } +956 } +957 } +958} +959 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck; +002 +003import org.reactivestreams.Publisher; +004import org.reactivestreams.Subscriber; +005import org.reactivestreams.Subscription; +006import org.reactivestreams.tck.support.SubscriberBufferOverflowException; +007import org.reactivestreams.tck.support.Optional; +008 +009import java.util.LinkedList; +010import java.util.List; +011import java.util.concurrent.ArrayBlockingQueue; +012import java.util.concurrent.CopyOnWriteArrayList; +013import java.util.concurrent.CountDownLatch; +014import java.util.concurrent.TimeUnit; +015 +016import static org.testng.Assert.assertTrue; +017import static org.testng.Assert.fail; +018 +019public class TestEnvironment { +020 public static final int TEST_BUFFER_SIZE = 16; +021 +022 private static final String DEFAULT_TIMEOUT_MILLIS_ENV = "DEFAULT_TIMEOUT_MILLIS"; +023 private static final long DEFAULT_TIMEOUT_MILLIS = 100; +024 +025 private final long defaultTimeoutMillis; +026 private final boolean printlnDebug; +027 +028 private CopyOnWriteArrayList<Throwable> asyncErrors = new CopyOnWriteArrayList<Throwable>(); +029 +030 /** +031 * Tests must specify the timeout for expected outcome of asynchronous +032 * interactions. Longer timeout does not invalidate the correctness of +033 * the implementation, but can in some cases result in longer time to +034 * run the tests. +035 * +036 * @param defaultTimeoutMillis default timeout to be used in all expect* methods +037 * @param printlnDebug if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output, +038 * often helpful to pinpoint simple race conditions etc. +039 */ +040 public TestEnvironment(long defaultTimeoutMillis, boolean printlnDebug) { +041 this.defaultTimeoutMillis = defaultTimeoutMillis; +042 this.printlnDebug = printlnDebug; +043 } +044 +045 /** +046 * Tests must specify the timeout for expected outcome of asynchronous +047 * interactions. Longer timeout does not invalidate the correctness of +048 * the implementation, but can in some cases result in longer time to +049 * run the tests. +050 * +051 * @param defaultTimeoutMillis default timeout to be used in all expect* methods +052 */ +053 public TestEnvironment(long defaultTimeoutMillis) { +054 this(defaultTimeoutMillis, false); +055 } +056 +057 /** +058 * Tests must specify the timeout for expected outcome of asynchronous +059 * interactions. Longer timeout does not invalidate the correctness of +060 * the implementation, but can in some cases result in longer time to +061 * run the tests. +062 * +063 * The default timeout for all expect* methods will be obtained by either the env variable {@code DEFAULT_TIMEOUT_MILLIS} +064 * or the default value ({@link TestEnvironment#DEFAULT_TIMEOUT_MILLIS}) will be used. +065 * +066 * @param printlnDebug if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output, +067 * often helpful to pinpoint simple race conditions etc. +068 */ +069 public TestEnvironment(boolean printlnDebug) { +070 this(envDefaultTimeoutMillis(), printlnDebug); +071 } +072 +073 /** +074 * Tests must specify the timeout for expected outcome of asynchronous +075 * interactions. Longer timeout does not invalidate the correctness of +076 * the implementation, but can in some cases result in longer time to +077 * run the tests. +078 * +079 * The default timeout for all expect* methods will be obtained by either the env variable {@code DEFAULT_TIMEOUT_MILLIS} +080 * or the default value ({@link TestEnvironment#DEFAULT_TIMEOUT_MILLIS}) will be used. +081 */ +082 public TestEnvironment() { +083 this(envDefaultTimeoutMillis()); +084 } +085 +086 public long defaultTimeoutMillis() { +087 return defaultTimeoutMillis; +088 } +089 +090 /** +091 * Tries to parse the env variable {@code DEFAULT_TIMEOUT_MILLIS} as long and returns the value if present OR its default value. +092 * +093 * @throws java.lang.IllegalArgumentException when unable to parse the env variable +094 */ +095 public static long envDefaultTimeoutMillis() { +096 final String envMillis = System.getenv(DEFAULT_TIMEOUT_MILLIS_ENV); +097 if (envMillis == null) return DEFAULT_TIMEOUT_MILLIS; +098 else try { +099 return Long.parseLong(envMillis); +100 } catch(NumberFormatException ex) { +101 throw new IllegalArgumentException(String.format("Unable to parse %s env value [%s] as long!", DEFAULT_TIMEOUT_MILLIS_ENV, envMillis), ex); +102 } +103 } +104 +105 /** +106 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +107 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +108 * +109 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +110 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +111 * from the environment using {@code env.dropAsyncError()}. +112 * +113 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +114 */ +115 public void flop(String msg) { +116 try { +117 fail(msg); +118 } catch (Throwable t) { +119 asyncErrors.add(t); +120 } +121 } +122 +123 /** +124 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +125 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +126 * +127 * This overload keeps the passed in throwable as the asyncError, instead of creating an AssertionError for this. +128 * +129 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +130 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +131 * from the environment using {@code env.dropAsyncError()}. +132 * +133 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +134 */ +135 public void flop(Throwable thr, String msg) { +136 try { +137 fail(msg, thr); +138 } catch (Throwable t) { +139 asyncErrors.add(thr); +140 } +141 } +142 +143 /** +144 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +145 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +146 * +147 * This overload keeps the passed in throwable as the asyncError, instead of creating an AssertionError for this. +148 * +149 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +150 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +151 * from the environment using {@code env.dropAsyncError()}. +152 * +153 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +154 */ +155 public void flop(Throwable thr) { +156 try { +157 fail(thr.getMessage(), thr); +158 } catch (Throwable t) { +159 asyncErrors.add(thr); +160 } +161 } +162 +163 /** +164 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +165 * +166 * This method DOES fail the test right away (it tries to, by throwing an AssertionException), +167 * in such it is different from {@link org.reactivestreams.tck.TestEnvironment#flop} which only records the error. +168 * +169 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +170 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +171 * from the environment using {@code env.dropAsyncError()}. +172 * +173 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +174 */ +175 public <T> T flopAndFail(String msg) { +176 try { +177 fail(msg); +178 } catch (Throwable t) { +179 asyncErrors.add(t); +180 fail(msg, t); +181 } +182 return null; // unreachable, the previous block will always exit by throwing +183 } +184 +185 +186 +187 public <T> void subscribe(Publisher<T> pub, TestSubscriber<T> sub) throws InterruptedException { +188 subscribe(pub, sub, defaultTimeoutMillis); +189 } +190 +191 public <T> void subscribe(Publisher<T> pub, TestSubscriber<T> sub, long timeoutMillis) throws InterruptedException { +192 pub.subscribe(sub); +193 sub.subscription.expectCompletion(timeoutMillis, String.format("Could not subscribe %s to Publisher %s", sub, pub)); +194 verifyNoAsyncErrorsNoDelay(); +195 } +196 +197 public <T> ManualSubscriber<T> newBlackholeSubscriber(Publisher<T> pub) throws InterruptedException { +198 ManualSubscriberWithSubscriptionSupport<T> sub = new BlackholeSubscriberWithSubscriptionSupport<T>(this); +199 subscribe(pub, sub, defaultTimeoutMillis()); +200 return sub; +201 } +202 +203 public <T> ManualSubscriber<T> newManualSubscriber(Publisher<T> pub) throws InterruptedException { +204 return newManualSubscriber(pub, defaultTimeoutMillis()); +205 } +206 +207 public <T> ManualSubscriber<T> newManualSubscriber(Publisher<T> pub, long timeoutMillis) throws InterruptedException { +208 ManualSubscriberWithSubscriptionSupport<T> sub = new ManualSubscriberWithSubscriptionSupport<T>(this); +209 subscribe(pub, sub, timeoutMillis); +210 return sub; +211 } +212 +213 public void clearAsyncErrors() { +214 asyncErrors.clear(); +215 } +216 +217 public Throwable dropAsyncError() { +218 try { +219 return asyncErrors.remove(0); +220 } catch (IndexOutOfBoundsException ex) { +221 return null; +222 } +223 } +224 +225 /** +226 * Waits for {@link TestEnvironment#defaultTimeoutMillis()} and then verifies that no asynchronous errors +227 * were signalled pior to, or during that time (by calling {@code flop()}). +228 */ +229 public void verifyNoAsyncErrors() { +230 verifyNoAsyncErrors(defaultTimeoutMillis()); +231 } +232 +233 /** +234 * This version of {@code verifyNoAsyncErrors} should be used when errors still could be signalled +235 * asynchronously during {@link TestEnvironment#defaultTimeoutMillis()} time. +236 * <p></p> +237 * It will immediatly check if any async errors were signaled (using {@link TestEnvironment#flop(String)}, +238 * and if no errors encountered wait for another default timeout as the errors may yet be signalled. +239 * The initial check is performed in order to fail-fast in case of an already failed test. +240 */ +241 public void verifyNoAsyncErrors(long delay) { +242 try { +243 verifyNoAsyncErrorsNoDelay(); +244 +245 Thread.sleep(delay); +246 verifyNoAsyncErrorsNoDelay(); +247 } catch (InterruptedException e) { +248 throw new RuntimeException(e); +249 } +250 } +251 +252 /** +253 * Verifies that no asynchronous errors were signalled pior to calling this method (by calling {@code flop()}). +254 * This version of verifyNoAsyncError <b>does not wait before checking for asynchronous errors</b>, and is to be used +255 * for example in tight loops etc. +256 */ +257 public void verifyNoAsyncErrorsNoDelay() { +258 for (Throwable e : asyncErrors) { +259 if (e instanceof AssertionError) { +260 throw (AssertionError) e; +261 } else { +262 fail(String.format("Async error during test execution: %s", e.getMessage()), e); +263 } +264 } +265 } +266 +267 /** If {@code TestEnvironment#printlnDebug} is true, print debug message to std out. */ +268 public void debug(String msg) { +269 if (printlnDebug) +270 System.out.printf("[TCK-DEBUG] %s%n", msg); +271 } +272 +273 /** +274 * Looks for given {@code method} method in stack trace. +275 * Can be used to answer questions like "was this method called from onComplete?". +276 * +277 * @return the caller's StackTraceElement at which he the looked for method was found in the call stack, EMPTY otherwise +278 */ +279 public Optional<StackTraceElement> findCallerMethodInStackTrace(String method) { +280 final Throwable thr = new Throwable(); // gets the stacktrace +281 +282 for (StackTraceElement stackElement : thr.getStackTrace()) { +283 if (stackElement.getMethodName().equals(method)) { +284 return Optional.of(stackElement); +285 } +286 } +287 return Optional.empty(); +288 } +289 +290 // ---- classes ---- +291 +292 /** +293 * {@link Subscriber} implementation which can be steered by test code and asserted on. +294 */ +295 public static class ManualSubscriber<T> extends TestSubscriber<T> { +296 Receptacle<T> received; +297 +298 public ManualSubscriber(TestEnvironment env) { +299 super(env); +300 received = new Receptacle<T>(this.env); +301 } +302 +303 @Override +304 public void onNext(T element) { +305 try { +306 received.add(element); +307 } catch (IllegalStateException ex) { +308 // error message refinement +309 throw new SubscriberBufferOverflowException( +310 String.format("Received more than bufferSize (%d) onNext signals. " + +311 "The Publisher probably emited more signals than expected!", +312 received.QUEUE_SIZE), ex); +313 } +314 } +315 +316 @Override +317 public void onComplete() { +318 received.complete(); +319 } +320 +321 public void request(long elements) { +322 subscription.value().request(elements); +323 } +324 +325 public T requestNextElement() throws InterruptedException { +326 return requestNextElement(env.defaultTimeoutMillis()); +327 } +328 +329 public T requestNextElement(long timeoutMillis) throws InterruptedException { +330 return requestNextElement(timeoutMillis, "Did not receive expected element"); +331 } +332 +333 public T requestNextElement(String errorMsg) throws InterruptedException { +334 return requestNextElement(env.defaultTimeoutMillis(), errorMsg); +335 } +336 +337 public T requestNextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +338 request(1); +339 return nextElement(timeoutMillis, errorMsg); +340 } +341 +342 public Optional<T> requestNextElementOrEndOfStream(String errorMsg) throws InterruptedException { +343 return requestNextElementOrEndOfStream(env.defaultTimeoutMillis(), errorMsg); +344 } +345 +346 public Optional<T> requestNextElementOrEndOfStream(long timeoutMillis) throws InterruptedException { +347 return requestNextElementOrEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +348 } +349 +350 public Optional<T> requestNextElementOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +351 request(1); +352 return nextElementOrEndOfStream(timeoutMillis, errorMsg); +353 } +354 +355 public void requestEndOfStream() throws InterruptedException { +356 requestEndOfStream(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +357 } +358 +359 public void requestEndOfStream(long timeoutMillis) throws InterruptedException { +360 requestEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +361 } +362 +363 public void requestEndOfStream(String errorMsg) throws InterruptedException { +364 requestEndOfStream(env.defaultTimeoutMillis(), errorMsg); +365 } +366 +367 public void requestEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +368 request(1); +369 expectCompletion(timeoutMillis, errorMsg); +370 } +371 +372 public List<T> requestNextElements(long elements) throws InterruptedException { +373 request(elements); +374 return nextElements(elements, env.defaultTimeoutMillis()); +375 } +376 +377 public List<T> requestNextElements(long elements, long timeoutMillis) throws InterruptedException { +378 request(elements); +379 return nextElements(elements, timeoutMillis, String.format("Did not receive %d expected elements", elements)); +380 } +381 +382 public List<T> requestNextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +383 request(elements); +384 return nextElements(elements, timeoutMillis, errorMsg); +385 } +386 +387 public T nextElement() throws InterruptedException { +388 return nextElement(env.defaultTimeoutMillis()); +389 } +390 +391 public T nextElement(long timeoutMillis) throws InterruptedException { +392 return nextElement(timeoutMillis, "Did not receive expected element"); +393 } +394 +395 public T nextElement(String errorMsg) throws InterruptedException { +396 return nextElement(env.defaultTimeoutMillis(), errorMsg); +397 } +398 +399 public T nextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +400 return received.next(timeoutMillis, errorMsg); +401 } +402 +403 public Optional<T> nextElementOrEndOfStream() throws InterruptedException { +404 return nextElementOrEndOfStream(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +405 } +406 +407 public Optional<T> nextElementOrEndOfStream(long timeoutMillis) throws InterruptedException { +408 return nextElementOrEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +409 } +410 +411 public Optional<T> nextElementOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +412 return received.nextOrEndOfStream(timeoutMillis, errorMsg); +413 } +414 +415 public List<T> nextElements(long elements) throws InterruptedException { +416 return nextElements(elements, env.defaultTimeoutMillis(), "Did not receive expected element or completion"); +417 } +418 +419 public List<T> nextElements(long elements, String errorMsg) throws InterruptedException { +420 return nextElements(elements, env.defaultTimeoutMillis(), errorMsg); +421 } +422 +423 public List<T> nextElements(long elements, long timeoutMillis) throws InterruptedException { +424 return nextElements(elements, timeoutMillis, "Did not receive expected element or completion"); +425 } +426 +427 public List<T> nextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +428 return received.nextN(elements, timeoutMillis, errorMsg); +429 } +430 +431 public void expectNext(T expected) throws InterruptedException { +432 expectNext(expected, env.defaultTimeoutMillis()); +433 } +434 +435 public void expectNext(T expected, long timeoutMillis) throws InterruptedException { +436 T received = nextElement(timeoutMillis, "Did not receive expected element on downstream"); +437 if (!received.equals(expected)) { +438 env.flop(String.format("Expected element %s on downstream but received %s", expected, received)); +439 } +440 } +441 +442 public void expectCompletion() throws InterruptedException { +443 expectCompletion(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +444 } +445 +446 public void expectCompletion(long timeoutMillis) throws InterruptedException { +447 expectCompletion(timeoutMillis, "Did not receive expected stream completion"); +448 } +449 +450 public void expectCompletion(String errorMsg) throws InterruptedException { +451 expectCompletion(env.defaultTimeoutMillis(), errorMsg); +452 } +453 +454 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +455 received.expectCompletion(timeoutMillis, errorMsg); +456 } +457 +458 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart) throws Exception { +459 expectErrorWithMessage(expected, requiredMessagePart, env.defaultTimeoutMillis()); +460 } +461 +462 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +463 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart, long timeoutMillis) throws Exception { +464 final E err = expectError(expected, timeoutMillis); +465 final String message = err.getMessage(); +466 assertTrue(message.contains(requiredMessagePart), +467 String.format("Got expected exception [%s] but missing message part [%s], was: %s", +468 err.getClass(), requiredMessagePart, err.getMessage())); +469 } +470 +471 public <E extends Throwable> E expectError(Class<E> expected) throws Exception { +472 return expectError(expected, env.defaultTimeoutMillis()); +473 } +474 +475 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis) throws Exception { +476 return expectError(expected, timeoutMillis, String.format("Expected onError(%s)", expected.getName())); +477 } +478 +479 public <E extends Throwable> E expectError(Class<E> expected, String errorMsg) throws Exception { +480 return expectError(expected, env.defaultTimeoutMillis(), errorMsg); +481 } +482 +483 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis, String errorMsg) throws Exception { +484 return received.expectError(expected, timeoutMillis, errorMsg); +485 } +486 +487 public void expectNone() throws InterruptedException { +488 expectNone(env.defaultTimeoutMillis()); +489 } +490 +491 public void expectNone(String errMsgPrefix) throws InterruptedException { +492 expectNone(env.defaultTimeoutMillis(), errMsgPrefix); +493 } +494 +495 public void expectNone(long withinMillis) throws InterruptedException { +496 expectNone(withinMillis, "Did not expect an element but got element"); +497 } +498 +499 public void expectNone(long withinMillis, String errMsgPrefix) throws InterruptedException { +500 received.expectNone(withinMillis, errMsgPrefix); +501 } +502 +503 } +504 +505 public static class ManualSubscriberWithSubscriptionSupport<T> extends ManualSubscriber<T> { +506 +507 public ManualSubscriberWithSubscriptionSupport(TestEnvironment env) { +508 super(env); +509 } +510 +511 @Override +512 public void onNext(T element) { +513 env.debug(String.format("%s::onNext(%s)", this, element)); +514 if (subscription.isCompleted()) { +515 super.onNext(element); +516 } else { +517 env.flop(String.format("Subscriber::onNext(%s) called before Subscriber::onSubscribe", element)); +518 } +519 } +520 +521 @Override +522 public void onComplete() { +523 env.debug(this + "::onComplete()"); +524 if (subscription.isCompleted()) { +525 super.onComplete(); +526 } else { +527 env.flop("Subscriber::onComplete() called before Subscriber::onSubscribe"); +528 } +529 } +530 +531 @Override +532 public void onSubscribe(Subscription s) { +533 env.debug(String.format("%s::onSubscribe(%s)", this, s)); +534 if (!subscription.isCompleted()) { +535 subscription.complete(s); +536 } else { +537 env.flop("Subscriber::onSubscribe called on an already-subscribed Subscriber"); +538 } +539 } +540 +541 @Override +542 public void onError(Throwable cause) { +543 env.debug(String.format("%s::onError(%s)", this, cause)); +544 if (subscription.isCompleted()) { +545 super.onError(cause); +546 } else { +547 env.flop(cause, String.format("Subscriber::onError(%s) called before Subscriber::onSubscribe", cause)); +548 } +549 } +550 } +551 +552 /** +553 * Similar to {@link org.reactivestreams.tck.TestEnvironment.ManualSubscriberWithSubscriptionSupport} +554 * but does not accumulate values signalled via <code>onNext</code>, thus it can not be used to assert +555 * values signalled to this subscriber. Instead it may be used to quickly drain a given publisher. +556 */ +557 public static class BlackholeSubscriberWithSubscriptionSupport<T> +558 extends ManualSubscriberWithSubscriptionSupport<T> { +559 +560 public BlackholeSubscriberWithSubscriptionSupport(TestEnvironment env) { +561 super(env); +562 } +563 +564 @Override +565 public void onNext(T element) { +566 env.debug(String.format("%s::onNext(%s)", this, element)); +567 if (!subscription.isCompleted()) { +568 env.flop(String.format("Subscriber::onNext(%s) called before Subscriber::onSubscribe", element)); +569 } +570 } +571 +572 @Override +573 public T nextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +574 throw new RuntimeException("Can not expect elements from BlackholeSubscriber, use ManualSubscriber instead!"); +575 } +576 +577 @Override +578 public List<T> nextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +579 throw new RuntimeException("Can not expect elements from BlackholeSubscriber, use ManualSubscriber instead!"); +580 } +581 } +582 +583 public static class TestSubscriber<T> implements Subscriber<T> { +584 final Promise<Subscription> subscription; +585 +586 protected final TestEnvironment env; +587 +588 public TestSubscriber(TestEnvironment env) { +589 this.env = env; +590 subscription = new Promise<Subscription>(env); +591 } +592 +593 @Override +594 public void onError(Throwable cause) { +595 env.flop(cause, String.format("Unexpected Subscriber::onError(%s)", cause)); +596 } +597 +598 @Override +599 public void onComplete() { +600 env.flop("Unexpected Subscriber::onComplete()"); +601 } +602 +603 @Override +604 public void onNext(T element) { +605 env.flop(String.format("Unexpected Subscriber::onNext(%s)", element)); +606 } +607 +608 @Override +609 public void onSubscribe(Subscription subscription) { +610 env.flop(String.format("Unexpected Subscriber::onSubscribe(%s)", subscription)); +611 } +612 +613 public void cancel() { +614 if (subscription.isCompleted()) { +615 subscription.value().cancel(); +616 } else { +617 env.flop("Cannot cancel a subscription before having received it"); +618 } +619 } +620 } +621 +622 public static class ManualPublisher<T> implements Publisher<T> { +623 protected final TestEnvironment env; +624 +625 protected long pendingDemand = 0L; +626 protected Promise<Subscriber<? super T>> subscriber; +627 +628 protected final Receptacle<Long> requests; +629 +630 protected final Latch cancelled; +631 +632 public ManualPublisher(TestEnvironment env) { +633 this.env = env; +634 requests = new Receptacle<Long>(env); +635 cancelled = new Latch(env); +636 subscriber = new Promise<Subscriber<? super T>>(this.env); +637 } +638 +639 @Override +640 public void subscribe(Subscriber<? super T> s) { +641 if (!subscriber.isCompleted()) { +642 subscriber.completeImmediatly(s); +643 +644 Subscription subs = new Subscription() { +645 @Override +646 public void request(long elements) { +647 requests.add(elements); +648 } +649 +650 @Override +651 public void cancel() { +652 cancelled.close(); +653 } +654 }; +655 s.onSubscribe(subs); +656 +657 } else { +658 env.flop("TestPublisher doesn't support more than one Subscriber"); +659 } +660 } +661 +662 public void sendNext(T element) { +663 if (subscriber.isCompleted()) { +664 subscriber.value().onNext(element); +665 } else { +666 env.flop("Cannot sendNext before having a Subscriber"); +667 } +668 } +669 +670 public void sendCompletion() { +671 if (subscriber.isCompleted()) { +672 subscriber.value().onComplete(); +673 } else { +674 env.flop("Cannot sendCompletion before having a Subscriber"); +675 } +676 } +677 +678 public void sendError(Throwable cause) { +679 if (subscriber.isCompleted()) { +680 subscriber.value().onError(cause); +681 } else { +682 env.flop("Cannot sendError before having a Subscriber"); +683 } +684 } +685 +686 public long expectRequest() throws InterruptedException { +687 return expectRequest(env.defaultTimeoutMillis()); +688 } +689 +690 public long expectRequest(long timeoutMillis) throws InterruptedException { +691 long requested = requests.next(timeoutMillis, "Did not receive expected `request` call"); +692 if (requested <= 0) { +693 return env.<Long>flopAndFail(String.format("Requests cannot be zero or negative but received request(%s)", requested)); +694 } else { +695 pendingDemand += requested; +696 return requested; +697 } +698 } +699 +700 public void expectExactRequest(long expected) throws InterruptedException { +701 expectExactRequest(expected, env.defaultTimeoutMillis()); +702 } +703 +704 public void expectExactRequest(long expected, long timeoutMillis) throws InterruptedException { +705 long requested = expectRequest(timeoutMillis); +706 if (requested != expected) { +707 env.flop(String.format("Received `request(%d)` on upstream but expected `request(%d)`", requested, expected)); +708 } +709 pendingDemand += requested; +710 } +711 +712 public void expectNoRequest() throws InterruptedException { +713 expectNoRequest(env.defaultTimeoutMillis()); +714 } +715 +716 public void expectNoRequest(long timeoutMillis) throws InterruptedException { +717 requests.expectNone(timeoutMillis, "Received an unexpected call to: request: "); +718 } +719 +720 public void expectCancelling() throws InterruptedException { +721 expectCancelling(env.defaultTimeoutMillis()); +722 } +723 +724 public void expectCancelling(long timeoutMillis) throws InterruptedException { +725 cancelled.expectClose(timeoutMillis, "Did not receive expected cancelling of upstream subscription"); +726 } +727 } +728 +729 /** +730 * Like a CountDownLatch, but resettable and with some convenience methods +731 */ +732 public static class Latch { +733 private final TestEnvironment env; +734 volatile private CountDownLatch countDownLatch = new CountDownLatch(1); +735 +736 public Latch(TestEnvironment env) { +737 this.env = env; +738 } +739 +740 public void reOpen() { +741 countDownLatch = new CountDownLatch(1); +742 } +743 +744 public boolean isClosed() { +745 return countDownLatch.getCount() == 0; +746 } +747 +748 public void close() { +749 countDownLatch.countDown(); +750 } +751 +752 public void assertClosed(String openErrorMsg) { +753 if (!isClosed()) { +754 env.flop(new ExpectedClosedLatchException(openErrorMsg)); +755 } +756 } +757 +758 public void assertOpen(String closedErrorMsg) { +759 if (isClosed()) { +760 env.flop(new ExpectedOpenLatchException(closedErrorMsg)); +761 } +762 } +763 +764 public void expectClose(String notClosedErrorMsg) throws InterruptedException { +765 expectClose(env.defaultTimeoutMillis(), notClosedErrorMsg); +766 } +767 +768 public void expectClose(long timeoutMillis, String notClosedErrorMsg) throws InterruptedException { +769 countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS); +770 if (countDownLatch.getCount() > 0) { +771 env.flop(String.format("%s within %d ms", notClosedErrorMsg, timeoutMillis)); +772 } +773 } +774 +775 static final class ExpectedOpenLatchException extends RuntimeException { +776 public ExpectedOpenLatchException(String message) { +777 super(message); +778 } +779 } +780 +781 static final class ExpectedClosedLatchException extends RuntimeException { +782 public ExpectedClosedLatchException(String message) { +783 super(message); +784 } +785 } +786 +787 } +788 +789 // simple promise for *one* value, which cannot be reset +790 public static class Promise<T> { +791 private final TestEnvironment env; +792 +793 public static <T> Promise<T> completed(TestEnvironment env, T value) { +794 Promise<T> promise = new Promise<T>(env); +795 promise.completeImmediatly(value); +796 return promise; +797 } +798 +799 public Promise(TestEnvironment env) { +800 this.env = env; +801 } +802 +803 private ArrayBlockingQueue<T> abq = new ArrayBlockingQueue<T>(1); +804 private volatile T _value = null; +805 +806 public T value() { +807 if (isCompleted()) { +808 return _value; +809 } else { +810 env.flop("Cannot access promise value before completion"); +811 return null; +812 } +813 } +814 +815 public boolean isCompleted() { +816 return _value != null; +817 } +818 +819 /** +820 * Allows using expectCompletion to await for completion of the value and complete it _then_ +821 */ +822 public void complete(T value) { +823 abq.add(value); +824 } +825 +826 /** +827 * Completes the promise right away, it is not possible to expectCompletion on a Promise completed this way +828 */ +829 public void completeImmediatly(T value) { +830 complete(value); // complete! +831 _value = value; // immediatly! +832 } +833 +834 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +835 if (!isCompleted()) { +836 T val = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +837 +838 if (val == null) { +839 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +840 } else { +841 _value = val; +842 } +843 } +844 } +845 } +846 +847 // a "Promise" for multiple values, which also supports "end-of-stream reached" +848 public static class Receptacle<T> { +849 final int QUEUE_SIZE = 2 * TEST_BUFFER_SIZE; +850 private final TestEnvironment env; +851 +852 private final ArrayBlockingQueue<Optional<T>> abq = new ArrayBlockingQueue<Optional<T>>(QUEUE_SIZE); +853 +854 private final Latch completedLatch; +855 +856 Receptacle(TestEnvironment env) { +857 this.env = env; +858 this.completedLatch = new Latch(env); +859 } +860 +861 public void add(T value) { +862 completedLatch.assertOpen(String.format("Unexpected element %s received after stream completed", value)); +863 +864 abq.add(Optional.of(value)); +865 } +866 +867 public void complete() { +868 completedLatch.assertOpen("Unexpected additional complete signal received!"); +869 completedLatch.close(); +870 +871 abq.add(Optional.<T>empty()); +872 } +873 +874 public T next(long timeoutMillis, String errorMsg) throws InterruptedException { +875 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +876 +877 if (value == null) { +878 return env.flopAndFail(String.format("%s within %d ms", errorMsg, timeoutMillis)); +879 } else if (value.isDefined()) { +880 return value.get(); +881 } else { +882 return env.flopAndFail("Expected element but got end-of-stream"); +883 } +884 } +885 +886 public Optional<T> nextOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +887 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +888 +889 if (value == null) { +890 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +891 return Optional.empty(); +892 } +893 +894 return value; +895 } +896 +897 /** +898 * @param timeoutMillis total timeout time for awaiting all {@code elements} number of elements +899 */ +900 public List<T> nextN(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +901 List<T> result = new LinkedList<T>(); +902 long remaining = elements; +903 long deadline = System.currentTimeMillis() + timeoutMillis; +904 while (remaining > 0) { +905 long remainingMillis = deadline - System.currentTimeMillis(); +906 +907 result.add(next(remainingMillis, errorMsg)); +908 remaining--; +909 } +910 +911 return result; +912 } +913 +914 +915 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +916 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +917 +918 if (value == null) { +919 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +920 } else if (value.isDefined()) { +921 env.flop(String.format("Expected end-of-stream but got element [%s]", value.get())); +922 } // else, ok +923 } +924 +925 @SuppressWarnings("unchecked") +926 public <E extends Throwable> E expectError(Class<E> clazz, long timeoutMillis, String errorMsg) throws Exception { +927 Thread.sleep(timeoutMillis); +928 +929 if (env.asyncErrors.isEmpty()) { +930 return env.flopAndFail(String.format("%s within %d ms", errorMsg, timeoutMillis)); +931 } else { +932 // ok, there was an expected error +933 Throwable thrown = env.asyncErrors.remove(0); +934 +935 if (clazz.isInstance(thrown)) { +936 return (E) thrown; +937 } else { +938 +939 return env.flopAndFail(String.format("%s within %d ms; Got %s but expected %s", +940 errorMsg, timeoutMillis, thrown.getClass().getCanonicalName(), clazz.getCanonicalName())); +941 } +942 } +943 } +944 +945 public void expectNone(long withinMillis, String errorMsgPrefix) throws InterruptedException { +946 Thread.sleep(withinMillis); +947 Optional<T> value = abq.poll(); +948 +949 if (value == null) { +950 // ok +951 } else if (value.isDefined()) { +952 env.flop(String.format("%s [%s]", errorMsgPrefix, value.get())); +953 } else { +954 env.flop("Expected no element but got end-of-stream"); +955 } +956 } +957 } +958} +959 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck; +002 +003import org.reactivestreams.Publisher; +004import org.reactivestreams.Subscriber; +005import org.reactivestreams.Subscription; +006import org.reactivestreams.tck.support.SubscriberBufferOverflowException; +007import org.reactivestreams.tck.support.Optional; +008 +009import java.util.LinkedList; +010import java.util.List; +011import java.util.concurrent.ArrayBlockingQueue; +012import java.util.concurrent.CopyOnWriteArrayList; +013import java.util.concurrent.CountDownLatch; +014import java.util.concurrent.TimeUnit; +015 +016import static org.testng.Assert.assertTrue; +017import static org.testng.Assert.fail; +018 +019public class TestEnvironment { +020 public static final int TEST_BUFFER_SIZE = 16; +021 +022 private static final String DEFAULT_TIMEOUT_MILLIS_ENV = "DEFAULT_TIMEOUT_MILLIS"; +023 private static final long DEFAULT_TIMEOUT_MILLIS = 100; +024 +025 private final long defaultTimeoutMillis; +026 private final boolean printlnDebug; +027 +028 private CopyOnWriteArrayList<Throwable> asyncErrors = new CopyOnWriteArrayList<Throwable>(); +029 +030 /** +031 * Tests must specify the timeout for expected outcome of asynchronous +032 * interactions. Longer timeout does not invalidate the correctness of +033 * the implementation, but can in some cases result in longer time to +034 * run the tests. +035 * +036 * @param defaultTimeoutMillis default timeout to be used in all expect* methods +037 * @param printlnDebug if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output, +038 * often helpful to pinpoint simple race conditions etc. +039 */ +040 public TestEnvironment(long defaultTimeoutMillis, boolean printlnDebug) { +041 this.defaultTimeoutMillis = defaultTimeoutMillis; +042 this.printlnDebug = printlnDebug; +043 } +044 +045 /** +046 * Tests must specify the timeout for expected outcome of asynchronous +047 * interactions. Longer timeout does not invalidate the correctness of +048 * the implementation, but can in some cases result in longer time to +049 * run the tests. +050 * +051 * @param defaultTimeoutMillis default timeout to be used in all expect* methods +052 */ +053 public TestEnvironment(long defaultTimeoutMillis) { +054 this(defaultTimeoutMillis, false); +055 } +056 +057 /** +058 * Tests must specify the timeout for expected outcome of asynchronous +059 * interactions. Longer timeout does not invalidate the correctness of +060 * the implementation, but can in some cases result in longer time to +061 * run the tests. +062 * +063 * The default timeout for all expect* methods will be obtained by either the env variable {@code DEFAULT_TIMEOUT_MILLIS} +064 * or the default value ({@link TestEnvironment#DEFAULT_TIMEOUT_MILLIS}) will be used. +065 * +066 * @param printlnDebug if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output, +067 * often helpful to pinpoint simple race conditions etc. +068 */ +069 public TestEnvironment(boolean printlnDebug) { +070 this(envDefaultTimeoutMillis(), printlnDebug); +071 } +072 +073 /** +074 * Tests must specify the timeout for expected outcome of asynchronous +075 * interactions. Longer timeout does not invalidate the correctness of +076 * the implementation, but can in some cases result in longer time to +077 * run the tests. +078 * +079 * The default timeout for all expect* methods will be obtained by either the env variable {@code DEFAULT_TIMEOUT_MILLIS} +080 * or the default value ({@link TestEnvironment#DEFAULT_TIMEOUT_MILLIS}) will be used. +081 */ +082 public TestEnvironment() { +083 this(envDefaultTimeoutMillis()); +084 } +085 +086 public long defaultTimeoutMillis() { +087 return defaultTimeoutMillis; +088 } +089 +090 /** +091 * Tries to parse the env variable {@code DEFAULT_TIMEOUT_MILLIS} as long and returns the value if present OR its default value. +092 * +093 * @throws java.lang.IllegalArgumentException when unable to parse the env variable +094 */ +095 public static long envDefaultTimeoutMillis() { +096 final String envMillis = System.getenv(DEFAULT_TIMEOUT_MILLIS_ENV); +097 if (envMillis == null) return DEFAULT_TIMEOUT_MILLIS; +098 else try { +099 return Long.parseLong(envMillis); +100 } catch(NumberFormatException ex) { +101 throw new IllegalArgumentException(String.format("Unable to parse %s env value [%s] as long!", DEFAULT_TIMEOUT_MILLIS_ENV, envMillis), ex); +102 } +103 } +104 +105 /** +106 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +107 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +108 * +109 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +110 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +111 * from the environment using {@code env.dropAsyncError()}. +112 * +113 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +114 */ +115 public void flop(String msg) { +116 try { +117 fail(msg); +118 } catch (Throwable t) { +119 asyncErrors.add(t); +120 } +121 } +122 +123 /** +124 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +125 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +126 * +127 * This overload keeps the passed in throwable as the asyncError, instead of creating an AssertionError for this. +128 * +129 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +130 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +131 * from the environment using {@code env.dropAsyncError()}. +132 * +133 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +134 */ +135 public void flop(Throwable thr, String msg) { +136 try { +137 fail(msg, thr); +138 } catch (Throwable t) { +139 asyncErrors.add(thr); +140 } +141 } +142 +143 /** +144 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +145 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +146 * +147 * This overload keeps the passed in throwable as the asyncError, instead of creating an AssertionError for this. +148 * +149 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +150 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +151 * from the environment using {@code env.dropAsyncError()}. +152 * +153 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +154 */ +155 public void flop(Throwable thr) { +156 try { +157 fail(thr.getMessage(), thr); +158 } catch (Throwable t) { +159 asyncErrors.add(thr); +160 } +161 } +162 +163 /** +164 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +165 * +166 * This method DOES fail the test right away (it tries to, by throwing an AssertionException), +167 * in such it is different from {@link org.reactivestreams.tck.TestEnvironment#flop} which only records the error. +168 * +169 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +170 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +171 * from the environment using {@code env.dropAsyncError()}. +172 * +173 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +174 */ +175 public <T> T flopAndFail(String msg) { +176 try { +177 fail(msg); +178 } catch (Throwable t) { +179 asyncErrors.add(t); +180 fail(msg, t); +181 } +182 return null; // unreachable, the previous block will always exit by throwing +183 } +184 +185 +186 +187 public <T> void subscribe(Publisher<T> pub, TestSubscriber<T> sub) throws InterruptedException { +188 subscribe(pub, sub, defaultTimeoutMillis); +189 } +190 +191 public <T> void subscribe(Publisher<T> pub, TestSubscriber<T> sub, long timeoutMillis) throws InterruptedException { +192 pub.subscribe(sub); +193 sub.subscription.expectCompletion(timeoutMillis, String.format("Could not subscribe %s to Publisher %s", sub, pub)); +194 verifyNoAsyncErrorsNoDelay(); +195 } +196 +197 public <T> ManualSubscriber<T> newBlackholeSubscriber(Publisher<T> pub) throws InterruptedException { +198 ManualSubscriberWithSubscriptionSupport<T> sub = new BlackholeSubscriberWithSubscriptionSupport<T>(this); +199 subscribe(pub, sub, defaultTimeoutMillis()); +200 return sub; +201 } +202 +203 public <T> ManualSubscriber<T> newManualSubscriber(Publisher<T> pub) throws InterruptedException { +204 return newManualSubscriber(pub, defaultTimeoutMillis()); +205 } +206 +207 public <T> ManualSubscriber<T> newManualSubscriber(Publisher<T> pub, long timeoutMillis) throws InterruptedException { +208 ManualSubscriberWithSubscriptionSupport<T> sub = new ManualSubscriberWithSubscriptionSupport<T>(this); +209 subscribe(pub, sub, timeoutMillis); +210 return sub; +211 } +212 +213 public void clearAsyncErrors() { +214 asyncErrors.clear(); +215 } +216 +217 public Throwable dropAsyncError() { +218 try { +219 return asyncErrors.remove(0); +220 } catch (IndexOutOfBoundsException ex) { +221 return null; +222 } +223 } +224 +225 /** +226 * Waits for {@link TestEnvironment#defaultTimeoutMillis()} and then verifies that no asynchronous errors +227 * were signalled pior to, or during that time (by calling {@code flop()}). +228 */ +229 public void verifyNoAsyncErrors() { +230 verifyNoAsyncErrors(defaultTimeoutMillis()); +231 } +232 +233 /** +234 * This version of {@code verifyNoAsyncErrors} should be used when errors still could be signalled +235 * asynchronously during {@link TestEnvironment#defaultTimeoutMillis()} time. +236 * <p></p> +237 * It will immediatly check if any async errors were signaled (using {@link TestEnvironment#flop(String)}, +238 * and if no errors encountered wait for another default timeout as the errors may yet be signalled. +239 * The initial check is performed in order to fail-fast in case of an already failed test. +240 */ +241 public void verifyNoAsyncErrors(long delay) { +242 try { +243 verifyNoAsyncErrorsNoDelay(); +244 +245 Thread.sleep(delay); +246 verifyNoAsyncErrorsNoDelay(); +247 } catch (InterruptedException e) { +248 throw new RuntimeException(e); +249 } +250 } +251 +252 /** +253 * Verifies that no asynchronous errors were signalled pior to calling this method (by calling {@code flop()}). +254 * This version of verifyNoAsyncError <b>does not wait before checking for asynchronous errors</b>, and is to be used +255 * for example in tight loops etc. +256 */ +257 public void verifyNoAsyncErrorsNoDelay() { +258 for (Throwable e : asyncErrors) { +259 if (e instanceof AssertionError) { +260 throw (AssertionError) e; +261 } else { +262 fail(String.format("Async error during test execution: %s", e.getMessage()), e); +263 } +264 } +265 } +266 +267 /** If {@code TestEnvironment#printlnDebug} is true, print debug message to std out. */ +268 public void debug(String msg) { +269 if (printlnDebug) +270 System.out.printf("[TCK-DEBUG] %s%n", msg); +271 } +272 +273 /** +274 * Looks for given {@code method} method in stack trace. +275 * Can be used to answer questions like "was this method called from onComplete?". +276 * +277 * @return the caller's StackTraceElement at which he the looked for method was found in the call stack, EMPTY otherwise +278 */ +279 public Optional<StackTraceElement> findCallerMethodInStackTrace(String method) { +280 final Throwable thr = new Throwable(); // gets the stacktrace +281 +282 for (StackTraceElement stackElement : thr.getStackTrace()) { +283 if (stackElement.getMethodName().equals(method)) { +284 return Optional.of(stackElement); +285 } +286 } +287 return Optional.empty(); +288 } +289 +290 // ---- classes ---- +291 +292 /** +293 * {@link Subscriber} implementation which can be steered by test code and asserted on. +294 */ +295 public static class ManualSubscriber<T> extends TestSubscriber<T> { +296 Receptacle<T> received; +297 +298 public ManualSubscriber(TestEnvironment env) { +299 super(env); +300 received = new Receptacle<T>(this.env); +301 } +302 +303 @Override +304 public void onNext(T element) { +305 try { +306 received.add(element); +307 } catch (IllegalStateException ex) { +308 // error message refinement +309 throw new SubscriberBufferOverflowException( +310 String.format("Received more than bufferSize (%d) onNext signals. " + +311 "The Publisher probably emited more signals than expected!", +312 received.QUEUE_SIZE), ex); +313 } +314 } +315 +316 @Override +317 public void onComplete() { +318 received.complete(); +319 } +320 +321 public void request(long elements) { +322 subscription.value().request(elements); +323 } +324 +325 public T requestNextElement() throws InterruptedException { +326 return requestNextElement(env.defaultTimeoutMillis()); +327 } +328 +329 public T requestNextElement(long timeoutMillis) throws InterruptedException { +330 return requestNextElement(timeoutMillis, "Did not receive expected element"); +331 } +332 +333 public T requestNextElement(String errorMsg) throws InterruptedException { +334 return requestNextElement(env.defaultTimeoutMillis(), errorMsg); +335 } +336 +337 public T requestNextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +338 request(1); +339 return nextElement(timeoutMillis, errorMsg); +340 } +341 +342 public Optional<T> requestNextElementOrEndOfStream(String errorMsg) throws InterruptedException { +343 return requestNextElementOrEndOfStream(env.defaultTimeoutMillis(), errorMsg); +344 } +345 +346 public Optional<T> requestNextElementOrEndOfStream(long timeoutMillis) throws InterruptedException { +347 return requestNextElementOrEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +348 } +349 +350 public Optional<T> requestNextElementOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +351 request(1); +352 return nextElementOrEndOfStream(timeoutMillis, errorMsg); +353 } +354 +355 public void requestEndOfStream() throws InterruptedException { +356 requestEndOfStream(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +357 } +358 +359 public void requestEndOfStream(long timeoutMillis) throws InterruptedException { +360 requestEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +361 } +362 +363 public void requestEndOfStream(String errorMsg) throws InterruptedException { +364 requestEndOfStream(env.defaultTimeoutMillis(), errorMsg); +365 } +366 +367 public void requestEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +368 request(1); +369 expectCompletion(timeoutMillis, errorMsg); +370 } +371 +372 public List<T> requestNextElements(long elements) throws InterruptedException { +373 request(elements); +374 return nextElements(elements, env.defaultTimeoutMillis()); +375 } +376 +377 public List<T> requestNextElements(long elements, long timeoutMillis) throws InterruptedException { +378 request(elements); +379 return nextElements(elements, timeoutMillis, String.format("Did not receive %d expected elements", elements)); +380 } +381 +382 public List<T> requestNextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +383 request(elements); +384 return nextElements(elements, timeoutMillis, errorMsg); +385 } +386 +387 public T nextElement() throws InterruptedException { +388 return nextElement(env.defaultTimeoutMillis()); +389 } +390 +391 public T nextElement(long timeoutMillis) throws InterruptedException { +392 return nextElement(timeoutMillis, "Did not receive expected element"); +393 } +394 +395 public T nextElement(String errorMsg) throws InterruptedException { +396 return nextElement(env.defaultTimeoutMillis(), errorMsg); +397 } +398 +399 public T nextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +400 return received.next(timeoutMillis, errorMsg); +401 } +402 +403 public Optional<T> nextElementOrEndOfStream() throws InterruptedException { +404 return nextElementOrEndOfStream(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +405 } +406 +407 public Optional<T> nextElementOrEndOfStream(long timeoutMillis) throws InterruptedException { +408 return nextElementOrEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +409 } +410 +411 public Optional<T> nextElementOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +412 return received.nextOrEndOfStream(timeoutMillis, errorMsg); +413 } +414 +415 public List<T> nextElements(long elements) throws InterruptedException { +416 return nextElements(elements, env.defaultTimeoutMillis(), "Did not receive expected element or completion"); +417 } +418 +419 public List<T> nextElements(long elements, String errorMsg) throws InterruptedException { +420 return nextElements(elements, env.defaultTimeoutMillis(), errorMsg); +421 } +422 +423 public List<T> nextElements(long elements, long timeoutMillis) throws InterruptedException { +424 return nextElements(elements, timeoutMillis, "Did not receive expected element or completion"); +425 } +426 +427 public List<T> nextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +428 return received.nextN(elements, timeoutMillis, errorMsg); +429 } +430 +431 public void expectNext(T expected) throws InterruptedException { +432 expectNext(expected, env.defaultTimeoutMillis()); +433 } +434 +435 public void expectNext(T expected, long timeoutMillis) throws InterruptedException { +436 T received = nextElement(timeoutMillis, "Did not receive expected element on downstream"); +437 if (!received.equals(expected)) { +438 env.flop(String.format("Expected element %s on downstream but received %s", expected, received)); +439 } +440 } +441 +442 public void expectCompletion() throws InterruptedException { +443 expectCompletion(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +444 } +445 +446 public void expectCompletion(long timeoutMillis) throws InterruptedException { +447 expectCompletion(timeoutMillis, "Did not receive expected stream completion"); +448 } +449 +450 public void expectCompletion(String errorMsg) throws InterruptedException { +451 expectCompletion(env.defaultTimeoutMillis(), errorMsg); +452 } +453 +454 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +455 received.expectCompletion(timeoutMillis, errorMsg); +456 } +457 +458 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart) throws Exception { +459 expectErrorWithMessage(expected, requiredMessagePart, env.defaultTimeoutMillis()); +460 } +461 +462 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +463 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart, long timeoutMillis) throws Exception { +464 final E err = expectError(expected, timeoutMillis); +465 final String message = err.getMessage(); +466 assertTrue(message.contains(requiredMessagePart), +467 String.format("Got expected exception [%s] but missing message part [%s], was: %s", +468 err.getClass(), requiredMessagePart, err.getMessage())); +469 } +470 +471 public <E extends Throwable> E expectError(Class<E> expected) throws Exception { +472 return expectError(expected, env.defaultTimeoutMillis()); +473 } +474 +475 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis) throws Exception { +476 return expectError(expected, timeoutMillis, String.format("Expected onError(%s)", expected.getName())); +477 } +478 +479 public <E extends Throwable> E expectError(Class<E> expected, String errorMsg) throws Exception { +480 return expectError(expected, env.defaultTimeoutMillis(), errorMsg); +481 } +482 +483 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis, String errorMsg) throws Exception { +484 return received.expectError(expected, timeoutMillis, errorMsg); +485 } +486 +487 public void expectNone() throws InterruptedException { +488 expectNone(env.defaultTimeoutMillis()); +489 } +490 +491 public void expectNone(String errMsgPrefix) throws InterruptedException { +492 expectNone(env.defaultTimeoutMillis(), errMsgPrefix); +493 } +494 +495 public void expectNone(long withinMillis) throws InterruptedException { +496 expectNone(withinMillis, "Did not expect an element but got element"); +497 } +498 +499 public void expectNone(long withinMillis, String errMsgPrefix) throws InterruptedException { +500 received.expectNone(withinMillis, errMsgPrefix); +501 } +502 +503 } +504 +505 public static class ManualSubscriberWithSubscriptionSupport<T> extends ManualSubscriber<T> { +506 +507 public ManualSubscriberWithSubscriptionSupport(TestEnvironment env) { +508 super(env); +509 } +510 +511 @Override +512 public void onNext(T element) { +513 env.debug(String.format("%s::onNext(%s)", this, element)); +514 if (subscription.isCompleted()) { +515 super.onNext(element); +516 } else { +517 env.flop(String.format("Subscriber::onNext(%s) called before Subscriber::onSubscribe", element)); +518 } +519 } +520 +521 @Override +522 public void onComplete() { +523 env.debug(this + "::onComplete()"); +524 if (subscription.isCompleted()) { +525 super.onComplete(); +526 } else { +527 env.flop("Subscriber::onComplete() called before Subscriber::onSubscribe"); +528 } +529 } +530 +531 @Override +532 public void onSubscribe(Subscription s) { +533 env.debug(String.format("%s::onSubscribe(%s)", this, s)); +534 if (!subscription.isCompleted()) { +535 subscription.complete(s); +536 } else { +537 env.flop("Subscriber::onSubscribe called on an already-subscribed Subscriber"); +538 } +539 } +540 +541 @Override +542 public void onError(Throwable cause) { +543 env.debug(String.format("%s::onError(%s)", this, cause)); +544 if (subscription.isCompleted()) { +545 super.onError(cause); +546 } else { +547 env.flop(cause, String.format("Subscriber::onError(%s) called before Subscriber::onSubscribe", cause)); +548 } +549 } +550 } +551 +552 /** +553 * Similar to {@link org.reactivestreams.tck.TestEnvironment.ManualSubscriberWithSubscriptionSupport} +554 * but does not accumulate values signalled via <code>onNext</code>, thus it can not be used to assert +555 * values signalled to this subscriber. Instead it may be used to quickly drain a given publisher. +556 */ +557 public static class BlackholeSubscriberWithSubscriptionSupport<T> +558 extends ManualSubscriberWithSubscriptionSupport<T> { +559 +560 public BlackholeSubscriberWithSubscriptionSupport(TestEnvironment env) { +561 super(env); +562 } +563 +564 @Override +565 public void onNext(T element) { +566 env.debug(String.format("%s::onNext(%s)", this, element)); +567 if (!subscription.isCompleted()) { +568 env.flop(String.format("Subscriber::onNext(%s) called before Subscriber::onSubscribe", element)); +569 } +570 } +571 +572 @Override +573 public T nextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +574 throw new RuntimeException("Can not expect elements from BlackholeSubscriber, use ManualSubscriber instead!"); +575 } +576 +577 @Override +578 public List<T> nextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +579 throw new RuntimeException("Can not expect elements from BlackholeSubscriber, use ManualSubscriber instead!"); +580 } +581 } +582 +583 public static class TestSubscriber<T> implements Subscriber<T> { +584 final Promise<Subscription> subscription; +585 +586 protected final TestEnvironment env; +587 +588 public TestSubscriber(TestEnvironment env) { +589 this.env = env; +590 subscription = new Promise<Subscription>(env); +591 } +592 +593 @Override +594 public void onError(Throwable cause) { +595 env.flop(cause, String.format("Unexpected Subscriber::onError(%s)", cause)); +596 } +597 +598 @Override +599 public void onComplete() { +600 env.flop("Unexpected Subscriber::onComplete()"); +601 } +602 +603 @Override +604 public void onNext(T element) { +605 env.flop(String.format("Unexpected Subscriber::onNext(%s)", element)); +606 } +607 +608 @Override +609 public void onSubscribe(Subscription subscription) { +610 env.flop(String.format("Unexpected Subscriber::onSubscribe(%s)", subscription)); +611 } +612 +613 public void cancel() { +614 if (subscription.isCompleted()) { +615 subscription.value().cancel(); +616 } else { +617 env.flop("Cannot cancel a subscription before having received it"); +618 } +619 } +620 } +621 +622 public static class ManualPublisher<T> implements Publisher<T> { +623 protected final TestEnvironment env; +624 +625 protected long pendingDemand = 0L; +626 protected Promise<Subscriber<? super T>> subscriber; +627 +628 protected final Receptacle<Long> requests; +629 +630 protected final Latch cancelled; +631 +632 public ManualPublisher(TestEnvironment env) { +633 this.env = env; +634 requests = new Receptacle<Long>(env); +635 cancelled = new Latch(env); +636 subscriber = new Promise<Subscriber<? super T>>(this.env); +637 } +638 +639 @Override +640 public void subscribe(Subscriber<? super T> s) { +641 if (!subscriber.isCompleted()) { +642 subscriber.completeImmediatly(s); +643 +644 Subscription subs = new Subscription() { +645 @Override +646 public void request(long elements) { +647 requests.add(elements); +648 } +649 +650 @Override +651 public void cancel() { +652 cancelled.close(); +653 } +654 }; +655 s.onSubscribe(subs); +656 +657 } else { +658 env.flop("TestPublisher doesn't support more than one Subscriber"); +659 } +660 } +661 +662 public void sendNext(T element) { +663 if (subscriber.isCompleted()) { +664 subscriber.value().onNext(element); +665 } else { +666 env.flop("Cannot sendNext before having a Subscriber"); +667 } +668 } +669 +670 public void sendCompletion() { +671 if (subscriber.isCompleted()) { +672 subscriber.value().onComplete(); +673 } else { +674 env.flop("Cannot sendCompletion before having a Subscriber"); +675 } +676 } +677 +678 public void sendError(Throwable cause) { +679 if (subscriber.isCompleted()) { +680 subscriber.value().onError(cause); +681 } else { +682 env.flop("Cannot sendError before having a Subscriber"); +683 } +684 } +685 +686 public long expectRequest() throws InterruptedException { +687 return expectRequest(env.defaultTimeoutMillis()); +688 } +689 +690 public long expectRequest(long timeoutMillis) throws InterruptedException { +691 long requested = requests.next(timeoutMillis, "Did not receive expected `request` call"); +692 if (requested <= 0) { +693 return env.<Long>flopAndFail(String.format("Requests cannot be zero or negative but received request(%s)", requested)); +694 } else { +695 pendingDemand += requested; +696 return requested; +697 } +698 } +699 +700 public void expectExactRequest(long expected) throws InterruptedException { +701 expectExactRequest(expected, env.defaultTimeoutMillis()); +702 } +703 +704 public void expectExactRequest(long expected, long timeoutMillis) throws InterruptedException { +705 long requested = expectRequest(timeoutMillis); +706 if (requested != expected) { +707 env.flop(String.format("Received `request(%d)` on upstream but expected `request(%d)`", requested, expected)); +708 } +709 pendingDemand += requested; +710 } +711 +712 public void expectNoRequest() throws InterruptedException { +713 expectNoRequest(env.defaultTimeoutMillis()); +714 } +715 +716 public void expectNoRequest(long timeoutMillis) throws InterruptedException { +717 requests.expectNone(timeoutMillis, "Received an unexpected call to: request: "); +718 } +719 +720 public void expectCancelling() throws InterruptedException { +721 expectCancelling(env.defaultTimeoutMillis()); +722 } +723 +724 public void expectCancelling(long timeoutMillis) throws InterruptedException { +725 cancelled.expectClose(timeoutMillis, "Did not receive expected cancelling of upstream subscription"); +726 } +727 } +728 +729 /** +730 * Like a CountDownLatch, but resettable and with some convenience methods +731 */ +732 public static class Latch { +733 private final TestEnvironment env; +734 volatile private CountDownLatch countDownLatch = new CountDownLatch(1); +735 +736 public Latch(TestEnvironment env) { +737 this.env = env; +738 } +739 +740 public void reOpen() { +741 countDownLatch = new CountDownLatch(1); +742 } +743 +744 public boolean isClosed() { +745 return countDownLatch.getCount() == 0; +746 } +747 +748 public void close() { +749 countDownLatch.countDown(); +750 } +751 +752 public void assertClosed(String openErrorMsg) { +753 if (!isClosed()) { +754 env.flop(new ExpectedClosedLatchException(openErrorMsg)); +755 } +756 } +757 +758 public void assertOpen(String closedErrorMsg) { +759 if (isClosed()) { +760 env.flop(new ExpectedOpenLatchException(closedErrorMsg)); +761 } +762 } +763 +764 public void expectClose(String notClosedErrorMsg) throws InterruptedException { +765 expectClose(env.defaultTimeoutMillis(), notClosedErrorMsg); +766 } +767 +768 public void expectClose(long timeoutMillis, String notClosedErrorMsg) throws InterruptedException { +769 countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS); +770 if (countDownLatch.getCount() > 0) { +771 env.flop(String.format("%s within %d ms", notClosedErrorMsg, timeoutMillis)); +772 } +773 } +774 +775 static final class ExpectedOpenLatchException extends RuntimeException { +776 public ExpectedOpenLatchException(String message) { +777 super(message); +778 } +779 } +780 +781 static final class ExpectedClosedLatchException extends RuntimeException { +782 public ExpectedClosedLatchException(String message) { +783 super(message); +784 } +785 } +786 +787 } +788 +789 // simple promise for *one* value, which cannot be reset +790 public static class Promise<T> { +791 private final TestEnvironment env; +792 +793 public static <T> Promise<T> completed(TestEnvironment env, T value) { +794 Promise<T> promise = new Promise<T>(env); +795 promise.completeImmediatly(value); +796 return promise; +797 } +798 +799 public Promise(TestEnvironment env) { +800 this.env = env; +801 } +802 +803 private ArrayBlockingQueue<T> abq = new ArrayBlockingQueue<T>(1); +804 private volatile T _value = null; +805 +806 public T value() { +807 if (isCompleted()) { +808 return _value; +809 } else { +810 env.flop("Cannot access promise value before completion"); +811 return null; +812 } +813 } +814 +815 public boolean isCompleted() { +816 return _value != null; +817 } +818 +819 /** +820 * Allows using expectCompletion to await for completion of the value and complete it _then_ +821 */ +822 public void complete(T value) { +823 abq.add(value); +824 } +825 +826 /** +827 * Completes the promise right away, it is not possible to expectCompletion on a Promise completed this way +828 */ +829 public void completeImmediatly(T value) { +830 complete(value); // complete! +831 _value = value; // immediatly! +832 } +833 +834 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +835 if (!isCompleted()) { +836 T val = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +837 +838 if (val == null) { +839 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +840 } else { +841 _value = val; +842 } +843 } +844 } +845 } +846 +847 // a "Promise" for multiple values, which also supports "end-of-stream reached" +848 public static class Receptacle<T> { +849 final int QUEUE_SIZE = 2 * TEST_BUFFER_SIZE; +850 private final TestEnvironment env; +851 +852 private final ArrayBlockingQueue<Optional<T>> abq = new ArrayBlockingQueue<Optional<T>>(QUEUE_SIZE); +853 +854 private final Latch completedLatch; +855 +856 Receptacle(TestEnvironment env) { +857 this.env = env; +858 this.completedLatch = new Latch(env); +859 } +860 +861 public void add(T value) { +862 completedLatch.assertOpen(String.format("Unexpected element %s received after stream completed", value)); +863 +864 abq.add(Optional.of(value)); +865 } +866 +867 public void complete() { +868 completedLatch.assertOpen("Unexpected additional complete signal received!"); +869 completedLatch.close(); +870 +871 abq.add(Optional.<T>empty()); +872 } +873 +874 public T next(long timeoutMillis, String errorMsg) throws InterruptedException { +875 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +876 +877 if (value == null) { +878 return env.flopAndFail(String.format("%s within %d ms", errorMsg, timeoutMillis)); +879 } else if (value.isDefined()) { +880 return value.get(); +881 } else { +882 return env.flopAndFail("Expected element but got end-of-stream"); +883 } +884 } +885 +886 public Optional<T> nextOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +887 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +888 +889 if (value == null) { +890 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +891 return Optional.empty(); +892 } +893 +894 return value; +895 } +896 +897 /** +898 * @param timeoutMillis total timeout time for awaiting all {@code elements} number of elements +899 */ +900 public List<T> nextN(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +901 List<T> result = new LinkedList<T>(); +902 long remaining = elements; +903 long deadline = System.currentTimeMillis() + timeoutMillis; +904 while (remaining > 0) { +905 long remainingMillis = deadline - System.currentTimeMillis(); +906 +907 result.add(next(remainingMillis, errorMsg)); +908 remaining--; +909 } +910 +911 return result; +912 } +913 +914 +915 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +916 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +917 +918 if (value == null) { +919 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +920 } else if (value.isDefined()) { +921 env.flop(String.format("Expected end-of-stream but got element [%s]", value.get())); +922 } // else, ok +923 } +924 +925 @SuppressWarnings("unchecked") +926 public <E extends Throwable> E expectError(Class<E> clazz, long timeoutMillis, String errorMsg) throws Exception { +927 Thread.sleep(timeoutMillis); +928 +929 if (env.asyncErrors.isEmpty()) { +930 return env.flopAndFail(String.format("%s within %d ms", errorMsg, timeoutMillis)); +931 } else { +932 // ok, there was an expected error +933 Throwable thrown = env.asyncErrors.remove(0); +934 +935 if (clazz.isInstance(thrown)) { +936 return (E) thrown; +937 } else { +938 +939 return env.flopAndFail(String.format("%s within %d ms; Got %s but expected %s", +940 errorMsg, timeoutMillis, thrown.getClass().getCanonicalName(), clazz.getCanonicalName())); +941 } +942 } +943 } +944 +945 public void expectNone(long withinMillis, String errorMsgPrefix) throws InterruptedException { +946 Thread.sleep(withinMillis); +947 Optional<T> value = abq.poll(); +948 +949 if (value == null) { +950 // ok +951 } else if (value.isDefined()) { +952 env.flop(String.format("%s [%s]", errorMsgPrefix, value.get())); +953 } else { +954 env.flop("Expected no element but got end-of-stream"); +955 } +956 } +957 } +958} +959 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck; +002 +003import org.reactivestreams.Publisher; +004import org.reactivestreams.Subscriber; +005import org.reactivestreams.Subscription; +006import org.reactivestreams.tck.support.SubscriberBufferOverflowException; +007import org.reactivestreams.tck.support.Optional; +008 +009import java.util.LinkedList; +010import java.util.List; +011import java.util.concurrent.ArrayBlockingQueue; +012import java.util.concurrent.CopyOnWriteArrayList; +013import java.util.concurrent.CountDownLatch; +014import java.util.concurrent.TimeUnit; +015 +016import static org.testng.Assert.assertTrue; +017import static org.testng.Assert.fail; +018 +019public class TestEnvironment { +020 public static final int TEST_BUFFER_SIZE = 16; +021 +022 private static final String DEFAULT_TIMEOUT_MILLIS_ENV = "DEFAULT_TIMEOUT_MILLIS"; +023 private static final long DEFAULT_TIMEOUT_MILLIS = 100; +024 +025 private final long defaultTimeoutMillis; +026 private final boolean printlnDebug; +027 +028 private CopyOnWriteArrayList<Throwable> asyncErrors = new CopyOnWriteArrayList<Throwable>(); +029 +030 /** +031 * Tests must specify the timeout for expected outcome of asynchronous +032 * interactions. Longer timeout does not invalidate the correctness of +033 * the implementation, but can in some cases result in longer time to +034 * run the tests. +035 * +036 * @param defaultTimeoutMillis default timeout to be used in all expect* methods +037 * @param printlnDebug if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output, +038 * often helpful to pinpoint simple race conditions etc. +039 */ +040 public TestEnvironment(long defaultTimeoutMillis, boolean printlnDebug) { +041 this.defaultTimeoutMillis = defaultTimeoutMillis; +042 this.printlnDebug = printlnDebug; +043 } +044 +045 /** +046 * Tests must specify the timeout for expected outcome of asynchronous +047 * interactions. Longer timeout does not invalidate the correctness of +048 * the implementation, but can in some cases result in longer time to +049 * run the tests. +050 * +051 * @param defaultTimeoutMillis default timeout to be used in all expect* methods +052 */ +053 public TestEnvironment(long defaultTimeoutMillis) { +054 this(defaultTimeoutMillis, false); +055 } +056 +057 /** +058 * Tests must specify the timeout for expected outcome of asynchronous +059 * interactions. Longer timeout does not invalidate the correctness of +060 * the implementation, but can in some cases result in longer time to +061 * run the tests. +062 * +063 * The default timeout for all expect* methods will be obtained by either the env variable {@code DEFAULT_TIMEOUT_MILLIS} +064 * or the default value ({@link TestEnvironment#DEFAULT_TIMEOUT_MILLIS}) will be used. +065 * +066 * @param printlnDebug if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output, +067 * often helpful to pinpoint simple race conditions etc. +068 */ +069 public TestEnvironment(boolean printlnDebug) { +070 this(envDefaultTimeoutMillis(), printlnDebug); +071 } +072 +073 /** +074 * Tests must specify the timeout for expected outcome of asynchronous +075 * interactions. Longer timeout does not invalidate the correctness of +076 * the implementation, but can in some cases result in longer time to +077 * run the tests. +078 * +079 * The default timeout for all expect* methods will be obtained by either the env variable {@code DEFAULT_TIMEOUT_MILLIS} +080 * or the default value ({@link TestEnvironment#DEFAULT_TIMEOUT_MILLIS}) will be used. +081 */ +082 public TestEnvironment() { +083 this(envDefaultTimeoutMillis()); +084 } +085 +086 public long defaultTimeoutMillis() { +087 return defaultTimeoutMillis; +088 } +089 +090 /** +091 * Tries to parse the env variable {@code DEFAULT_TIMEOUT_MILLIS} as long and returns the value if present OR its default value. +092 * +093 * @throws java.lang.IllegalArgumentException when unable to parse the env variable +094 */ +095 public static long envDefaultTimeoutMillis() { +096 final String envMillis = System.getenv(DEFAULT_TIMEOUT_MILLIS_ENV); +097 if (envMillis == null) return DEFAULT_TIMEOUT_MILLIS; +098 else try { +099 return Long.parseLong(envMillis); +100 } catch(NumberFormatException ex) { +101 throw new IllegalArgumentException(String.format("Unable to parse %s env value [%s] as long!", DEFAULT_TIMEOUT_MILLIS_ENV, envMillis), ex); +102 } +103 } +104 +105 /** +106 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +107 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +108 * +109 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +110 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +111 * from the environment using {@code env.dropAsyncError()}. +112 * +113 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +114 */ +115 public void flop(String msg) { +116 try { +117 fail(msg); +118 } catch (Throwable t) { +119 asyncErrors.add(t); +120 } +121 } +122 +123 /** +124 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +125 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +126 * +127 * This overload keeps the passed in throwable as the asyncError, instead of creating an AssertionError for this. +128 * +129 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +130 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +131 * from the environment using {@code env.dropAsyncError()}. +132 * +133 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +134 */ +135 public void flop(Throwable thr, String msg) { +136 try { +137 fail(msg, thr); +138 } catch (Throwable t) { +139 asyncErrors.add(thr); +140 } +141 } +142 +143 /** +144 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +145 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +146 * +147 * This overload keeps the passed in throwable as the asyncError, instead of creating an AssertionError for this. +148 * +149 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +150 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +151 * from the environment using {@code env.dropAsyncError()}. +152 * +153 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +154 */ +155 public void flop(Throwable thr) { +156 try { +157 fail(thr.getMessage(), thr); +158 } catch (Throwable t) { +159 asyncErrors.add(thr); +160 } +161 } +162 +163 /** +164 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +165 * +166 * This method DOES fail the test right away (it tries to, by throwing an AssertionException), +167 * in such it is different from {@link org.reactivestreams.tck.TestEnvironment#flop} which only records the error. +168 * +169 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +170 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +171 * from the environment using {@code env.dropAsyncError()}. +172 * +173 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +174 */ +175 public <T> T flopAndFail(String msg) { +176 try { +177 fail(msg); +178 } catch (Throwable t) { +179 asyncErrors.add(t); +180 fail(msg, t); +181 } +182 return null; // unreachable, the previous block will always exit by throwing +183 } +184 +185 +186 +187 public <T> void subscribe(Publisher<T> pub, TestSubscriber<T> sub) throws InterruptedException { +188 subscribe(pub, sub, defaultTimeoutMillis); +189 } +190 +191 public <T> void subscribe(Publisher<T> pub, TestSubscriber<T> sub, long timeoutMillis) throws InterruptedException { +192 pub.subscribe(sub); +193 sub.subscription.expectCompletion(timeoutMillis, String.format("Could not subscribe %s to Publisher %s", sub, pub)); +194 verifyNoAsyncErrorsNoDelay(); +195 } +196 +197 public <T> ManualSubscriber<T> newBlackholeSubscriber(Publisher<T> pub) throws InterruptedException { +198 ManualSubscriberWithSubscriptionSupport<T> sub = new BlackholeSubscriberWithSubscriptionSupport<T>(this); +199 subscribe(pub, sub, defaultTimeoutMillis()); +200 return sub; +201 } +202 +203 public <T> ManualSubscriber<T> newManualSubscriber(Publisher<T> pub) throws InterruptedException { +204 return newManualSubscriber(pub, defaultTimeoutMillis()); +205 } +206 +207 public <T> ManualSubscriber<T> newManualSubscriber(Publisher<T> pub, long timeoutMillis) throws InterruptedException { +208 ManualSubscriberWithSubscriptionSupport<T> sub = new ManualSubscriberWithSubscriptionSupport<T>(this); +209 subscribe(pub, sub, timeoutMillis); +210 return sub; +211 } +212 +213 public void clearAsyncErrors() { +214 asyncErrors.clear(); +215 } +216 +217 public Throwable dropAsyncError() { +218 try { +219 return asyncErrors.remove(0); +220 } catch (IndexOutOfBoundsException ex) { +221 return null; +222 } +223 } +224 +225 /** +226 * Waits for {@link TestEnvironment#defaultTimeoutMillis()} and then verifies that no asynchronous errors +227 * were signalled pior to, or during that time (by calling {@code flop()}). +228 */ +229 public void verifyNoAsyncErrors() { +230 verifyNoAsyncErrors(defaultTimeoutMillis()); +231 } +232 +233 /** +234 * This version of {@code verifyNoAsyncErrors} should be used when errors still could be signalled +235 * asynchronously during {@link TestEnvironment#defaultTimeoutMillis()} time. +236 * <p></p> +237 * It will immediatly check if any async errors were signaled (using {@link TestEnvironment#flop(String)}, +238 * and if no errors encountered wait for another default timeout as the errors may yet be signalled. +239 * The initial check is performed in order to fail-fast in case of an already failed test. +240 */ +241 public void verifyNoAsyncErrors(long delay) { +242 try { +243 verifyNoAsyncErrorsNoDelay(); +244 +245 Thread.sleep(delay); +246 verifyNoAsyncErrorsNoDelay(); +247 } catch (InterruptedException e) { +248 throw new RuntimeException(e); +249 } +250 } +251 +252 /** +253 * Verifies that no asynchronous errors were signalled pior to calling this method (by calling {@code flop()}). +254 * This version of verifyNoAsyncError <b>does not wait before checking for asynchronous errors</b>, and is to be used +255 * for example in tight loops etc. +256 */ +257 public void verifyNoAsyncErrorsNoDelay() { +258 for (Throwable e : asyncErrors) { +259 if (e instanceof AssertionError) { +260 throw (AssertionError) e; +261 } else { +262 fail(String.format("Async error during test execution: %s", e.getMessage()), e); +263 } +264 } +265 } +266 +267 /** If {@code TestEnvironment#printlnDebug} is true, print debug message to std out. */ +268 public void debug(String msg) { +269 if (printlnDebug) +270 System.out.printf("[TCK-DEBUG] %s%n", msg); +271 } +272 +273 /** +274 * Looks for given {@code method} method in stack trace. +275 * Can be used to answer questions like "was this method called from onComplete?". +276 * +277 * @return the caller's StackTraceElement at which he the looked for method was found in the call stack, EMPTY otherwise +278 */ +279 public Optional<StackTraceElement> findCallerMethodInStackTrace(String method) { +280 final Throwable thr = new Throwable(); // gets the stacktrace +281 +282 for (StackTraceElement stackElement : thr.getStackTrace()) { +283 if (stackElement.getMethodName().equals(method)) { +284 return Optional.of(stackElement); +285 } +286 } +287 return Optional.empty(); +288 } +289 +290 // ---- classes ---- +291 +292 /** +293 * {@link Subscriber} implementation which can be steered by test code and asserted on. +294 */ +295 public static class ManualSubscriber<T> extends TestSubscriber<T> { +296 Receptacle<T> received; +297 +298 public ManualSubscriber(TestEnvironment env) { +299 super(env); +300 received = new Receptacle<T>(this.env); +301 } +302 +303 @Override +304 public void onNext(T element) { +305 try { +306 received.add(element); +307 } catch (IllegalStateException ex) { +308 // error message refinement +309 throw new SubscriberBufferOverflowException( +310 String.format("Received more than bufferSize (%d) onNext signals. " + +311 "The Publisher probably emited more signals than expected!", +312 received.QUEUE_SIZE), ex); +313 } +314 } +315 +316 @Override +317 public void onComplete() { +318 received.complete(); +319 } +320 +321 public void request(long elements) { +322 subscription.value().request(elements); +323 } +324 +325 public T requestNextElement() throws InterruptedException { +326 return requestNextElement(env.defaultTimeoutMillis()); +327 } +328 +329 public T requestNextElement(long timeoutMillis) throws InterruptedException { +330 return requestNextElement(timeoutMillis, "Did not receive expected element"); +331 } +332 +333 public T requestNextElement(String errorMsg) throws InterruptedException { +334 return requestNextElement(env.defaultTimeoutMillis(), errorMsg); +335 } +336 +337 public T requestNextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +338 request(1); +339 return nextElement(timeoutMillis, errorMsg); +340 } +341 +342 public Optional<T> requestNextElementOrEndOfStream(String errorMsg) throws InterruptedException { +343 return requestNextElementOrEndOfStream(env.defaultTimeoutMillis(), errorMsg); +344 } +345 +346 public Optional<T> requestNextElementOrEndOfStream(long timeoutMillis) throws InterruptedException { +347 return requestNextElementOrEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +348 } +349 +350 public Optional<T> requestNextElementOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +351 request(1); +352 return nextElementOrEndOfStream(timeoutMillis, errorMsg); +353 } +354 +355 public void requestEndOfStream() throws InterruptedException { +356 requestEndOfStream(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +357 } +358 +359 public void requestEndOfStream(long timeoutMillis) throws InterruptedException { +360 requestEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +361 } +362 +363 public void requestEndOfStream(String errorMsg) throws InterruptedException { +364 requestEndOfStream(env.defaultTimeoutMillis(), errorMsg); +365 } +366 +367 public void requestEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +368 request(1); +369 expectCompletion(timeoutMillis, errorMsg); +370 } +371 +372 public List<T> requestNextElements(long elements) throws InterruptedException { +373 request(elements); +374 return nextElements(elements, env.defaultTimeoutMillis()); +375 } +376 +377 public List<T> requestNextElements(long elements, long timeoutMillis) throws InterruptedException { +378 request(elements); +379 return nextElements(elements, timeoutMillis, String.format("Did not receive %d expected elements", elements)); +380 } +381 +382 public List<T> requestNextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +383 request(elements); +384 return nextElements(elements, timeoutMillis, errorMsg); +385 } +386 +387 public T nextElement() throws InterruptedException { +388 return nextElement(env.defaultTimeoutMillis()); +389 } +390 +391 public T nextElement(long timeoutMillis) throws InterruptedException { +392 return nextElement(timeoutMillis, "Did not receive expected element"); +393 } +394 +395 public T nextElement(String errorMsg) throws InterruptedException { +396 return nextElement(env.defaultTimeoutMillis(), errorMsg); +397 } +398 +399 public T nextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +400 return received.next(timeoutMillis, errorMsg); +401 } +402 +403 public Optional<T> nextElementOrEndOfStream() throws InterruptedException { +404 return nextElementOrEndOfStream(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +405 } +406 +407 public Optional<T> nextElementOrEndOfStream(long timeoutMillis) throws InterruptedException { +408 return nextElementOrEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +409 } +410 +411 public Optional<T> nextElementOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +412 return received.nextOrEndOfStream(timeoutMillis, errorMsg); +413 } +414 +415 public List<T> nextElements(long elements) throws InterruptedException { +416 return nextElements(elements, env.defaultTimeoutMillis(), "Did not receive expected element or completion"); +417 } +418 +419 public List<T> nextElements(long elements, String errorMsg) throws InterruptedException { +420 return nextElements(elements, env.defaultTimeoutMillis(), errorMsg); +421 } +422 +423 public List<T> nextElements(long elements, long timeoutMillis) throws InterruptedException { +424 return nextElements(elements, timeoutMillis, "Did not receive expected element or completion"); +425 } +426 +427 public List<T> nextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +428 return received.nextN(elements, timeoutMillis, errorMsg); +429 } +430 +431 public void expectNext(T expected) throws InterruptedException { +432 expectNext(expected, env.defaultTimeoutMillis()); +433 } +434 +435 public void expectNext(T expected, long timeoutMillis) throws InterruptedException { +436 T received = nextElement(timeoutMillis, "Did not receive expected element on downstream"); +437 if (!received.equals(expected)) { +438 env.flop(String.format("Expected element %s on downstream but received %s", expected, received)); +439 } +440 } +441 +442 public void expectCompletion() throws InterruptedException { +443 expectCompletion(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +444 } +445 +446 public void expectCompletion(long timeoutMillis) throws InterruptedException { +447 expectCompletion(timeoutMillis, "Did not receive expected stream completion"); +448 } +449 +450 public void expectCompletion(String errorMsg) throws InterruptedException { +451 expectCompletion(env.defaultTimeoutMillis(), errorMsg); +452 } +453 +454 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +455 received.expectCompletion(timeoutMillis, errorMsg); +456 } +457 +458 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart) throws Exception { +459 expectErrorWithMessage(expected, requiredMessagePart, env.defaultTimeoutMillis()); +460 } +461 +462 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +463 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart, long timeoutMillis) throws Exception { +464 final E err = expectError(expected, timeoutMillis); +465 final String message = err.getMessage(); +466 assertTrue(message.contains(requiredMessagePart), +467 String.format("Got expected exception [%s] but missing message part [%s], was: %s", +468 err.getClass(), requiredMessagePart, err.getMessage())); +469 } +470 +471 public <E extends Throwable> E expectError(Class<E> expected) throws Exception { +472 return expectError(expected, env.defaultTimeoutMillis()); +473 } +474 +475 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis) throws Exception { +476 return expectError(expected, timeoutMillis, String.format("Expected onError(%s)", expected.getName())); +477 } +478 +479 public <E extends Throwable> E expectError(Class<E> expected, String errorMsg) throws Exception { +480 return expectError(expected, env.defaultTimeoutMillis(), errorMsg); +481 } +482 +483 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis, String errorMsg) throws Exception { +484 return received.expectError(expected, timeoutMillis, errorMsg); +485 } +486 +487 public void expectNone() throws InterruptedException { +488 expectNone(env.defaultTimeoutMillis()); +489 } +490 +491 public void expectNone(String errMsgPrefix) throws InterruptedException { +492 expectNone(env.defaultTimeoutMillis(), errMsgPrefix); +493 } +494 +495 public void expectNone(long withinMillis) throws InterruptedException { +496 expectNone(withinMillis, "Did not expect an element but got element"); +497 } +498 +499 public void expectNone(long withinMillis, String errMsgPrefix) throws InterruptedException { +500 received.expectNone(withinMillis, errMsgPrefix); +501 } +502 +503 } +504 +505 public static class ManualSubscriberWithSubscriptionSupport<T> extends ManualSubscriber<T> { +506 +507 public ManualSubscriberWithSubscriptionSupport(TestEnvironment env) { +508 super(env); +509 } +510 +511 @Override +512 public void onNext(T element) { +513 env.debug(String.format("%s::onNext(%s)", this, element)); +514 if (subscription.isCompleted()) { +515 super.onNext(element); +516 } else { +517 env.flop(String.format("Subscriber::onNext(%s) called before Subscriber::onSubscribe", element)); +518 } +519 } +520 +521 @Override +522 public void onComplete() { +523 env.debug(this + "::onComplete()"); +524 if (subscription.isCompleted()) { +525 super.onComplete(); +526 } else { +527 env.flop("Subscriber::onComplete() called before Subscriber::onSubscribe"); +528 } +529 } +530 +531 @Override +532 public void onSubscribe(Subscription s) { +533 env.debug(String.format("%s::onSubscribe(%s)", this, s)); +534 if (!subscription.isCompleted()) { +535 subscription.complete(s); +536 } else { +537 env.flop("Subscriber::onSubscribe called on an already-subscribed Subscriber"); +538 } +539 } +540 +541 @Override +542 public void onError(Throwable cause) { +543 env.debug(String.format("%s::onError(%s)", this, cause)); +544 if (subscription.isCompleted()) { +545 super.onError(cause); +546 } else { +547 env.flop(cause, String.format("Subscriber::onError(%s) called before Subscriber::onSubscribe", cause)); +548 } +549 } +550 } +551 +552 /** +553 * Similar to {@link org.reactivestreams.tck.TestEnvironment.ManualSubscriberWithSubscriptionSupport} +554 * but does not accumulate values signalled via <code>onNext</code>, thus it can not be used to assert +555 * values signalled to this subscriber. Instead it may be used to quickly drain a given publisher. +556 */ +557 public static class BlackholeSubscriberWithSubscriptionSupport<T> +558 extends ManualSubscriberWithSubscriptionSupport<T> { +559 +560 public BlackholeSubscriberWithSubscriptionSupport(TestEnvironment env) { +561 super(env); +562 } +563 +564 @Override +565 public void onNext(T element) { +566 env.debug(String.format("%s::onNext(%s)", this, element)); +567 if (!subscription.isCompleted()) { +568 env.flop(String.format("Subscriber::onNext(%s) called before Subscriber::onSubscribe", element)); +569 } +570 } +571 +572 @Override +573 public T nextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +574 throw new RuntimeException("Can not expect elements from BlackholeSubscriber, use ManualSubscriber instead!"); +575 } +576 +577 @Override +578 public List<T> nextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +579 throw new RuntimeException("Can not expect elements from BlackholeSubscriber, use ManualSubscriber instead!"); +580 } +581 } +582 +583 public static class TestSubscriber<T> implements Subscriber<T> { +584 final Promise<Subscription> subscription; +585 +586 protected final TestEnvironment env; +587 +588 public TestSubscriber(TestEnvironment env) { +589 this.env = env; +590 subscription = new Promise<Subscription>(env); +591 } +592 +593 @Override +594 public void onError(Throwable cause) { +595 env.flop(cause, String.format("Unexpected Subscriber::onError(%s)", cause)); +596 } +597 +598 @Override +599 public void onComplete() { +600 env.flop("Unexpected Subscriber::onComplete()"); +601 } +602 +603 @Override +604 public void onNext(T element) { +605 env.flop(String.format("Unexpected Subscriber::onNext(%s)", element)); +606 } +607 +608 @Override +609 public void onSubscribe(Subscription subscription) { +610 env.flop(String.format("Unexpected Subscriber::onSubscribe(%s)", subscription)); +611 } +612 +613 public void cancel() { +614 if (subscription.isCompleted()) { +615 subscription.value().cancel(); +616 } else { +617 env.flop("Cannot cancel a subscription before having received it"); +618 } +619 } +620 } +621 +622 public static class ManualPublisher<T> implements Publisher<T> { +623 protected final TestEnvironment env; +624 +625 protected long pendingDemand = 0L; +626 protected Promise<Subscriber<? super T>> subscriber; +627 +628 protected final Receptacle<Long> requests; +629 +630 protected final Latch cancelled; +631 +632 public ManualPublisher(TestEnvironment env) { +633 this.env = env; +634 requests = new Receptacle<Long>(env); +635 cancelled = new Latch(env); +636 subscriber = new Promise<Subscriber<? super T>>(this.env); +637 } +638 +639 @Override +640 public void subscribe(Subscriber<? super T> s) { +641 if (!subscriber.isCompleted()) { +642 subscriber.completeImmediatly(s); +643 +644 Subscription subs = new Subscription() { +645 @Override +646 public void request(long elements) { +647 requests.add(elements); +648 } +649 +650 @Override +651 public void cancel() { +652 cancelled.close(); +653 } +654 }; +655 s.onSubscribe(subs); +656 +657 } else { +658 env.flop("TestPublisher doesn't support more than one Subscriber"); +659 } +660 } +661 +662 public void sendNext(T element) { +663 if (subscriber.isCompleted()) { +664 subscriber.value().onNext(element); +665 } else { +666 env.flop("Cannot sendNext before having a Subscriber"); +667 } +668 } +669 +670 public void sendCompletion() { +671 if (subscriber.isCompleted()) { +672 subscriber.value().onComplete(); +673 } else { +674 env.flop("Cannot sendCompletion before having a Subscriber"); +675 } +676 } +677 +678 public void sendError(Throwable cause) { +679 if (subscriber.isCompleted()) { +680 subscriber.value().onError(cause); +681 } else { +682 env.flop("Cannot sendError before having a Subscriber"); +683 } +684 } +685 +686 public long expectRequest() throws InterruptedException { +687 return expectRequest(env.defaultTimeoutMillis()); +688 } +689 +690 public long expectRequest(long timeoutMillis) throws InterruptedException { +691 long requested = requests.next(timeoutMillis, "Did not receive expected `request` call"); +692 if (requested <= 0) { +693 return env.<Long>flopAndFail(String.format("Requests cannot be zero or negative but received request(%s)", requested)); +694 } else { +695 pendingDemand += requested; +696 return requested; +697 } +698 } +699 +700 public void expectExactRequest(long expected) throws InterruptedException { +701 expectExactRequest(expected, env.defaultTimeoutMillis()); +702 } +703 +704 public void expectExactRequest(long expected, long timeoutMillis) throws InterruptedException { +705 long requested = expectRequest(timeoutMillis); +706 if (requested != expected) { +707 env.flop(String.format("Received `request(%d)` on upstream but expected `request(%d)`", requested, expected)); +708 } +709 pendingDemand += requested; +710 } +711 +712 public void expectNoRequest() throws InterruptedException { +713 expectNoRequest(env.defaultTimeoutMillis()); +714 } +715 +716 public void expectNoRequest(long timeoutMillis) throws InterruptedException { +717 requests.expectNone(timeoutMillis, "Received an unexpected call to: request: "); +718 } +719 +720 public void expectCancelling() throws InterruptedException { +721 expectCancelling(env.defaultTimeoutMillis()); +722 } +723 +724 public void expectCancelling(long timeoutMillis) throws InterruptedException { +725 cancelled.expectClose(timeoutMillis, "Did not receive expected cancelling of upstream subscription"); +726 } +727 } +728 +729 /** +730 * Like a CountDownLatch, but resettable and with some convenience methods +731 */ +732 public static class Latch { +733 private final TestEnvironment env; +734 volatile private CountDownLatch countDownLatch = new CountDownLatch(1); +735 +736 public Latch(TestEnvironment env) { +737 this.env = env; +738 } +739 +740 public void reOpen() { +741 countDownLatch = new CountDownLatch(1); +742 } +743 +744 public boolean isClosed() { +745 return countDownLatch.getCount() == 0; +746 } +747 +748 public void close() { +749 countDownLatch.countDown(); +750 } +751 +752 public void assertClosed(String openErrorMsg) { +753 if (!isClosed()) { +754 env.flop(new ExpectedClosedLatchException(openErrorMsg)); +755 } +756 } +757 +758 public void assertOpen(String closedErrorMsg) { +759 if (isClosed()) { +760 env.flop(new ExpectedOpenLatchException(closedErrorMsg)); +761 } +762 } +763 +764 public void expectClose(String notClosedErrorMsg) throws InterruptedException { +765 expectClose(env.defaultTimeoutMillis(), notClosedErrorMsg); +766 } +767 +768 public void expectClose(long timeoutMillis, String notClosedErrorMsg) throws InterruptedException { +769 countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS); +770 if (countDownLatch.getCount() > 0) { +771 env.flop(String.format("%s within %d ms", notClosedErrorMsg, timeoutMillis)); +772 } +773 } +774 +775 static final class ExpectedOpenLatchException extends RuntimeException { +776 public ExpectedOpenLatchException(String message) { +777 super(message); +778 } +779 } +780 +781 static final class ExpectedClosedLatchException extends RuntimeException { +782 public ExpectedClosedLatchException(String message) { +783 super(message); +784 } +785 } +786 +787 } +788 +789 // simple promise for *one* value, which cannot be reset +790 public static class Promise<T> { +791 private final TestEnvironment env; +792 +793 public static <T> Promise<T> completed(TestEnvironment env, T value) { +794 Promise<T> promise = new Promise<T>(env); +795 promise.completeImmediatly(value); +796 return promise; +797 } +798 +799 public Promise(TestEnvironment env) { +800 this.env = env; +801 } +802 +803 private ArrayBlockingQueue<T> abq = new ArrayBlockingQueue<T>(1); +804 private volatile T _value = null; +805 +806 public T value() { +807 if (isCompleted()) { +808 return _value; +809 } else { +810 env.flop("Cannot access promise value before completion"); +811 return null; +812 } +813 } +814 +815 public boolean isCompleted() { +816 return _value != null; +817 } +818 +819 /** +820 * Allows using expectCompletion to await for completion of the value and complete it _then_ +821 */ +822 public void complete(T value) { +823 abq.add(value); +824 } +825 +826 /** +827 * Completes the promise right away, it is not possible to expectCompletion on a Promise completed this way +828 */ +829 public void completeImmediatly(T value) { +830 complete(value); // complete! +831 _value = value; // immediatly! +832 } +833 +834 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +835 if (!isCompleted()) { +836 T val = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +837 +838 if (val == null) { +839 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +840 } else { +841 _value = val; +842 } +843 } +844 } +845 } +846 +847 // a "Promise" for multiple values, which also supports "end-of-stream reached" +848 public static class Receptacle<T> { +849 final int QUEUE_SIZE = 2 * TEST_BUFFER_SIZE; +850 private final TestEnvironment env; +851 +852 private final ArrayBlockingQueue<Optional<T>> abq = new ArrayBlockingQueue<Optional<T>>(QUEUE_SIZE); +853 +854 private final Latch completedLatch; +855 +856 Receptacle(TestEnvironment env) { +857 this.env = env; +858 this.completedLatch = new Latch(env); +859 } +860 +861 public void add(T value) { +862 completedLatch.assertOpen(String.format("Unexpected element %s received after stream completed", value)); +863 +864 abq.add(Optional.of(value)); +865 } +866 +867 public void complete() { +868 completedLatch.assertOpen("Unexpected additional complete signal received!"); +869 completedLatch.close(); +870 +871 abq.add(Optional.<T>empty()); +872 } +873 +874 public T next(long timeoutMillis, String errorMsg) throws InterruptedException { +875 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +876 +877 if (value == null) { +878 return env.flopAndFail(String.format("%s within %d ms", errorMsg, timeoutMillis)); +879 } else if (value.isDefined()) { +880 return value.get(); +881 } else { +882 return env.flopAndFail("Expected element but got end-of-stream"); +883 } +884 } +885 +886 public Optional<T> nextOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +887 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +888 +889 if (value == null) { +890 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +891 return Optional.empty(); +892 } +893 +894 return value; +895 } +896 +897 /** +898 * @param timeoutMillis total timeout time for awaiting all {@code elements} number of elements +899 */ +900 public List<T> nextN(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +901 List<T> result = new LinkedList<T>(); +902 long remaining = elements; +903 long deadline = System.currentTimeMillis() + timeoutMillis; +904 while (remaining > 0) { +905 long remainingMillis = deadline - System.currentTimeMillis(); +906 +907 result.add(next(remainingMillis, errorMsg)); +908 remaining--; +909 } +910 +911 return result; +912 } +913 +914 +915 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +916 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +917 +918 if (value == null) { +919 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +920 } else if (value.isDefined()) { +921 env.flop(String.format("Expected end-of-stream but got element [%s]", value.get())); +922 } // else, ok +923 } +924 +925 @SuppressWarnings("unchecked") +926 public <E extends Throwable> E expectError(Class<E> clazz, long timeoutMillis, String errorMsg) throws Exception { +927 Thread.sleep(timeoutMillis); +928 +929 if (env.asyncErrors.isEmpty()) { +930 return env.flopAndFail(String.format("%s within %d ms", errorMsg, timeoutMillis)); +931 } else { +932 // ok, there was an expected error +933 Throwable thrown = env.asyncErrors.remove(0); +934 +935 if (clazz.isInstance(thrown)) { +936 return (E) thrown; +937 } else { +938 +939 return env.flopAndFail(String.format("%s within %d ms; Got %s but expected %s", +940 errorMsg, timeoutMillis, thrown.getClass().getCanonicalName(), clazz.getCanonicalName())); +941 } +942 } +943 } +944 +945 public void expectNone(long withinMillis, String errorMsgPrefix) throws InterruptedException { +946 Thread.sleep(withinMillis); +947 Optional<T> value = abq.poll(); +948 +949 if (value == null) { +950 // ok +951 } else if (value.isDefined()) { +952 env.flop(String.format("%s [%s]", errorMsgPrefix, value.get())); +953 } else { +954 env.flop("Expected no element but got end-of-stream"); +955 } +956 } +957 } +958} +959 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck; +002 +003import org.reactivestreams.Publisher; +004import org.reactivestreams.Subscriber; +005import org.reactivestreams.Subscription; +006import org.reactivestreams.tck.support.SubscriberBufferOverflowException; +007import org.reactivestreams.tck.support.Optional; +008 +009import java.util.LinkedList; +010import java.util.List; +011import java.util.concurrent.ArrayBlockingQueue; +012import java.util.concurrent.CopyOnWriteArrayList; +013import java.util.concurrent.CountDownLatch; +014import java.util.concurrent.TimeUnit; +015 +016import static org.testng.Assert.assertTrue; +017import static org.testng.Assert.fail; +018 +019public class TestEnvironment { +020 public static final int TEST_BUFFER_SIZE = 16; +021 +022 private static final String DEFAULT_TIMEOUT_MILLIS_ENV = "DEFAULT_TIMEOUT_MILLIS"; +023 private static final long DEFAULT_TIMEOUT_MILLIS = 100; +024 +025 private final long defaultTimeoutMillis; +026 private final boolean printlnDebug; +027 +028 private CopyOnWriteArrayList<Throwable> asyncErrors = new CopyOnWriteArrayList<Throwable>(); +029 +030 /** +031 * Tests must specify the timeout for expected outcome of asynchronous +032 * interactions. Longer timeout does not invalidate the correctness of +033 * the implementation, but can in some cases result in longer time to +034 * run the tests. +035 * +036 * @param defaultTimeoutMillis default timeout to be used in all expect* methods +037 * @param printlnDebug if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output, +038 * often helpful to pinpoint simple race conditions etc. +039 */ +040 public TestEnvironment(long defaultTimeoutMillis, boolean printlnDebug) { +041 this.defaultTimeoutMillis = defaultTimeoutMillis; +042 this.printlnDebug = printlnDebug; +043 } +044 +045 /** +046 * Tests must specify the timeout for expected outcome of asynchronous +047 * interactions. Longer timeout does not invalidate the correctness of +048 * the implementation, but can in some cases result in longer time to +049 * run the tests. +050 * +051 * @param defaultTimeoutMillis default timeout to be used in all expect* methods +052 */ +053 public TestEnvironment(long defaultTimeoutMillis) { +054 this(defaultTimeoutMillis, false); +055 } +056 +057 /** +058 * Tests must specify the timeout for expected outcome of asynchronous +059 * interactions. Longer timeout does not invalidate the correctness of +060 * the implementation, but can in some cases result in longer time to +061 * run the tests. +062 * +063 * The default timeout for all expect* methods will be obtained by either the env variable {@code DEFAULT_TIMEOUT_MILLIS} +064 * or the default value ({@link TestEnvironment#DEFAULT_TIMEOUT_MILLIS}) will be used. +065 * +066 * @param printlnDebug if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output, +067 * often helpful to pinpoint simple race conditions etc. +068 */ +069 public TestEnvironment(boolean printlnDebug) { +070 this(envDefaultTimeoutMillis(), printlnDebug); +071 } +072 +073 /** +074 * Tests must specify the timeout for expected outcome of asynchronous +075 * interactions. Longer timeout does not invalidate the correctness of +076 * the implementation, but can in some cases result in longer time to +077 * run the tests. +078 * +079 * The default timeout for all expect* methods will be obtained by either the env variable {@code DEFAULT_TIMEOUT_MILLIS} +080 * or the default value ({@link TestEnvironment#DEFAULT_TIMEOUT_MILLIS}) will be used. +081 */ +082 public TestEnvironment() { +083 this(envDefaultTimeoutMillis()); +084 } +085 +086 public long defaultTimeoutMillis() { +087 return defaultTimeoutMillis; +088 } +089 +090 /** +091 * Tries to parse the env variable {@code DEFAULT_TIMEOUT_MILLIS} as long and returns the value if present OR its default value. +092 * +093 * @throws java.lang.IllegalArgumentException when unable to parse the env variable +094 */ +095 public static long envDefaultTimeoutMillis() { +096 final String envMillis = System.getenv(DEFAULT_TIMEOUT_MILLIS_ENV); +097 if (envMillis == null) return DEFAULT_TIMEOUT_MILLIS; +098 else try { +099 return Long.parseLong(envMillis); +100 } catch(NumberFormatException ex) { +101 throw new IllegalArgumentException(String.format("Unable to parse %s env value [%s] as long!", DEFAULT_TIMEOUT_MILLIS_ENV, envMillis), ex); +102 } +103 } +104 +105 /** +106 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +107 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +108 * +109 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +110 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +111 * from the environment using {@code env.dropAsyncError()}. +112 * +113 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +114 */ +115 public void flop(String msg) { +116 try { +117 fail(msg); +118 } catch (Throwable t) { +119 asyncErrors.add(t); +120 } +121 } +122 +123 /** +124 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +125 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +126 * +127 * This overload keeps the passed in throwable as the asyncError, instead of creating an AssertionError for this. +128 * +129 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +130 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +131 * from the environment using {@code env.dropAsyncError()}. +132 * +133 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +134 */ +135 public void flop(Throwable thr, String msg) { +136 try { +137 fail(msg, thr); +138 } catch (Throwable t) { +139 asyncErrors.add(thr); +140 } +141 } +142 +143 /** +144 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +145 * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required. +146 * +147 * This overload keeps the passed in throwable as the asyncError, instead of creating an AssertionError for this. +148 * +149 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +150 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +151 * from the environment using {@code env.dropAsyncError()}. +152 * +153 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +154 */ +155 public void flop(Throwable thr) { +156 try { +157 fail(thr.getMessage(), thr); +158 } catch (Throwable t) { +159 asyncErrors.add(thr); +160 } +161 } +162 +163 /** +164 * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously. +165 * +166 * This method DOES fail the test right away (it tries to, by throwing an AssertionException), +167 * in such it is different from {@link org.reactivestreams.tck.TestEnvironment#flop} which only records the error. +168 * +169 * Use {@code env.verifyNoAsyncErrorsNoDelay()} at the end of your TCK tests to verify there no flops called during it's execution. +170 * To check investigate asyncErrors more closely you can use {@code expectError} methods or collect the error directly +171 * from the environment using {@code env.dropAsyncError()}. +172 * +173 * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()} +174 */ +175 public <T> T flopAndFail(String msg) { +176 try { +177 fail(msg); +178 } catch (Throwable t) { +179 asyncErrors.add(t); +180 fail(msg, t); +181 } +182 return null; // unreachable, the previous block will always exit by throwing +183 } +184 +185 +186 +187 public <T> void subscribe(Publisher<T> pub, TestSubscriber<T> sub) throws InterruptedException { +188 subscribe(pub, sub, defaultTimeoutMillis); +189 } +190 +191 public <T> void subscribe(Publisher<T> pub, TestSubscriber<T> sub, long timeoutMillis) throws InterruptedException { +192 pub.subscribe(sub); +193 sub.subscription.expectCompletion(timeoutMillis, String.format("Could not subscribe %s to Publisher %s", sub, pub)); +194 verifyNoAsyncErrorsNoDelay(); +195 } +196 +197 public <T> ManualSubscriber<T> newBlackholeSubscriber(Publisher<T> pub) throws InterruptedException { +198 ManualSubscriberWithSubscriptionSupport<T> sub = new BlackholeSubscriberWithSubscriptionSupport<T>(this); +199 subscribe(pub, sub, defaultTimeoutMillis()); +200 return sub; +201 } +202 +203 public <T> ManualSubscriber<T> newManualSubscriber(Publisher<T> pub) throws InterruptedException { +204 return newManualSubscriber(pub, defaultTimeoutMillis()); +205 } +206 +207 public <T> ManualSubscriber<T> newManualSubscriber(Publisher<T> pub, long timeoutMillis) throws InterruptedException { +208 ManualSubscriberWithSubscriptionSupport<T> sub = new ManualSubscriberWithSubscriptionSupport<T>(this); +209 subscribe(pub, sub, timeoutMillis); +210 return sub; +211 } +212 +213 public void clearAsyncErrors() { +214 asyncErrors.clear(); +215 } +216 +217 public Throwable dropAsyncError() { +218 try { +219 return asyncErrors.remove(0); +220 } catch (IndexOutOfBoundsException ex) { +221 return null; +222 } +223 } +224 +225 /** +226 * Waits for {@link TestEnvironment#defaultTimeoutMillis()} and then verifies that no asynchronous errors +227 * were signalled pior to, or during that time (by calling {@code flop()}). +228 */ +229 public void verifyNoAsyncErrors() { +230 verifyNoAsyncErrors(defaultTimeoutMillis()); +231 } +232 +233 /** +234 * This version of {@code verifyNoAsyncErrors} should be used when errors still could be signalled +235 * asynchronously during {@link TestEnvironment#defaultTimeoutMillis()} time. +236 * <p></p> +237 * It will immediatly check if any async errors were signaled (using {@link TestEnvironment#flop(String)}, +238 * and if no errors encountered wait for another default timeout as the errors may yet be signalled. +239 * The initial check is performed in order to fail-fast in case of an already failed test. +240 */ +241 public void verifyNoAsyncErrors(long delay) { +242 try { +243 verifyNoAsyncErrorsNoDelay(); +244 +245 Thread.sleep(delay); +246 verifyNoAsyncErrorsNoDelay(); +247 } catch (InterruptedException e) { +248 throw new RuntimeException(e); +249 } +250 } +251 +252 /** +253 * Verifies that no asynchronous errors were signalled pior to calling this method (by calling {@code flop()}). +254 * This version of verifyNoAsyncError <b>does not wait before checking for asynchronous errors</b>, and is to be used +255 * for example in tight loops etc. +256 */ +257 public void verifyNoAsyncErrorsNoDelay() { +258 for (Throwable e : asyncErrors) { +259 if (e instanceof AssertionError) { +260 throw (AssertionError) e; +261 } else { +262 fail(String.format("Async error during test execution: %s", e.getMessage()), e); +263 } +264 } +265 } +266 +267 /** If {@code TestEnvironment#printlnDebug} is true, print debug message to std out. */ +268 public void debug(String msg) { +269 if (printlnDebug) +270 System.out.printf("[TCK-DEBUG] %s%n", msg); +271 } +272 +273 /** +274 * Looks for given {@code method} method in stack trace. +275 * Can be used to answer questions like "was this method called from onComplete?". +276 * +277 * @return the caller's StackTraceElement at which he the looked for method was found in the call stack, EMPTY otherwise +278 */ +279 public Optional<StackTraceElement> findCallerMethodInStackTrace(String method) { +280 final Throwable thr = new Throwable(); // gets the stacktrace +281 +282 for (StackTraceElement stackElement : thr.getStackTrace()) { +283 if (stackElement.getMethodName().equals(method)) { +284 return Optional.of(stackElement); +285 } +286 } +287 return Optional.empty(); +288 } +289 +290 // ---- classes ---- +291 +292 /** +293 * {@link Subscriber} implementation which can be steered by test code and asserted on. +294 */ +295 public static class ManualSubscriber<T> extends TestSubscriber<T> { +296 Receptacle<T> received; +297 +298 public ManualSubscriber(TestEnvironment env) { +299 super(env); +300 received = new Receptacle<T>(this.env); +301 } +302 +303 @Override +304 public void onNext(T element) { +305 try { +306 received.add(element); +307 } catch (IllegalStateException ex) { +308 // error message refinement +309 throw new SubscriberBufferOverflowException( +310 String.format("Received more than bufferSize (%d) onNext signals. " + +311 "The Publisher probably emited more signals than expected!", +312 received.QUEUE_SIZE), ex); +313 } +314 } +315 +316 @Override +317 public void onComplete() { +318 received.complete(); +319 } +320 +321 public void request(long elements) { +322 subscription.value().request(elements); +323 } +324 +325 public T requestNextElement() throws InterruptedException { +326 return requestNextElement(env.defaultTimeoutMillis()); +327 } +328 +329 public T requestNextElement(long timeoutMillis) throws InterruptedException { +330 return requestNextElement(timeoutMillis, "Did not receive expected element"); +331 } +332 +333 public T requestNextElement(String errorMsg) throws InterruptedException { +334 return requestNextElement(env.defaultTimeoutMillis(), errorMsg); +335 } +336 +337 public T requestNextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +338 request(1); +339 return nextElement(timeoutMillis, errorMsg); +340 } +341 +342 public Optional<T> requestNextElementOrEndOfStream(String errorMsg) throws InterruptedException { +343 return requestNextElementOrEndOfStream(env.defaultTimeoutMillis(), errorMsg); +344 } +345 +346 public Optional<T> requestNextElementOrEndOfStream(long timeoutMillis) throws InterruptedException { +347 return requestNextElementOrEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +348 } +349 +350 public Optional<T> requestNextElementOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +351 request(1); +352 return nextElementOrEndOfStream(timeoutMillis, errorMsg); +353 } +354 +355 public void requestEndOfStream() throws InterruptedException { +356 requestEndOfStream(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +357 } +358 +359 public void requestEndOfStream(long timeoutMillis) throws InterruptedException { +360 requestEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +361 } +362 +363 public void requestEndOfStream(String errorMsg) throws InterruptedException { +364 requestEndOfStream(env.defaultTimeoutMillis(), errorMsg); +365 } +366 +367 public void requestEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +368 request(1); +369 expectCompletion(timeoutMillis, errorMsg); +370 } +371 +372 public List<T> requestNextElements(long elements) throws InterruptedException { +373 request(elements); +374 return nextElements(elements, env.defaultTimeoutMillis()); +375 } +376 +377 public List<T> requestNextElements(long elements, long timeoutMillis) throws InterruptedException { +378 request(elements); +379 return nextElements(elements, timeoutMillis, String.format("Did not receive %d expected elements", elements)); +380 } +381 +382 public List<T> requestNextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +383 request(elements); +384 return nextElements(elements, timeoutMillis, errorMsg); +385 } +386 +387 public T nextElement() throws InterruptedException { +388 return nextElement(env.defaultTimeoutMillis()); +389 } +390 +391 public T nextElement(long timeoutMillis) throws InterruptedException { +392 return nextElement(timeoutMillis, "Did not receive expected element"); +393 } +394 +395 public T nextElement(String errorMsg) throws InterruptedException { +396 return nextElement(env.defaultTimeoutMillis(), errorMsg); +397 } +398 +399 public T nextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +400 return received.next(timeoutMillis, errorMsg); +401 } +402 +403 public Optional<T> nextElementOrEndOfStream() throws InterruptedException { +404 return nextElementOrEndOfStream(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +405 } +406 +407 public Optional<T> nextElementOrEndOfStream(long timeoutMillis) throws InterruptedException { +408 return nextElementOrEndOfStream(timeoutMillis, "Did not receive expected stream completion"); +409 } +410 +411 public Optional<T> nextElementOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +412 return received.nextOrEndOfStream(timeoutMillis, errorMsg); +413 } +414 +415 public List<T> nextElements(long elements) throws InterruptedException { +416 return nextElements(elements, env.defaultTimeoutMillis(), "Did not receive expected element or completion"); +417 } +418 +419 public List<T> nextElements(long elements, String errorMsg) throws InterruptedException { +420 return nextElements(elements, env.defaultTimeoutMillis(), errorMsg); +421 } +422 +423 public List<T> nextElements(long elements, long timeoutMillis) throws InterruptedException { +424 return nextElements(elements, timeoutMillis, "Did not receive expected element or completion"); +425 } +426 +427 public List<T> nextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +428 return received.nextN(elements, timeoutMillis, errorMsg); +429 } +430 +431 public void expectNext(T expected) throws InterruptedException { +432 expectNext(expected, env.defaultTimeoutMillis()); +433 } +434 +435 public void expectNext(T expected, long timeoutMillis) throws InterruptedException { +436 T received = nextElement(timeoutMillis, "Did not receive expected element on downstream"); +437 if (!received.equals(expected)) { +438 env.flop(String.format("Expected element %s on downstream but received %s", expected, received)); +439 } +440 } +441 +442 public void expectCompletion() throws InterruptedException { +443 expectCompletion(env.defaultTimeoutMillis(), "Did not receive expected stream completion"); +444 } +445 +446 public void expectCompletion(long timeoutMillis) throws InterruptedException { +447 expectCompletion(timeoutMillis, "Did not receive expected stream completion"); +448 } +449 +450 public void expectCompletion(String errorMsg) throws InterruptedException { +451 expectCompletion(env.defaultTimeoutMillis(), errorMsg); +452 } +453 +454 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +455 received.expectCompletion(timeoutMillis, errorMsg); +456 } +457 +458 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart) throws Exception { +459 expectErrorWithMessage(expected, requiredMessagePart, env.defaultTimeoutMillis()); +460 } +461 +462 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") +463 public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart, long timeoutMillis) throws Exception { +464 final E err = expectError(expected, timeoutMillis); +465 final String message = err.getMessage(); +466 assertTrue(message.contains(requiredMessagePart), +467 String.format("Got expected exception [%s] but missing message part [%s], was: %s", +468 err.getClass(), requiredMessagePart, err.getMessage())); +469 } +470 +471 public <E extends Throwable> E expectError(Class<E> expected) throws Exception { +472 return expectError(expected, env.defaultTimeoutMillis()); +473 } +474 +475 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis) throws Exception { +476 return expectError(expected, timeoutMillis, String.format("Expected onError(%s)", expected.getName())); +477 } +478 +479 public <E extends Throwable> E expectError(Class<E> expected, String errorMsg) throws Exception { +480 return expectError(expected, env.defaultTimeoutMillis(), errorMsg); +481 } +482 +483 public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis, String errorMsg) throws Exception { +484 return received.expectError(expected, timeoutMillis, errorMsg); +485 } +486 +487 public void expectNone() throws InterruptedException { +488 expectNone(env.defaultTimeoutMillis()); +489 } +490 +491 public void expectNone(String errMsgPrefix) throws InterruptedException { +492 expectNone(env.defaultTimeoutMillis(), errMsgPrefix); +493 } +494 +495 public void expectNone(long withinMillis) throws InterruptedException { +496 expectNone(withinMillis, "Did not expect an element but got element"); +497 } +498 +499 public void expectNone(long withinMillis, String errMsgPrefix) throws InterruptedException { +500 received.expectNone(withinMillis, errMsgPrefix); +501 } +502 +503 } +504 +505 public static class ManualSubscriberWithSubscriptionSupport<T> extends ManualSubscriber<T> { +506 +507 public ManualSubscriberWithSubscriptionSupport(TestEnvironment env) { +508 super(env); +509 } +510 +511 @Override +512 public void onNext(T element) { +513 env.debug(String.format("%s::onNext(%s)", this, element)); +514 if (subscription.isCompleted()) { +515 super.onNext(element); +516 } else { +517 env.flop(String.format("Subscriber::onNext(%s) called before Subscriber::onSubscribe", element)); +518 } +519 } +520 +521 @Override +522 public void onComplete() { +523 env.debug(this + "::onComplete()"); +524 if (subscription.isCompleted()) { +525 super.onComplete(); +526 } else { +527 env.flop("Subscriber::onComplete() called before Subscriber::onSubscribe"); +528 } +529 } +530 +531 @Override +532 public void onSubscribe(Subscription s) { +533 env.debug(String.format("%s::onSubscribe(%s)", this, s)); +534 if (!subscription.isCompleted()) { +535 subscription.complete(s); +536 } else { +537 env.flop("Subscriber::onSubscribe called on an already-subscribed Subscriber"); +538 } +539 } +540 +541 @Override +542 public void onError(Throwable cause) { +543 env.debug(String.format("%s::onError(%s)", this, cause)); +544 if (subscription.isCompleted()) { +545 super.onError(cause); +546 } else { +547 env.flop(cause, String.format("Subscriber::onError(%s) called before Subscriber::onSubscribe", cause)); +548 } +549 } +550 } +551 +552 /** +553 * Similar to {@link org.reactivestreams.tck.TestEnvironment.ManualSubscriberWithSubscriptionSupport} +554 * but does not accumulate values signalled via <code>onNext</code>, thus it can not be used to assert +555 * values signalled to this subscriber. Instead it may be used to quickly drain a given publisher. +556 */ +557 public static class BlackholeSubscriberWithSubscriptionSupport<T> +558 extends ManualSubscriberWithSubscriptionSupport<T> { +559 +560 public BlackholeSubscriberWithSubscriptionSupport(TestEnvironment env) { +561 super(env); +562 } +563 +564 @Override +565 public void onNext(T element) { +566 env.debug(String.format("%s::onNext(%s)", this, element)); +567 if (!subscription.isCompleted()) { +568 env.flop(String.format("Subscriber::onNext(%s) called before Subscriber::onSubscribe", element)); +569 } +570 } +571 +572 @Override +573 public T nextElement(long timeoutMillis, String errorMsg) throws InterruptedException { +574 throw new RuntimeException("Can not expect elements from BlackholeSubscriber, use ManualSubscriber instead!"); +575 } +576 +577 @Override +578 public List<T> nextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +579 throw new RuntimeException("Can not expect elements from BlackholeSubscriber, use ManualSubscriber instead!"); +580 } +581 } +582 +583 public static class TestSubscriber<T> implements Subscriber<T> { +584 final Promise<Subscription> subscription; +585 +586 protected final TestEnvironment env; +587 +588 public TestSubscriber(TestEnvironment env) { +589 this.env = env; +590 subscription = new Promise<Subscription>(env); +591 } +592 +593 @Override +594 public void onError(Throwable cause) { +595 env.flop(cause, String.format("Unexpected Subscriber::onError(%s)", cause)); +596 } +597 +598 @Override +599 public void onComplete() { +600 env.flop("Unexpected Subscriber::onComplete()"); +601 } +602 +603 @Override +604 public void onNext(T element) { +605 env.flop(String.format("Unexpected Subscriber::onNext(%s)", element)); +606 } +607 +608 @Override +609 public void onSubscribe(Subscription subscription) { +610 env.flop(String.format("Unexpected Subscriber::onSubscribe(%s)", subscription)); +611 } +612 +613 public void cancel() { +614 if (subscription.isCompleted()) { +615 subscription.value().cancel(); +616 } else { +617 env.flop("Cannot cancel a subscription before having received it"); +618 } +619 } +620 } +621 +622 public static class ManualPublisher<T> implements Publisher<T> { +623 protected final TestEnvironment env; +624 +625 protected long pendingDemand = 0L; +626 protected Promise<Subscriber<? super T>> subscriber; +627 +628 protected final Receptacle<Long> requests; +629 +630 protected final Latch cancelled; +631 +632 public ManualPublisher(TestEnvironment env) { +633 this.env = env; +634 requests = new Receptacle<Long>(env); +635 cancelled = new Latch(env); +636 subscriber = new Promise<Subscriber<? super T>>(this.env); +637 } +638 +639 @Override +640 public void subscribe(Subscriber<? super T> s) { +641 if (!subscriber.isCompleted()) { +642 subscriber.completeImmediatly(s); +643 +644 Subscription subs = new Subscription() { +645 @Override +646 public void request(long elements) { +647 requests.add(elements); +648 } +649 +650 @Override +651 public void cancel() { +652 cancelled.close(); +653 } +654 }; +655 s.onSubscribe(subs); +656 +657 } else { +658 env.flop("TestPublisher doesn't support more than one Subscriber"); +659 } +660 } +661 +662 public void sendNext(T element) { +663 if (subscriber.isCompleted()) { +664 subscriber.value().onNext(element); +665 } else { +666 env.flop("Cannot sendNext before having a Subscriber"); +667 } +668 } +669 +670 public void sendCompletion() { +671 if (subscriber.isCompleted()) { +672 subscriber.value().onComplete(); +673 } else { +674 env.flop("Cannot sendCompletion before having a Subscriber"); +675 } +676 } +677 +678 public void sendError(Throwable cause) { +679 if (subscriber.isCompleted()) { +680 subscriber.value().onError(cause); +681 } else { +682 env.flop("Cannot sendError before having a Subscriber"); +683 } +684 } +685 +686 public long expectRequest() throws InterruptedException { +687 return expectRequest(env.defaultTimeoutMillis()); +688 } +689 +690 public long expectRequest(long timeoutMillis) throws InterruptedException { +691 long requested = requests.next(timeoutMillis, "Did not receive expected `request` call"); +692 if (requested <= 0) { +693 return env.<Long>flopAndFail(String.format("Requests cannot be zero or negative but received request(%s)", requested)); +694 } else { +695 pendingDemand += requested; +696 return requested; +697 } +698 } +699 +700 public void expectExactRequest(long expected) throws InterruptedException { +701 expectExactRequest(expected, env.defaultTimeoutMillis()); +702 } +703 +704 public void expectExactRequest(long expected, long timeoutMillis) throws InterruptedException { +705 long requested = expectRequest(timeoutMillis); +706 if (requested != expected) { +707 env.flop(String.format("Received `request(%d)` on upstream but expected `request(%d)`", requested, expected)); +708 } +709 pendingDemand += requested; +710 } +711 +712 public void expectNoRequest() throws InterruptedException { +713 expectNoRequest(env.defaultTimeoutMillis()); +714 } +715 +716 public void expectNoRequest(long timeoutMillis) throws InterruptedException { +717 requests.expectNone(timeoutMillis, "Received an unexpected call to: request: "); +718 } +719 +720 public void expectCancelling() throws InterruptedException { +721 expectCancelling(env.defaultTimeoutMillis()); +722 } +723 +724 public void expectCancelling(long timeoutMillis) throws InterruptedException { +725 cancelled.expectClose(timeoutMillis, "Did not receive expected cancelling of upstream subscription"); +726 } +727 } +728 +729 /** +730 * Like a CountDownLatch, but resettable and with some convenience methods +731 */ +732 public static class Latch { +733 private final TestEnvironment env; +734 volatile private CountDownLatch countDownLatch = new CountDownLatch(1); +735 +736 public Latch(TestEnvironment env) { +737 this.env = env; +738 } +739 +740 public void reOpen() { +741 countDownLatch = new CountDownLatch(1); +742 } +743 +744 public boolean isClosed() { +745 return countDownLatch.getCount() == 0; +746 } +747 +748 public void close() { +749 countDownLatch.countDown(); +750 } +751 +752 public void assertClosed(String openErrorMsg) { +753 if (!isClosed()) { +754 env.flop(new ExpectedClosedLatchException(openErrorMsg)); +755 } +756 } +757 +758 public void assertOpen(String closedErrorMsg) { +759 if (isClosed()) { +760 env.flop(new ExpectedOpenLatchException(closedErrorMsg)); +761 } +762 } +763 +764 public void expectClose(String notClosedErrorMsg) throws InterruptedException { +765 expectClose(env.defaultTimeoutMillis(), notClosedErrorMsg); +766 } +767 +768 public void expectClose(long timeoutMillis, String notClosedErrorMsg) throws InterruptedException { +769 countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS); +770 if (countDownLatch.getCount() > 0) { +771 env.flop(String.format("%s within %d ms", notClosedErrorMsg, timeoutMillis)); +772 } +773 } +774 +775 static final class ExpectedOpenLatchException extends RuntimeException { +776 public ExpectedOpenLatchException(String message) { +777 super(message); +778 } +779 } +780 +781 static final class ExpectedClosedLatchException extends RuntimeException { +782 public ExpectedClosedLatchException(String message) { +783 super(message); +784 } +785 } +786 +787 } +788 +789 // simple promise for *one* value, which cannot be reset +790 public static class Promise<T> { +791 private final TestEnvironment env; +792 +793 public static <T> Promise<T> completed(TestEnvironment env, T value) { +794 Promise<T> promise = new Promise<T>(env); +795 promise.completeImmediatly(value); +796 return promise; +797 } +798 +799 public Promise(TestEnvironment env) { +800 this.env = env; +801 } +802 +803 private ArrayBlockingQueue<T> abq = new ArrayBlockingQueue<T>(1); +804 private volatile T _value = null; +805 +806 public T value() { +807 if (isCompleted()) { +808 return _value; +809 } else { +810 env.flop("Cannot access promise value before completion"); +811 return null; +812 } +813 } +814 +815 public boolean isCompleted() { +816 return _value != null; +817 } +818 +819 /** +820 * Allows using expectCompletion to await for completion of the value and complete it _then_ +821 */ +822 public void complete(T value) { +823 abq.add(value); +824 } +825 +826 /** +827 * Completes the promise right away, it is not possible to expectCompletion on a Promise completed this way +828 */ +829 public void completeImmediatly(T value) { +830 complete(value); // complete! +831 _value = value; // immediatly! +832 } +833 +834 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +835 if (!isCompleted()) { +836 T val = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +837 +838 if (val == null) { +839 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +840 } else { +841 _value = val; +842 } +843 } +844 } +845 } +846 +847 // a "Promise" for multiple values, which also supports "end-of-stream reached" +848 public static class Receptacle<T> { +849 final int QUEUE_SIZE = 2 * TEST_BUFFER_SIZE; +850 private final TestEnvironment env; +851 +852 private final ArrayBlockingQueue<Optional<T>> abq = new ArrayBlockingQueue<Optional<T>>(QUEUE_SIZE); +853 +854 private final Latch completedLatch; +855 +856 Receptacle(TestEnvironment env) { +857 this.env = env; +858 this.completedLatch = new Latch(env); +859 } +860 +861 public void add(T value) { +862 completedLatch.assertOpen(String.format("Unexpected element %s received after stream completed", value)); +863 +864 abq.add(Optional.of(value)); +865 } +866 +867 public void complete() { +868 completedLatch.assertOpen("Unexpected additional complete signal received!"); +869 completedLatch.close(); +870 +871 abq.add(Optional.<T>empty()); +872 } +873 +874 public T next(long timeoutMillis, String errorMsg) throws InterruptedException { +875 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +876 +877 if (value == null) { +878 return env.flopAndFail(String.format("%s within %d ms", errorMsg, timeoutMillis)); +879 } else if (value.isDefined()) { +880 return value.get(); +881 } else { +882 return env.flopAndFail("Expected element but got end-of-stream"); +883 } +884 } +885 +886 public Optional<T> nextOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException { +887 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +888 +889 if (value == null) { +890 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +891 return Optional.empty(); +892 } +893 +894 return value; +895 } +896 +897 /** +898 * @param timeoutMillis total timeout time for awaiting all {@code elements} number of elements +899 */ +900 public List<T> nextN(long elements, long timeoutMillis, String errorMsg) throws InterruptedException { +901 List<T> result = new LinkedList<T>(); +902 long remaining = elements; +903 long deadline = System.currentTimeMillis() + timeoutMillis; +904 while (remaining > 0) { +905 long remainingMillis = deadline - System.currentTimeMillis(); +906 +907 result.add(next(remainingMillis, errorMsg)); +908 remaining--; +909 } +910 +911 return result; +912 } +913 +914 +915 public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException { +916 Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS); +917 +918 if (value == null) { +919 env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis)); +920 } else if (value.isDefined()) { +921 env.flop(String.format("Expected end-of-stream but got element [%s]", value.get())); +922 } // else, ok +923 } +924 +925 @SuppressWarnings("unchecked") +926 public <E extends Throwable> E expectError(Class<E> clazz, long timeoutMillis, String errorMsg) throws Exception { +927 Thread.sleep(timeoutMillis); +928 +929 if (env.asyncErrors.isEmpty()) { +930 return env.flopAndFail(String.format("%s within %d ms", errorMsg, timeoutMillis)); +931 } else { +932 // ok, there was an expected error +933 Throwable thrown = env.asyncErrors.remove(0); +934 +935 if (clazz.isInstance(thrown)) { +936 return (E) thrown; +937 } else { +938 +939 return env.flopAndFail(String.format("%s within %d ms; Got %s but expected %s", +940 errorMsg, timeoutMillis, thrown.getClass().getCanonicalName(), clazz.getCanonicalName())); +941 } +942 } +943 } +944 +945 public void expectNone(long withinMillis, String errorMsgPrefix) throws InterruptedException { +946 Thread.sleep(withinMillis); +947 Optional<T> value = abq.poll(); +948 +949 if (value == null) { +950 // ok +951 } else if (value.isDefined()) { +952 env.flop(String.format("%s [%s]", errorMsgPrefix, value.get())); +953 } else { +954 env.flop("Expected no element but got end-of-stream"); +955 } +956 } +957 } +958} +959 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck; +002 +003import org.reactivestreams.Publisher; +004import org.reactivestreams.tck.support.Function; +005import org.reactivestreams.tck.support.HelperPublisher; +006import org.reactivestreams.tck.support.InfiniteHelperPublisher; +007 +008import java.util.concurrent.ExecutorService; +009 +010/** +011 * Type which is able to create elements based on a seed {@code id} value. +012 * <p> +013 * Simplest implementations will simply return the incoming id as the element. +014 * +015 * @param <T> type of element to be delivered to the Subscriber +016 */ +017public abstract class WithHelperPublisher<T> { +018 +019 /** ExecutorService to be used by the provided helper {@link org.reactivestreams.Publisher} */ +020 public abstract ExecutorService publisherExecutorService(); +021 +022 /** +023 * Implement this method to match your expected element type. +024 * In case of implementing a simple Subscriber which is able to consume any kind of element simply return the +025 * incoming {@code element} element. +026 * <p> +027 * Sometimes the Subscriber may be limited in what type of element it is able to consume, this you may have to implement +028 * this method such that the emitted element matches the Subscribers requirements. Simplest implementations would be +029 * to simply pass in the {@code element} as payload of your custom element, such as appending it to a String or other identifier. +030 * <p> +031 * <b>Warning:</b> This method may be called concurrently by the helper publisher, thus it should be implemented in a +032 * thread-safe manner. +033 * +034 * @return element of the matching type {@code T} that will be delivered to the tested Subscriber +035 */ +036 public abstract T createElement(int element); +037 +038 /** +039 * Helper method required for creating the Publisher to which the tested Subscriber will be subscribed and tested against. +040 * <p> +041 * By default an <b>asynchronously signalling Publisher</b> is provided, which will use {@link #createElement(int)} +042 * to generate elements type your Subscriber is able to consume. +043 * <p> +044 * Sometimes you may want to implement your own custom custom helper Publisher - to validate behaviour of a Subscriber +045 * when facing a synchronous Publisher for example. If you do, it MUST emit the exact number of elements asked for +046 * (via the {@code elements} parameter) and MUST also must treat the following numbers of elements in these specific ways: +047 * <ul> +048 * <li> +049 * If {@code elements} is {@code Long.MAX_VALUE} the produced stream must be infinite. +050 * </li> +051 * <li> +052 * If {@code elements} is {@code 0} the {@code Publisher} should signal {@code onComplete} immediatly. +053 * In other words, it should represent a "completed stream". +054 * </li> +055 * </ul> +056 */ +057 @SuppressWarnings("unchecked") +058 public Publisher<T> createHelperPublisher(long elements) { +059 final Function<Integer, T> mkElement = new Function<Integer, T>() { +060 @Override public T apply(Integer id) throws Throwable { +061 return createElement(id); +062 } +063 }; +064 +065 if (elements > Integer.MAX_VALUE) return new InfiniteHelperPublisher(mkElement, publisherExecutorService()); +066 else return new HelperPublisher(0, (int) elements, mkElement, publisherExecutorService()); +067 } +068 +069} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck.support; +002 +003public interface Function<In, Out> { +004 public Out apply(In in) throws Throwable; +005} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck.support; +002 +003import java.util.Collections; +004import java.util.Iterator; +005import java.util.concurrent.Executor; +006import org.reactivestreams.Subscription; +007import org.reactivestreams.Subscriber; +008import org.reactivestreams.Publisher; +009import org.reactivestreams.example.unicast.AsyncIterablePublisher; +010 +011public class HelperPublisher<T> extends AsyncIterablePublisher<T> { +012 +013 public HelperPublisher(final int from, final int to, final Function<Integer, T> create, final Executor executor) { +014 super(new Iterable<T>() { +015 { if(from > to) throw new IllegalArgumentException("from must be equal or greater than to!"); } +016 @Override public Iterator<T> iterator() { +017 return new Iterator<T>() { +018 private int at = from; +019 @Override public boolean hasNext() { return at < to; } +020 @Override public T next() { +021 if (!hasNext()) return Collections.<T>emptyList().iterator().next(); +022 else try { +023 return create.apply(at++); +024 } catch (Throwable t) { +025 throw new IllegalStateException(String.format("Failed to create element for id %d!", at - 1), t); +026 } +027 } +028 @Override public void remove() { throw new UnsupportedOperationException(); } +029 }; +030 } +031 }, executor); +032 } +033} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck.support; +002 +003import org.reactivestreams.example.unicast.AsyncIterablePublisher; +004 +005import java.util.Iterator; +006import java.util.concurrent.Executor; +007 +008public class InfiniteHelperPublisher<T> extends AsyncIterablePublisher<T> { +009 +010 public InfiniteHelperPublisher(final Function<Integer, T> create, final Executor executor) { +011 super(new Iterable<T>() { +012 @Override public Iterator<T> iterator() { +013 return new Iterator<T>() { +014 private int at = 0; +015 +016 @Override public boolean hasNext() { return true; } +017 @Override public T next() { +018 try { +019 return create.apply(at++); // Wraps around on overflow +020 } catch (Throwable t) { +021 throw new IllegalStateException( +022 String.format("Failed to create element in %s for id %s!", getClass().getSimpleName(), at - 1), t); +023 } +024 } +025 @Override public void remove() { throw new UnsupportedOperationException(); } +026 }; +027 } +028 }, executor); +029 } +030} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck.support; +002 +003 +004/** +005 * Copy of scala.control.util.NonFatal in order to not depend on scala-library +006 */ +007public class NonFatal { +008 private NonFatal() { +009 // no instances, please. +010 } +011 +012 /** +013 * Returns true if the provided `Throwable` is to be considered non-fatal, or false if it is to be considered fatal +014 * +015 * @param t throwable to be matched for fatal-ness +016 * @return true if is a non-fatal throwable, false otherwise +017 */ +018 public static boolean isNonFatal(Throwable t) { +019 if (t instanceof StackOverflowError) { +020 // StackOverflowError ok even though it is a VirtualMachineError +021 return true; +022 } else if (t instanceof VirtualMachineError || +023 t instanceof ThreadDeath || +024 t instanceof InterruptedException || +025 t instanceof LinkageError) { +026 // VirtualMachineError includes OutOfMemoryError and other fatal errors +027 return false; +028 } else { +029 return true; +030 } +031 } +032} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck.support; +002 +003import java.util.NoSuchElementException; +004 +005// simplest possible version of Scala's Option type +006public abstract class Optional<T> { +007 +008 private static final Optional<Object> NONE = new Optional<Object>() { +009 @Override +010 public Object get() { +011 throw new NoSuchElementException(".get call on None!"); +012 } +013 +014 @Override +015 public boolean isEmpty() { +016 return true; +017 } +018 }; +019 +020 private Optional() { +021 } +022 +023 @SuppressWarnings("unchecked") +024 public static <T> Optional<T> empty() { +025 return (Optional<T>) NONE; +026 } +027 +028 @SuppressWarnings("unchecked") +029 public static <T> Optional<T> of(T it) { +030 if (it == null) return (Optional<T>) Optional.NONE; +031 else return new Some(it); +032 } +033 +034 public abstract T get(); +035 +036 public abstract boolean isEmpty(); +037 +038 public boolean isDefined() { +039 return !isEmpty(); +040 } +041 +042 public static class Some<T> extends Optional<T> { +043 private final T value; +044 +045 Some(T value) { +046 this.value = value; +047 } +048 +049 @Override +050 public T get() { +051 return value; +052 } +053 +054 @Override +055 public boolean isEmpty() { +056 return false; +057 } +058 +059 @Override +060 public String toString() { +061 return String.format("Some(%s)", value); +062 } +063 } +064 +065 @Override +066 public String toString() { +067 return "None"; +068 } +069} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck.support; +002 +003import java.util.NoSuchElementException; +004 +005// simplest possible version of Scala's Option type +006public abstract class Optional<T> { +007 +008 private static final Optional<Object> NONE = new Optional<Object>() { +009 @Override +010 public Object get() { +011 throw new NoSuchElementException(".get call on None!"); +012 } +013 +014 @Override +015 public boolean isEmpty() { +016 return true; +017 } +018 }; +019 +020 private Optional() { +021 } +022 +023 @SuppressWarnings("unchecked") +024 public static <T> Optional<T> empty() { +025 return (Optional<T>) NONE; +026 } +027 +028 @SuppressWarnings("unchecked") +029 public static <T> Optional<T> of(T it) { +030 if (it == null) return (Optional<T>) Optional.NONE; +031 else return new Some(it); +032 } +033 +034 public abstract T get(); +035 +036 public abstract boolean isEmpty(); +037 +038 public boolean isDefined() { +039 return !isEmpty(); +040 } +041 +042 public static class Some<T> extends Optional<T> { +043 private final T value; +044 +045 Some(T value) { +046 this.value = value; +047 } +048 +049 @Override +050 public T get() { +051 return value; +052 } +053 +054 @Override +055 public boolean isEmpty() { +056 return false; +057 } +058 +059 @Override +060 public String toString() { +061 return String.format("Some(%s)", value); +062 } +063 } +064 +065 @Override +066 public String toString() { +067 return "None"; +068 } +069} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck.support; +002 +003 +004/** +005 * Internal TCK use only. +006 * Add / Remove tests for PublisherVerification here to make sure that they arre added/removed in the other places. +007 */ +008public interface PublisherVerificationRules { +009 void required_validate_maxElementsFromPublisher() throws Exception; +010 void required_validate_boundedDepthOfOnNextAndRequestRecursion() throws Exception; +011 void required_createPublisher1MustProduceAStreamOfExactly1Element() throws Throwable; +012 void required_createPublisher3MustProduceAStreamOfExactly3Elements() throws Throwable; +013 void required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements() throws Throwable; +014 void required_spec102_maySignalLessThanRequestedAndTerminateSubscription() throws Throwable; +015 void stochastic_spec103_mustSignalOnMethodsSequentially() throws Throwable; +016 void optional_spec104_mustSignalOnErrorWhenFails() throws Throwable; +017 void required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates() throws Throwable; +018 void optional_spec105_emptyStreamMustTerminateBySignallingOnComplete() throws Throwable; +019 void untested_spec106_mustConsiderSubscriptionCancelledAfterOnErrorOrOnCompleteHasBeenCalled() throws Throwable; +020 void required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled() throws Throwable; +021 void untested_spec107_mustNotEmitFurtherSignalsOnceOnErrorHasBeenSignalled() throws Throwable; +022 void untested_spec108_possiblyCanceledSubscriptionShouldNotReceiveOnErrorOrOnCompleteSignals() throws Throwable; +023 void required_spec109_mustIssueOnSubscribeForNonNullSubscriber() throws Throwable; +024 void untested_spec109_subscribeShouldNotThrowNonFatalThrowable() throws Throwable; +025 void required_spec109_subscribeThrowNPEOnNullSubscriber() throws Throwable; +026 void required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe() throws Throwable; +027 void untested_spec110_rejectASubscriptionRequestIfTheSameSubscriberSubscribesTwice() throws Throwable; +028 void optional_spec111_maySupportMultiSubscribe() throws Throwable; +029 void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingOneByOne() throws Throwable; +030 void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfront() throws Throwable; +031 void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfrontAndCompleteAsExpected() throws Throwable; +032 void required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe() throws Throwable; +033 void required_spec303_mustNotAllowUnboundedRecursion() throws Throwable; +034 void untested_spec304_requestShouldNotPerformHeavyComputations() throws Exception; +035 void untested_spec305_cancelMustNotSynchronouslyPerformHeavyCompuatation() throws Exception; +036 void required_spec306_afterSubscriptionIsCancelledRequestMustBeNops() throws Throwable; +037 void required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops() throws Throwable; +038 void required_spec309_requestZeroMustSignalIllegalArgumentException() throws Throwable; +039 void required_spec309_requestNegativeNumberMustSignalIllegalArgumentException() throws Throwable; +040 void required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling() throws Throwable; +041 void required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber() throws Throwable; +042 void required_spec317_mustSupportAPendingElementCountUpToLongMaxValue() throws Throwable; +043 void required_spec317_mustSupportACumulativePendingElementCountUpToLongMaxValue() throws Throwable; +044 void required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue() throws Throwable; +045} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck.support; +002 +003/** +004 * Internal TCK use only. +005 * Add / Remove tests for SubscriberBlackboxVerification here to make sure that they arre added/removed in the other places. +006 */ +007public interface SubscriberBlackboxVerificationRules { +008 void required_spec201_blackbox_mustSignalDemandViaSubscriptionRequest() throws Throwable; +009 void untested_spec202_blackbox_shouldAsynchronouslyDispatch() throws Exception; +010 void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() throws Throwable; +011 void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() throws Throwable; +012 void untested_spec204_blackbox_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() throws Exception; +013 void required_spec205_blackbox_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() throws Exception; +014 void untested_spec206_blackbox_mustCallSubscriptionCancelIfItIsNoLongerValid() throws Exception; +015 void untested_spec207_blackbox_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() throws Exception; +016 void untested_spec208_blackbox_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() throws Throwable; +017 void required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() throws Throwable; +018 void required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() throws Throwable; +019 void required_spec210_blackbox_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() throws Throwable; +020 void untested_spec211_blackbox_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() throws Exception; +021 void untested_spec212_blackbox_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality() throws Throwable; +022 void untested_spec213_blackbox_failingOnSignalInvocation() throws Exception; +023 void required_spec213_blackbox_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable; +024 void required_spec213_blackbox_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable; +025 void required_spec213_blackbox_onError_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable; +026 void untested_spec301_blackbox_mustNotBeCalledOutsideSubscriberContext() throws Exception; +027 void untested_spec308_blackbox_requestMustRegisterGivenNumberElementsToBeProduced() throws Throwable; +028 void untested_spec310_blackbox_requestMaySynchronouslyCallOnNextOnSubscriber() throws Exception; +029 void untested_spec311_blackbox_requestMaySynchronouslyCallOnCompleteOrOnError() throws Exception; +030 void untested_spec314_blackbox_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() throws Exception; +031 void untested_spec315_blackbox_cancelMustNotThrowExceptionAndMustSignalOnError() throws Exception; +032 void untested_spec316_blackbox_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() throws Exception; +033} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck.support; +002 +003public final class SubscriberBufferOverflowException extends RuntimeException { +004 public SubscriberBufferOverflowException() { +005 } +006 +007 public SubscriberBufferOverflowException(String message) { +008 super(message); +009 } +010 +011 public SubscriberBufferOverflowException(String message, Throwable cause) { +012 super(message, cause); +013 } +014 +015 public SubscriberBufferOverflowException(Throwable cause) { +016 super(cause); +017 } +018} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck.support; +002 +003/** +004 * Internal TCK use only. +005 * Add / Remove tests for PublisherVerificaSubscriberWhiteboxVerification here to make sure that they arre added/removed in the other places. +006 */ +007public interface SubscriberWhiteboxVerificationRules { +008 void required_exerciseWhiteboxHappyPath() throws Throwable; +009 void required_spec201_mustSignalDemandViaSubscriptionRequest() throws Throwable; +010 void untested_spec202_shouldAsynchronouslyDispatch() throws Exception; +011 void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() throws Throwable; +012 void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() throws Throwable; +013 void untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() throws Exception; +014 void required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() throws Throwable; +015 void untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid() throws Exception; +016 void untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() throws Exception; +017 void required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() throws Throwable; +018 void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() throws Throwable; +019 void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() throws Throwable; +020 void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() throws Throwable; +021 void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall() throws Throwable; +022 void untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() throws Exception; +023 void untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation() throws Throwable; +024 void untested_spec213_failingOnSignalInvocation() throws Exception; +025 void required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable; +026 void required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable; +027 void required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable; +028 void untested_spec301_mustNotBeCalledOutsideSubscriberContext() throws Exception; +029 void required_spec308_requestMustRegisterGivenNumberElementsToBeProduced() throws Throwable; +030 void untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber() throws Exception; +031 void untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError() throws Exception; +032 void untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() throws Exception; +033 void untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError() throws Exception; +034 void untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() throws Exception; +035} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++
001package org.reactivestreams.tck.support; +002 +003/** +004 * Exception used by the TCK to signal failures. +005 * May be thrown or signalled through {@link org.reactivestreams.Subscriber#onError(Throwable)}. +006 */ +007public final class TestException extends RuntimeException { +008 public TestException() { +009 super("Test Exception: Boom!"); +010 } +011} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ++