[reactive-streams] 03/04: Imported Upstream version 1.0.0

Emmanuel Bourg ebourg-guest at moszumanska.debian.org
Fri Jul 17 13:14:57 UTC 2015


This is an automated email from the git hooks/post-receive script.

ebourg-guest pushed a commit to annotated tag debian/1.0.0-1
in repository reactive-streams.

commit 092ad52944da6a5723e1ec0d51c98d8585036bfc
Author: Emmanuel Bourg <ebourg at apache.org>
Date:   Fri Jul 17 15:13:56 2015 +0200

    Imported Upstream version 1.0.0
---
 .gitignore                                         |   28 +
 CONTRIBUTING.md                                    |   75 ++
 COPYING                                            |  121 ++
 CopyrightWaivers.txt                               |   31 +
 LICENSE                                            |    8 +
 README.md                                          |  240 ++++
 RELEASE-NOTES.md                                   |    3 +
 api/.gitignore                                     |    1 +
 api/build.gradle                                   |    1 +
 .../main/java/org/reactivestreams/Processor.java   |   11 +
 .../main/java/org/reactivestreams/Publisher.java   |   29 +
 .../main/java/org/reactivestreams/Subscriber.java  |   55 +
 .../java/org/reactivestreams/Subscription.java     |   33 +
 build.gradle                                       |  136 +++
 examples/build.gradle                              |    6 +
 .../example/unicast/AsyncIterablePublisher.java    |  258 +++++
 .../example/unicast/AsyncSubscriber.java           |  238 ++++
 .../unicast/InfiniteIncrementNumberPublisher.java  |   23 +
 .../example/unicast/NumberIterablePublisher.java   |   27 +
 .../example/unicast/SyncSubscriber.java            |  111 ++
 .../example/unicast/AsyncSubscriberTest.java       |   63 ++
 .../example/unicast/IterablePublisherTest.java     |   44 +
 .../example/unicast/SyncSubscriberTest.java        |   41 +
 .../unicast/SyncSubscriberWhiteboxTest.java        |   75 ++
 .../UnboundedIntegerIncrementPublisherTest.java    |   39 +
 settings.gradle                                    |    9 +
 tck/README.md                                      |  616 ++++++++++
 tck/build.gradle                                   |    7 +
 .../tck/IdentityProcessorVerification.java         |  762 +++++++++++++
 .../reactivestreams/tck/PublisherVerification.java | 1183 ++++++++++++++++++++
 .../tck/SubscriberBlackboxVerification.java        |  524 +++++++++
 .../tck/SubscriberWhiteboxVerification.java        |  785 +++++++++++++
 .../org/reactivestreams/tck/TestEnvironment.java   |  959 ++++++++++++++++
 .../reactivestreams/tck/WithHelperPublisher.java   |   69 ++
 .../org/reactivestreams/tck/support/Function.java  |    5 +
 .../tck/support/HelperPublisher.java               |   33 +
 .../tck/support/InfiniteHelperPublisher.java       |   30 +
 .../org/reactivestreams/tck/support/NonFatal.java  |   32 +
 .../org/reactivestreams/tck/support/Optional.java  |   69 ++
 .../tck/support/PublisherVerificationRules.java    |   45 +
 .../SubscriberBlackboxVerificationRules.java       |   33 +
 .../support/SubscriberBufferOverflowException.java |   18 +
 .../SubscriberWhiteboxVerificationRules.java       |   35 +
 .../reactivestreams/tck/support/TestException.java |   11 +
 .../tck/EmptyLazyPublisherTest.java                |   42 +
 ...dentityProcessorVerificationDelegationTest.java |   75 ++
 .../tck/IdentityProcessorVerificationTest.java     |  162 +++
 .../tck/PublisherVerificationTest.java             |  829 ++++++++++++++
 .../tck/SingleElementPublisherTest.java            |   42 +
 .../tck/SubscriberBlackboxVerificationTest.java    |  260 +++++
 .../tck/SubscriberWhiteboxVerificationTest.java    |  415 +++++++
 .../tck/SyncTriggeredDemandSubscriberTest.java     |   47 +
 .../SyncTriggeredDemandSubscriberWhiteboxTest.java |   77 ++
 .../tck/support/SyncTriggeredDemandSubscriber.java |  123 ++
 .../tck/support/TCKVerificationSupport.java        |  129 +++
 tck/src/test/resources/testng.yaml                 |   10 +
 56 files changed, 9133 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b234705
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,28 @@
+target/
+.project
+.classpath
+.cache
+.target/
+*~
+.#*
+.*.swp
+.DS_Store
+.codefellow
+.ensime*
+.eprj
+.history
+.idea
+.idea_modules
+.gradle
+.settings
+bin
+build
+out
+*.iml
+*.ipr
+*.iws
+test-output
+test-results
+test-tmp
+*.class
+gradle.properties
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..e18c435
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,75 @@
+# Contributing to the Reactive Streams Project
+
+The Reactive Streams project welcomes contributions from anybody who wants to participate in moving this initiative forward. All code or documentation that is contributed will have to be covered by a waiver of all copyrights and other rights as detailed by the LICENSE and COPYING files at each repository root, the rationale for this is that the APIs defined by this project shall be freely implementable and usable by everyone.
+
+## Copyright Statement
+
+The aforementioned waiver of copyrights and other rights is represented by the addition of a line to the file [CopyrightWaivers.txt](https://github.com/reactive-streams/reactive-streams-jvm/blob/master/CopyrightWaivers.txt). For a pull request to be considered every contributor must have signed the copyright statement in this way; this may be included within that same pull request.
+
+## Gatekeepers
+
+To ensure consistent development of Reactive Streams towards their goal, a group of gatekeepers is defined:
+
+* Kaazing Corp., currently represented by Todd Montgomery (@tmontgomery)
+* Netflix Inc., currently represented by Ben Christensen (@benjchristensen)
+* Pivotal Software Inc., currently represented by Jon Brisbin (@jbrisbin) and Stéphane Maldini (@smaldini)
+* Red Hat Inc., currently represented by Tim Fox (@purplefox) and Norman Maurer (@normanmaurer)
+* Twitter Inc., currently represented by Marius Eriksen (@mariusaeriksen)
+* Typesafe Inc., currently represented by Viktor Klang (@viktorklang) and Roland Kuhn (@rkuhn)
+
+The role of this group is detailed in the following, additions to this list are made by pull request as defined below, removals require the consent of the entity to be removed or unanimous consent of all other Gatekeepers. Changing a representative of one of the gatekeeper entities can be done by a member of that entity without requiring consent from the other Gatekeepers.
+
+Gatekeepers commit to the following:
+
+1. 1-week SLA on :+1: or :-1: Pull Requests
+   * If a Gatekeeper will be unavailable for a period of time, notify @reactive-streams/contributors and appoint who will vote in his/her place in the mean time
+2. tag @reactive-streams/contributors with a deadline when there needs to be a vote on an Issue,
+    with at least 1 week of notice (see rule 1 above)
+
+## General Workflow
+
+1. Make sure you have signed the Copyright Statement, see above.
+2. Before starting to work on a change, make sure that:
+    1. There is a ticket for your work in the project's issue tracker. If not, create it first. It can help accelerating the pull request acceptance process if the change is agreed upon beforehand within the ticket, but in some cases it may be preferable to discuss the necessity of the change in consideration of a concrete proposal.
+    2. The ticket has been scheduled for the current milestone.
+3. You should always perform your work in a Git feature branch within your own fork of the repository your are targeting (even if you should have push rights to the target repository).
+4. When the change is completed you should open a [Pull Request](https://help.github.com/articles/using-pull-requests) on GitHub.
+5. Anyone can comment on the pull request while it is open, and you are expected to answer questions or incorporate feedback.
+6. Once at least two thirds of the gatekeepers have signaled their consent, the pull request is merged by one of the gatekeepers into the branch and repository it is targeting. Consent is signaled by commenting on the pull request with the text “LGTM”, and it suffices for one representative of a gatekeeper to signal consent for that gatekeeper to be counted towards the two thirds quorum.
+7. It is not allowed to force-push to the branch on which the pull request is based. Replacing or adding to the commits on that branch will invalidate all previous consenting comments and consent needs to be re-established.
+8. Before merging a branch that contains multiple commits, it is recommended that these are squashed into a single commit before performing the merge. To aid in verification that no new changes are introduced, a new pull request should be opened in this case, targeting the same branch and repository and containing just one commit which encompasses the full change set of the original pull request.
+
+## Pull Request Requirements
+
+For a Pull Request to be considered at all it has to meet these requirements:
+
+1. If applicable, the new or fixed features must be accompanied by comprehensive tests.
+2. If applicable, the pull request must contain all necessary documentation updates required by the changes introduced.
+3. The pull request must not contain changes that are unrelated to the ticket that it corresponds to. One pull request is meant to introduce only one logically contiguous change.
+
+## Creating Commits And Writing Commit Messages
+
+Follow these guidelines when creating public commits and writing commit messages.
+
+1. If your work spans multiple local commits (for example; if you do safe point commits while working in a feature branch or work in a branch for long time doing merges/rebases etc.) then please do not commit it all but rewrite the history by squashing the commits into a single big commit which you write a good commit message for (like discussed in the following sections). For more info read this article: [Git Workflow](http://sandofsky.com/blog/git-workflow.html). Every commit should be [...]
+
+2. First line should be a descriptive sentence what the commit is doing. It should be possible to fully understand what the commit does—but not necessarily how it does it—by just reading this single line. We follow the “imperative present tense” style for commit messages ([more info here](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)).
+
+   It is **not ok** to only list the ticket number, type "minor fix" or similar. In order to help with automatic filtering of the commit history (generating ChangeLogs, writing the migration guide, code archaeology) we use the following encoding:
+
+3. Following the single line description should be a blank line followed by an enumerated list with the details of the commit. For very simple commits this may be empty.
+
+4. Add keywords for your commit:
+    * ``Review by @gituser`` - if you want to notify someone specifically for review; this has no influence on the acceptance process described above
+
+Example:
+
+    add CONTRIBUTING.md
+
+    * clarify how pull requests should look like
+    * describe the acceptance process
+    * define the Copyright Statement signing process
+
+## Performing Official Releases
+
+Creating binary artifacts, uploading them to central repositories and declaring these to be an official release of the Reactive Streams project requires the consent of all gatekeepers. The process is initiated by creating a ticket in the `reactive-streams` repository for this purpose and consent is signaled in the same way as for pull requests. The actual work of updating version numbers and publishing the artifacts will typically involve pull requests targeting the affected repositories.
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..0e259d4
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,121 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+    LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+    ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+    INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+    REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+    PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+    THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+    HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+  i. the right to reproduce, adapt, distribute, perform, display,
+     communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+     likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+     subject to the limitations in paragraph 4(a), below;
+  v. rights protecting the extraction, dissemination, use and reuse of data
+     in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+     European Parliament and of the Council of 11 March 1996 on the legal
+     protection of databases, and under any national implementation
+     thereof, including any amended or successor version of such
+     directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+     world based on applicable law or treaty, and any national
+     implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+    surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+    warranties of any kind concerning the Work, express, implied,
+    statutory or otherwise, including without limitation warranties of
+    title, merchantability, fitness for a particular purpose, non
+    infringement, or the absence of latent or other defects, accuracy, or
+    the present or absence of errors, whether or not discoverable, all to
+    the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+    that may apply to the Work or any use thereof, including without
+    limitation any person's Copyright and Related Rights in the Work.
+    Further, Affirmer disclaims responsibility for obtaining any necessary
+    consents, permissions or other rights required for any use of the
+    Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+    party to this document and has no duty or obligation with respect to
+    this CC0 or use of the Work.
diff --git a/CopyrightWaivers.txt b/CopyrightWaivers.txt
new file mode 100644
index 0000000..578c312
--- /dev/null
+++ b/CopyrightWaivers.txt
@@ -0,0 +1,31 @@
+Copyright Statement for Contributions to the Reactive Streams Project
+=====================================================================
+
+I hereby represent that all present, past and future contributions I make to
+the Reactive Streams project (which includes all repositories owned by the
+“reactive-streams” github organization) are governed by the Creative Commons
+Zero 1.0 Universal copyright statement, placing my contributions in the public
+domain. This entails that to the extent possible under law I waive all
+copyright and related or neighboring rights to the code or documents I
+contribute. I also represent that I have the authority to perform the above
+waiver with respect to the entirety of my contributions.
+
+The text of the copyright statement is included in the COPYING file at the root
+of the reactive-streams repository at
+https://github.com/reactive-streams/reactive-streams-jvm/blob/master/COPYING.
+
+Underwriting parties:
+
+github name    | Real Name, Email Address used for git commits, Company
+---------------+----------------------------------------------------------------------------
+rkuhn          | Roland Kuhn, rk at rkuhn.info, Typesafe Inc.
+benjchristensen| Ben Christensen, benjchristensen at gmail.com, Netflix Inc.
+viktorklang    | Viktor Klang, viktor.klang at gmail.com, Typesafe Inc.
+smaldini       | Stephane Maldini, stephane.maldini at gmail.com, Pivotal Software Inc.
+savulchik      | Stanislav Savulchik, s.savulchik at gmail.com
+ktoso          | Konrad Malawski, konrad.malawski at project13.pl, Typesafe Inc.
+ouertani       | Slim Ouertani, ouertani at gmail.com
+2m             | Martynas Mickevičius, mmartynas at gmail.com, Typesafe Inc.
+ldaley         | Luke Daley, luke.daley at gradleware.com, Gradleware Inc.
+colinrgodsey   | Colin Godsey, crgodsey at gmail.com, MediaMath Inc.
+davidmoten     | Dave Moten, davidmoten at gmail.com
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..696f2c0
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,8 @@
+Licensed under Public Domain (CC0)
+
+To the extent possible under law, the person who associated CC0 with
+this code has waived all copyright and related or neighboring
+rights to this code.
+
+You should have received a copy of the CC0 legalcode along with this
+work. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..faedf3a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,240 @@
+# Reactive Streams #
+
+The purpose of Reactive Streams is to provide a standard for asynchronous stream processing with non-blocking backpressure.
+
+The latest preview release is available on Maven Central as
+
+```xml
+<dependency>
+  <groupId>org.reactivestreams</groupId>
+  <artifactId>reactive-streams</artifactId>
+  <version>1.0.0</version>
+</dependency>
+<dependency>
+  <groupId>org.reactivestreams</groupId>
+  <artifactId>reactive-streams-tck</artifactId>
+  <version>1.0.0</version>
+  <scope>test</scope>
+</dependency>
+```
+
+## Goals, Design and Scope ##
+
+Handling streams of data—especially “live” data whose volume is not predetermined—requires special care in an asynchronous system. The most prominent issue is that resource consumption needs to be carefully controlled such that a fast data source does not overwhelm the stream destination. Asynchrony is needed in order to enable the parallel use of computing resources, on collaborating network hosts or multiple CPU cores within a single machine.
+
+The main goal of Reactive Streams is to govern the exchange of stream data across an asynchronous boundary – think passing elements on to another thread or thread-pool — while ensuring that the receiving side is not forced to buffer arbitrary amounts of data. In other words, backpressure is an integral part of this model in order to allow the queues which mediate between threads to be bounded. The benefits of asynchronous processing would be negated if the communication of backpressure w [...]
+
+It is the intention of this specification to allow the creation of many conforming implementations, which by virtue of abiding by the rules will be able to interoperate smoothly, preserving the aforementioned benefits and characteristics across the whole processing graph of a stream application.
+
+It should be noted that the precise nature of stream manipulations (transformation, splitting, merging, etc.) is not covered by this specification. Reactive Streams are only concerned with mediating the stream of data between different processing elements. In their development care has been taken to ensure that all basic ways of combining streams can be expressed.
+
+In summary, Reactive Streams is a standard and specification for Stream-oriented libraries for the JVM that
+
+ - process a potentially unbounded number of elements
+ - in sequence,
+ - asynchronously passing elements between components,
+ - with mandatory non-blocking backpressure.
+
+The Reactive Streams specification consists of the following parts:
+
+***The API*** specifies the types to implement Reactive Streams and achieve interoperability between different implementations.
+
+***The Technology Compatibility Kit (TCK)*** is a standard test suite for conformance testing of implementations.
+
+Implementations are free to implement additional features not covered by the specification as long as they conform to the API requirements and pass the tests in the TCK.
+
+### API Components ###
+
+The API consists of the following components that are required to be provided by Reactive Stream implementations:
+
+1. Publisher
+2. Subscriber
+3. Subscription
+4. Processor
+
+A *Publisher* is a provider of a potentially unbounded number of sequenced elements, publishing them according to the demand received from its Subscriber(s).
+
+In response to a call to `Publisher.subscribe(Subscriber)` the possible invocation sequences for methods on the `Subscriber` are given by the following protocol:
+
+```
+onSubscribe onNext* (onError | onComplete)?
+```
+
+This means that `onSubscribe` is always signalled,
+followed by a possibly unbounded number of `onNext` signals (as requested by `Subscriber`) followed by an `onError` signal if there is a failure, or an `onComplete` signal when no more elements are available—all as long as the `Subscription` is not cancelled.
+
+#### NOTES
+
+- The specifications below use binding words in capital letters from https://www.ietf.org/rfc/rfc2119.txt
+- The terms `emit`, `signal` or `send` are interchangeable. The specifications below will use `signal`.
+- The terms `synchronously` or `synchronous` refer to executing in the calling `Thread`.
+- The term "return normally" means "only throws exceptions that are explicitly allowed by the rule".
+
+### SPECIFICATION
+
+#### 1. Publisher ([Code](https://github.com/reactive-streams/reactive-streams-jvm/blob/v1.0.0.RC5/api/src/main/java/org/reactivestreams/Publisher.java))
+
+```java
+public interface Publisher<T> {
+    public void subscribe(Subscriber<? super T> s);
+}
+````
+
+| ID                        | Rule                                                                                                   |
+| ------------------------- | ------------------------------------------------------------------------------------------------------ |
+| <a name="1.1">1</a>       | The total number of `onNext` signals sent by a `Publisher` to a `Subscriber` MUST be less than or equal to the total number of elements requested by that `Subscriber`´s `Subscription` at all times. |
+| <a name="1.2">2</a>       | A `Publisher` MAY signal less `onNext` than requested and terminate the `Subscription` by calling `onComplete` or `onError`. |
+| <a name="1.3">3</a>       | `onSubscribe`, `onNext`, `onError` and `onComplete` signaled to a `Subscriber` MUST be signaled sequentially (no concurrent notifications). |
+| <a name="1.4">4</a>       | If a `Publisher` fails it MUST signal an `onError`. |
+| <a name="1.5">5</a>       | If a `Publisher` terminates successfully (finite stream) it MUST signal an `onComplete`. |
+| <a name="1.6">6</a>       | If a `Publisher` signals either `onError` or `onComplete` on a `Subscriber`, that `Subscriber`’s `Subscription` MUST be considered cancelled. |
+| <a name="1.7">7</a>       | Once a terminal state has been signaled (`onError`, `onComplete`) it is REQUIRED that no further signals occur. |
+| <a name="1.8">8</a>       | If a `Subscription` is cancelled its `Subscriber` MUST eventually stop being signaled. |
+| <a name="1.9">9</a>       | `Publisher.subscribe` MUST call `onSubscribe` on the provided `Subscriber` prior to any other signals to that `Subscriber` and MUST return normally, except when the provided `Subscriber` is `null` in which case it MUST throw a `java.lang.NullPointerException` to the caller, for all other situations the only legal way to signal failure (or reject the `Subscriber`) is by calling `onError` (after calling `onSubscribe`). |
+| <a name="1.10">10</a>     | `Publisher.subscribe` MAY be called as many times as wanted but MUST be with a different `Subscriber` each time [see [2.12](#2.12)]. |
+| <a name="1.11">11</a>     | A `Publisher` MAY support multiple `Subscriber`s and decides whether each `Subscription` is unicast or multicast. |
+
+[<a name="footnote-1-1">1</a>] :  A stateful Publisher can be overwhelmed, bounded by a finite number of underlying resources, exhausted, shut-down or in a failed state.
+
+#### 2. Subscriber ([Code](https://github.com/reactive-streams/reactive-streams-jvm/blob/v1.0.0.RC5/api/src/main/java/org/reactivestreams/Subscriber.java))
+
+```java
+public interface Subscriber<T> {
+    public void onSubscribe(Subscription s);
+    public void onNext(T t);
+    public void onError(Throwable t);
+    public void onComplete();
+}
+````
+
+| ID                        | Rule                                                                                                   |
+| ------------------------- | ------------------------------------------------------------------------------------------------------ |
+| <a name="2.1">1</a>       | A `Subscriber` MUST signal demand via `Subscription.request(long n)` to receive `onNext` signals. |
+| <a name="2.2">2</a>       | If a `Subscriber` suspects that its processing of signals will negatively impact its `Publisher`'s responsivity, it is RECOMMENDED that it asynchronously dispatches its signals. |
+| <a name="2.3">3</a>       | `Subscriber.onComplete()` and `Subscriber.onError(Throwable t)` MUST NOT call any methods on the `Subscription` or the `Publisher`. |
+| <a name="2.4">4</a>       | `Subscriber.onComplete()` and `Subscriber.onError(Throwable t)` MUST consider the Subscription cancelled after having received the signal. |
+| <a name="2.5">5</a>       | A `Subscriber` MUST call `Subscription.cancel()` on the given `Subscription` after an `onSubscribe` signal if it already has an active `Subscription`. |
+| <a name="2.6">6</a>       | A `Subscriber` MUST call `Subscription.cancel()` if it is no longer valid to the `Publisher` without the `Publisher` having signaled `onError` or `onComplete`. |
+| <a name="2.7">7</a>       | A `Subscriber` MUST ensure that all calls on its `Subscription` take place from the same thread or provide for respective external synchronization. |
+| <a name="2.8">8</a>       | A `Subscriber` MUST be prepared to receive one or more `onNext` signals after having called `Subscription.cancel()` if there are still requested elements pending [see [3.12](#3.12)]. `Subscription.cancel()` does not guarantee to perform the underlying cleaning operations immediately. |
+| <a name="2.9">9</a>       | A `Subscriber` MUST be prepared to receive an `onComplete` signal with or without a preceding `Subscription.request(long n)` call. |
+| <a name="2.10">10</a>     | A `Subscriber` MUST be prepared to receive an `onError` signal with or without a preceding `Subscription.request(long n)` call. |
+| <a name="2.11">11</a>     | A `Subscriber` MUST make sure that all calls on its `onXXX` methods happen-before [[1]](#footnote-2-1) the processing of the respective signals. I.e. the Subscriber must take care of properly publishing the signal to its processing logic. |
+| <a name="2.12">12</a>     | `Subscriber.onSubscribe` MUST be called at most once for a given `Subscriber` (based on object equality). |
+| <a name="2.13">13</a>     | Calling `onSubscribe`, `onNext`, `onError` or `onComplete` MUST return normally except when any provided parameter is `null` in which case it MUST throw a `java.lang.NullPointerException` to the caller, for all other situations the only legal way for a `Subscriber` to signal failure is by cancelling its `Subscription`. In the case that this rule is violated, any associated `Subscription` to the `Subscriber` MUST be considered as cancelled, and the caller MUS [...]
+
+[<a name="footnote-2-1">1</a>] : See JMM definition of Happen-Before in section 17.4.5. on http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html
+
+#### 3. Subscription ([Code](https://github.com/reactive-streams/reactive-streams-jvm/blob/v1.0.0.RC5/api/src/main/java/org/reactivestreams/Subscription.java))
+
+```java
+public interface Subscription {
+    public void request(long n);
+    public void cancel();
+}
+````
+
+| ID                        | Rule                                                                                                   |
+| ------------------------- | ------------------------------------------------------------------------------------------------------ |
+| <a name="3.1">1</a>       | `Subscription.request` and `Subscription.cancel` MUST only be called inside of its `Subscriber` context. A `Subscription` represents the unique relationship between a `Subscriber` and a `Publisher` [see [2.12](#2.12)]. |
+| <a name="3.2">2</a>       | The `Subscription` MUST allow the `Subscriber` to call `Subscription.request` synchronously from within `onNext` or `onSubscribe`. |
+| <a name="3.3">3</a>       | `Subscription.request` MUST place an upper bound on possible synchronous recursion between `Publisher` and `Subscriber`[[1](#footnote-3-1)]. |
+| <a name="3.4">4</a>       | `Subscription.request` SHOULD respect the responsivity of its caller by returning in a timely manner[[2](#footnote-3-2)]. |
+| <a name="3.5">5</a>       | `Subscription.cancel` MUST respect the responsivity of its caller by returning in a timely manner[[2](#footnote-3-2)], MUST be idempotent and MUST be thread-safe. |
+| <a name="3.6">6</a>       | After the `Subscription` is cancelled, additional `Subscription.request(long n)` MUST be NOPs. |
+| <a name="3.7">7</a>       | After the `Subscription` is cancelled, additional `Subscription.cancel()` MUST be NOPs. |
+| <a name="3.8">8</a>       | While the `Subscription` is not cancelled, `Subscription.request(long n)` MUST register the given number of additional elements to be produced to the respective subscriber. |
+| <a name="3.9">9</a>       | While the `Subscription` is not cancelled, `Subscription.request(long n)` MUST signal `onError` with a `java.lang.IllegalArgumentException` if the argument is <= 0. The cause message MUST include a reference to this rule and/or quote the full rule. |
+| <a name="3.10">10</a>     | While the `Subscription` is not cancelled, `Subscription.request(long n)` MAY synchronously call `onNext` on this (or other) subscriber(s). |
+| <a name="3.11">11</a>     | While the `Subscription` is not cancelled, `Subscription.request(long n)` MAY synchronously call `onComplete` or `onError` on this (or other) subscriber(s). |
+| <a name="3.12">12</a>     | While the `Subscription` is not cancelled, `Subscription.cancel()` MUST request the `Publisher` to eventually stop signaling its `Subscriber`. The operation is NOT REQUIRED to affect the `Subscription` immediately. |
+| <a name="3.13">13</a>     | While the `Subscription` is not cancelled, `Subscription.cancel()` MUST request the `Publisher` to eventually drop any references to the corresponding subscriber. Re-subscribing with the same `Subscriber` object is discouraged [see [2.12](#2.12)], but this specification does not mandate that it is disallowed since that would mean having to store previously cancelled subscriptions indefinitely. |
+| <a name="3.14">14</a>     | While the `Subscription` is not cancelled, calling `Subscription.cancel` MAY cause the `Publisher`, if stateful, to transition into the `shut-down` state if no other `Subscription` exists at this point [see [1.9](#1.9)].
+| <a name="3.15">15</a>     | Calling `Subscription.cancel` MUST return normally. The only legal way to signal failure to a `Subscriber` is via the `onError` method. |
+| <a name="3.16">16</a>     | Calling `Subscription.request` MUST return normally. The only legal way to signal failure to a `Subscriber` is via the `onError` method. |
+| <a name="3.17">17</a>     | A `Subscription` MUST support an unbounded number of calls to request and MUST support a demand (sum requested - sum delivered) up to 2^63-1 (`java.lang.Long.MAX_VALUE`). A demand equal or greater than 2^63-1 (`java.lang.Long.MAX_VALUE`) MAY be considered by the `Publisher` as “effectively unbounded”[[3](#footnote-3-3)]. |
+
+[<a name="footnote-3-1">1</a>] : An example for undesirable synchronous, open recursion would be `Subscriber.onNext` -> `Subscription.request` -> `Subscriber.onNext` -> …, as it very quickly would result in blowing the calling Thread´s stack.
+
+[<a name="footnote-3-2">2</a>] : Avoid heavy computations and other things that would stall the caller´s thread of execution
+
+[<a name="footnote-3-3">3</a>] : As it is not feasibly reachable with current or foreseen hardware within a reasonable amount of time (1 element per nanosecond would take 292 years) to fulfill a demand of 2^63-1, it is allowed for a `Publisher` to stop tracking demand beyond this point.
+
+A `Subscription` is shared by exactly one `Publisher` and one `Subscriber` for the purpose of mediating the data exchange between this pair. This is the reason why the `subscribe()` method does not return the created `Subscription`, but instead returns `void`; the `Subscription` is only passed to the `Subscriber` via the `onSubscribe` callback.
+
+#### 4.Processor ([Code](https://github.com/reactive-streams/reactive-streams-jvm/blob/v1.0.0.RC5/api/src/main/java/org/reactivestreams/Processor.java))
+
+```java
+public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
+}
+````
+
+| ID                       | Rule                                                                                                   |
+| ------------------------ | ------------------------------------------------------------------------------------------------------ |
+| <a name="4.1">1</a>      | A `Processor` represents a processing stage—which is both a `Subscriber` and a `Publisher` and MUST obey the contracts of both. |
+| <a name="4.2">2</a>      | A `Processor` MAY choose to recover an `onError` signal. If it chooses to do so, it MUST consider the `Subscription` cancelled, otherwise it MUST propagate the `onError` signal to its Subscribers immediately. |
+
+While not mandated, it can be a good idea to cancel a `Processors` upstream `Subscription` when/if its last `Subscriber` cancels their `Subscription`,
+to let the cancellation signal propagate upstream.
+
+### Asynchronous vs Synchronous Processing ###
+
+The Reactive Streams API prescribes that all processing of elements (`onNext`) or termination signals (`onError`, `onComplete`) MUST NOT *block* the `Publisher`. However, each of the `on*` handlers can process the events synchronously or asynchronously.
+
+Take this example:
+
+```
+nioSelectorThreadOrigin map(f) filter(p) consumeTo(toNioSelectorOutput)
+```
+
+It has an async origin and an async destination. Let's assume that both origin and destination are selector event loops. The `Subscription.request(n)` must be chained from the destination to the origin. This is now where each implementation can choose how to do this.
+
+The following uses the pipe `|` character to signal async boundaries (queue and schedule) and `R#` to represent resources (possibly threads).
+
+```
+nioSelectorThreadOrigin | map(f) | filter(p) | consumeTo(toNioSelectorOutput)
+-------------- R1 ----  | - R2 - | -- R3 --- | ---------- R4 ----------------
+```
+
+In this example each of the 3 consumers, `map`, `filter` and `consumeTo` asynchronously schedule the work. It could be on the same event loop (trampoline), separate threads, whatever.
+
+```
+nioSelectorThreadOrigin map(f) filter(p) | consumeTo(toNioSelectorOutput)
+------------------- R1 ----------------- | ---------- R2 ----------------
+```
+
+Here it is only the final step that asynchronously schedules, by adding work to the NioSelectorOutput event loop. The `map` and `filter` steps are synchronously performed on the origin thread.
+
+Or another implementation could fuse the operations to the final consumer:
+
+```
+nioSelectorThreadOrigin | map(f) filter(p) consumeTo(toNioSelectorOutput)
+--------- R1 ---------- | ------------------ R2 -------------------------
+```
+
+All of these variants are "asynchronous streams". They all have their place and each has different tradeoffs including performance and implementation complexity.
+
+The Reactive Streams contract allows implementations the flexibility to manage resources and scheduling and mix asynchronous and synchronous processing within the bounds of a non-blocking, asynchronous, push-based stream.
+
+In order to allow fully asynchronous implementations of all participating API elements—`Publisher`/`Subscription`/`Subscriber`/`Processor`—all methods defined by these interfaces return `void`.
+
+### Subscriber controlled queue bounds ###
+
+One of the underlying design principles is that all buffer sizes are to be bounded and these bounds must be *known* and *controlled* by the subscribers. These bounds are expressed in terms of *element count* (which in turn translates to the invocation count of onNext). Any implementation that aims to support infinite streams (especially high output rate streams) needs to enforce bounds all along the way to avoid out-of-memory errors and constrain resource usage in general.
+
+Since back-pressure is mandatory the use of unbounded buffers can be avoided. In general, the only time when a queue might grow without bounds is when the publisher side maintains a higher rate than the subscriber for an extended period of time, but this scenario is handled by backpressure instead.
+
+Queue bounds can be controlled by a subscriber signaling demand for the appropriate number of elements. At any point in time the subscriber knows:
+
+ - the total number of elements requested: `P`
+ - the number of elements that have been processed: `N`
+
+Then the maximum number of elements that may arrive—until more demand is signaled to the Publisher—is `P - N`. In the case that the subscriber also knows the number of elements B in its input buffer then this bound can be refined to `P - B - N`.
+
+These bounds must be respected by a publisher independent of whether the source it represents can be backpressured or not. In the case of sources whose production rate cannot be influenced—for example clock ticks or mouse movement—the publisher must choose to either buffer or drop elements to obey the imposed bounds.
+
+Subscribers signaling a demand for one element after the reception of an element effectively implement a Stop-and-Wait protocol where the demand signal is equivalent to acknowledgement. By providing demand for multiple elements the cost of acknowledgement is amortized. It is worth noting that the subscriber is allowed to signal demand at any point in time, allowing it to avoid unnecessary delays between the publisher and the subscriber (i.e. keeping its input buffer filled without having [...]
+
+## Legal
+
+This project is a collaboration between engineers from Kaazing, Netflix, Pivotal, RedHat, Twitter, Typesafe and many others. The code is offered to the Public Domain in order to allow free use by interested parties who want to create compatible implementations. For details see `COPYING`.
diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
new file mode 100644
index 0000000..a643174
--- /dev/null
+++ b/RELEASE-NOTES.md
@@ -0,0 +1,3 @@
+# Release notes for Reactive Streams
+
+Changes will be listed below after version 1.0.0 is released.
\ No newline at end of file
diff --git a/api/.gitignore b/api/.gitignore
new file mode 100644
index 0000000..5e56e04
--- /dev/null
+++ b/api/.gitignore
@@ -0,0 +1 @@
+/bin
diff --git a/api/build.gradle b/api/build.gradle
new file mode 100644
index 0000000..79d0aed
--- /dev/null
+++ b/api/build.gradle
@@ -0,0 +1 @@
+description = "reactive-streams"
\ No newline at end of file
diff --git a/api/src/main/java/org/reactivestreams/Processor.java b/api/src/main/java/org/reactivestreams/Processor.java
new file mode 100644
index 0000000..b22e25f
--- /dev/null
+++ b/api/src/main/java/org/reactivestreams/Processor.java
@@ -0,0 +1,11 @@
+package org.reactivestreams;
+
+/**
+ * A Processor represents a processing stage—which is both a {@link Subscriber}
+ * and a {@link Publisher} and obeys the contracts of both.
+ *
+ * @param <T> the type of element signaled to the {@link Subscriber}
+ * @param <R> the type of element signaled by the {@link Publisher}
+ */
+public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
+}
diff --git a/api/src/main/java/org/reactivestreams/Publisher.java b/api/src/main/java/org/reactivestreams/Publisher.java
new file mode 100644
index 0000000..d7ed6ab
--- /dev/null
+++ b/api/src/main/java/org/reactivestreams/Publisher.java
@@ -0,0 +1,29 @@
+package org.reactivestreams;
+
+/**
+ * A {@link Publisher} is a provider of a potentially unbounded number of sequenced elements, publishing them according to
+ * the demand received from its {@link Subscriber}(s).
+ * <p>
+ * A {@link Publisher} can serve multiple {@link Subscriber}s subscribed {@link #subscribe(Subscriber)} dynamically
+ * at various points in time.
+ *
+ * @param <T> the type of element signaled.
+ */
+public interface Publisher<T> {
+
+    /**
+     * Request {@link Publisher} to start streaming data.
+     * <p>
+     * This is a "factory method" and can be called multiple times, each time starting a new {@link Subscription}.
+     * <p>
+     * Each {@link Subscription} will work for only a single {@link Subscriber}.
+     * <p>
+     * A {@link Subscriber} should only subscribe once to a single {@link Publisher}.
+     * <p>
+     * If the {@link Publisher} rejects the subscription attempt or otherwise fails it will
+     * signal the error via {@link Subscriber#onError}.
+     *
+     * @param s the {@link Subscriber} that will consume signals from this {@link Publisher}
+     */
+    public void subscribe(Subscriber<? super T> s);
+}
diff --git a/api/src/main/java/org/reactivestreams/Subscriber.java b/api/src/main/java/org/reactivestreams/Subscriber.java
new file mode 100644
index 0000000..6165352
--- /dev/null
+++ b/api/src/main/java/org/reactivestreams/Subscriber.java
@@ -0,0 +1,55 @@
+package org.reactivestreams;
+
+/**
+ * Will receive call to {@link #onSubscribe(Subscription)} once after passing an instance of {@link Subscriber} to {@link Publisher#subscribe(Subscriber)}.
+ * <p>
+ * No further notifications will be received until {@link Subscription#request(long)} is called.
+ * <p>
+ * After signaling demand:
+ * <ul>
+ * <li>One or more invocations of {@link #onNext(Object)} up to the maximum number defined by {@link Subscription#request(long)}</li>
+ * <li>Single invocation of {@link #onError(Throwable)} or {@link Subscriber#onComplete()} which signals a terminal state after which no further events will be sent.
+ * </ul>
+ * <p>
+ * Demand can be signaled via {@link Subscription#request(long)} whenever the {@link Subscriber} instance is capable of handling more.
+ *
+ * @param <T> the type of element signaled.
+ */
+public interface Subscriber<T> {
+    /**
+     * Invoked after calling {@link Publisher#subscribe(Subscriber)}.
+     * <p>
+     * No data will start flowing until {@link Subscription#request(long)} is invoked.
+     * <p>
+     * It is the responsibility of this {@link Subscriber} instance to call {@link Subscription#request(long)} whenever more data is wanted.
+     * <p>
+     * The {@link Publisher} will send notifications only in response to {@link Subscription#request(long)}.
+     * 
+     * @param s
+     *            {@link Subscription} that allows requesting data via {@link Subscription#request(long)}
+     */
+    public void onSubscribe(Subscription s);
+
+    /**
+     * Data notification sent by the {@link Publisher} in response to requests to {@link Subscription#request(long)}.
+     * 
+     * @param t the element signaled
+     */
+    public void onNext(T t);
+
+    /**
+     * Failed terminal state.
+     * <p>
+     * No further events will be sent even if {@link Subscription#request(long)} is invoked again.
+     *
+     * @param t the throwable signaled
+     */
+    public void onError(Throwable t);
+
+    /**
+     * Successful terminal state.
+     * <p>
+     * No further events will be sent even if {@link Subscription#request(long)} is invoked again.
+     */
+    public void onComplete();
+}
diff --git a/api/src/main/java/org/reactivestreams/Subscription.java b/api/src/main/java/org/reactivestreams/Subscription.java
new file mode 100644
index 0000000..bab4236
--- /dev/null
+++ b/api/src/main/java/org/reactivestreams/Subscription.java
@@ -0,0 +1,33 @@
+package org.reactivestreams;
+
+/**
+ * A {@link Subscription} represents a one-to-one lifecycle of a {@link Subscriber} subscribing to a {@link Publisher}.
+ * <p>
+ * It can only be used once by a single {@link Subscriber}.
+ * <p>
+ * It is used to both signal desire for data and cancel demand (and allow resource cleanup).
+ *
+ */
+public interface Subscription {
+    /**
+     * No events will be sent by a {@link Publisher} until demand is signaled via this method.
+     * <p>
+     * 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 {@link Publisher} as "effectively unbounded".
+     * <p>
+     * Whatever has been requested can be sent by the {@link Publisher} so only signal demand for what can be safely handled.
+     * <p>
+     * A {@link Publisher} can send less than is requested if the stream ends but
+     * then must emit either {@link Subscriber#onError(Throwable)} or {@link Subscriber#onComplete()}.
+     * 
+     * @param n the strictly positive number of elements to requests to the upstream {@link Publisher}
+     */
+    public void request(long n);
+
+    /**
+     * Request the {@link Publisher} to stop sending data and clean up resources.
+     * <p>
+     * Data may still be sent to meet previously signalled demand after calling cancel as this request is asynchronous.
+     */
+    public void cancel();
+}
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..d761094
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,136 @@
+subprojects {
+    apply plugin: "java"
+    apply plugin: "osgi"
+
+    group = "org.reactivestreams"
+    version = "1.0.0"
+
+    sourceCompatibility = 1.6
+    targetCompatibility = 1.6
+
+    tasks.withType(JavaCompile) {
+        configure(options) {
+            compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
+            encoding = "UTF-8"
+        }
+    }
+
+    tasks.withType(Javadoc) {
+        configure(options) {
+            encoding "UTF-8"
+            docEncoding "UTF-8"
+            charSet "UTF-8"
+            linkSource true
+            noTimestamp true
+        }
+    }
+
+    tasks.withType(Test) {
+      testLogging {
+        exceptionFormat "full"
+        events "failed", "started"
+      }
+    }
+
+    repositories {
+        mavenCentral()
+    }
+
+
+    jar {
+        manifest {
+            instructionReplace "Bundle-Vendor", "Reactive Streams SIG"
+            instructionReplace "Bundle-Description", "Reactive Streams API"
+            instructionReplace "Bundle-DocURL", "http://reactive-streams.org"
+            instructionReplace "Bundle-Version", "1.0.0.release"
+        }
+    }
+
+    if (name in ["reactive-streams", "reactive-streams-tck", "reactive-streams-examples"]) {
+        apply plugin: "maven"
+        apply plugin: "signing"
+
+        signing {
+            sign configurations.archives
+            required { gradle.taskGraph.hasTask(uploadArchives) }
+        }
+        task sourcesJar(type: Jar) {
+            classifier "sources"
+            from sourceSets.main.allSource
+        }
+
+        task javadocJar(type: Jar) {
+            classifier "javadoc"
+            from javadoc
+        }
+
+        artifacts {
+            archives sourcesJar, javadocJar
+        }
+
+        uploadArchives {
+            repositories {
+                mavenDeployer {
+                    gradle.taskGraph.whenReady { taskGraph ->
+                        if (taskGraph.hasTask(uploadArchives)) {
+                            def userProp = "sonatypeOssUsername"
+                            def passwordProp = "sonatypeOssPassword"
+                            def user = project.properties[userProp]
+                            def password = project.properties[passwordProp]
+
+                            if (user == null || password == null) {
+                                throw new InvalidUserDataException(
+                                        "Cannot perform $uploadArchives.path due to missing credentials.\n" +
+                                        "Run with command line args `-P$userProp=«username» -P$passwordProp=«password»` or add these properties to $gradle.gradleUserHomeDir/gradle.properties.\n")
+                            }
+
+                            repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
+                                authentication(userName: user, password: password)
+                            }
+                            snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") {
+                                authentication(userName: user, password: password)
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        tasks.withType(Upload) {
+            repositories.withType(MavenResolver) {
+                it.beforeDeployment { signing.signPom(it) }
+                it.pom.whenConfigured { pom ->
+                    pom.project {
+                        url "http://www.reactive-streams.org/"
+                        name "reactive-streams"
+                        description "A Protocol for Asynchronous Non-Blocking Data Sequence"
+                        inceptionYear "2014"
+
+                        scm {
+                            url "git at github.com:reactive-streams/reactive-streams.git"
+                            connection "scm:git:git at github.com:reactive-streams/reactive-streams.git"
+                        }
+
+                        licenses {
+                            license {
+                                name "CC0"
+                                url "http://creativecommons.org/publicdomain/zero/1.0/"
+                                distribution "repo"
+                            }
+                        }
+
+                        developers {
+                            developer {
+                                id "reactive-streams-sig"
+                                name "Reactive Streams SIG"
+                                url "http://www.reactive-streams.org/"
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    } else {
+        uploadArchives.enabled = false
+    }
+}
diff --git a/examples/build.gradle b/examples/build.gradle
new file mode 100644
index 0000000..0ad0b07
--- /dev/null
+++ b/examples/build.gradle
@@ -0,0 +1,6 @@
+description = 'reactive-streams-examples'
+dependencies {
+    compile project(':reactive-streams')
+    testCompile project(':reactive-streams-tck')
+}
+test.useTestNG()
diff --git a/examples/src/main/java/org/reactivestreams/example/unicast/AsyncIterablePublisher.java b/examples/src/main/java/org/reactivestreams/example/unicast/AsyncIterablePublisher.java
new file mode 100644
index 0000000..b1e7cae
--- /dev/null
+++ b/examples/src/main/java/org/reactivestreams/example/unicast/AsyncIterablePublisher.java
@@ -0,0 +1,258 @@
+package org.reactivestreams.example.unicast;
+
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+
+import java.util.Iterator;
+import java.util.Collections;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ * 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`.
+ *
+ * NOTE: The code below uses a lot of try-catches to show the reader where exceptions can be expected, and where they are forbidden.
+ */
+public class AsyncIterablePublisher<T> implements Publisher<T> {
+  private final static int DEFAULT_BATCHSIZE = 1024;
+
+  private final Iterable<T> elements; // This is our data source / generator
+  private final Executor executor; // This is our thread pool, which will make sure that our Publisher runs asynchronously to its Subscribers
+  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
+
+  public AsyncIterablePublisher(final Iterable<T> elements, final Executor executor) {
+    this(elements, DEFAULT_BATCHSIZE, executor);
+  }
+
+  public AsyncIterablePublisher(final Iterable<T> elements, final int batchSize, final Executor executor) {
+    if (elements == null) throw null;
+    if (executor == null) throw null;
+    if (batchSize < 1) throw new IllegalArgumentException("batchSize must be greater than zero!");
+    this.elements = elements;
+    this.executor = executor;
+    this.batchSize = batchSize;
+  }
+
+  @Override
+  public void subscribe(final Subscriber<? super T> s) {
+    // As per rule 1.11, we have decided to support multiple subscribers in a unicast configuration
+    // for this `Publisher` implementation.
+    // As per 2.13, this method must return normally (i.e. not throw)
+    new SubscriptionImpl(s).init();
+  }
+
+  // These represent the protocol of the `AsyncIterablePublishers` SubscriptionImpls
+  static interface Signal {};
+  enum Cancel implements Signal { Instance; };
+  enum Subscribe implements Signal { Instance; };
+  enum Send implements Signal { Instance; };
+  static final class Request implements Signal {
+    final long n;
+    Request(final long n) {
+      this.n = n;
+    }
+  };
+
+  // This is our implementation of the Reactive Streams `Subscription`,
+  // which represents the association between a `Publisher` and a `Subscriber`.
+  final class SubscriptionImpl implements Subscription, Runnable {
+    final Subscriber<? super T> subscriber; // We need a reference to the `Subscriber` so we can talk to it
+    private boolean cancelled = false; // This flag will track whether this `Subscription` is to be considered cancelled or not
+    private long demand = 0; // Here we track the current demand, i.e. what has been requested but not yet delivered
+    private Iterator<T> iterator; // This is our cursor into the data stream, which we will send to the `Subscriber`
+
+    SubscriptionImpl(final Subscriber<? super T> subscriber) {
+      // As per rule 1.09, we need to throw a `java.lang.NullPointerException` if the `Subscriber` is `null`
+      if (subscriber == null) throw null;
+      this.subscriber = subscriber;
+    }
+
+    // This `ConcurrentLinkedQueue` will track signals that are sent to this `Subscription`, like `request` and `cancel`
+    private final ConcurrentLinkedQueue<Signal> inboundSignals = new ConcurrentLinkedQueue<Signal>();
+
+    // We are using this `AtomicBoolean` to make sure that this `Subscription` doesn't run concurrently with itself,
+    // which would violate rule 1.3 among others (no concurrent notifications).
+    private final AtomicBoolean on = new AtomicBoolean(false);
+
+    // This method will register inbound demand from our `Subscriber` and validate it against rule 3.9 and rule 3.17
+    private void doRequest(final long n) {
+      if (n < 1)
+        terminateDueTo(new IllegalArgumentException(subscriber + " violated the Reactive Streams rule 3.9 by requesting a non-positive number of elements."));
+      else if (demand + n < 1) {
+        // As governed by rule 3.17, when demand overflows `Long.MAX_VALUE` we treat the signalled demand as "effectively unbounded"
+        demand = Long.MAX_VALUE;  // Here we protect from the overflow and treat it as "effectively unbounded"
+        doSend(); // Then we proceed with sending data downstream
+      } else {
+        demand += n; // Here we record the downstream demand
+        doSend(); // Then we can proceed with sending data downstream
+      }
+    }
+
+    // This handles cancellation requests, and is idempotent, thread-safe and not synchronously performing heavy computations as specified in rule 3.5
+    private void doCancel() {
+      cancelled = true;
+    }
+
+    // Instead of executing `subscriber.onSubscribe` synchronously from within `Publisher.subscribe`
+    // we execute it asynchronously, this is to avoid executing the user code (`Iterable.iterator`) on the calling thread.
+    // It also makes it easier to follow rule 1.9
+    private void doSubscribe() {
+      try {
+        iterator = elements.iterator();
+        if (iterator == null)
+          iterator = Collections.<T>emptyList().iterator(); // So we can assume that `iterator` is never null
+      } catch(final Throwable t) {
+        subscriber.onSubscribe(new Subscription() { // We need to make sure we signal onSubscribe before onError, obeying rule 1.9
+          @Override public void cancel() {}
+          @Override public void request(long n) {}
+        });
+        terminateDueTo(t); // Here we send onError, obeying rule 1.09
+      }
+
+      if (!cancelled) {
+        // Deal with setting up the subscription with the subscriber
+        try {
+          subscriber.onSubscribe(this);
+        } catch(final Throwable t) { // Due diligence to obey 2.13
+          terminateDueTo(new IllegalStateException(subscriber + " violated the Reactive Streams rule 2.13 by throwing an exception from onSubscribe.", t));
+        }
+
+        // Deal with already complete iterators promptly
+        boolean hasElements = false;
+        try {
+          hasElements = iterator.hasNext();
+        } catch(final Throwable t) {
+          terminateDueTo(t); // If hasNext throws, there's something wrong and we need to signal onError as per 1.2, 1.4, 
+        }
+
+        // If we don't have anything to deliver, we're already done, so lets do the right thing and
+        // not wait for demand to deliver `onComplete` as per rule 1.2 and 1.3
+        if (!hasElements) {
+          try {
+            doCancel(); // Rule 1.6 says we need to consider the `Subscription` cancelled when `onComplete` is signalled
+            subscriber.onComplete();
+          } 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.
+            (new IllegalStateException(subscriber + " violated the Reactive Streams rule 2.13 by throwing an exception from onComplete.", t)).printStackTrace(System.err);
+          }
+        }
+      }
+    }
+
+    // This is our behavior for producing elements downstream
+    private void doSend() {
+      try {
+        // In order to play nice with the `Executor` we will only send at-most `batchSize` before
+        // rescheduing ourselves and relinquishing the current thread.
+        int leftInBatch = batchSize;
+        do {
+          T next;
+          boolean hasNext;
+          try {
+            next = iterator.next(); // We have already checked `hasNext` when subscribing, so we can fall back to testing -after- `next` is called.
+            hasNext = iterator.hasNext(); // Need to keep track of End-of-Stream
+          } catch (final Throwable t) {
+            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
+            return;
+          }
+          subscriber.onNext(next); // Then we signal the next element downstream to the `Subscriber`
+          if (!hasNext) { // If we are at End-of-Stream
+            doCancel(); // We need to consider this `Subscription` as cancelled as per rule 1.6
+            subscriber.onComplete(); // Then we signal `onComplete` as per rule 1.2 and 1.5
+          }
+        } while (!cancelled           // This makes sure that rule 1.8 is upheld, i.e. we need to stop signalling "eventually"
+                 && --leftInBatch > 0 // This makes sure that we only send `batchSize` number of elements in one go (so we can yield to other Runnables)
+                 && --demand > 0);    // This makes sure that rule 1.1 is upheld (sending more than was demanded)
+
+        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
+          signal(Send.Instance);
+      } catch(final Throwable t) {
+        // 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.
+        doCancel(); // Make sure that we are cancelled, since we cannot do anything else since the `Subscriber` is faulty.
+        (new IllegalStateException(subscriber + " violated the Reactive Streams rule 2.13 by throwing an exception from onNext or onComplete.", t)).printStackTrace(System.err);
+      }
+    }
+
+    // This is a helper method to ensure that we always `cancel` when we signal `onError` as per rule 1.6
+    private void terminateDueTo(final Throwable t) {
+      cancelled = true; // When we signal onError, the subscription must be considered as cancelled, as per rule 1.6
+      try {
+        subscriber.onError(t); // Then we signal the error downstream, to the `Subscriber`
+      } 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.
+        (new IllegalStateException(subscriber + " violated the Reactive Streams rule 2.13 by throwing an exception from onError.", t2)).printStackTrace(System.err);
+      }
+    }
+
+    // What `signal` does is that it sends signals to the `Subscription` asynchronously
+    private void signal(final Signal signal) {
+      if (inboundSignals.offer(signal)) // No need to null-check here as ConcurrentLinkedQueue does this for us
+        tryScheduleToExecute(); // Then we try to schedule it for execution, if it isn't already
+    }
+
+    // This is the main "event loop" if you so will
+    @Override public final void run() {
+      if(on.get()) { // establishes a happens-before relationship with the end of the previous run
+        try {
+          final Signal s = inboundSignals.poll(); // We take a signal off the queue
+          if (!cancelled) { // to make sure that we follow rule 1.8, 3.6 and 3.7
+
+            // Below we simply unpack the `Signal`s and invoke the corresponding methods
+            if (s instanceof Request)
+              doRequest(((Request)s).n);
+            else if (s == Send.Instance)
+              doSend();
+            else if (s == Cancel.Instance)
+              doCancel();
+            else if (s == Subscribe.Instance)
+              doSubscribe();
+          }
+        } finally {
+          on.set(false); // establishes a happens-before relationship with the beginning of the next run
+          if(!inboundSignals.isEmpty()) // If we still have signals to process
+            tryScheduleToExecute(); // Then we try to schedule ourselves to execute again
+        }
+      }
+    }
+
+    // This method makes sure that this `Subscription` is only running on one Thread at a time,
+    // this is important to make sure that we follow rule 1.3
+    private final void tryScheduleToExecute() {
+      if(on.compareAndSet(false, true)) {
+        try {
+          executor.execute(this);
+        } catch(Throwable t) { // If we can't run on the `Executor`, we need to fail gracefully
+          if (!cancelled) {
+            doCancel(); // First of all, this failure is not recoverable, so we need to follow rule 1.4 and 1.6
+            try {
+              terminateDueTo(new IllegalStateException("Publisher terminated due to unavailable Executor.", t));
+            } finally {
+              inboundSignals.clear(); // We're not going to need these anymore
+              // This subscription is cancelled by now, but letting it become schedulable again means
+              // that we can drain the inboundSignals queue if anything arrives after clearing
+              on.set(false);
+            }
+          }
+        }
+      }
+    }
+
+    // Our implementation of `Subscription.request` sends a signal to the Subscription that more elements are in demand
+    @Override public void request(final long n) {
+      signal(new Request(n));
+    }
+    // Our implementation of `Subscription.cancel` sends a signal to the Subscription that the `Subscriber` is not interested in any more elements
+    @Override public void cancel() {
+      signal(Cancel.Instance);
+    }
+    // The reason for the `init` method is that we want to ensure the `SubscriptionImpl`
+    // is completely constructed before it is exposed to the thread pool, therefor this
+    // method is only intended to be invoked once, and immediately after the constructor has
+    // finished.
+    void init() {
+      signal(Subscribe.Instance);
+    }
+  };
+}
\ No newline at end of file
diff --git a/examples/src/main/java/org/reactivestreams/example/unicast/AsyncSubscriber.java b/examples/src/main/java/org/reactivestreams/example/unicast/AsyncSubscriber.java
new file mode 100644
index 0000000..c561b8b
--- /dev/null
+++ b/examples/src/main/java/org/reactivestreams/example/unicast/AsyncSubscriber.java
@@ -0,0 +1,238 @@
+package org.reactivestreams.example.unicast;
+
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ * 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.
+ *
+ * NOTE: The code below uses a lot of try-catches to show the reader where exceptions can be expected, and where they are forbidden.
+ */
+public abstract class AsyncSubscriber<T> implements Subscriber<T>, Runnable {
+
+  // Signal represents the asynchronous protocol between the Publisher and Subscriber
+  private static interface Signal {}
+
+  private enum OnComplete implements Signal { Instance; }
+
+  private static class OnError implements Signal {
+    public final Throwable error;
+    public OnError(final Throwable error) { this.error = error; }
+  }
+
+  private static class OnNext<T> implements Signal {
+    public final T next;
+    public OnNext(final T next) { this.next = next; }
+  }
+
+  private static class OnSubscribe implements Signal {
+    public final Subscription subscription;
+    public OnSubscribe(final Subscription subscription) { this.subscription = subscription; }
+  }
+
+  private Subscription subscription; // Obeying rule 3.1, we make this private!
+  private boolean done; // It's useful to keep track of whether this Subscriber is done or not
+  private final Executor executor; // This is the Executor we'll use to be asynchronous, obeying rule 2.2
+
+  // Only one constructor, and it's only accessible for the subclasses
+  protected AsyncSubscriber(Executor executor) {
+    if (executor == null) throw null;
+    this.executor = executor;
+  }
+
+  // Showcases a convenience method to idempotently marking the Subscriber as "done", so we don't want to process more elements
+  // herefor we also need to cancel our `Subscription`.
+  private final void done() {
+    //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.
+    done = true; // If we `foreach` throws an exception, let's consider ourselves done (not accepting more elements)
+    if (subscription != null) { // If we are bailing out before we got a `Subscription` there's little need for cancelling it.
+      try {
+        subscription.cancel(); // Cancel the subscription
+      } catch(final Throwable t) {
+        //Subscription.cancel is not allowed to throw an exception, according to rule 3.15
+        (new IllegalStateException(subscription + " violated the Reactive Streams rule 3.15 by throwing an exception from cancel.", t)).printStackTrace(System.err);
+      }
+    }
+  }
+
+  // This method is invoked when the OnNext signals arrive
+  // Returns whether more elements are desired or not, and if no more elements are desired,
+  // for convenience.
+  protected abstract boolean whenNext(final T element);
+
+  // This method is invoked when the OnComplete signal arrives
+  // override this method to implement your own custom onComplete logic.
+  protected void whenComplete() { }
+
+  // This method is invoked if the OnError signal arrives
+  // override this method to implement your own custom onError logic.
+  protected void whenError(Throwable error) { }
+
+  private final void handleOnSubscribe(final Subscription s) {
+    if (s == null) {
+      // Getting a null `Subscription` here is not valid so lets just ignore it.
+    } else if (subscription != null) { // If someone has made a mistake and added this Subscriber multiple times, let's handle it gracefully
+      try {
+        s.cancel(); // Cancel the additional subscription to follow rule 2.5
+      } catch(final Throwable t) {
+        //Subscription.cancel is not allowed to throw an exception, according to rule 3.15
+        (new IllegalStateException(s + " violated the Reactive Streams rule 3.15 by throwing an exception from cancel.", t)).printStackTrace(System.err);
+      }
+    } else {
+      // We have to assign it locally before we use it, if we want to be a synchronous `Subscriber`
+      // Because according to rule 3.10, the Subscription is allowed to call `onNext` synchronously from within `request`
+      subscription = s;
+      try {
+        // If we want elements, according to rule 2.1 we need to call `request`
+        // And, according to rule 3.2 we are allowed to call this synchronously from within the `onSubscribe` method
+        s.request(1); // Our Subscriber is unbuffered and modest, it requests one element at a time
+      } catch(final Throwable t) {
+        // Subscription.request is not allowed to throw according to rule 3.16
+        (new IllegalStateException(s + " violated the Reactive Streams rule 3.16 by throwing an exception from request.", t)).printStackTrace(System.err);
+      }
+    }
+  }
+
+  private final void handleOnNext(final T element) {
+    if (!done) { // If we aren't already done
+      if(subscription == null) { // Technically this check is not needed, since we are expecting Publishers to conform to the spec
+        // Check for spec violation of 2.1 and 1.09
+        (new IllegalStateException("Someone violated the Reactive Streams rule 1.09 and 2.1 by signalling OnNext before `Subscription.request`. (no Subscription)")).printStackTrace(System.err);
+      } else {
+        try {
+          if (whenNext(element)) {
+            try {
+              subscription.request(1); // Our Subscriber is unbuffered and modest, it requests one element at a time
+            } catch(final Throwable t) {
+              // Subscription.request is not allowed to throw according to rule 3.16
+              (new IllegalStateException(subscription + " violated the Reactive Streams rule 3.16 by throwing an exception from request.", t)).printStackTrace(System.err);
+            }
+          } else {
+            done(); // This is legal according to rule 2.6
+          }
+        } catch(final Throwable t) {
+          done();
+          try {  
+            onError(t);
+          } catch(final Throwable t2) {
+            //Subscriber.onError is not allowed to throw an exception, according to rule 2.13
+            (new IllegalStateException(this + " violated the Reactive Streams rule 2.13 by throwing an exception from onError.", t2)).printStackTrace(System.err);
+          }
+        }
+      }
+    }
+  }
+
+  // Here it is important that we do not violate 2.2 and 2.3 by calling methods on the `Subscription` or `Publisher`
+  private void handleOnComplete() {
+    if (subscription == null) { // Technically this check is not needed, since we are expecting Publishers to conform to the spec
+      // Publisher is not allowed to signal onComplete before onSubscribe according to rule 1.09
+      (new IllegalStateException("Publisher violated the Reactive Streams rule 1.09 signalling onComplete prior to onSubscribe.")).printStackTrace(System.err);
+    } else {
+      done = true; // Obey rule 2.4
+      whenComplete();
+    }
+  }
+
+  // Here it is important that we do not violate 2.2 and 2.3 by calling methods on the `Subscription` or `Publisher`
+  private void handleOnError(final Throwable error) {
+    if (subscription == null) { // Technically this check is not needed, since we are expecting Publishers to conform to the spec
+      // Publisher is not allowed to signal onError before onSubscribe according to rule 1.09
+      (new IllegalStateException("Publisher violated the Reactive Streams rule 1.09 signalling onError prior to onSubscribe.")).printStackTrace(System.err);
+    } else {
+      done = true; // Obey rule 2.4
+      whenError(error);
+    }
+  }
+
+  // We implement the OnX methods on `Subscriber` to send Signals that we will process asycnhronously, but only one at a time
+
+  @Override public final void onSubscribe(final Subscription s) {
+    // As per rule 2.13, we need to throw a `java.lang.NullPointerException` if the `Subscription` is `null`
+    if (s == null) throw null;
+
+    signal(new OnSubscribe(s));
+  }
+
+  @Override public final void onNext(final T element) {
+    // As per rule 2.13, we need to throw a `java.lang.NullPointerException` if the `element` is `null`
+    if (element == null) throw null;
+
+    signal(new OnNext<T>(element));
+  }
+
+  @Override public final void onError(final Throwable t) {
+    // As per rule 2.13, we need to throw a `java.lang.NullPointerException` if the `Throwable` is `null`
+    if (t == null) throw null;
+
+    signal(new OnError(t));
+  }
+
+  @Override public final void onComplete() {
+     signal(OnComplete.Instance);
+  }
+
+  // This `ConcurrentLinkedQueue` will track signals that are sent to this `Subscriber`, like `OnComplete` and `OnNext` ,
+  // and obeying rule 2.11
+  private final ConcurrentLinkedQueue<Signal> inboundSignals = new ConcurrentLinkedQueue<Signal>();
+
+  // We are using this `AtomicBoolean` to make sure that this `Subscriber` doesn't run concurrently with itself,
+  // obeying rule 2.7 and 2.11
+  private final AtomicBoolean on = new AtomicBoolean(false);
+
+   @SuppressWarnings("unchecked")
+   @Override public final void run() {
+    if(on.get()) { // establishes a happens-before relationship with the end of the previous run
+      try {
+        final Signal s = inboundSignals.poll(); // We take a signal off the queue
+        if (!done) { // If we're done, we shouldn't process any more signals, obeying rule 2.8
+          // Below we simply unpack the `Signal`s and invoke the corresponding methods
+          if (s instanceof OnNext<?>)
+            handleOnNext(((OnNext<T>)s).next);
+          else if (s instanceof OnSubscribe)
+            handleOnSubscribe(((OnSubscribe)s).subscription);
+          else if (s instanceof OnError) // We are always able to handle OnError, obeying rule 2.10
+            handleOnError(((OnError)s).error);
+          else if (s == OnComplete.Instance) // We are always able to handle OnError, obeying rule 2.9
+            handleOnComplete();
+        }
+      } finally {
+        on.set(false); // establishes a happens-before relationship with the beginning of the next run
+        if(!inboundSignals.isEmpty()) // If we still have signals to process
+          tryScheduleToExecute(); // Then we try to schedule ourselves to execute again
+      }
+    }
+  }
+
+  // What `signal` does is that it sends signals to the `Subscription` asynchronously
+  private void signal(final Signal signal) {
+    if (inboundSignals.offer(signal)) // No need to null-check here as ConcurrentLinkedQueue does this for us
+      tryScheduleToExecute(); // Then we try to schedule it for execution, if it isn't already
+  }
+
+  // This method makes sure that this `Subscriber` is only executing on one Thread at a time
+  private final void tryScheduleToExecute() {
+    if(on.compareAndSet(false, true)) {
+      try {
+        executor.execute(this);
+      } catch(Throwable t) { // If we can't run on the `Executor`, we need to fail gracefully and not violate rule 2.13
+        if (!done) {
+          try {
+            done(); // First of all, this failure is not recoverable, so we need to cancel our subscription
+          } finally {
+            inboundSignals.clear(); // We're not going to need these anymore
+            // This subscription is cancelled by now, but letting the Subscriber become schedulable again means
+            // that we can drain the inboundSignals queue if anything arrives after clearing
+            on.set(false);
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/examples/src/main/java/org/reactivestreams/example/unicast/InfiniteIncrementNumberPublisher.java b/examples/src/main/java/org/reactivestreams/example/unicast/InfiniteIncrementNumberPublisher.java
new file mode 100644
index 0000000..0e82c0f
--- /dev/null
+++ b/examples/src/main/java/org/reactivestreams/example/unicast/InfiniteIncrementNumberPublisher.java
@@ -0,0 +1,23 @@
+package org.reactivestreams.example.unicast;
+
+import java.util.Iterator;
+import java.util.concurrent.Executor;
+
+import org.reactivestreams.Subscription;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Publisher;
+
+public class InfiniteIncrementNumberPublisher extends AsyncIterablePublisher<Integer> {
+    public InfiniteIncrementNumberPublisher(final Executor executor) {
+        super(new Iterable<Integer>() {
+          @Override public Iterator<Integer> iterator() {
+            return new Iterator<Integer>() {
+              private int at = 0;
+              @Override public boolean hasNext() { return true; }
+              @Override public Integer next() { return at++; } // Wraps around on overflow
+              @Override public void remove() { throw new UnsupportedOperationException(); }
+            };
+          }
+        }, executor);
+    }
+}
\ No newline at end of file
diff --git a/examples/src/main/java/org/reactivestreams/example/unicast/NumberIterablePublisher.java b/examples/src/main/java/org/reactivestreams/example/unicast/NumberIterablePublisher.java
new file mode 100644
index 0000000..20e19fe
--- /dev/null
+++ b/examples/src/main/java/org/reactivestreams/example/unicast/NumberIterablePublisher.java
@@ -0,0 +1,27 @@
+package org.reactivestreams.example.unicast;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.concurrent.Executor;
+import org.reactivestreams.Subscription;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Publisher;
+
+public class NumberIterablePublisher extends AsyncIterablePublisher<Integer> {
+    public NumberIterablePublisher(final int from, final int to, final Executor executor) {
+        super(new Iterable<Integer>() {
+          { if(from > to) throw new IllegalArgumentException("from must be equal or greater than to!"); }
+          @Override public Iterator<Integer> iterator() {
+            return new Iterator<Integer>() {
+              private int at = from;
+              @Override public boolean hasNext() { return at < to; }
+              @Override public Integer next() {
+                if (!hasNext()) return Collections.<Integer>emptyList().iterator().next();
+                else return at++;
+              }
+              @Override public void remove() { throw new UnsupportedOperationException(); }
+            };
+          }
+        }, executor);
+    }
+}
\ No newline at end of file
diff --git a/examples/src/main/java/org/reactivestreams/example/unicast/SyncSubscriber.java b/examples/src/main/java/org/reactivestreams/example/unicast/SyncSubscriber.java
new file mode 100644
index 0000000..a603101
--- /dev/null
+++ b/examples/src/main/java/org/reactivestreams/example/unicast/SyncSubscriber.java
@@ -0,0 +1,111 @@
+package org.reactivestreams.example.unicast;
+
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+
+/**
+ * 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.
+ *
+ * NOTE: The code below uses a lot of try-catches to show the reader where exceptions can be expected, and where they are forbidden.
+ */
+public abstract class SyncSubscriber<T> implements Subscriber<T> {
+  private Subscription subscription; // Obeying rule 3.1, we make this private!
+  private boolean done = false;
+
+  @Override public void onSubscribe(final Subscription s) {
+    // As per rule 2.13, we need to throw a `java.lang.NullPointerException` if the `Subscription` is `null`
+    if (s == null) throw null;
+
+    if (subscription != null) { // If someone has made a mistake and added this Subscriber multiple times, let's handle it gracefully
+      try {
+        s.cancel(); // Cancel the additional subscription
+      } catch(final Throwable t) {
+        //Subscription.cancel is not allowed to throw an exception, according to rule 3.15
+        (new IllegalStateException(s + " violated the Reactive Streams rule 3.15 by throwing an exception from cancel.", t)).printStackTrace(System.err);
+      }
+    } else {
+      // We have to assign it locally before we use it, if we want to be a synchronous `Subscriber`
+      // Because according to rule 3.10, the Subscription is allowed to call `onNext` synchronously from within `request`
+      subscription = s;
+      try {
+        // If we want elements, according to rule 2.1 we need to call `request`
+        // And, according to rule 3.2 we are allowed to call this synchronously from within the `onSubscribe` method
+        s.request(1); // Our Subscriber is unbuffered and modest, it requests one element at a time
+      } catch(final Throwable t) {
+        // Subscription.request is not allowed to throw according to rule 3.16
+        (new IllegalStateException(s + " violated the Reactive Streams rule 3.16 by throwing an exception from request.", t)).printStackTrace(System.err);
+      }
+    }
+  }
+
+  @Override public void onNext(final T element) {
+    if (subscription == null) { // Technically this check is not needed, since we are expecting Publishers to conform to the spec
+      (new IllegalStateException("Publisher violated the Reactive Streams rule 1.09 signalling onNext prior to onSubscribe.")).printStackTrace(System.err);
+    } else {
+      // As per rule 2.13, we need to throw a `java.lang.NullPointerException` if the `element` is `null`
+      if (element == null) throw null;
+
+      if (!done) { // If we aren't already done
+        try {
+          if (foreach(element)) {
+            try {
+              subscription.request(1); // Our Subscriber is unbuffered and modest, it requests one element at a time
+            } catch (final Throwable t) {
+              // Subscription.request is not allowed to throw according to rule 3.16
+              (new IllegalStateException(subscription + " violated the Reactive Streams rule 3.16 by throwing an exception from request.", t)).printStackTrace(System.err);
+            }
+          } else {
+            done();
+          }
+        } catch (final Throwable t) {
+          done();
+          try {
+            onError(t);
+          } catch (final Throwable t2) {
+            //Subscriber.onError is not allowed to throw an exception, according to rule 2.13
+            (new IllegalStateException(this + " violated the Reactive Streams rule 2.13 by throwing an exception from onError.", t2)).printStackTrace(System.err);
+          }
+        }
+      }
+    }
+  }
+
+  // Showcases a convenience method to idempotently marking the Subscriber as "done", so we don't want to process more elements
+  // herefor we also need to cancel our `Subscription`.
+  private void done() {
+    //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.
+    done = true; // If we `foreach` throws an exception, let's consider ourselves done (not accepting more elements)
+    try {
+      subscription.cancel(); // Cancel the subscription
+    } catch(final Throwable t) {
+      //Subscription.cancel is not allowed to throw an exception, according to rule 3.15
+      (new IllegalStateException(subscription + " violated the Reactive Streams rule 3.15 by throwing an exception from cancel.", t)).printStackTrace(System.err);
+    }
+  }
+
+  // This method is left as an exercise to the reader/extension point
+  // Returns whether more elements are desired or not, and if no more elements are desired
+  protected abstract boolean foreach(final T element);
+
+  @Override public void onError(final Throwable t) {
+    if (subscription == null) { // Technically this check is not needed, since we are expecting Publishers to conform to the spec
+      (new IllegalStateException("Publisher violated the Reactive Streams rule 1.09 signalling onError prior to onSubscribe.")).printStackTrace(System.err);
+    } else {
+      // As per rule 2.13, we need to throw a `java.lang.NullPointerException` if the `Throwable` is `null`
+      if (t == null) throw null;
+      // Here we are not allowed to call any methods on the `Subscription` or the `Publisher`, as per rule 2.3
+      // And anyway, the `Subscription` is considered to be cancelled if this method gets called, as per rule 2.4
+    }
+  }
+
+  @Override public void onComplete() {
+    if (subscription == null) { // Technically this check is not needed, since we are expecting Publishers to conform to the spec
+      (new IllegalStateException("Publisher violated the Reactive Streams rule 1.09 signalling onComplete prior to onSubscribe.")).printStackTrace(System.err);
+    } else {
+      // Here we are not allowed to call any methods on the `Subscription` or the `Publisher`, as per rule 2.3
+      // And anyway, the `Subscription` is considered to be cancelled if this method gets called, as per rule 2.4
+    }
+  }
+}
\ No newline at end of file
diff --git a/examples/src/test/java/org/reactivestreams/example/unicast/AsyncSubscriberTest.java b/examples/src/test/java/org/reactivestreams/example/unicast/AsyncSubscriberTest.java
new file mode 100644
index 0000000..596ae1d
--- /dev/null
+++ b/examples/src/test/java/org/reactivestreams/example/unicast/AsyncSubscriberTest.java
@@ -0,0 +1,63 @@
+package org.reactivestreams.example.unicast;
+
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.tck.SubscriberBlackboxVerification;
+import org.reactivestreams.tck.TestEnvironment;
+import org.testng.annotations.Test;
+import static org.testng.Assert.assertEquals;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.AfterClass;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.TimeUnit;
+
+ at Test // Must be here for TestNG to find and run this, do not remove
+public class AsyncSubscriberTest extends SubscriberBlackboxVerification<Integer> {
+
+  private ExecutorService e;
+  @BeforeClass void before() { e = Executors.newFixedThreadPool(4); }
+  @AfterClass void after() { if (e != null) e.shutdown(); }
+
+  public AsyncSubscriberTest() {
+    super(new TestEnvironment());
+  }
+
+  @Override public Subscriber<Integer> createSubscriber() {
+    return new AsyncSubscriber<Integer>(e) {
+      @Override protected boolean whenNext(final Integer element) {
+        return true;
+      }
+    };
+  }
+
+  @Test public void testAccumulation() throws InterruptedException {
+
+    final AtomicLong i = new AtomicLong(Long.MIN_VALUE);
+    final CountDownLatch latch = new CountDownLatch(1);
+    final Subscriber<Integer> sub =  new AsyncSubscriber<Integer>(e) {
+      private long acc;
+      @Override protected boolean whenNext(final Integer element) {
+        acc += element;
+        return true;
+      }
+
+      @Override protected void whenComplete() {
+        i.set(acc);
+        latch.countDown();
+      }
+    };
+
+    new NumberIterablePublisher(0, 10, e).subscribe(sub);
+    latch.await(env.defaultTimeoutMillis() * 10, TimeUnit.MILLISECONDS);
+    assertEquals(i.get(), 45);
+  }
+
+  @Override public Integer createElement(int element) {
+    return element;
+  }
+
+}
diff --git a/examples/src/test/java/org/reactivestreams/example/unicast/IterablePublisherTest.java b/examples/src/test/java/org/reactivestreams/example/unicast/IterablePublisherTest.java
new file mode 100644
index 0000000..0519ca2
--- /dev/null
+++ b/examples/src/test/java/org/reactivestreams/example/unicast/IterablePublisherTest.java
@@ -0,0 +1,44 @@
+package org.reactivestreams.example.unicast;
+
+import java.lang.Override;
+import java.util.Collections;
+import java.util.Iterator;
+import org.reactivestreams.Publisher;
+import org.reactivestreams.tck.PublisherVerification;
+import org.reactivestreams.tck.TestEnvironment;
+import org.testng.annotations.Test;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.AfterClass;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
+
+ at Test // Must be here for TestNG to find and run this, do not remove
+public class IterablePublisherTest extends PublisherVerification<Integer> {
+
+  private ExecutorService e;
+  @BeforeClass void before() { e = Executors.newFixedThreadPool(4); }
+  @AfterClass void after() { if (e != null) e.shutdown(); }
+
+  public IterablePublisherTest() {
+    super(new TestEnvironment());
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override public Publisher<Integer> createPublisher(final long elements) {
+    assert(elements <= maxElementsFromPublisher());
+    return new NumberIterablePublisher(0, (int)elements, e);
+  }
+
+  @Override public Publisher<Integer> createFailedPublisher() {
+    return new AsyncIterablePublisher<Integer>(new Iterable<Integer>() {
+      @Override public Iterator<Integer> iterator() {
+        throw new RuntimeException("Error state signal!");
+      }
+    }, e);
+  }
+
+  @Override public long maxElementsFromPublisher() {
+    return Integer.MAX_VALUE;
+  }
+}
diff --git a/examples/src/test/java/org/reactivestreams/example/unicast/SyncSubscriberTest.java b/examples/src/test/java/org/reactivestreams/example/unicast/SyncSubscriberTest.java
new file mode 100644
index 0000000..c9303a0
--- /dev/null
+++ b/examples/src/test/java/org/reactivestreams/example/unicast/SyncSubscriberTest.java
@@ -0,0 +1,41 @@
+package org.reactivestreams.example.unicast;
+
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.tck.SubscriberBlackboxVerification;
+import org.reactivestreams.tck.TestEnvironment;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+ at Test // Must be here for TestNG to find and run this, do not remove
+public class SyncSubscriberTest extends SubscriberBlackboxVerification<Integer> {
+
+  private ExecutorService e;
+  @BeforeClass void before() { e = Executors.newFixedThreadPool(4); }
+  @AfterClass void after() { if (e != null) e.shutdown(); }
+
+  public SyncSubscriberTest() {
+    super(new TestEnvironment());
+  }
+
+  @Override public Subscriber<Integer> createSubscriber() {
+    return new SyncSubscriber<Integer>() {
+      private long acc;
+      @Override protected boolean foreach(final Integer element) {
+        acc += element;
+        return true;
+      }
+
+      @Override public void onComplete() {
+        System.out.println("Accumulated: " + acc);
+      }
+    };
+  }
+
+  @Override public Integer createElement(int element) {
+    return element;
+  }
+}
diff --git a/examples/src/test/java/org/reactivestreams/example/unicast/SyncSubscriberWhiteboxTest.java b/examples/src/test/java/org/reactivestreams/example/unicast/SyncSubscriberWhiteboxTest.java
new file mode 100644
index 0000000..a4812f8
--- /dev/null
+++ b/examples/src/test/java/org/reactivestreams/example/unicast/SyncSubscriberWhiteboxTest.java
@@ -0,0 +1,75 @@
+package org.reactivestreams.example.unicast;
+
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import org.reactivestreams.tck.SubscriberBlackboxVerification;
+import org.reactivestreams.tck.SubscriberWhiteboxVerification;
+import org.reactivestreams.tck.TestEnvironment;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+ at Test // Must be here for TestNG to find and run this, do not remove
+public class SyncSubscriberWhiteboxTest extends SubscriberWhiteboxVerification<Integer> {
+
+  private ExecutorService e;
+  @BeforeClass void before() { e = Executors.newFixedThreadPool(4); }
+  @AfterClass void after() { if (e != null) e.shutdown(); }
+
+  public SyncSubscriberWhiteboxTest() {
+    super(new TestEnvironment());
+  }
+
+  @Override
+  public Subscriber<Integer> createSubscriber(final WhiteboxSubscriberProbe<Integer> probe) {
+    return new SyncSubscriber<Integer>() {
+      @Override
+      public void onSubscribe(final Subscription s) {
+        super.onSubscribe(s);
+
+        probe.registerOnSubscribe(new SubscriberPuppet() {
+          @Override
+          public void triggerRequest(long elements) {
+            s.request(elements);
+          }
+
+          @Override
+          public void signalCancel() {
+            s.cancel();
+          }
+        });
+      }
+
+      @Override
+      public void onNext(Integer element) {
+        super.onNext(element);
+        probe.registerOnNext(element);
+      }
+
+      @Override
+      public void onError(Throwable cause) {
+        super.onError(cause);
+        probe.registerOnError(cause);
+      }
+
+      @Override
+      public void onComplete() {
+        super.onComplete();
+        probe.registerOnComplete();
+      }
+
+      @Override
+      protected boolean foreach(Integer element) {
+        return true;
+      }
+    };
+  }
+
+  @Override public Integer createElement(int element) {
+    return element;
+  }
+
+}
diff --git a/examples/src/test/java/org/reactivestreams/example/unicast/UnboundedIntegerIncrementPublisherTest.java b/examples/src/test/java/org/reactivestreams/example/unicast/UnboundedIntegerIncrementPublisherTest.java
new file mode 100644
index 0000000..b789611
--- /dev/null
+++ b/examples/src/test/java/org/reactivestreams/example/unicast/UnboundedIntegerIncrementPublisherTest.java
@@ -0,0 +1,39 @@
+package org.reactivestreams.example.unicast;
+
+import org.reactivestreams.Publisher;
+import org.reactivestreams.tck.PublisherVerification;
+import org.reactivestreams.tck.TestEnvironment;
+import org.testng.annotations.Test;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.AfterClass;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
+import java.util.Iterator;
+
+ at Test // Must be here for TestNG to find and run this, do not remove
+public class UnboundedIntegerIncrementPublisherTest extends PublisherVerification<Integer> {
+
+  private ExecutorService e;
+  @BeforeClass void before() { e = Executors.newFixedThreadPool(4); }
+  @AfterClass void after() { if (e != null) e.shutdown(); }
+
+  public UnboundedIntegerIncrementPublisherTest() {
+    super(new TestEnvironment());
+  }
+
+  @Override public Publisher<Integer> createPublisher(long elements) {
+    return new InfiniteIncrementNumberPublisher(e);
+  }
+
+  @Override public Publisher<Integer> createFailedPublisher() {
+    return new AsyncIterablePublisher<Integer>(new Iterable<Integer>() {
+      @Override public Iterator<Integer> iterator() {
+        throw new RuntimeException("Error state signal!");
+      }
+    }, e);
+  }
+
+  @Override public long maxElementsFromPublisher() {
+    return super.publisherUnableToSignalOnComplete();
+  }
+}
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..61cdbf6
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,9 @@
+rootProject.name = 'reactive-streams'
+include ':reactive-streams'
+include ':reactive-streams-tck'
+include ':reactive-streams-examples'
+
+project(':reactive-streams').projectDir = "$rootDir/api" as File
+project(':reactive-streams-tck').projectDir = "$rootDir/tck" as File
+project(':reactive-streams-examples').projectDir = "$rootDir/examples" as File
+
diff --git a/tck/README.md b/tck/README.md
new file mode 100644
index 0000000..147b111
--- /dev/null
+++ b/tck/README.md
@@ -0,0 +1,616 @@
+# Reactive Streams TCK #
+
+The purpose of the *Reactive Streams Technology Compatibility Kit* (from here on referred to as: *the TCK*) is to guide
+and help Reactive Streams library implementers to validate their implementations against the rules defined in [the Specification](https://github.com/reactive-streams/reactive-streams-jvm).
+
+The TCK is implemented using **plain Java (1.6)** and **TestNG** tests, and should be possible to use from other JVM-based languages and testing libraries.
+
+## Structure of the TCK
+
+The TCK aims to cover all rules defined in the Specification, however for some rules outlined in the Specification it is
+not possible (or viable) to construct automated tests, thus the TCK can not claim to fully verify an implementation, however it is very helpful and is able to validate the most important rules.
+
+The TCK is split up into 4 TestNG test classes which are to be extended by implementers, providing their `Publisher` / `Subscriber` / `Processor` implementations for the test harness to validate.
+
+The tests are split in the following way:
+
+* `PublisherVerification`
+* `SubscriberWhiteboxVerification`
+* `SubscriberBlackboxVerification`
+* `IdentityProcessorVerification`
+
+The sections below include examples on how these can be used and describe the various configuration options.
+
+The TCK is provided as binary artifact on [Maven Central](http://search.maven.org/#search|ga|1|reactive-streams-tck):
+
+```xml
+<dependency>
+  <groupId>org.reactivestreams</groupId>
+  <artifactId>reactive-streams-tck</artifactId>
+  <version>1.0.0</version>
+  <scope>test</scope>
+</dependency>
+```
+
+Please refer to the [Reactive Streams Specification](https://github.com/reactive-streams/reactive-streams-jvm) for the current latest version number. Make sure that your Reactive Streams API and TCK dependency versions match.
+
+### Test method naming convention
+
+Since the TCK is aimed at Reactive Stream implementers, looking into the sources of the TCK is well expected and encouraged as it should help during the implementation cycle.
+
+In order to make mapping between test cases and Specification rules easier, each test case covering a specific
+Specification rule abides the following naming convention: `TYPE_spec###_DESC` where:
+
+* `TYPE` is one of: [#type-required](required), [#type-optional](optional), [#type-stochastic](stochastic) or [#type-untested](untested) which describe if this test is covering a Rule that MUST or SHOULD be implemented. The specific words are explained in detail below.
+* `###` is the Rule number (`1.xx` Rules are about `Publisher`s, `2.xx` Rules are about Subscribers etc.)
+* `DESC` is a short explanation of what exactly is being tested in this test case, as sometimes one Rule may have multiple test cases in order to cover the entire Rule.
+
+Here is an example test method signature:
+
+```java
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.1
+  @Test public void required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements() throws Throwable {
+    // ...
+  }
+```
+
+#### Test types explained:
+
+```java
+ at Test public void required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements() throws Throwable
+```
+
+<a name="type-required"></a>
+The `required_` means that this test case is a hard requirement, it covers a *MUST* or *MUST NOT* Rule of the Specification.
+
+
+```java
+ at Test public void optional_spec104_mustSignalOnErrorWhenFails() throws Throwable
+```
+
+<a name="type-optional"></a>
+The `optional_` means that this test case is an optional requirement, it covers a *MAY* or *SHOULD* Rule of the Specification.
+This prefix is also used if more configuration is needed in order to run it, e.g.
+`@Additional(implement = "createFailedPublisher") @Test` signals the implementer that in order to run this test
+one has to implement the `Publisher<T> createFailedPublisher()` method.
+
+```java
+ at Test public void stochastic_spec103_mustSignalOnMethodsSequentially() throws Throwable
+```
+
+<a name="type-stochastic"></a>
+The `stochastic_` means that the Rule is impossible or infeasible to deterministically verify—
+usually this means that this test case can yield false positives ("be green") even if for some case, the given implementation may violate the tested behaviour.
+
+```java
+ at Test public void untested_spec106_mustConsiderSubscriptionCancelledAfterOnErrorOrOnCompleteHasBeenCalled() throws Throwable
+```
+
+<a name="type-untested"></a>
+The `untested_` means that the test case is not implemented, either because it is inherently hard to verify (e.g. Rules which use
+the wording "*SHOULD consider X as Y*"). Such tests will show up in your test runs as `SKIPPED`, with a message pointing out that the TCK is unable to validate this Rule. Solutions to deterministically test Rules which have been
+marked with this prefix are most welcome – pull requests are encouraged!
+
+### Test isolation
+
+All test assertions are isolated within the required `TestEnvironment`, so it is safe to run the TCK tests in parallel.
+
+### Testing Publishers with restricted capabilities
+
+Some `Publisher`s will not be able to pass through all TCK tests due to some internal or fundamental decisions in their design.
+For example, a `FuturePublisher` can be implemented such that it can only ever `onNext` **exactly once**—this means that it's not possible to run all TCK tests against it since some of them require multiple elements to be emitted.
+
+In order to allow such `Publisher`s to be tested against the spec's rules, the TCK provides the `maxElementsFromPublisher()` method as means of communicating the limited capabilities of the Publisher. For example, if a `Publisher` can only ever emit up to `2` elements,
+tests in the TCK which require more than 2 elements to verify a rule will be skipped.
+
+In order to inform the TCK that the `Publisher` is only able to signal up to `2` elements, override the `maxElementsFromPublisher` method like this:
+
+```java
+ at Override public long maxElementsFromPublisher() {
+  return 2;
+}
+```
+
+The TCK also supports `Publisher`s which are not able to signal completion. Imagine a `Publisher` being
+backed by a timer—such a `Publisher` does not have a natural way to "complete" after some number of ticks. It would be
+possible to implement a `Processor` which would "take n elements from the TickPublisher and then signal completion to the
+downstream", but this adds a layer of indirection between the TCK and the `Publisher` one initially wanted to test.
+It is suggested to test such unbouded `Publisher`s either way—using a "TakeNElementsProcessor" or by informing the TCK
+that the `Publisher` is not able to signal completion. The TCK will then skip all tests which require `onComplete` signals to be emitted.
+
+In order to inform the TCK that your Publiher is not able to signal completion, override the `maxElementsFromPublisher` method like this:
+
+```java
+ at Override public long maxElementsFromPublisher() {
+  return publisherUnableToSignalOnComplete(); // == Long.MAX_VALUE == unbounded
+}
+```
+
+### Testing a "failed" Publisher
+The Reactive Streams Specification mandates certain behaviours for `Publisher`s which are "failed",
+e.g. it was unable to initialize a connection it needs to emit elements.
+It may be useful to specifically such known to be failed `Publisher` using the TCK.
+
+In order to run additional tests on a failed `Publisher` implement the `createFailedPublisher` method.
+The expected behaviour from the returned implementation is to follow Rule 1.4 and Rule 1.9—which are concerned
+with the order of emiting the `Subscription` and signaling the failure.
+
+```java
+ at Override public Publisher<T> createFailedPublisher() {
+  final String invalidData = "this input string is known it to be failed";
+  return new MyPublisher(invalidData);
+}
+```
+
+In case there isn't a known up-front error state to put the `Publisher` into,
+ignore these tests by returning `null` from the `createFailedPublisher` method.
+It is important to remember that it is **illegal** to signal `onNext / onComplete / onError` before signalling the `Subscription` through `onSubscribe`, for details on this rule refer to the Reactive Streams specification.
+
+## Publisher Verification
+
+`PublisherVerification` tests verify `Publisher` as well as some `Subscription` Rules of the Specification.
+
+In order to include it's tests in your test suite simply extend it, like this:
+
+```java
+package com.example.streams;
+
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.tck.PublisherVerification;
+import org.reactivestreams.tck.TestEnvironment;
+
+public class RangePublisherTest extends PublisherVerification<Integer> {
+
+  public RangePublisherTest() {
+    super(new TestEnvironment());
+  }
+
+  @Override
+  public Publisher<Integer> createPublisher(long elements) {
+    return new RangePublisher<Integer>(1, elements);
+  }
+
+  @Override
+  public Publisher<Integer> createFailedPublisher() {
+    return new Publisher<Integer>() {
+      @Override
+      public void subscribe(Subscriber<Integer> s) {
+        s.onError(new RuntimeException("Can't subscribe subscriber: " + s + ", because of reasons."));
+      }
+    };
+  }
+
+  // ADDITIONAL CONFIGURATION
+
+  @Override
+  public long maxElementsFromPublisher() {
+    return Long.MAX_VALUE—1;
+  }
+
+  @Override
+  public long boundedDepthOfOnNextAndRequestRecursion() {
+    return 1;
+  }
+}
+```
+
+Notable configuration options include:
+
+* `maxElementsFromPublisher` – must be overridden in case the `Publisher` being tested is of bounded length, e.g. it's wrapping a `Future<T>` and thus can only publish up to 1 element, in which case you
+  would return `1` from this method. It will cause all tests which require more elements in order to validate a certain
+  Rule to be skipped,
+* `boundedDepthOfOnNextAndRequestRecursion` – which must be overridden when verifying synchronous `Publisher`s.
+  This number returned by this method will be used to validate if a `Subscription` adheres to Rule 3.3 and avoids "unbounded recursion".
+
+### Timeout configuration
+Publisher tests make use of two kinds of timeouts, one is the `defaultTimeoutMillis` which corresponds to all methods used
+within the TCK which await for something to happen. The other timeout is `publisherReferenceGCTimeoutMillis` which is only used in order to verify
+[Rule 3.13](https://github.com/reactive-streams/reactive-streams-jvm#3.13) which defines that `Subscriber` references MUST be dropped
+by the Publisher.
+
+In order to configure these timeouts (for example when running on a slow continious integtation machine), you can either:
+
+**Use env variables** to set these timeouts, in which case the you can do:
+
+```bash
+export DEFAULT_TIMEOUT_MILLIS=300
+export PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS=500
+```
+
+Or **define the timeouts explicitly in code**:
+
+```java
+public class RangePublisherTest extends PublisherVerification<Integer> {
+
+  public static final long DEFAULT_TIMEOUT_MILLIS = 300L;
+  public static final long PUBLISHER_REFERENCE_CLEANUP_TIMEOUT_MILLIS = 500L;
+
+  public RangePublisherTest() {
+    super(new TestEnvironment(DEFAULT_TIMEOUT_MILLIS), PUBLISHER_REFERENCE_CLEANUP_TIMEOUT_MILLIS);
+  }
+
+  // ...
+}
+```
+
+Note that explicitly passed in values take precedence over values provided by the environment
+
+## Subscriber Verification
+
+`Subscriber` Verification is split up into two files (styles) of tests.
+
+It is highly recommended to implement the `SubscriberWhiteboxVerification<T>` instead of the `SubscriberBlackboxVerification<T>` even if it is more work to do so, as it can test far more rules and corner cases in implementations that would otherwise be left untested—which is the case when using the Blackbox Verification.
+
+### createElement and Helper Publisher implementations
+Since testing a `Subscriber` is not possible without a corresponding `Publisher` the TCK `Subscriber` Verifications
+both provide a default "*helper publisher*" to drive its tests and also allow to replace this `Publisher` with a custom implementation.
+The helper `Publisher` is an asynchronous `Publisher` by default—meaning that a `Subscriber` can not blindly assume single threaded execution.
+
+When extending `Subscriber` Verification classes a type parameter representing the element type passed through the stream must be given.
+Implementations are typically not sensitive to the type of element being signalled, but sometimes a `Subscriber` may be limited to only be able to work within a known set of types -
+like a `FileSubscriber extends Subscriber<ByteBuffer>` for example, that writes each element (ByteBuffer) it receives into a file.
+For element type agnostic Subscribers the simplest way is to parameterize the tests using `Integer` and in the `createElement(int idx)` method (explained below in futher detail), return the incoming `int`. 
+In case an implementation needs to work on a specific type, the verification class should be parameterized using that type (e.g. `class StringSubTest extends SubscriberWhiteboxVerification<String>`) and the `createElement` method must be overriden to return a `String`.
+
+While the Helper `Publisher` implementation is provided, creating its elements is not – this is because a given `Subscriber`
+may for example only work with `HashedMessage` or some other specific kind of element. The TCK is unable to generate such
+special messages automatically, so the TCK provides the `T createElement(Integer id)` method to be implemented as part of
+`Subscriber` Verifications which should take the given `id` and return an element of type `T` (where `T` is the type of
+elements flowing into the `Subscriber<T>`, as known thanks to `... extends SubscriberWhiteboxVerification<T>`) representing
+an element of the stream that will be passed on to the Subscriber.
+
+The simplest valid implemenation is to return the incoming `id` *as the element* in a verification using `Integer`s as
+element types:
+
+```java
+public class MySubscriberTest extends SubscriberBlackboxVerification<Integer> {
+
+  // ...
+
+  @Override
+  public Integer createElement(int element) { return element; }
+}
+```
+
+
+NOTE: The `createElement` method *MAY* be called *concurrently from multiple threads*.
+
+**Very advanced**: While it is not expected for many implementations having to do so, it is possible to take full control of the `Publisher` which will be driving the TCKs test. This can be achieved by implementing the `createHelperPublisher` method in which one can implement the `createHelperPublisher` method by returning a custom `Publisher` implementation which will then be used by the TCK to drive your `Subscriber` tests:
+
+```java
+ at Override public Publisher<Message> createHelperPublisher(long elements) {
+  return new Publisher<Message>() { /* CUSTOM IMPL HERE WHICH OF COURSE ALSO SHOULD PASS THE TCK */ };
+}
+```
+
+
+### Subscriber Whitebox Verification
+
+The Whitebox Verification is able to verify most of the `Subscriber` Specification, at the additional cost that control over demand generation and cancellation must be handed over to the TCK via the `SubscriberPuppet`.
+
+Based on experience implementing the `SubscriberPuppet`—it can be tricky or even impossible for some implementations,
+as such, not all implementations are expected to make use of the plain `SubscriberWhiteboxVerification`, instead having to fall back to using the `SubscriberBlackboxVerification`.
+
+For the simplest possible (and most common) `Subscriber` implementation using the whitebox verification boils down to
+exteding (or delegating to) your implementation with additionally signalling and registering the test probe, as shown in the below example:
+
+```java
+package com.example.streams;
+
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import org.reactivestreams.tck.SubscriberWhiteboxVerification;
+import org.reactivestreams.tck.TestEnvironment;
+
+public class MySubscriberWhiteboxVerificationTest extends SubscriberWhiteboxVerification<Integer> {
+
+  public MySubscriberWhiteboxVerificationTest() {
+    super(new TestEnvironment());
+  }
+
+  // The implementation under test is "SyncSubscriber":
+  // class SyncSubscriber<T> extends Subscriber<T> { /* ... */ }
+
+  @Override
+  public Subscriber<Integer> createSubscriber(final WhiteboxSubscriberProbe<Integer> probe) {
+    // in order to test the SyncSubscriber we must instrument it by extending it,
+    // and calling the WhiteboxSubscriberProbe in all of the Subscribers methods:
+    return new SyncSubscriber<Integer>() {
+      @Override
+      public void onSubscribe(final Subscription s) {
+        super.onSubscribe(s);
+
+        // register a successful Subscription, and create a Puppet,
+        // for the WhiteboxVerification to be able to drive its tests:
+        probe.registerOnSubscribe(new SubscriberPuppet() {
+
+          @Override
+          public void triggerRequest(long elements) {
+            s.request(elements);
+          }
+
+          @Override
+          public void signalCancel() {
+            s.cancel();
+          }
+        });
+      }
+
+      @Override
+      public void onNext(Integer element) {
+        // in addition to normal Subscriber work that you're testing, register onNext with the probe
+        super.onNext(element);
+        probe.registerOnNext(element);
+      }
+
+      @Override
+      public void onError(Throwable cause) {
+        // in addition to normal Subscriber work that you're testing, register onError with the probe
+        super.onError(cause);
+        probe.registerOnError(cause);
+      }
+
+      @Override
+      public void onComplete() {
+        // in addition to normal Subscriber work that you're testing, register onComplete with the probe
+        super.onComplete();
+        probe.registerOnComplete();
+      }
+    };
+  }
+
+  @Override
+  public Integer createElement(int element) {
+    return element;
+  }
+
+}
+```
+
+### Subscriber Blackbox Verification
+
+Blackbox Verification does not require anything besides providing a `Subscriber` and `Publisher` instances to the TCK,
+at the expense of not being able to verify as much as the `SubscriberWhiteboxVerification`:
+
+```java
+package com.example.streams;
+
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import org.reactivestreams.tck.SubscriberBlackboxVerification;
+import org.reactivestreams.tck.TestEnvironment;
+
+public class MySubscriberBlackboxVerificationTest extends SubscriberBlackboxVerification<Integer> {
+
+  public MySubscriberBlackboxVerificationTest() {
+    super(new TestEnvironment());
+  }
+
+  @Override
+  public Subscriber<Integer> createSubscriber() {
+    return new MySubscriber<Integer>();
+  }
+
+  @Override
+  public Integer createElement(int element) {
+    return element;
+  }
+}
+```
+
+### Timeout configuration
+Similarily to `PublisherVerification`, it is possible to set the timeouts used by the TCK to validate `Subscriber` behaviour either hard-coded or by using environment variables.
+
+**Use env variables** to set the timeout value to be used by the TCK:
+
+```bash
+export DEFAULT_TIMEOUT_MILLIS=300
+```
+
+Or **define the timeout explicitly in code**:
+
+```java
+public class MySubscriberTest extends SubscriberBlackboxVerification<Integer> {
+
+  public static final long DEFAULT_TIMEOUT_MILLIS = 300L;
+
+  public RangePublisherTest() {
+    super(new TestEnvironment(DEFAULT_TIMEOUT_MILLIS));
+  }
+
+  // ...
+}
+```
+
+NOTE: hard-coded values *take precedence* over environment set values (!).
+
+
+## Subscription Verification
+
+Please note that while `Subscription` does **not** have it's own test class, it's rules are validated inside of the
+`Publisher` and `Subscriber` tests – depending if the Rule demands specific action to be taken by the publishing, or
+subscribing side of the `Subscription` contract.
+
+## Identity Processor Verification
+
+An `IdentityProcessorVerification` tests the given `Processor` for all `Subscriber`, `Publisher` as well as
+`Subscription` rules (internally the `WhiteboxSubscriberVerification` is used for that).
+
+```java
+package com.example.streams;
+
+import org.reactivestreams.Processor;
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import org.reactivestreams.tck.IdentityProcessorVerification;
+import org.reactivestreams.tck.SubscriberWhiteboxVerification;
+import org.reactivestreams.tck.TestEnvironment;
+
+public class MyIdentityProcessorVerificationTest extends IdentityProcessorVerification<Integer> {
+
+  public static final long DEFAULT_TIMEOUT_MILLIS = 300L;
+  public static final long PUBLISHER_REFERENCE_CLEANUP_TIMEOUT_MILLIS = 1000L;
+
+
+  public MyIdentityProcessorVerificationTest() {
+    super(new TestEnvironment(DEFAULT_TIMEOUT_MILLIS), PUBLISHER_REFERENCE_CLEANUP_TIMEOUT_MILLIS);
+  }
+
+  @Override
+  public Processor<Integer, Integer> createIdentityProcessor(int bufferSize) {
+    return new MyIdentityProcessor<Integer, Integer>(bufferSize);
+  }
+
+  @Override
+  public Publisher<Integer> createHelperPublisher(long elements) {
+    return new MyRangePublisher<Integer>(1, elements);
+  }
+
+  // ENABLE ADDITIONAL TESTS
+
+  @Override
+  public Publisher<Integer> createFailedPublisher() {
+    // return Publisher that only signals onError instead of null to run additional tests
+    // see this methods JavaDocs for more details on how the returned Publisher should work.
+    return null;
+  }
+
+  // OPTIONAL CONFIGURATION OVERRIDES
+  // only override these if understanding the implications of doing so.
+
+  @Override
+  public long maxElementsFromPublisher() {
+    return super.maxElementsFromPublisher();
+  }
+
+  @Override
+  public long boundedDepthOfOnNextAndRequestRecursion() {
+    return super.boundedDepthOfOnNextAndRequestRecursion();
+  }
+}
+```
+
+The additional configuration options reflect the options available in the `Subscriber` and `Publisher` Verifications.
+
+The `IdentityProcessorVerification` also runs additional "sanity" verifications, which are not directly mapped to
+Specification rules, but help to verify that a `Processor` won't "get stuck" or face similar problems. Please refer to the
+sources for details on the tests included.
+
+## Ignoring tests
+Since the tests are inherited instead of user defined it's not possible to use the usual `@Ignore` annotations
+to skip certain tests (which may be perfectly reasonable if the implementation has some know constraints on what it
+cannot implement). Below is a recommended pattern to skip tests inherited from the TCK's base classes:
+
+```java
+package com.example.streams;
+
+import org.reactivestreams.Processor;
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import org.reactivestreams.tck.IdentityProcessorVerification;
+import org.reactivestreams.tck.TestEnvironment;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class MyIdentityProcessorTest extends IdentityProcessorVerification<Integer> {
+
+  private ExecutorService e;
+
+  @BeforeClass
+  public void before() { e = Executors.newFixedThreadPool(4); }
+
+  @AfterClass
+  public void after() { if (e != null) e.shutdown(); }
+
+  public SkippingIdentityProcessorTest() {
+    super(new TestEnvironment());
+  }
+
+  @Override
+  public ExecutorService publisherExecutorService() {
+    return e;
+  }
+
+  @Override
+  public Integer createElement(int element) {
+    return element;
+  }
+
+  @Override
+  public Processor<Integer, Integer> createIdentityProcessor(int bufferSize) {
+    return new MyProcessor<Integer, Integer>(bufferSize); // return implementation to be tested
+  }
+
+  @Override
+  public Publisher<Integer> createFailedPublisher() {
+    return null; // returning null means that the tests validating a failed publisher will be skipped
+  }
+
+}
+```
+
+## Which verifications must be implemented by a compliant implementation?
+In order to be considered an Reactive Streams compliant require implementations to cover their
+`Publisher`s and `Subscriber`s with TCK verifications. If a library only implements `Subscriber`s, it does not have to implement `Publisher` tests, the same applies to `IdentityProcessorVerification`-it is not needed if the library does not contain `Processor`s.
+
+In the case of `Subscriber` Verification are two styles of verifications to available: Blackbox or Whitebox.
+It is *strongly* recommend to test `Subscriber` implementations with the `SubscriberWhiteboxVerification` as it is able to
+verify most of the specification. The `SubscriberBlackboxVerification` should only be used as a fallback,
+once it's certain that implementing the whitebox version will not be possible—if that happens
+feel free to open a ticket on the [reactive-streams-jvm](https://github.com/reactive-streams/reactive-streams-jvm) project explaining what made implementing the whitebox verification impossible.
+
+In summary: implementations are required to use Verifications for the parts of the Specification that they implement,
+and encouraged to using the Whitebox Verification over Blackbox for `Subscriber` whenever possible.
+
+## Upgrading the TCK to newer versions
+While it's not expected for the Reactive Streams Specification to change in the forseeable future,
+it *may be* that some semantics may need to change at some point. In this case it should expected for test
+methods being phased out in terms of deprecation or removal, new tests may also be added over time.
+
+In general this should not be of much concern, unless overriding test methods are overriden by implementers.
+Implementers who find the need of overriding provided test methods are encouraged to reach out via opening Issues
+on the [Reactive Streams](https://github.com/reactive-streams/reactive-streams-jvm) project, so the use case can be discussed and, most likely, the TCK improved.
+
+## Using the TCK from other programming languages
+
+The TCK was designed such that it should be possible to consume it using different JVM-based programming languages.
+The section below shows how to use the TCK using different languages (contributions of examples for more languages are very welcome):
+
+### Scala
+
+In order to run the TCK using [ScalaTest](http://www.scalatest.org/) the test class must mix-in the `TestNGSuiteLike` trait (as of ScalaTest `2.2.x`).
+
+```scala
+class IterablePublisherTest(env: TestEnvironment, publisherShutdownTimeout: Long)
+  extends PublisherVerification[Int](env, publisherShutdownTimeout)
+  with TestNGSuiteLike {
+
+  def this() {
+    this(new TestEnvironment(500), 1000)
+  }
+
+  def createPublisher(elements: Long): Publisher[Int] = ???
+
+  // example error state publisher implementation
+  override def createFailedPublisher(): Publisher[Int] =
+    new Publisher[Int] {
+      override def subscribe(s: Subscriber[Int]): Unit =
+        s.onError(new Exception("Unable to serve subscribers right now!"))
+    }
+
+}
+```
+
+### Groovy, JRuby, Kotlin, others...
+
+Contributions to this document are very welcome!
+
+When implementing Reactive Streams using the TCK in some yet undocumented here, language, please feel free to share an example!
diff --git a/tck/build.gradle b/tck/build.gradle
new file mode 100644
index 0000000..52cd98c
--- /dev/null
+++ b/tck/build.gradle
@@ -0,0 +1,7 @@
+description = 'reactive-streams-tck'
+dependencies {
+    compile group: 'org.testng', name: 'testng', version:'5.14.10'
+    compile project(':reactive-streams')
+    compile project(':reactive-streams-examples')
+}
+test.useTestNG()
diff --git a/tck/src/main/java/org/reactivestreams/tck/IdentityProcessorVerification.java b/tck/src/main/java/org/reactivestreams/tck/IdentityProcessorVerification.java
new file mode 100644
index 0000000..5a53448
--- /dev/null
+++ b/tck/src/main/java/org/reactivestreams/tck/IdentityProcessorVerification.java
@@ -0,0 +1,762 @@
+package org.reactivestreams.tck;
+
+import org.reactivestreams.Processor;
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import org.reactivestreams.tck.TestEnvironment.ManualPublisher;
+import org.reactivestreams.tck.TestEnvironment.ManualSubscriber;
+import org.reactivestreams.tck.TestEnvironment.ManualSubscriberWithSubscriptionSupport;
+import org.reactivestreams.tck.TestEnvironment.Promise;
+import org.reactivestreams.tck.support.Function;
+import org.reactivestreams.tck.support.SubscriberWhiteboxVerificationRules;
+import org.reactivestreams.tck.support.PublisherVerificationRules;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public abstract class IdentityProcessorVerification<T> extends WithHelperPublisher<T>
+  implements SubscriberWhiteboxVerificationRules, PublisherVerificationRules {
+
+  private final TestEnvironment env;
+
+  ////////////////////// DELEGATED TO SPECS //////////////////////
+
+  // for delegating tests
+  private final SubscriberWhiteboxVerification<T> subscriberVerification;
+
+  // for delegating tests
+  private final PublisherVerification<T> publisherVerification;
+
+  ////////////////// END OF DELEGATED TO SPECS //////////////////
+
+  // number of elements the processor under test must be able ot buffer,
+  // without dropping elements. Defaults to `TestEnvironment.TEST_BUFFER_SIZE`.
+  private final int processorBufferSize;
+
+  /**
+   * Test class must specify the expected time it takes for the publisher to
+   * shut itself down when the the last downstream {@code Subscription} is cancelled.
+   *
+   * The processor will be required to be able to buffer {@code TestEnvironment.TEST_BUFFER_SIZE} elements.
+   */
+  @SuppressWarnings("unused")
+  public IdentityProcessorVerification(final TestEnvironment env) {
+    this(env, PublisherVerification.envPublisherReferenceGCTimeoutMillis(), TestEnvironment.TEST_BUFFER_SIZE);
+  }
+
+  /**
+   * Test class must specify the expected time it takes for the publisher to
+   * shut itself down when the the last downstream {@code Subscription} is cancelled.
+   *
+   * The processor will be required to be able to buffer {@code TestEnvironment.TEST_BUFFER_SIZE} elements.
+   *
+   * @param publisherReferenceGCTimeoutMillis used to determine after how much time a reference to a Subscriber should be already dropped by the Publisher.
+   */
+  @SuppressWarnings("unused")
+  public IdentityProcessorVerification(final TestEnvironment env, long publisherReferenceGCTimeoutMillis) {
+    this(env, publisherReferenceGCTimeoutMillis, TestEnvironment.TEST_BUFFER_SIZE);
+  }
+
+  /**
+   * Test class must specify the expected time it takes for the publisher to
+   * shut itself down when the the last downstream {@code Subscription} is cancelled.
+   *
+   * @param publisherReferenceGCTimeoutMillis used to determine after how much time a reference to a Subscriber should be already dropped by the Publisher.
+   * @param processorBufferSize            number of elements the processor is required to be able to buffer.
+   */
+  public IdentityProcessorVerification(final TestEnvironment env, long publisherReferenceGCTimeoutMillis, int processorBufferSize) {
+    this.env = env;
+    this.processorBufferSize = processorBufferSize;
+
+    this.subscriberVerification = new SubscriberWhiteboxVerification<T>(env) {
+      @Override
+      public Subscriber<T> createSubscriber(WhiteboxSubscriberProbe<T> probe) {
+        return IdentityProcessorVerification.this.createSubscriber(probe);
+      }
+
+      @Override public T createElement(int element) {
+        return IdentityProcessorVerification.this.createElement(element);
+      }
+
+      @Override
+      public Publisher<T> createHelperPublisher(long elements) {
+        return IdentityProcessorVerification.this.createHelperPublisher(elements);
+      }
+    };
+
+    publisherVerification = new PublisherVerification<T>(env, publisherReferenceGCTimeoutMillis) {
+      @Override
+      public Publisher<T> createPublisher(long elements) {
+        return IdentityProcessorVerification.this.createPublisher(elements);
+      }
+
+      @Override
+      public Publisher<T> createFailedPublisher() {
+        return IdentityProcessorVerification.this.createFailedPublisher();
+      }
+
+      @Override
+      public long maxElementsFromPublisher() {
+        return IdentityProcessorVerification.this.maxElementsFromPublisher();
+      }
+
+      @Override
+      public long boundedDepthOfOnNextAndRequestRecursion() {
+        return IdentityProcessorVerification.this.boundedDepthOfOnNextAndRequestRecursion();
+      }
+
+      @Override
+      public boolean skipStochasticTests() {
+        return IdentityProcessorVerification.this.skipStochasticTests();
+      }
+    };
+  }
+
+  /**
+   * This is the main method you must implement in your test incarnation.
+   * It must create a Publisher, which simply forwards all stream elements from its upstream
+   * to its downstream. It must be able to internally buffer the given number of elements.
+   *
+   * @param bufferSize number of elements the processor is required to be able to buffer.
+   */
+  public abstract Processor<T, T> createIdentityProcessor(int bufferSize);
+
+  /**
+   * By implementing this method, additional TCK tests concerning a "failed" publishers will be run.
+   *
+   * The expected behaviour of the {@link Publisher} returned by this method is hand out a subscription,
+   * followed by signalling {@code onError} on it, as specified by Rule 1.9.
+   *
+   * If you ignore these additional tests, return {@code null} from this method.
+   */
+  public abstract Publisher<T> createFailedPublisher();
+
+  /**
+   * Override and return lower value if your Publisher is only able to produce a known number of elements.
+   * For example, if it is designed to return at-most-one element, return {@code 1} from this method.
+   *
+   * Defaults to {@code 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 {@code onComplete} override this method and return {@code Long.MAX_VALUE},
+   * which will result in *skipping all tests which require an onComplete to be triggered* (!).
+   */
+  public long maxElementsFromPublisher() {
+    return Long.MAX_VALUE - 1;
+  }
+
+  /**
+   * In order to verify rule 3.3 of the reactive streams spec, this number will be used to check if a
+   * {@code Subscription} actually solves the "unbounded recursion" problem by not allowing the number of
+   * recursive calls to exceed the number returned by this method.
+   *
+   * @see <a href="https://github.com/reactive-streams/reactive-streams-jvm#3.3">reactive streams spec, rule 3.3</a>
+   * @see PublisherVerification#required_spec303_mustNotAllowUnboundedRecursion()
+   */
+  public long boundedDepthOfOnNextAndRequestRecursion() {
+    return 1;
+  }
+
+  /**
+   * Override and return {@code true} in order to skip executing tests marked as {@code Stochastic}.
+   * Such tests MAY sometimes fail even though the impl
+   */
+  public boolean skipStochasticTests() {
+    return false;
+  }
+
+  /**
+   * Describes the tested implementation in terms of how many subscribers they can support.
+   * Some tests require the {@code 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 long maxSupportedSubscribers() {
+      return Long.MAX_VALUE;
+  }
+
+  ////////////////////// TEST ENV CLEANUP /////////////////////////////////////
+
+  @BeforeMethod
+  public void setUp() throws Exception {
+    publisherVerification.setUp();
+    subscriberVerification.setUp();
+  }
+
+  ////////////////////// PUBLISHER RULES VERIFICATION ///////////////////////////
+
+  // A Processor
+  //   must obey all Publisher rules on its publishing side
+  public Publisher<T> createPublisher(long elements) {
+    final Processor<T, T> processor = createIdentityProcessor(processorBufferSize);
+    final Publisher<T> pub = createHelperPublisher(elements);
+    pub.subscribe(processor);
+    return processor; // we run the PublisherVerification against this
+  }
+
+  @Override @Test
+  public void required_validate_maxElementsFromPublisher() throws Exception {
+    publisherVerification.required_validate_maxElementsFromPublisher();
+  }
+
+  @Override @Test
+  public void required_validate_boundedDepthOfOnNextAndRequestRecursion() throws Exception {
+    publisherVerification.required_validate_boundedDepthOfOnNextAndRequestRecursion();
+  }
+
+  /////////////////////// DELEGATED TESTS, A PROCESSOR "IS A" PUBLISHER //////////////////////
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#4.1
+
+  @Test
+  public void required_createPublisher1MustProduceAStreamOfExactly1Element() throws Throwable {
+    publisherVerification.required_createPublisher1MustProduceAStreamOfExactly1Element();
+  }
+
+  @Test
+  public void required_createPublisher3MustProduceAStreamOfExactly3Elements() throws Throwable {
+    publisherVerification.required_createPublisher3MustProduceAStreamOfExactly3Elements();
+  }
+
+  @Override @Test
+  public void required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements() throws Throwable {
+    publisherVerification.required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements();
+  }
+
+  @Override @Test
+  public void required_spec102_maySignalLessThanRequestedAndTerminateSubscription() throws Throwable {
+    publisherVerification.required_spec102_maySignalLessThanRequestedAndTerminateSubscription();
+  }
+
+  @Override @Test
+  public void stochastic_spec103_mustSignalOnMethodsSequentially() throws Throwable {
+    publisherVerification.stochastic_spec103_mustSignalOnMethodsSequentially();
+  }
+
+  @Override @Test
+  public void optional_spec104_mustSignalOnErrorWhenFails() throws Throwable {
+    publisherVerification.optional_spec104_mustSignalOnErrorWhenFails();
+  }
+
+  @Override @Test
+  public void required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates() throws Throwable {
+    publisherVerification.required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates();
+  }
+
+  @Override @Test
+  public void optional_spec105_emptyStreamMustTerminateBySignallingOnComplete() throws Throwable {
+    publisherVerification.optional_spec105_emptyStreamMustTerminateBySignallingOnComplete();
+  }
+
+  @Override @Test
+  public void untested_spec106_mustConsiderSubscriptionCancelledAfterOnErrorOrOnCompleteHasBeenCalled() throws Throwable {
+    publisherVerification.untested_spec106_mustConsiderSubscriptionCancelledAfterOnErrorOrOnCompleteHasBeenCalled();
+  }
+
+  @Override @Test
+  public void required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled() throws Throwable {
+    publisherVerification.required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled();
+  }
+
+  @Override @Test
+  public void untested_spec107_mustNotEmitFurtherSignalsOnceOnErrorHasBeenSignalled() throws Throwable {
+    publisherVerification.untested_spec107_mustNotEmitFurtherSignalsOnceOnErrorHasBeenSignalled();
+  }
+
+  @Override @Test
+  public void untested_spec108_possiblyCanceledSubscriptionShouldNotReceiveOnErrorOrOnCompleteSignals() throws Throwable {
+    publisherVerification.untested_spec108_possiblyCanceledSubscriptionShouldNotReceiveOnErrorOrOnCompleteSignals();
+  }
+
+  @Override @Test
+  public void untested_spec109_subscribeShouldNotThrowNonFatalThrowable() throws Throwable {
+    publisherVerification.untested_spec109_subscribeShouldNotThrowNonFatalThrowable();
+  }
+
+  @Override @Test
+  public void required_spec109_subscribeThrowNPEOnNullSubscriber() throws Throwable {
+    publisherVerification.required_spec109_subscribeThrowNPEOnNullSubscriber();
+  }
+
+  @Override @Test
+  public void required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe() throws Throwable {
+    publisherVerification.required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe();
+  }
+
+  @Override @Test
+  public void required_spec109_mustIssueOnSubscribeForNonNullSubscriber() throws Throwable {
+    publisherVerification.required_spec109_mustIssueOnSubscribeForNonNullSubscriber();
+  }
+
+  @Override @Test
+  public void untested_spec110_rejectASubscriptionRequestIfTheSameSubscriberSubscribesTwice() throws Throwable {
+    publisherVerification.untested_spec110_rejectASubscriptionRequestIfTheSameSubscriberSubscribesTwice();
+  }
+
+  @Override @Test
+  public void optional_spec111_maySupportMultiSubscribe() throws Throwable {
+    publisherVerification.optional_spec111_maySupportMultiSubscribe();
+  }
+
+  @Override @Test
+  public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingOneByOne() throws Throwable {
+    publisherVerification.optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingOneByOne();
+  }
+
+  @Override @Test
+  public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfront() throws Throwable {
+    publisherVerification.optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfront();
+  }
+
+  @Override @Test
+  public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfrontAndCompleteAsExpected() throws Throwable {
+    publisherVerification.optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfrontAndCompleteAsExpected();
+  }
+
+  @Override @Test
+  public void required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe() throws Throwable {
+    publisherVerification.required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe();
+  }
+
+  @Override @Test
+  public void required_spec303_mustNotAllowUnboundedRecursion() throws Throwable {
+    publisherVerification.required_spec303_mustNotAllowUnboundedRecursion();
+  }
+
+  @Override @Test
+  public void untested_spec304_requestShouldNotPerformHeavyComputations() throws Exception {
+    publisherVerification.untested_spec304_requestShouldNotPerformHeavyComputations();
+  }
+
+  @Override @Test
+  public void untested_spec305_cancelMustNotSynchronouslyPerformHeavyCompuatation() throws Exception {
+    publisherVerification.untested_spec305_cancelMustNotSynchronouslyPerformHeavyCompuatation();
+  }
+
+  @Override @Test
+  public void required_spec306_afterSubscriptionIsCancelledRequestMustBeNops() throws Throwable {
+    publisherVerification.required_spec306_afterSubscriptionIsCancelledRequestMustBeNops();
+  }
+
+  @Override @Test
+  public void required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops() throws Throwable {
+    publisherVerification.required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops();
+  }
+
+  @Override @Test
+  public void required_spec309_requestZeroMustSignalIllegalArgumentException() throws Throwable {
+    publisherVerification.required_spec309_requestZeroMustSignalIllegalArgumentException();
+  }
+
+  @Override @Test
+  public void required_spec309_requestNegativeNumberMustSignalIllegalArgumentException() throws Throwable {
+    publisherVerification.required_spec309_requestNegativeNumberMustSignalIllegalArgumentException();
+  }
+
+  @Override @Test
+  public void required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling() throws Throwable {
+    publisherVerification.required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling();
+  }
+
+  @Override @Test
+  public void required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber() throws Throwable {
+    publisherVerification.required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber();
+  }
+
+  @Override @Test
+  public void required_spec317_mustSupportAPendingElementCountUpToLongMaxValue() throws Throwable {
+    publisherVerification.required_spec317_mustSupportAPendingElementCountUpToLongMaxValue();
+  }
+
+  @Override @Test
+  public void required_spec317_mustSupportACumulativePendingElementCountUpToLongMaxValue() throws Throwable {
+    publisherVerification.required_spec317_mustSupportACumulativePendingElementCountUpToLongMaxValue();
+  }
+
+  @Override @Test
+  public void required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue() throws Throwable {
+    publisherVerification.required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue();
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.4
+  // for multiple subscribers
+  @Test
+  public void required_spec104_mustCallOnErrorOnAllItsSubscribersIfItEncountersANonRecoverableError() throws Throwable {
+    optionalMultipleSubscribersTest(2, new Function<Long,TestSetup>() {
+      @Override
+      public TestSetup apply(Long aLong) throws Throwable {
+        return new TestSetup(env, processorBufferSize) {{
+          final ManualSubscriberWithErrorCollection<T> sub1 = new ManualSubscriberWithErrorCollection<T>(env);
+          env.subscribe(processor, sub1);
+
+          final ManualSubscriberWithErrorCollection<T> sub2 = new ManualSubscriberWithErrorCollection<T>(env);
+          env.subscribe(processor, sub2);
+
+          sub1.request(1);
+          expectRequest();
+          final T x = sendNextTFromUpstream();
+          expectNextElement(sub1, x);
+          sub1.request(1);
+
+          // sub1 has received one element, and has one demand pending
+          // sub2 has not yet requested anything
+
+          final Exception ex = new RuntimeException("Test exception");
+          sendError(ex);
+          sub1.expectError(ex);
+          sub2.expectError(ex);
+
+          env.verifyNoAsyncErrorsNoDelay();
+        }};
+      }
+    });
+  }
+
+  ////////////////////// SUBSCRIBER RULES VERIFICATION ///////////////////////////
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#4.1
+
+  // A Processor
+  //   must obey all Subscriber rules on its consuming side
+  public Subscriber<T> createSubscriber(final SubscriberWhiteboxVerification.WhiteboxSubscriberProbe<T> probe) {
+    final Processor<T, T> processor = createIdentityProcessor(processorBufferSize);
+    processor.subscribe(
+        new Subscriber<T>() {
+          private final Promise<Subscription> subs = new Promise<Subscription>(env);
+
+          @Override
+          public void onSubscribe(final Subscription subscription) {
+            env.debug(String.format("whiteboxSubscriber::onSubscribe(%s)", subscription));
+            if (subs.isCompleted()) subscription.cancel(); // the Probe must also pass subscriber verification
+
+            probe.registerOnSubscribe(new SubscriberWhiteboxVerification.SubscriberPuppet() {
+
+              @Override
+              public void triggerRequest(long elements) {
+                subscription.request(elements);
+              }
+
+              @Override
+              public void signalCancel() {
+                subscription.cancel();
+              }
+            });
+          }
+
+          @Override
+          public void onNext(T element) {
+            env.debug(String.format("whiteboxSubscriber::onNext(%s)", element));
+            probe.registerOnNext(element);
+          }
+
+          @Override
+          public void onComplete() {
+            env.debug("whiteboxSubscriber::onComplete()");
+            probe.registerOnComplete();
+          }
+
+          @Override
+          public void onError(Throwable cause) {
+            env.debug(String.format("whiteboxSubscriber::onError(%s)", cause));
+            probe.registerOnError(cause);
+          }
+        });
+
+    return processor; // we run the SubscriberVerification against this
+  }
+
+  ////////////////////// OTHER RULE VERIFICATION ///////////////////////////
+
+  // A Processor
+  //   must immediately pass on `onError` events received from its upstream to its downstream
+  @Test
+  public void mustImmediatelyPassOnOnErrorEventsReceivedFromItsUpstreamToItsDownstream() throws Exception {
+    new TestSetup(env, processorBufferSize) {{
+      final ManualSubscriberWithErrorCollection<T> sub = new ManualSubscriberWithErrorCollection<T>(env);
+      env.subscribe(processor, sub);
+
+      final Exception ex = new RuntimeException("Test exception");
+      sendError(ex);
+      sub.expectError(ex); // "immediately", i.e. without a preceding request
+
+      env.verifyNoAsyncErrorsNoDelay();
+    }};
+  }
+
+  /////////////////////// DELEGATED TESTS, A PROCESSOR "IS A" SUBSCRIBER //////////////////////
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#4.1
+
+  @Test
+  public void required_exerciseWhiteboxHappyPath() throws Throwable {
+    subscriberVerification.required_exerciseWhiteboxHappyPath();
+  }
+
+  @Override @Test
+  public void required_spec201_mustSignalDemandViaSubscriptionRequest() throws Throwable {
+    subscriberVerification.required_spec201_mustSignalDemandViaSubscriptionRequest();
+  }
+
+  @Override @Test
+  public void untested_spec202_shouldAsynchronouslyDispatch() throws Exception {
+    subscriberVerification.untested_spec202_shouldAsynchronouslyDispatch();
+  }
+
+  @Override @Test
+  public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() throws Throwable {
+    subscriberVerification.required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete();
+  }
+
+  @Override @Test
+  public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() throws Throwable {
+    subscriberVerification.required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError();
+  }
+
+  @Override @Test
+  public void untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() throws Exception {
+    subscriberVerification.untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError();
+  }
+
+  @Override @Test
+  public void required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() throws Throwable {
+    subscriberVerification.required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal();
+  }
+
+  @Override @Test
+  public void untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid() throws Exception {
+    subscriberVerification.untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid();
+  }
+
+  @Override @Test
+  public void untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() throws Exception {
+    subscriberVerification.untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization();
+  }
+
+  @Override @Test
+  public void required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() throws Throwable {
+    subscriberVerification.required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel();
+  }
+
+  @Override @Test
+  public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() throws Throwable {
+    subscriberVerification.required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall();
+  }
+
+  @Override @Test
+  public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() throws Throwable {
+    subscriberVerification.required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall();
+  }
+
+  @Override @Test
+  public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() throws Throwable {
+    subscriberVerification.required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall();
+  }
+
+  @Override @Test
+  public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall() throws Throwable {
+    subscriberVerification.required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall();
+  }
+
+  @Override @Test
+  public void untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() throws Exception {
+    subscriberVerification.untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents();
+  }
+
+  @Override @Test
+  public void untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation() throws Throwable {
+    subscriberVerification.untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation();
+  }
+
+  @Override @Test
+  public void untested_spec213_failingOnSignalInvocation() throws Exception {
+    subscriberVerification.untested_spec213_failingOnSignalInvocation();
+  }
+
+  @Override @Test
+  public void required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable {
+    subscriberVerification.required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull();
+  }
+  @Override @Test
+  public void required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable {
+    subscriberVerification.required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull();
+  }
+  @Override @Test
+  public void required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable {
+    subscriberVerification.required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull();
+  }
+
+  @Override @Test
+  public void untested_spec301_mustNotBeCalledOutsideSubscriberContext() throws Exception {
+    subscriberVerification.untested_spec301_mustNotBeCalledOutsideSubscriberContext();
+  }
+
+  @Override @Test
+  public void required_spec308_requestMustRegisterGivenNumberElementsToBeProduced() throws Throwable {
+    subscriberVerification.required_spec308_requestMustRegisterGivenNumberElementsToBeProduced();
+  }
+
+  @Override @Test
+  public void untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber() throws Exception {
+    subscriberVerification.untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber();
+  }
+
+  @Override @Test
+  public void untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError() throws Exception {
+    subscriberVerification.untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError();
+  }
+
+  @Override @Test
+  public void untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() throws Exception {
+    subscriberVerification.untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists();
+  }
+
+  @Override @Test
+  public void untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError() throws Exception {
+    subscriberVerification.untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError();
+  }
+
+  @Override @Test
+  public void untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() throws Exception {
+    subscriberVerification.untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber();
+  }
+
+  /////////////////////// ADDITIONAL "COROLLARY" TESTS //////////////////////
+
+  // A Processor
+  //   must trigger `requestFromUpstream` for elements that have been requested 'long ago'
+  @Test
+  public void required_mustRequestFromUpstreamForElementsThatHaveBeenRequestedLongAgo() throws Throwable {
+    optionalMultipleSubscribersTest(2, new Function<Long,TestSetup>() {
+      @Override
+      public TestSetup apply(Long subscribers) throws Throwable {
+        return new TestSetup(env, processorBufferSize) {{
+          ManualSubscriber<T> sub1 = newSubscriber();
+          sub1.request(20);
+
+          long totalRequests = expectRequest();
+          final T x = sendNextTFromUpstream();
+          expectNextElement(sub1, x);
+
+          if (totalRequests == 1) {
+            totalRequests += expectRequest();
+          }
+          final T y = sendNextTFromUpstream();
+          expectNextElement(sub1, y);
+
+          if (totalRequests == 2) {
+            totalRequests += expectRequest();
+          }
+
+          final ManualSubscriber<T> sub2 = newSubscriber();
+
+          // sub1 now has 18 pending
+          // sub2 has 0 pending
+
+          final T z = sendNextTFromUpstream();
+          expectNextElement(sub1, z);
+          sub2.expectNone(); // since sub2 hasn't requested anything yet
+
+          sub2.request(1);
+          expectNextElement(sub2, z);
+
+          if (totalRequests == 3) {
+            expectRequest();
+          }
+
+          // to avoid error messages during test harness shutdown
+          sendCompletion();
+          sub1.expectCompletion(env.defaultTimeoutMillis());
+          sub2.expectCompletion(env.defaultTimeoutMillis());
+
+          env.verifyNoAsyncErrorsNoDelay();
+        }};
+      }
+    });
+  }
+
+  /////////////////////// TEST INFRASTRUCTURE //////////////////////
+
+  public void notVerified() {
+    publisherVerification.notVerified();
+  }
+
+  public void notVerified(String message) {
+    publisherVerification.notVerified(message);
+  }
+
+  /**
+   * Test for feature that REQUIRES multiple subscribers to be supported by Publisher.
+   */
+  public void optionalMultipleSubscribersTest(long requiredSubscribersSupport, Function<Long, TestSetup> body) throws Throwable {
+    if (requiredSubscribersSupport > maxSupportedSubscribers())
+      notVerified(String.format("The Publisher under test only supports %d subscribers, while this test requires at least %d to run.",
+                                maxSupportedSubscribers(), requiredSubscribersSupport));
+    else body.apply(requiredSubscribersSupport);
+  }
+
+  public abstract class TestSetup extends ManualPublisher<T> {
+    final private ManualSubscriber<T> tees; // gives us access to an infinite stream of T values
+    private Set<T> seenTees = new HashSet<T>();
+
+    final Processor<T, T> processor;
+
+    public TestSetup(TestEnvironment env, int testBufferSize) throws InterruptedException {
+      super(env);
+      tees = env.newManualSubscriber(createHelperPublisher(Long.MAX_VALUE));
+      processor = createIdentityProcessor(testBufferSize);
+      subscribe(processor);
+    }
+
+    public ManualSubscriber<T> newSubscriber() throws InterruptedException {
+      return env.newManualSubscriber(processor);
+    }
+
+    public T nextT() throws InterruptedException {
+      final T t = tees.requestNextElement();
+      if (seenTees.contains(t)) {
+        env.flop(String.format("Helper publisher illegally produced the same element %s twice", t));
+      }
+      seenTees.add(t);
+      return t;
+    }
+
+    public void expectNextElement(ManualSubscriber<T> sub, T expected) throws InterruptedException {
+      final T elem = sub.nextElement(String.format("timeout while awaiting %s", expected));
+      if (!elem.equals(expected)) {
+        env.flop(String.format("Received `onNext(%s)` on downstream but expected `onNext(%s)`", elem, expected));
+      }
+    }
+
+    public T sendNextTFromUpstream() throws InterruptedException {
+      final T x = nextT();
+      sendNext(x);
+      return x;
+    }
+  }
+
+  public class ManualSubscriberWithErrorCollection<A> extends ManualSubscriberWithSubscriptionSupport<A> {
+    Promise<Throwable> error;
+
+    public ManualSubscriberWithErrorCollection(TestEnvironment env) {
+      super(env);
+      error = new Promise<Throwable>(env);
+    }
+
+    @Override
+    public void onError(Throwable cause) {
+      error.complete(cause);
+    }
+
+    public void expectError(Throwable cause) throws InterruptedException {
+      expectError(cause, env.defaultTimeoutMillis());
+    }
+
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    public void expectError(Throwable cause, long timeoutMillis) throws InterruptedException {
+      error.expectCompletion(timeoutMillis, "Did not receive expected error on downstream");
+      if (!cause.equals(error.value())) {
+        env.flop(String.format("Expected error %s but got %s", cause, error.value()));
+      }
+    }
+  }
+}
diff --git a/tck/src/main/java/org/reactivestreams/tck/PublisherVerification.java b/tck/src/main/java/org/reactivestreams/tck/PublisherVerification.java
new file mode 100644
index 0000000..5098341
--- /dev/null
+++ b/tck/src/main/java/org/reactivestreams/tck/PublisherVerification.java
@@ -0,0 +1,1183 @@
+package org.reactivestreams.tck;
+
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import org.reactivestreams.tck.TestEnvironment.BlackholeSubscriberWithSubscriptionSupport;
+import org.reactivestreams.tck.TestEnvironment.Latch;
+import org.reactivestreams.tck.TestEnvironment.ManualSubscriber;
+import org.reactivestreams.tck.TestEnvironment.ManualSubscriberWithSubscriptionSupport;
+import org.reactivestreams.tck.support.Function;
+import org.reactivestreams.tck.support.Optional;
+import org.reactivestreams.tck.support.PublisherVerificationRules;
+import org.testng.SkipException;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.lang.Override;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+/**
+ * Provides tests for verifying {@code Publisher} specification rules.
+ *
+ * @see org.reactivestreams.Publisher
+ */
+public abstract class PublisherVerification<T> implements PublisherVerificationRules {
+
+  private static final String PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS_ENV = "PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS";
+  private static final long DEFAULT_PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS = 300L;
+
+  private final TestEnvironment env;
+
+  /**
+   * The amount of time after which a cancelled Subscriber reference should be dropped.
+   * See Rule 3.13 for details.
+   */
+  private final long publisherReferenceGCTimeoutMillis;
+
+  /**
+   * Constructs a new verification class using the given env and configuration.
+   *
+   * @param publisherReferenceGCTimeoutMillis used to determine after how much time a reference to a Subscriber should be already dropped by the Publisher.
+   */
+  public PublisherVerification(TestEnvironment env, long publisherReferenceGCTimeoutMillis) {
+    this.env = env;
+    this.publisherReferenceGCTimeoutMillis = publisherReferenceGCTimeoutMillis;
+  }
+
+  /**
+   * Constructs a new verification class using the given env and configuration.
+   *
+   * The value for {@code publisherReferenceGCTimeoutMillis} will be obtained by using {@link PublisherVerification#envPublisherReferenceGCTimeoutMillis()}.
+   */
+  public PublisherVerification(TestEnvironment env) {
+    this.env = env;
+    this.publisherReferenceGCTimeoutMillis = envPublisherReferenceGCTimeoutMillis();
+  }
+
+  /**
+   * Tries to parse the env variable {@code PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS} as long and returns the value if present,
+   * OR its default value ({@link PublisherVerification#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.
+   *
+   * @throws java.lang.IllegalArgumentException when unable to parse the env variable
+   */
+  public static long envPublisherReferenceGCTimeoutMillis() {
+    final String envMillis = System.getenv(PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS_ENV);
+    if (envMillis == null) return DEFAULT_PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS;
+    else try {
+      return Long.parseLong(envMillis);
+    } catch(NumberFormatException ex) {
+      throw new IllegalArgumentException(String.format("Unable to parse %s env value [%s] as long!", PUBLISHER_REFERENCE_GC_TIMEOUT_MILLIS_ENV, envMillis), ex);
+    }
+  }
+
+  /**
+   * This is the main method you must implement in your test incarnation.
+   * It must create a Publisher for a stream with exactly the given number of elements.
+   * If `elements` is `Long.MAX_VALUE` the produced stream must be infinite.
+   */
+  public abstract Publisher<T> createPublisher(long elements);
+
+  /**
+   * By implementing this method, additional TCK tests concerning a "failed" publishers will be run.
+   *
+   * The expected behaviour of the {@link Publisher} returned by this method is hand out a subscription,
+   * followed by signalling {@code onError} on it, as specified by Rule 1.9.
+   *
+   * If you ignore these additional tests, return {@code null} from this method.
+   */
+  public abstract Publisher<T> createFailedPublisher();
+
+
+  /**
+   * Override and return lower value if your Publisher is only able to produce a known number of elements.
+   * For example, if it is designed to return at-most-one element, return {@code 1} from this method.
+   *
+   * Defaults to {@code 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 {@code onComplete} override this method and return {@code Long.MAX_VALUE},
+   * which will result in *skipping all tests which require an onComplete to be triggered* (!).
+   */
+  public long maxElementsFromPublisher() {
+    return Long.MAX_VALUE - 1;
+  }
+
+  /**
+   * Override and return {@code true} in order to skip executing tests marked as {@code Stochastic}.
+   * Such tests MAY sometimes fail even though the impl
+   */
+  public boolean skipStochasticTests() {
+    return false;
+  }
+
+  /**
+   * In order to verify rule 3.3 of the reactive streams spec, this number will be used to check if a
+   * {@code Subscription} actually solves the "unbounded recursion" problem by not allowing the number of
+   * recursive calls to exceed the number returned by this method.
+   *
+   * @see <a href="https://github.com/reactive-streams/reactive-streams-jvm#3.3">reactive streams spec, rule 3.3</a>
+   * @see PublisherVerification#required_spec303_mustNotAllowUnboundedRecursion()
+   */
+  public long boundedDepthOfOnNextAndRequestRecursion() {
+    return 1;
+  }
+
+  ////////////////////// TEST ENV CLEANUP /////////////////////////////////////
+
+  @BeforeMethod
+  public void setUp() throws Exception {
+    env.clearAsyncErrors();
+  }
+
+  ////////////////////// TEST SETUP VERIFICATION //////////////////////////////
+
+  @Override @Test
+  public void required_createPublisher1MustProduceAStreamOfExactly1Element() throws Throwable {
+    activePublisherTest(1, true, new PublisherTestRun<T>() {
+      @Override
+      public void run(Publisher<T> pub) throws InterruptedException {
+        ManualSubscriber<T> sub = env.newManualSubscriber(pub);
+        assertTrue(requestNextElementOrEndOfStream(pub, sub).isDefined(), String.format("Publisher %s produced no elements", pub));
+        sub.requestEndOfStream();
+      }
+
+      Optional<T> requestNextElementOrEndOfStream(Publisher<T> pub, ManualSubscriber<T> sub) throws InterruptedException {
+        return sub.requestNextElementOrEndOfStream(String.format("Timeout while waiting for next element from Publisher %s", pub));
+      }
+
+    });
+  }
+
+  @Override @Test
+  public void required_createPublisher3MustProduceAStreamOfExactly3Elements() throws Throwable {
+    activePublisherTest(3, true, new PublisherTestRun<T>() {
+      @Override
+      public void run(Publisher<T> pub) throws InterruptedException {
+        ManualSubscriber<T> sub = env.newManualSubscriber(pub);
+        assertTrue(requestNextElementOrEndOfStream(pub, sub).isDefined(), String.format("Publisher %s produced no elements", pub));
+        assertTrue(requestNextElementOrEndOfStream(pub, sub).isDefined(), String.format("Publisher %s produced only 1 element", pub));
+        assertTrue(requestNextElementOrEndOfStream(pub, sub).isDefined(), String.format("Publisher %s produced only 2 elements", pub));
+        sub.requestEndOfStream();
+      }
+
+      Optional<T> requestNextElementOrEndOfStream(Publisher<T> pub, ManualSubscriber<T> sub) throws InterruptedException {
+        return sub.requestNextElementOrEndOfStream(String.format("Timeout while waiting for next element from Publisher %s", pub));
+      }
+
+    });
+  }
+
+  @Override @Test
+  public void required_validate_maxElementsFromPublisher() throws Exception {
+    assertTrue(maxElementsFromPublisher() >= 0, "maxElementsFromPublisher MUST return a number >= 0");
+  }
+
+  @Override @Test
+  public void required_validate_boundedDepthOfOnNextAndRequestRecursion() throws Exception {
+    assertTrue(boundedDepthOfOnNextAndRequestRecursion() >= 1, "boundedDepthOfOnNextAndRequestRecursion must return a number >= 1");
+  }
+
+
+  ////////////////////// SPEC RULE VERIFICATION ///////////////////////////////
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.1
+  @Override @Test
+  public void required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements() throws Throwable {
+    activePublisherTest(5, false, new PublisherTestRun<T>() {
+      @Override
+      public void run(Publisher<T> pub) throws InterruptedException {
+
+        ManualSubscriber<T> sub = env.newManualSubscriber(pub);
+
+        sub.expectNone(String.format("Publisher %s produced value before the first `request`: ", pub));
+        sub.request(1);
+        sub.nextElement(String.format("Publisher %s produced no element after first `request`", pub));
+        sub.expectNone(String.format("Publisher %s produced unrequested: ", pub));
+
+        sub.request(1);
+        sub.request(2);
+        sub.nextElements(3, env.defaultTimeoutMillis(), String.format("Publisher %s produced less than 3 elements after two respective `request` calls", pub));
+
+        sub.expectNone(String.format("Publisher %sproduced unrequested ", pub));
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.2
+  @Override @Test
+  public void required_spec102_maySignalLessThanRequestedAndTerminateSubscription() throws Throwable {
+    final int elements = 3;
+    final int requested = 10;
+
+    activePublisherTest(elements, true, new PublisherTestRun<T>() {
+      @Override
+      public void run(Publisher<T> pub) throws Throwable {
+        final ManualSubscriber<T> sub = env.newManualSubscriber(pub);
+        sub.request(requested);
+        sub.nextElements(elements);
+        sub.expectCompletion();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.3
+  @Override @Test
+  public void stochastic_spec103_mustSignalOnMethodsSequentially() throws Throwable {
+    final int iterations = 100;
+    final int elements = 10;
+
+    stochasticTest(iterations, new Function<Integer, Void>() {
+      @Override
+      public Void apply(final Integer runNumber) throws Throwable {
+        activePublisherTest(elements, true, new PublisherTestRun<T>() {
+          @Override
+          public void run(Publisher<T> pub) throws Throwable {
+            final Latch completionLatch = new Latch(env);
+
+            pub.subscribe(new Subscriber<T>() {
+              private Subscription subs;
+              private long gotElements = 0;
+
+              private ConcurrentAccessBarrier concurrentAccessBarrier = new ConcurrentAccessBarrier();
+
+              /**
+               * Concept wise very similar to a {@link org.reactivestreams.tck.TestEnvironment.Latch}, serves to protect
+               * a critical section from concurrent access, with the added benefit of Thread tracking and same-thread-access awareness.
+               *
+               * Since a <i>Synchronous</i> Publisher may choose to synchronously (using the same {@link Thread}) call
+               * {@code onNext} directly from either {@code subscribe} or {@code request} a plain Latch is not enough
+               * to verify concurrent access safety - one needs to track if the caller is not still using the calling thread
+               * to enter subsequent critical sections ("nesting" them effectively).
+               */
+              final class ConcurrentAccessBarrier {
+                private AtomicReference<Thread> currentlySignallingThread = new AtomicReference<Thread>(null);
+                private volatile String previousSignal = null;
+
+                public void enterSignal(String signalName) {
+                  if((!currentlySignallingThread.compareAndSet(null, Thread.currentThread())) && !isSynchronousSignal()) {
+                    env.flop(String.format(
+                      "Illegal concurrent access detected (entering critical section)! " +
+                        "%s emited %s signal, before %s finished its %s signal.",
+                        Thread.currentThread(), signalName, currentlySignallingThread.get(), previousSignal));
+                  }
+                  this.previousSignal = signalName;
+                }
+
+                public void leaveSignal(String signalName) {
+                  currentlySignallingThread.set(null);
+                  this.previousSignal = signalName;
+                }
+
+                private boolean isSynchronousSignal() {
+                  return (previousSignal != null) && Thread.currentThread().equals(currentlySignallingThread.get());
+                }
+
+              }
+
+              @Override
+              public void onSubscribe(Subscription s) {
+                final String signal = "onSubscribe()";
+                concurrentAccessBarrier.enterSignal(signal);
+
+                subs = s;
+                subs.request(1);
+
+                concurrentAccessBarrier.leaveSignal(signal);
+              }
+
+              @Override
+              public void onNext(T ignore) {
+                final String signal = String.format("onNext(%s)", ignore);
+                concurrentAccessBarrier.enterSignal(signal);
+
+                gotElements += 1;
+                if (gotElements <= elements) // requesting one more than we know are in the stream (some Publishers need this)
+                  subs.request(1);
+
+                concurrentAccessBarrier.leaveSignal(signal);
+              }
+
+              @Override
+              public void onError(Throwable t) {
+                final String signal = String.format("onError(%s)", t.getMessage());
+                concurrentAccessBarrier.enterSignal(signal);
+
+                // ignore value
+
+                concurrentAccessBarrier.leaveSignal(signal);
+              }
+
+              @Override
+              public void onComplete() {
+                final String signal = "onComplete()";
+                concurrentAccessBarrier.enterSignal(signal);
+
+                // entering for completeness
+
+                concurrentAccessBarrier.leaveSignal(signal);
+                completionLatch.close();
+              }
+            });
+
+            completionLatch.expectClose(elements * env.defaultTimeoutMillis(), "Expected 10 elements to be drained");
+          }
+        });
+        return null;
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.4
+  @Override @Test
+  public void optional_spec104_mustSignalOnErrorWhenFails() throws Throwable {
+    try {
+      whenHasErrorPublisherTest(new PublisherTestRun<T>() {
+        @Override
+        public void run(final Publisher<T> pub) throws InterruptedException {
+          final Latch onErrorlatch = new Latch(env);
+          final Latch onSubscribeLatch = new Latch(env);
+          pub.subscribe(new TestEnvironment.TestSubscriber<T>(env) {
+            @Override
+            public void onSubscribe(Subscription subs) {
+              onSubscribeLatch.assertOpen("Only one onSubscribe call expected");
+              onSubscribeLatch.close();
+            }
+            @Override
+            public void onError(Throwable cause) {
+              onSubscribeLatch.assertClosed("onSubscribe should be called prior to onError always");
+              onErrorlatch.assertOpen(String.format("Error-state Publisher %s called `onError` twice on new Subscriber", pub));
+              onErrorlatch.close();
+            }
+          });
+
+          onSubscribeLatch.expectClose("Should have received onSubscribe");
+          onErrorlatch.expectClose(String.format("Error-state Publisher %s did not call `onError` on new Subscriber", pub));
+
+          env.verifyNoAsyncErrors(env.defaultTimeoutMillis());
+          }
+      });
+    } catch (SkipException se) {
+      throw se;
+    } catch (Throwable ex) {
+      // we also want to catch AssertionErrors and anything the publisher may have thrown inside subscribe
+      // which was wrong of him - he should have signalled on error using onError
+      throw new RuntimeException(String.format("Publisher threw exception (%s) instead of signalling error via onError!", ex.getMessage()), ex);
+    }
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.5
+  @Override @Test
+  public void required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates() throws Throwable {
+    activePublisherTest(3, true, new PublisherTestRun<T>() {
+      @Override
+      public void run(Publisher<T> pub) throws Throwable {
+        ManualSubscriber<T> sub = env.newManualSubscriber(pub);
+        sub.requestNextElement();
+        sub.requestNextElement();
+        sub.requestNextElement();
+        sub.requestEndOfStream();
+        sub.expectNone();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.5
+  @Override @Test
+  public void optional_spec105_emptyStreamMustTerminateBySignallingOnComplete() throws Throwable {
+    optionalActivePublisherTest(0, true, new PublisherTestRun<T>() {
+      @Override
+      public void run(Publisher<T> pub) throws Throwable {
+        ManualSubscriber<T> sub = env.newManualSubscriber(pub);
+        sub.request(1);
+        sub.expectCompletion();
+        sub.expectNone();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.6
+  @Override @Test
+  public void untested_spec106_mustConsiderSubscriptionCancelledAfterOnErrorOrOnCompleteHasBeenCalled() throws Throwable {
+    notVerified(); // not really testable without more control over the Publisher
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.7
+  @Override @Test
+  public void required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled() throws Throwable {
+    activePublisherTest(1, true, new PublisherTestRun<T>() {
+      @Override
+      public void run(Publisher<T> pub) throws Throwable {
+        ManualSubscriber<T> sub = env.newManualSubscriber(pub);
+        sub.request(10);
+        sub.nextElement();
+        sub.expectCompletion();
+
+        sub.request(10);
+        sub.expectNone();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.7
+  @Override @Test
+  public void untested_spec107_mustNotEmitFurtherSignalsOnceOnErrorHasBeenSignalled() throws Throwable {
+    notVerified(); // can we meaningfully test this, without more control over the publisher?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.8
+  @Override @Test
+  public void untested_spec108_possiblyCanceledSubscriptionShouldNotReceiveOnErrorOrOnCompleteSignals() throws Throwable {
+    notVerified(); // can we meaningfully test this?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.9
+  @Override @Test
+  public void untested_spec109_subscribeShouldNotThrowNonFatalThrowable() throws Throwable {
+    notVerified(); // can we meaningfully test this?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.9
+  @Override @Test
+  public void required_spec109_subscribeThrowNPEOnNullSubscriber() throws Throwable {
+    activePublisherTest(0, false, new PublisherTestRun<T>() {
+      @Override
+      public void run(Publisher<T> pub) throws Throwable {
+        try {
+            pub.subscribe(null);
+            env.flop("Publisher did not throw a NullPointerException when given a null Subscribe in subscribe");
+        } catch (NullPointerException ignored) {
+          // valid behaviour
+        }
+        env.verifyNoAsyncErrorsNoDelay();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.9
+  @Override @Test
+  public void required_spec109_mustIssueOnSubscribeForNonNullSubscriber() throws Throwable {
+    activePublisherTest(0, false, new PublisherTestRun<T>() {
+      @Override
+      public void run(Publisher<T> pub) throws Throwable {
+        final Latch onSubscribeLatch = new Latch(env);
+        pub.subscribe(new Subscriber<T>() {
+          @Override
+          public void onError(Throwable cause) {
+            onSubscribeLatch.assertClosed("onSubscribe should be called prior to onError always");
+          }
+
+          @Override
+          public void onSubscribe(Subscription subs) {
+            onSubscribeLatch.assertOpen("Only one onSubscribe call expected");
+            onSubscribeLatch.close();
+          }
+
+          @Override
+          public void onNext(T elem) {
+            onSubscribeLatch.assertClosed("onSubscribe should be called prior to onNext always");
+          }
+
+          @Override
+          public void onComplete() {
+            onSubscribeLatch.assertClosed("onSubscribe should be called prior to onComplete always");
+          }
+        });
+        onSubscribeLatch.expectClose("Should have received onSubscribe");
+        env.verifyNoAsyncErrorsNoDelay();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.9
+  @Override @Test
+  public void required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe() throws Throwable {
+    whenHasErrorPublisherTest(new PublisherTestRun<T>() {
+      @Override
+      public void run(Publisher<T> pub) throws Throwable {
+        final Latch onErrorLatch = new Latch(env);
+        final Latch onSubscribeLatch = new Latch(env);
+        ManualSubscriberWithSubscriptionSupport<T> sub = new ManualSubscriberWithSubscriptionSupport<T>(env) {
+          @Override
+          public void onError(Throwable cause) {
+            onSubscribeLatch.assertClosed("onSubscribe should be called prior to onError always");
+            onErrorLatch.assertOpen("Only one onError call expected");
+            onErrorLatch.close();
+          }
+
+          @Override
+          public void onSubscribe(Subscription subs) {
+            onSubscribeLatch.assertOpen("Only one onSubscribe call expected");
+            onSubscribeLatch.close();
+          }
+        };
+        pub.subscribe(sub);
+        onSubscribeLatch.expectClose("Should have received onSubscribe");
+        onErrorLatch.expectClose("Should have received onError");
+
+        env.verifyNoAsyncErrorsNoDelay();
+      }
+    });
+  }
+
+    // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.10
+  @Override @Test
+  public void untested_spec110_rejectASubscriptionRequestIfTheSameSubscriberSubscribesTwice() throws Throwable {
+    notVerified(); // can we meaningfully test this?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.11
+  @Override @Test
+  public void optional_spec111_maySupportMultiSubscribe() throws Throwable {
+    optionalActivePublisherTest(1, false, new PublisherTestRun<T>() {
+      @Override
+      public void run(Publisher<T> pub) throws Throwable {
+        ManualSubscriber<T> sub1 = env.newManualSubscriber(pub);
+        ManualSubscriber<T> sub2 = env.newManualSubscriber(pub);
+
+        env.verifyNoAsyncErrors();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.11
+  @Override @Test
+  public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingOneByOne() throws Throwable {
+    optionalActivePublisherTest(5, true, new PublisherTestRun<T>() { // This test is skipped if the publisher is unbounded (never sends onComplete)
+      @Override
+      public void run(Publisher<T> pub) throws InterruptedException {
+        ManualSubscriber<T> sub1 = env.newManualSubscriber(pub);
+        ManualSubscriber<T> sub2 = env.newManualSubscriber(pub);
+        ManualSubscriber<T> sub3 = env.newManualSubscriber(pub);
+
+        sub1.request(1);
+        T x1 = sub1.nextElement(String.format("Publisher %s did not produce the requested 1 element on 1st subscriber", pub));
+        sub2.request(2);
+        List<T> y1 = sub2.nextElements(2, String.format("Publisher %s did not produce the requested 2 elements on 2nd subscriber", pub));
+        sub1.request(1);
+        T x2 = sub1.nextElement(String.format("Publisher %s did not produce the requested 1 element on 1st subscriber", pub));
+        sub3.request(3);
+        List<T> z1 = sub3.nextElements(3, String.format("Publisher %s did not produce the requested 3 elements on 3rd subscriber", pub));
+        sub3.request(1);
+        T z2 = sub3.nextElement(String.format("Publisher %s did not produce the requested 1 element on 3rd subscriber", pub));
+        sub3.request(1);
+        T z3 = sub3.nextElement(String.format("Publisher %s did not produce the requested 1 element on 3rd subscriber", pub));
+        sub3.requestEndOfStream(String.format("Publisher %s did not complete the stream as expected on 3rd subscriber", pub));
+        sub2.request(3);
+        List<T> y2 = sub2.nextElements(3, String.format("Publisher %s did not produce the requested 3 elements on 2nd subscriber", pub));
+        sub2.requestEndOfStream(String.format("Publisher %s did not complete the stream as expected on 2nd subscriber", pub));
+        sub1.request(2);
+        List<T> x3 = sub1.nextElements(2, String.format("Publisher %s did not produce the requested 2 elements on 1st subscriber", pub));
+        sub1.request(1);
+        T x4 = sub1.nextElement(String.format("Publisher %s did not produce the requested 1 element on 1st subscriber", pub));
+        sub1.requestEndOfStream(String.format("Publisher %s did not complete the stream as expected on 1st subscriber", pub));
+
+        @SuppressWarnings("unchecked")
+        List<T> r = new ArrayList<T>(Arrays.asList(x1, x2));
+        r.addAll(x3);
+        r.addAll(Collections.singleton(x4));
+
+        List<T> check1 = new ArrayList<T>(y1);
+        check1.addAll(y2);
+
+        //noinspection unchecked
+        List<T> check2 = new ArrayList<T>(z1);
+        check2.add(z2);
+        check2.add(z3);
+
+        assertEquals(r, check1, String.format("Publisher %s did not produce the same element sequence for subscribers 1 and 2", pub));
+        assertEquals(r, check2, String.format("Publisher %s did not produce the same element sequence for subscribers 1 and 3", pub));
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.11
+  @Override @Test
+  public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfront() throws Throwable {
+    optionalActivePublisherTest(3, false, new PublisherTestRun<T>() { // This test is skipped if the publisher cannot produce enough elements
+      @Override
+      public void run(Publisher<T> pub) throws Throwable {
+        ManualSubscriber<T> sub1 = env.newManualSubscriber(pub);
+        ManualSubscriber<T> sub2 = env.newManualSubscriber(pub);
+        ManualSubscriber<T> sub3 = env.newManualSubscriber(pub);
+
+        List<T> received1 = new ArrayList<T>();
+        List<T> received2 = new ArrayList<T>();
+        List<T> received3 = new ArrayList<T>();
+
+        // 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...
+        // edgy edge case?
+        sub1.request(4);
+        sub2.request(4);
+        sub3.request(4);
+
+        received1.addAll(sub1.nextElements(3));
+        received2.addAll(sub2.nextElements(3));
+        received3.addAll(sub3.nextElements(3));
+
+        // NOTE: can't check completion, the Publisher may not be able to signal it
+        //       a similar test *with* completion checking is implemented
+
+        assertEquals(received1, received2, String.format("Expected elements to be signaled in the same sequence to 1st and 2nd subscribers"));
+        assertEquals(received2, received3, String.format("Expected elements to be signaled in the same sequence to 2nd and 3rd subscribers"));
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.11
+  @Override @Test
+  public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfrontAndCompleteAsExpected() throws Throwable {
+    optionalActivePublisherTest(3, true, new PublisherTestRun<T>() { // This test is skipped if the publisher is unbounded (never sends onComplete)
+      @Override
+      public void run(Publisher<T> pub) throws Throwable {
+        ManualSubscriber<T> sub1 = env.newManualSubscriber(pub);
+        ManualSubscriber<T> sub2 = env.newManualSubscriber(pub);
+        ManualSubscriber<T> sub3 = env.newManualSubscriber(pub);
+
+        List<T> received1 = new ArrayList<T>();
+        List<T> received2 = new ArrayList<T>();
+        List<T> received3 = new ArrayList<T>();
+
+        // 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...
+        // edgy edge case?
+        sub1.request(4);
+        sub2.request(4);
+        sub3.request(4);
+
+        received1.addAll(sub1.nextElements(3));
+        received2.addAll(sub2.nextElements(3));
+        received3.addAll(sub3.nextElements(3));
+
+        sub1.expectCompletion();
+        sub2.expectCompletion();
+        sub3.expectCompletion();
+
+        assertEquals(received1, received2, String.format("Expected elements to be signaled in the same sequence to 1st and 2nd subscribers"));
+        assertEquals(received2, received3, String.format("Expected elements to be signaled in the same sequence to 2nd and 3rd subscribers"));
+      }
+    });
+  }
+
+  ///////////////////// SUBSCRIPTION TESTS //////////////////////////////////
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.2
+  @Override @Test
+  public void required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe() throws Throwable {
+    activePublisherTest(6, false, new PublisherTestRun<T>() {
+      @Override
+      public void run(Publisher<T> pub) throws Throwable {
+        ManualSubscriber<T> sub = new ManualSubscriber<T>(env) {
+          @Override
+          public void onSubscribe(Subscription subs) {
+            this.subscription.completeImmediatly(subs);
+
+            subs.request(1);
+            subs.request(1);
+            subs.request(1);
+          }
+
+          @Override
+          public void onNext(T element) {
+            Subscription subs = this.subscription.value();
+            subs.request(1);
+          }
+        };
+
+        env.subscribe(pub, sub);
+
+        long delay = env.defaultTimeoutMillis();
+        env.verifyNoAsyncErrors(delay);
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.3
+  @Override @Test
+  public void required_spec303_mustNotAllowUnboundedRecursion() throws Throwable {
+    final long oneMoreThanBoundedLimit = boundedDepthOfOnNextAndRequestRecursion() + 1;
+
+    activePublisherTest(oneMoreThanBoundedLimit, false, new PublisherTestRun<T>() {
+      @Override
+      public void run(Publisher<T> pub) throws Throwable {
+        final ThreadLocal<Long> stackDepthCounter = new ThreadLocal<Long>() {
+          @Override
+          protected Long initialValue() {
+            return 0L;
+          }
+        };
+
+        final Latch runCompleted = new Latch(env);
+
+        final ManualSubscriber<T> sub = new ManualSubscriberWithSubscriptionSupport<T>(env) {
+          // counts the number of signals received, used to break out from possibly infinite request/onNext loops
+          long signalsReceived = 0L;
+
+          @Override
+          public void onNext(T element) {
+            // NOT calling super.onNext as this test only cares about stack depths, not the actual values of elements
+            // which also simplifies this test as we do not have to drain the test buffer, which would otherwise be in danger of overflowing
+
+            signalsReceived += 1;
+            stackDepthCounter.set(stackDepthCounter.get() + 1);
+            env.debug(String.format("%s(recursion depth: %d)::onNext(%s)", this, stackDepthCounter.get(), element));
+
+            final long callsUntilNow = stackDepthCounter.get();
+            if (callsUntilNow > boundedDepthOfOnNextAndRequestRecursion()) {
+              env.flop(String.format("Got %d onNext calls within thread: %s, yet expected recursive bound was %d",
+                                     callsUntilNow, Thread.currentThread(), boundedDepthOfOnNextAndRequestRecursion()));
+
+              // stop the recursive call chain
+              runCompleted.close();
+              return;
+            } else if (signalsReceived >= oneMoreThanBoundedLimit) {
+              // since max number of signals reached, and recursion depth not exceeded, we judge this as a success and
+              // stop the recursive call chain
+              runCompleted.close();
+              return;
+            }
+
+            // request more right away, the Publisher must break the recursion
+            subscription.value().request(1);
+
+            stackDepthCounter.set(stackDepthCounter.get() - 1);
+          }
+
+          @Override
+          public void onComplete() {
+            super.onComplete();
+            runCompleted.close();
+          }
+
+          @Override
+          public void onError(Throwable cause) {
+            super.onError(cause);
+            runCompleted.close();
+          }
+        };
+
+        try {
+          env.subscribe(pub, sub);
+
+          sub.request(1); // kick-off the `request -> onNext -> request -> onNext -> ...`
+
+          final String msg = String.format("Unable to validate call stack depth safety, " +
+                                               "awaited at-most %s signals (`maxOnNextSignalsInRecursionTest()`) or completion",
+                                           oneMoreThanBoundedLimit);
+          runCompleted.expectClose(env.defaultTimeoutMillis(), msg);
+          env.verifyNoAsyncErrorsNoDelay();
+        } finally {
+          // since the request/onNext recursive calls may keep the publisher running "forever",
+          // we MUST cancel it manually before exiting this test case
+          sub.cancel();
+        }
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.4
+  @Override @Test
+  public void untested_spec304_requestShouldNotPerformHeavyComputations() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.5
+  @Override @Test
+  public void untested_spec305_cancelMustNotSynchronouslyPerformHeavyCompuatation() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.6
+  @Override @Test
+  public void required_spec306_afterSubscriptionIsCancelledRequestMustBeNops() throws Throwable {
+    activePublisherTest(3, false, new PublisherTestRun<T>() {
+      @Override
+      public void run(Publisher<T> pub) throws Throwable {
+
+        // override ManualSubscriberWithSubscriptionSupport#cancel because by default a ManualSubscriber will drop the
+        // subscription once it's cancelled (as expected).
+        // In this test however it must keep the cancelled Subscription and keep issuing `request(long)` to it.
+        ManualSubscriber<T> sub = new ManualSubscriberWithSubscriptionSupport<T>(env) {
+          @Override
+          public void cancel() {
+            if (subscription.isCompleted()) {
+              subscription.value().cancel();
+            } else {
+              env.flop("Cannot cancel a subscription before having received it");
+            }
+          }
+        };
+
+        env.subscribe(pub, sub);
+
+        sub.cancel();
+        sub.request(1);
+        sub.request(1);
+        sub.request(1);
+
+        sub.expectNone();
+        env.verifyNoAsyncErrorsNoDelay();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.7
+  @Override @Test
+  public void required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops() throws Throwable {
+    activePublisherTest(1, false, new PublisherTestRun<T>() {
+      @Override
+      public void run(Publisher<T> pub) throws Throwable {
+        final ManualSubscriber<T> sub = env.newManualSubscriber(pub);
+
+        // leak the Subscription
+        final Subscription subs = sub.subscription.value();
+
+        subs.cancel();
+        subs.cancel();
+        subs.cancel();
+
+        sub.expectNone();
+        env.verifyNoAsyncErrorsNoDelay();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.9
+  @Override @Test
+  public void required_spec309_requestZeroMustSignalIllegalArgumentException() throws Throwable {
+    activePublisherTest(10, false, new PublisherTestRun<T>() {
+      @Override public void run(Publisher<T> pub) throws Throwable {
+        final ManualSubscriber<T> sub = env.newManualSubscriber(pub);
+        sub.request(0);
+        sub.expectErrorWithMessage(IllegalArgumentException.class, "3.9"); // we do require implementations to mention the rule number at the very least
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.9
+  @Override @Test
+  public void required_spec309_requestNegativeNumberMustSignalIllegalArgumentException() throws Throwable {
+    activePublisherTest(10, false, new PublisherTestRun<T>() {
+      @Override
+      public void run(Publisher<T> pub) throws Throwable {
+        final ManualSubscriber<T> sub = env.newManualSubscriber(pub);
+        final Random r = new Random();
+        sub.request(-r.nextInt(Integer.MAX_VALUE));
+        sub.expectErrorWithMessage(IllegalArgumentException.class, "3.9"); // we do require implementations to mention the rule number at the very least
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.12
+  @Override @Test
+  public void required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling() throws Throwable {
+    // the publisher is able to signal more elements than the subscriber will be requesting in total
+    final int publisherElements = 20;
+
+    final int demand1 = 10;
+    final int demand2 = 5;
+    final int totalDemand = demand1 + demand2;
+
+    activePublisherTest(publisherElements, false, new PublisherTestRun<T>() {
+      @Override @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+      public void run(Publisher<T> pub) throws Throwable {
+        final ManualSubscriber<T> sub = env.newManualSubscriber(pub);
+
+        sub.request(demand1);
+        sub.request(demand2);
+
+        /*
+          NOTE: The order of the nextElement/cancel calls below is very important (!)
+
+          If this ordering was reversed, given an asynchronous publisher,
+          the following scenario would be *legal* and would break this test:
+
+          > AsyncPublisher receives request(10) - it does not emit data right away, it's asynchronous
+          > AsyncPublisher receives request(5) - demand is now 15
+          ! AsyncPublisher didn't emit any onNext yet (!)
+          > AsyncPublisher receives cancel() - handles it right away, by "stopping itself" for example
+          ! cancel was handled hefore the AsyncPublisher ever got the chance to emit data
+          ! the subscriber ends up never receiving even one element - the test is stuck (and fails, even on valid Publisher)
+
+          Which is why we must first expect an element, and then cancel, once the producing is "running".
+         */
+        sub.nextElement();
+        sub.cancel();
+
+        int onNextsSignalled = 1;
+
+        boolean stillBeingSignalled;
+        do {
+          // put asyncError if onNext signal received
+          sub.expectNone();
+          Throwable error = env.dropAsyncError();
+
+          if (error == null) {
+            stillBeingSignalled = false;
+          } else {
+            onNextsSignalled += 1;
+            stillBeingSignalled = true;
+          }
+
+          // if the Publisher tries to emit more elements than was requested (and/or ignores cancellation) this will throw
+          assertTrue(onNextsSignalled <= totalDemand,
+                     String.format("Publisher signalled [%d] elements, which is more than the signalled demand: %d",
+                                   onNextsSignalled, totalDemand));
+
+        } while (stillBeingSignalled);
+      }
+    });
+
+    env.verifyNoAsyncErrorsNoDelay();
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.13
+  @Override @Test
+  public void required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber() throws Throwable {
+    final ReferenceQueue<ManualSubscriber<T>> queue = new ReferenceQueue<ManualSubscriber<T>>();
+
+    final Function<Publisher<T>, WeakReference<ManualSubscriber<T>>> run = new Function<Publisher<T>, WeakReference<ManualSubscriber<T>>>() {
+      @Override
+      public WeakReference<ManualSubscriber<T>> apply(Publisher<T> pub) throws Exception {
+        final ManualSubscriber<T> sub = env.newManualSubscriber(pub);
+        final WeakReference<ManualSubscriber<T>> ref = new WeakReference<ManualSubscriber<T>>(sub, queue);
+
+        sub.request(1);
+        sub.nextElement();
+        sub.cancel();
+
+        return ref;
+      }
+    };
+
+    activePublisherTest(3, false, new PublisherTestRun<T>() {
+      @Override
+      public void run(Publisher<T> pub) throws Throwable {
+        final WeakReference<ManualSubscriber<T>> ref = run.apply(pub);
+
+        // cancel may be run asynchronously so we add a sleep before running the GC
+        // to "resolve" the race
+        Thread.sleep(publisherReferenceGCTimeoutMillis);
+        System.gc();
+
+        if (!ref.equals(queue.remove(100))) {
+          env.flop(String.format("Publisher %s did not drop reference to test subscriber after subscription cancellation", pub));
+        }
+
+        env.verifyNoAsyncErrorsNoDelay();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.17
+  @Override @Test
+  public void required_spec317_mustSupportAPendingElementCountUpToLongMaxValue() throws Throwable {
+    final int totalElements = 3;
+
+    activePublisherTest(totalElements, true, new PublisherTestRun<T>() {
+      @Override
+      public void run(Publisher<T> pub) throws Throwable {
+        ManualSubscriber<T> sub = env.newManualSubscriber(pub);
+        sub.request(Long.MAX_VALUE);
+
+        sub.nextElements(totalElements);
+        sub.expectCompletion();
+
+        env.verifyNoAsyncErrorsNoDelay();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.17
+  @Override @Test
+  public void required_spec317_mustSupportACumulativePendingElementCountUpToLongMaxValue() throws Throwable {
+    final int totalElements = 3;
+
+    activePublisherTest(totalElements, true, new PublisherTestRun<T>() {
+      @Override
+      public void run(Publisher<T> pub) throws Throwable {
+        final ManualSubscriber<T> sub = env.newManualSubscriber(pub);
+        sub.request(Long.MAX_VALUE / 2); // pending = Long.MAX_VALUE / 2
+        sub.request(Long.MAX_VALUE / 2); // pending = Long.MAX_VALUE - 1
+        sub.request(1); // pending = Long.MAX_VALUE
+
+        sub.nextElements(totalElements);
+        sub.expectCompletion();
+
+        try {
+          env.verifyNoAsyncErrorsNoDelay();
+        } finally {
+          sub.cancel();
+        }
+        
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.17
+  @Override @Test
+  public void required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue() throws Throwable {
+    activePublisherTest(Integer.MAX_VALUE, false, new PublisherTestRun<T>() {
+      @Override public void run(Publisher<T> pub) throws Throwable {
+        final ManualSubscriberWithSubscriptionSupport<T> sub = new BlackholeSubscriberWithSubscriptionSupport<T>(env) {
+           // arbitrarily set limit on nuber of request calls signalled, we expect overflow after already 2 calls,
+           // so 10 is relatively high and safe even if arbitrarily chosen
+          int callsCounter = 10;
+
+          @Override
+          public void onNext(T element) {
+            env.debug(String.format("%s::onNext(%s)", this, element));
+            if (subscription.isCompleted()) {
+              if (callsCounter > 0) {
+                subscription.value().request(Long.MAX_VALUE - 1);
+                callsCounter--;
+              } else {
+                  subscription.value().cancel();
+              }
+            } else {
+              env.flop(String.format("Subscriber::onNext(%s) called before Subscriber::onSubscribe", element));
+            }
+          }
+        };
+        env.subscribe(pub, sub, env.defaultTimeoutMillis());
+
+        // eventually triggers `onNext`, which will then trigger up to `callsCounter` times `request(Long.MAX_VALUE - 1)`
+        // we're pretty sure to overflow from those
+        sub.request(1);
+
+        // no onError should be signalled
+        try {
+          env.verifyNoAsyncErrors(env.defaultTimeoutMillis());
+        } finally {
+          sub.cancel();
+        }
+      }
+    });
+  }
+
+  ///////////////////// ADDITIONAL "COROLLARY" TESTS ////////////////////////
+
+  ///////////////////// TEST INFRASTRUCTURE /////////////////////////////////
+
+  public interface PublisherTestRun<T> {
+    public void run(Publisher<T> pub) throws Throwable;
+  }
+
+  /**
+   * Test for feature that SHOULD/MUST be implemented, using a live publisher.
+   *
+   * @param elements the number of elements the Publisher under test  must be able to emit to run this test
+   * @param completionSignalRequired true if an {@code 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 {@link PublisherVerification#maxElementsFromPublisher()}.
+   */
+  public void activePublisherTest(long elements, boolean completionSignalRequired, PublisherTestRun<T> body) throws Throwable {
+    if (elements > maxElementsFromPublisher()) {
+      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()));
+    } else if (completionSignalRequired && maxElementsFromPublisher() == Long.MAX_VALUE) {
+      throw new SkipException("Unable to run this test, as it requires an onComplete signal, " +
+                                "which this Publisher is unable to provide (as signalled by returning Long.MAX_VALUE from `maxElementsFromPublisher()`)");
+    } else {
+      Publisher<T> pub = createPublisher(elements);
+      body.run(pub);
+      env.verifyNoAsyncErrorsNoDelay();
+    }
+  }
+
+  /**
+   * Test for feature that MAY be implemented. This test will be marked as SKIPPED if it fails.
+   *
+   * @param elements the number of elements the Publisher under test  must be able to emit to run this test
+   * @param completionSignalRequired true if an {@code 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 {@link PublisherVerification#maxElementsFromPublisher()}.
+   */
+  public void optionalActivePublisherTest(long elements, boolean completionSignalRequired, PublisherTestRun<T> body) throws Throwable {
+    if (elements > maxElementsFromPublisher()) {
+      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()));
+    } else if (completionSignalRequired && maxElementsFromPublisher() == Long.MAX_VALUE) {
+      throw new SkipException("Unable to run this test, as it requires an onComplete signal, " +
+                                "which this Publisher is unable to provide (as signalled by returning Long.MAX_VALUE from `maxElementsFromPublisher()`)");
+    } else {
+
+      final Publisher<T> pub = createPublisher(elements);
+      final String skipMessage = "Skipped because tested publisher does NOT implement this OPTIONAL requirement.";
+
+      try {
+        potentiallyPendingTest(pub, body);
+      } catch (Exception ex) {
+        notVerified(skipMessage);
+      } catch (AssertionError ex) {
+        notVerified(skipMessage + " Reason for skipping was: " + ex.getMessage());
+      }
+    }
+  }
+
+  public static final 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 String SKIPPING_OPTIONAL_TEST_FAILED =
+    "Skipping, because provided Publisher does not pass this *additional* verification.";
+  /**
+   * Additional test for Publisher in error state
+   */
+  public void whenHasErrorPublisherTest(PublisherTestRun<T> body) throws Throwable {
+    potentiallyPendingTest(createFailedPublisher(), body, SKIPPING_NO_ERROR_PUBLISHER_AVAILABLE);
+  }
+
+  public void potentiallyPendingTest(Publisher<T> pub, PublisherTestRun<T> body) throws Throwable {
+    potentiallyPendingTest(pub, body, SKIPPING_OPTIONAL_TEST_FAILED);
+  }
+
+  public void potentiallyPendingTest(Publisher<T> pub, PublisherTestRun<T> body, String message) throws Throwable {
+    if (pub != null) {
+      body.run(pub);
+    } else {
+      throw new SkipException(message);
+    }
+  }
+
+  /**
+   * Executes a given test body {@code n} times.
+   * All the test runs must pass in order for the stochastic test to pass.
+   */
+  public void stochasticTest(int n, Function<Integer, Void> body) throws Throwable {
+    if (skipStochasticTests()) {
+      notVerified("Skipping @Stochastic test because `skipStochasticTests()` returned `true`!");
+    }
+
+    for (int i = 0; i < n; i++) {
+      body.apply(i);
+    }
+  }
+
+  public void notVerified() {
+    throw new SkipException("Not verified by this TCK.");
+  }
+
+  /**
+   * Return this value from {@link PublisherVerification#maxElementsFromPublisher()} to mark that the given {@link org.reactivestreams.Publisher},
+   * is not able to signal completion. For example it is strictly a time-bound or unbounded source of data.
+   *
+   * <b>Returning this value from {@link PublisherVerification#maxElementsFromPublisher()} will result in skipping all TCK tests which require onComplete signals!</b>
+   */
+  public long publisherUnableToSignalOnComplete() {
+    return Long.MAX_VALUE;
+  }
+
+  public void notVerified(String message) {
+    throw new SkipException(message);
+  }
+
+}
diff --git a/tck/src/main/java/org/reactivestreams/tck/SubscriberBlackboxVerification.java b/tck/src/main/java/org/reactivestreams/tck/SubscriberBlackboxVerification.java
new file mode 100644
index 0000000..70cfdb8
--- /dev/null
+++ b/tck/src/main/java/org/reactivestreams/tck/SubscriberBlackboxVerification.java
@@ -0,0 +1,524 @@
+package org.reactivestreams.tck;
+
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import org.reactivestreams.tck.TestEnvironment.ManualPublisher;
+import org.reactivestreams.tck.TestEnvironment.ManualSubscriber;
+import org.reactivestreams.tck.support.Optional;
+import org.reactivestreams.tck.support.SubscriberBlackboxVerificationRules;
+import org.reactivestreams.tck.support.TestException;
+import org.testng.SkipException;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.reactivestreams.tck.SubscriberWhiteboxVerification.BlackboxSubscriberProxy;
+import static org.testng.Assert.assertTrue;
+
+/**
+ * Provides tests for verifying {@link org.reactivestreams.Subscriber} and {@link org.reactivestreams.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 {@code org.reactivestreams.tck.SubscriberWhiteboxVerification}
+ * instead.
+ *
+ * @see org.reactivestreams.Subscriber
+ * @see org.reactivestreams.Subscription
+ */
+public abstract class SubscriberBlackboxVerification<T> extends WithHelperPublisher<T> 
+  implements SubscriberBlackboxVerificationRules {
+
+  protected final TestEnvironment env;
+
+  protected SubscriberBlackboxVerification(TestEnvironment env) {
+    this.env = env;
+  }
+
+  // USER API
+
+  /**
+   * This is the main method you must implement in your test incarnation.
+   * It must create a new {@link org.reactivestreams.Subscriber} instance to be subjected to the testing logic.
+   */
+  public abstract Subscriber<T> createSubscriber();
+
+  /**
+   * Override this method if the Subscriber implementation you are verifying
+   * needs an external signal before it signals demand to its Publisher.
+   *
+   * By default this method does nothing.
+   */
+  public void triggerRequest(final Subscriber<? super T> subscriber) {
+
+  }
+
+  // ENV SETUP
+
+  /**
+   * Executor service used by the default provided asynchronous Publisher.
+   * @see #createHelperPublisher(long)
+   */
+  private ExecutorService publisherExecutor;
+  @BeforeClass public void startPublisherExecutorService() { publisherExecutor = Executors.newFixedThreadPool(4); }
+  @AfterClass public void shutdownPublisherExecutorService() { if (publisherExecutor != null) publisherExecutor.shutdown(); }
+  @Override public ExecutorService publisherExecutorService() { return publisherExecutor; }
+
+  ////////////////////// TEST ENV CLEANUP /////////////////////////////////////
+
+  @BeforeMethod
+  public void setUp() throws Exception {
+    env.clearAsyncErrors();
+  }
+
+  ////////////////////// SPEC RULE VERIFICATION ///////////////////////////////
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.1
+  @Override @Test
+  public void required_spec201_blackbox_mustSignalDemandViaSubscriptionRequest() throws Throwable {
+    blackboxSubscriberTest(new BlackboxTestStageTestRun() {
+      @Override
+      public void run(BlackboxTestStage stage) throws InterruptedException {
+        triggerRequest(stage.subProxy().sub());
+        final long n = stage.expectRequest();// assuming subscriber wants to consume elements...
+
+        // should cope with up to requested number of elements
+        for (int i = 0; i < n; i++)
+          stage.signalNext();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.2
+  @Override @Test
+  public void untested_spec202_blackbox_shouldAsynchronouslyDispatch() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.3
+  @Override @Test
+  public void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() throws Throwable {
+    blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() {
+      @Override
+      public void run(BlackboxTestStage stage) throws Throwable {
+        final Subscription subs = new Subscription() {
+          @Override
+          public void request(long n) {
+            final Optional<StackTraceElement> onCompleteStackTraceElement = env.findCallerMethodInStackTrace("onComplete");
+            if (onCompleteStackTraceElement.isDefined()) {
+              final StackTraceElement stackElem = onCompleteStackTraceElement.get();
+              env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)",
+                                     stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber()));
+            }
+          }
+
+          @Override
+          public void cancel() {
+            final Optional<StackTraceElement> onCompleteStackElement = env.findCallerMethodInStackTrace("onComplete");
+            if (onCompleteStackElement.isDefined()) {
+              final StackTraceElement stackElem = onCompleteStackElement.get();
+              env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)",
+                                     stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber()));
+            }
+          }
+        };
+
+        final Subscriber<T> sub = createSubscriber();
+        sub.onSubscribe(subs);
+        sub.onComplete();
+
+        env.verifyNoAsyncErrorsNoDelay();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.3
+  @Override @Test
+  public void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() throws Throwable {
+    blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() {
+      @Override
+      public void run(BlackboxTestStage stage) throws Throwable {
+        final Subscription subs = new Subscription() {
+          @Override
+          public void request(long n) {
+            Throwable thr = new Throwable();
+            for (StackTraceElement stackElem : thr.getStackTrace()) {
+              if (stackElem.getMethodName().equals("onError")) {
+                env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)",
+                                       stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber()));
+              }
+            }
+          }
+
+          @Override
+          public void cancel() {
+            Throwable thr = new Throwable();
+            for (StackTraceElement stackElem : thr.getStackTrace()) {
+              if (stackElem.getMethodName().equals("onError")) {
+                env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)",
+                                       stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber()));
+              }
+            }
+          }
+        };
+
+        final Subscriber<T> sub = createSubscriber();
+        sub.onSubscribe(subs);
+        sub.onError(new TestException());
+
+        env.verifyNoAsyncErrorsNoDelay();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.4
+  @Override @Test
+  public void untested_spec204_blackbox_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.5
+  @Override @Test
+  public void required_spec205_blackbox_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() throws Exception {
+    new BlackboxTestStage(env) {{
+      // try to subscribe another time, if the subscriber calls `probe.registerOnSubscribe` the test will fail
+      final TestEnvironment.Latch secondSubscriptionCancelled = new TestEnvironment.Latch(env);
+      sub().onSubscribe(
+          new Subscription() {
+            @Override
+            public void request(long elements) {
+              env.flop(String.format("Subscriber %s illegally called `subscription.request(%s)`!", sub(), elements));
+            }
+
+            @Override
+            public void cancel() {
+              secondSubscriptionCancelled.close();
+            }
+
+            @Override
+            public String toString() {
+              return "SecondSubscription(should get cancelled)";
+            }
+          });
+
+      secondSubscriptionCancelled.expectClose("Expected SecondSubscription given to subscriber to be cancelled, but `Subscription.cancel()` was not called.");
+      env.verifyNoAsyncErrorsNoDelay();
+    }};
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.6
+  @Override @Test
+  public void untested_spec206_blackbox_mustCallSubscriptionCancelIfItIsNoLongerValid() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.7
+  @Override @Test
+  public void untested_spec207_blackbox_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+    // the same thread part of the clause can be verified but that is not very useful, or is it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.8
+  @Override @Test
+  public void untested_spec208_blackbox_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() throws Throwable {
+    notVerified(); // cannot be meaningfully tested as black box, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.9
+  @Override @Test
+  public void required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() throws Throwable {
+    blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() {
+      @Override
+      public void run(BlackboxTestStage stage) throws Throwable {
+        final Publisher<T> pub = new Publisher<T>() {
+          @Override public void subscribe(final Subscriber<? super T> s) {
+            s.onSubscribe(new Subscription() {
+              private boolean completed = false;
+
+              @Override public void request(long n) {
+                if (!completed) {
+                  completed = true;
+                  s.onComplete(); // Publisher now realises that it is in fact already completed
+                }
+              }
+
+              @Override public void cancel() {
+                // noop, ignore
+              }
+            });
+          }
+        };
+
+        final Subscriber<T> sub = createSubscriber();
+        final BlackboxSubscriberProxy<T> probe = stage.createBlackboxSubscriberProxy(env, sub);
+
+        pub.subscribe(probe);
+        triggerRequest(sub);
+        probe.expectCompletion();
+        probe.expectNone();
+
+        env.verifyNoAsyncErrorsNoDelay();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.9
+  @Override @Test
+  public void required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() throws Throwable {
+    blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() {
+      @Override
+      public void run(BlackboxTestStage stage) throws Throwable {
+        final Publisher<T> pub = new Publisher<T>() {
+          @Override
+          public void subscribe(Subscriber<? super T> s) {
+            s.onComplete();
+          }
+        };
+
+        final Subscriber<T> sub = createSubscriber();
+        final BlackboxSubscriberProxy<T> probe = stage.createBlackboxSubscriberProxy(env, sub);
+
+        pub.subscribe(probe);
+        probe.expectCompletion();
+
+        env.verifyNoAsyncErrorsNoDelay();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.10
+  @Override @Test
+  public void required_spec210_blackbox_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() throws Throwable {
+    blackboxSubscriberTest(new BlackboxTestStageTestRun() {
+      @Override
+      @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+      public void run(BlackboxTestStage stage) throws Throwable {
+        stage.sub().onError(new TestException());
+        stage.subProxy().expectError(Throwable.class);
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.11
+  @Override @Test
+  public void untested_spec211_blackbox_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.12
+  @Override @Test
+  public void untested_spec212_blackbox_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality() throws Throwable {
+    notVerified(); // cannot be meaningfully tested as black box, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13
+  @Override @Test
+  public void untested_spec213_blackbox_failingOnSignalInvocation() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13
+  @Override @Test
+  public void required_spec213_blackbox_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable {
+    blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() {
+      @Override
+      public void run(BlackboxTestStage stage) throws Throwable {
+
+        {
+          final Subscriber<T> sub = createSubscriber();
+          boolean gotNPE = false;
+          try {
+            sub.onSubscribe(null);
+          } catch(final NullPointerException expected) {
+            gotNPE = true;
+          }
+          assertTrue(gotNPE, "onSubscribe(null) did not throw NullPointerException");
+        }
+
+        env.verifyNoAsyncErrorsNoDelay();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13
+  @Override @Test
+  public void required_spec213_blackbox_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable {
+    blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() {
+      @Override
+      public void run(BlackboxTestStage stage) throws Throwable {
+        final Subscription subscription = new Subscription() {
+          @Override public void request(final long elements) {}
+          @Override public void cancel() {}
+        };
+
+        {
+          final Subscriber<T> sub = createSubscriber();
+          boolean gotNPE = false;
+          sub.onSubscribe(subscription);
+          try {
+            sub.onNext(null);
+          } catch(final NullPointerException expected) {
+            gotNPE = true;
+          }
+          assertTrue(gotNPE, "onNext(null) did not throw NullPointerException");
+        }
+
+        env.verifyNoAsyncErrorsNoDelay();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13
+  @Override @Test
+  public void required_spec213_blackbox_onError_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable {
+    blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() {
+      @Override
+      public void run(BlackboxTestStage stage) throws Throwable {
+        final Subscription subscription = new Subscription() {
+          @Override public void request(final long elements) {}
+          @Override public void cancel() {}
+        };
+
+        {
+          final Subscriber<T> sub = createSubscriber();
+          boolean gotNPE = false;
+          sub.onSubscribe(subscription);
+          try {
+            sub.onError(null);
+          } catch(final NullPointerException expected) {
+            gotNPE = true;
+          }
+          assertTrue(gotNPE, "onError(null) did not throw NullPointerException");
+        }
+
+        env.verifyNoAsyncErrorsNoDelay();
+      }
+    });
+  }
+
+  ////////////////////// SUBSCRIPTION SPEC RULE VERIFICATION //////////////////
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.1
+  @Override @Test
+  public void untested_spec301_blackbox_mustNotBeCalledOutsideSubscriberContext() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.8
+  @Override @Test
+  public void untested_spec308_blackbox_requestMustRegisterGivenNumberElementsToBeProduced() throws Throwable {
+    notVerified(); // cannot be meaningfully tested as black box, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.10
+  @Override @Test
+  public void untested_spec310_blackbox_requestMaySynchronouslyCallOnNextOnSubscriber() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.11
+  @Override @Test
+  public void untested_spec311_blackbox_requestMaySynchronouslyCallOnCompleteOrOnError() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.14
+  @Override @Test
+  public void untested_spec314_blackbox_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.15
+  @Override @Test
+  public void untested_spec315_blackbox_cancelMustNotThrowExceptionAndMustSignalOnError() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.16
+  @Override @Test
+  public void untested_spec316_blackbox_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+  }
+
+  /////////////////////// ADDITIONAL "COROLLARY" TESTS ////////////////////////
+
+  /////////////////////// TEST INFRASTRUCTURE /////////////////////////////////
+
+  abstract class BlackboxTestStageTestRun {
+    public abstract void run(BlackboxTestStage stage) throws Throwable;
+  }
+
+  public void blackboxSubscriberTest(BlackboxTestStageTestRun body) throws Throwable {
+    BlackboxTestStage stage = new BlackboxTestStage(env, true);
+    body.run(stage);
+  }
+
+  public void blackboxSubscriberWithoutSetupTest(BlackboxTestStageTestRun body) throws Throwable {
+    BlackboxTestStage stage = new BlackboxTestStage(env, false);
+    body.run(stage);
+  }
+
+  public class BlackboxTestStage extends ManualPublisher<T> {
+    public Publisher<T> pub;
+    public ManualSubscriber<T> tees; // gives us access to an infinite stream of T values
+
+    public T lastT = null;
+    private Optional<BlackboxSubscriberProxy<T>> subProxy = Optional.empty();
+
+    public BlackboxTestStage(TestEnvironment env) throws InterruptedException {
+      this(env, true);
+    }
+
+    public BlackboxTestStage(TestEnvironment env, boolean runDefaultInit) throws InterruptedException {
+      super(env);
+      if (runDefaultInit) {
+        pub = this.createHelperPublisher(Long.MAX_VALUE);
+        tees = env.newManualSubscriber(pub);
+        Subscriber<T> sub = createSubscriber();
+        subProxy = Optional.of(createBlackboxSubscriberProxy(env, sub));
+        subscribe(subProxy.get());
+      }
+    }
+
+    public Subscriber<? super T> sub() {
+      return subscriber.value();
+    }
+
+    /**
+     * Proxy for the {@link #sub()} {@code Subscriber}, providing certain assertions on methods being called on the Subscriber.
+     */
+    public BlackboxSubscriberProxy<T> subProxy() {
+      return subProxy.get();
+    }
+
+    public Publisher<T> createHelperPublisher(long elements) {
+      return SubscriberBlackboxVerification.this.createHelperPublisher(elements);
+    }
+
+    public BlackboxSubscriberProxy<T> createBlackboxSubscriberProxy(TestEnvironment env, Subscriber<T> sub) {
+      return new BlackboxSubscriberProxy<T>(env, sub);
+    }
+
+    public T signalNext() throws InterruptedException {
+      T element = nextT();
+      sendNext(element);
+      return element;
+    }
+
+    public T nextT() throws InterruptedException {
+      lastT = tees.requestNextElement();
+      return lastT;
+    }
+
+  }
+
+  public void notVerified() {
+    throw new SkipException("Not verified using this TCK.");
+  }
+}
\ No newline at end of file
diff --git a/tck/src/main/java/org/reactivestreams/tck/SubscriberWhiteboxVerification.java b/tck/src/main/java/org/reactivestreams/tck/SubscriberWhiteboxVerification.java
new file mode 100644
index 0000000..109c883
--- /dev/null
+++ b/tck/src/main/java/org/reactivestreams/tck/SubscriberWhiteboxVerification.java
@@ -0,0 +1,785 @@
+package org.reactivestreams.tck;
+
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import org.reactivestreams.tck.TestEnvironment.*;
+import org.reactivestreams.tck.support.Optional;
+import org.reactivestreams.tck.support.SubscriberWhiteboxVerificationRules;
+import org.reactivestreams.tck.support.TestException;
+import org.testng.SkipException;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.testng.Assert.assertTrue;
+
+/**
+ * Provides tests for verifying {@link org.reactivestreams.Subscriber} and {@link org.reactivestreams.Subscription} specification rules.
+ *
+ * @see org.reactivestreams.Subscriber
+ * @see org.reactivestreams.Subscription
+ */
+public abstract class SubscriberWhiteboxVerification<T> extends WithHelperPublisher<T>
+  implements SubscriberWhiteboxVerificationRules {
+
+  private final TestEnvironment env;
+
+  protected SubscriberWhiteboxVerification(TestEnvironment env) {
+    this.env = env;
+  }
+
+  // USER API
+
+  /**
+   * This is the main method you must implement in your test incarnation.
+   * It must create a new {@link org.reactivestreams.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 abstract Subscriber<T> createSubscriber(WhiteboxSubscriberProbe<T> probe);
+
+  // ENV SETUP
+
+  /**
+   * Executor service used by the default provided asynchronous Publisher.
+   * @see #createHelperPublisher(long)
+   */
+  private ExecutorService publisherExecutor;
+  @BeforeClass public void startPublisherExecutorService() { publisherExecutor = Executors.newFixedThreadPool(4); }
+  @AfterClass public void shutdownPublisherExecutorService() { if (publisherExecutor != null) publisherExecutor.shutdown(); }
+  @Override public ExecutorService publisherExecutorService() { return publisherExecutor; }
+
+  ////////////////////// TEST ENV CLEANUP /////////////////////////////////////
+
+  @BeforeMethod
+  public void setUp() throws Exception {
+    env.clearAsyncErrors();
+  }
+
+  ////////////////////// TEST SETUP VERIFICATION //////////////////////////////
+
+  @Test
+  public void required_exerciseWhiteboxHappyPath() throws Throwable {
+    subscriberTest(new TestStageTestRun() {
+      @Override
+      public void run(WhiteboxTestStage stage) throws InterruptedException {
+        stage.puppet().triggerRequest(1);
+        stage.puppet().triggerRequest(1);
+
+        long receivedRequests = stage.expectRequest();
+
+        stage.signalNext();
+        stage.probe.expectNext(stage.lastT);
+
+        stage.puppet().triggerRequest(1);
+        if (receivedRequests == 1) {
+          stage.expectRequest();
+        }
+
+        stage.signalNext();
+        stage.probe.expectNext(stage.lastT);
+
+        stage.puppet().signalCancel();
+        stage.expectCancelling();
+
+        stage.verifyNoAsyncErrors();
+      }
+    });
+  }
+
+  ////////////////////// SPEC RULE VERIFICATION ///////////////////////////////
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.1
+  @Override @Test
+  public void required_spec201_mustSignalDemandViaSubscriptionRequest() throws Throwable {
+    subscriberTest(new TestStageTestRun() {
+      @Override
+      public void run(WhiteboxTestStage stage) throws InterruptedException {
+        stage.puppet().triggerRequest(1);
+        stage.expectRequest();
+
+        stage.signalNext();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.2
+  @Override @Test
+  public void untested_spec202_shouldAsynchronouslyDispatch() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.3
+  @Override @Test
+  public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() throws Throwable {
+    subscriberTestWithoutSetup(new TestStageTestRun() {
+      @Override
+      public void run(WhiteboxTestStage stage) throws Throwable {
+        final Subscription subs = new Subscription() {
+          @Override
+          public void request(long n) {
+            final Optional<StackTraceElement> onCompleteStackTraceElement = env.findCallerMethodInStackTrace("onComplete");
+            if (onCompleteStackTraceElement.isDefined()) {
+              final StackTraceElement stackElem = onCompleteStackTraceElement.get();
+              env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)",
+                                     stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber()));
+            }
+          }
+
+          @Override
+          public void cancel() {
+            final Optional<StackTraceElement> onCompleteStackElement = env.findCallerMethodInStackTrace("onComplete");
+            if (onCompleteStackElement.isDefined()) {
+              final StackTraceElement stackElem = onCompleteStackElement.get();
+              env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)",
+                                     stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber()));
+            }
+          }
+        };
+
+        stage.probe = stage.createWhiteboxSubscriberProbe(env);
+        final Subscriber<T> sub = createSubscriber(stage.probe);
+
+        sub.onSubscribe(subs);
+        sub.onComplete();
+
+        env.verifyNoAsyncErrorsNoDelay();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.3
+  @Override @Test
+  public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() throws Throwable {
+    subscriberTestWithoutSetup(new TestStageTestRun() {
+      @Override
+      public void run(WhiteboxTestStage stage) throws Throwable {
+        final Subscription subs = new Subscription() {
+          @Override
+          public void request(long n) {
+            Throwable thr = new Throwable();
+            for (StackTraceElement stackElem : thr.getStackTrace()) {
+              if (stackElem.getMethodName().equals("onError")) {
+                env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)",
+                                       stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber()));
+              }
+            }
+          }
+
+          @Override
+          public void cancel() {
+            Throwable thr = new Throwable();
+            for (StackTraceElement stackElem : thr.getStackTrace()) {
+              if (stackElem.getMethodName().equals("onError")) {
+                env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)",
+                                       stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber()));
+              }
+            }
+          }
+        };
+
+        stage.probe = stage.createWhiteboxSubscriberProbe(env);
+        final Subscriber<T> sub = createSubscriber(stage.probe);
+
+        sub.onSubscribe(subs);
+        sub.onError(new TestException());
+
+        env.verifyNoAsyncErrorsNoDelay();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.4
+  @Override @Test
+  public void untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.5
+  @Override @Test
+  public void required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() throws Throwable {
+    subscriberTest(new TestStageTestRun() {
+      @Override
+      public void run(WhiteboxTestStage stage) throws Throwable {
+        // try to subscribe another time, if the subscriber calls `probe.registerOnSubscribe` the test will fail
+        final Latch secondSubscriptionCancelled = new Latch(env);
+        final Subscriber<? super T> sub = stage.sub();
+        final Subscription subscription = new Subscription() {
+          @Override
+          public void request(long elements) {
+            // ignore...
+          }
+
+          @Override
+          public void cancel() {
+            secondSubscriptionCancelled.close();
+          }
+
+          @Override
+          public String toString() {
+            return "SecondSubscription(should get cancelled)";
+          }
+        };
+        sub.onSubscribe(subscription);
+
+        secondSubscriptionCancelled.expectClose("Expected 2nd Subscription given to subscriber to be cancelled, but `Subscription.cancel()` was not called");
+        env.verifyNoAsyncErrors();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.6
+  @Override @Test
+  public void untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.7
+  @Override @Test
+  public void untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+    // the same thread part of the clause can be verified but that is not very useful, or is it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.8
+  @Override @Test
+  public void required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() throws Throwable {
+    subscriberTest(new TestStageTestRun() {
+      @Override
+      public void run(WhiteboxTestStage stage) throws InterruptedException {
+        stage.puppet().triggerRequest(1);
+        stage.puppet().signalCancel();
+        stage.signalNext();
+
+        stage.puppet().triggerRequest(1);
+        stage.puppet().triggerRequest(1);
+
+        stage.verifyNoAsyncErrors();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.9
+  @Override @Test
+  public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() throws Throwable {
+    subscriberTest(new TestStageTestRun() {
+      @Override
+      public void run(WhiteboxTestStage stage) throws InterruptedException {
+        stage.puppet().triggerRequest(1);
+        stage.sendCompletion();
+        stage.probe.expectCompletion();
+
+        stage.verifyNoAsyncErrors();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.9
+  @Override @Test
+  public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() throws Throwable {
+    subscriberTest(new TestStageTestRun() {
+      @Override
+      public void run(WhiteboxTestStage stage) throws InterruptedException {
+        stage.sendCompletion();
+        stage.probe.expectCompletion();
+
+        stage.verifyNoAsyncErrors();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.10
+  @Override @Test
+  public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() throws Throwable {
+    subscriberTest(new TestStageTestRun() {
+      @Override
+      public void run(WhiteboxTestStage stage) throws InterruptedException {
+        stage.puppet().triggerRequest(1);
+        stage.puppet().triggerRequest(1);
+
+        Exception ex = new TestException();
+        stage.sendError(ex);
+        stage.probe.expectError(ex);
+
+        env.verifyNoAsyncErrorsNoDelay();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.10
+  @Override @Test
+  public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall() throws Throwable {
+    subscriberTest(new TestStageTestRun() {
+      @Override
+      public void run(WhiteboxTestStage stage) throws InterruptedException {
+        Exception ex = new TestException();
+        stage.sendError(ex);
+        stage.probe.expectError(ex);
+
+        env.verifyNoAsyncErrorsNoDelay();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.11
+  @Override @Test
+  public void untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.12
+  @Override @Test
+  public void untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation() throws Throwable {
+    notVerified(); // cannot be meaningfully tested, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13
+  @Override @Test
+  public void untested_spec213_failingOnSignalInvocation() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13
+  @Override @Test
+  public void required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable {
+    subscriberTest(new TestStageTestRun() {
+      @Override
+      public void run(WhiteboxTestStage stage) throws Throwable {
+
+        final Subscriber<? super T> sub = stage.sub();
+        boolean gotNPE = false;
+        try {
+          sub.onSubscribe(null);
+        } catch (final NullPointerException expected) {
+          gotNPE = true;
+        } 
+
+        assertTrue(gotNPE, "onSubscribe(null) did not throw NullPointerException");
+        env.verifyNoAsyncErrorsNoDelay();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13
+  @Override @Test
+  public void required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable {
+    subscriberTest(new TestStageTestRun() {
+      @Override
+      public void run(WhiteboxTestStage stage) throws Throwable {
+
+        final Subscriber<? super T> sub = stage.sub();
+        boolean gotNPE = false;
+        try {
+          sub.onNext(null);
+        } catch (final NullPointerException expected) {
+          gotNPE = true;
+        }
+
+        assertTrue(gotNPE, "onNext(null) did not throw NullPointerException");
+        env.verifyNoAsyncErrorsNoDelay();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.13
+  @Override @Test
+  public void required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable {
+    subscriberTest(new TestStageTestRun() {
+      @Override
+      public void run(WhiteboxTestStage stage) throws Throwable {
+
+          final Subscriber<? super T> sub = stage.sub();
+          boolean gotNPE = false;
+          try {
+            sub.onError(null);
+          } catch (final NullPointerException expected) {
+            gotNPE = true;
+          } finally {
+            assertTrue(gotNPE, "onError(null) did not throw NullPointerException");
+          }
+
+        env.verifyNoAsyncErrorsNoDelay();
+      }
+    });
+  }
+
+
+  ////////////////////// SUBSCRIPTION SPEC RULE VERIFICATION //////////////////
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.1
+  @Override @Test
+  public void untested_spec301_mustNotBeCalledOutsideSubscriberContext() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.8
+  @Override @Test
+  public void required_spec308_requestMustRegisterGivenNumberElementsToBeProduced() throws Throwable {
+    subscriberTest(new TestStageTestRun() {
+      @Override
+      public void run(WhiteboxTestStage stage) throws InterruptedException {
+        stage.puppet().triggerRequest(2);
+        stage.probe.expectNext(stage.signalNext());
+        stage.probe.expectNext(stage.signalNext());
+
+        stage.probe.expectNone();
+        stage.puppet().triggerRequest(3);
+
+        stage.verifyNoAsyncErrors();
+      }
+    });
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.10
+  @Override @Test
+  public void untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.11
+  @Override @Test
+  public void untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.14
+  @Override @Test
+  public void untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.15
+  @Override @Test
+  public void untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+  }
+
+  // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.16
+  @Override @Test
+  public void untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() throws Exception {
+    notVerified(); // cannot be meaningfully tested, or can it?
+  }
+
+  /////////////////////// ADDITIONAL "COROLLARY" TESTS ////////////////////////
+
+  /////////////////////// TEST INFRASTRUCTURE /////////////////////////////////
+
+  abstract class TestStageTestRun {
+    public abstract void run(WhiteboxTestStage stage) throws Throwable;
+  }
+
+  /**
+   * Prepares subscriber and publisher pair (by subscribing the first to the latter),
+   * and then hands over the tests {@link 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.
+   */
+  public void subscriberTest(TestStageTestRun body) throws Throwable {
+    WhiteboxTestStage stage = new WhiteboxTestStage(env, true);
+    body.run(stage);
+  }
+
+  /**
+   * Provides a {@link WhiteboxTestStage} without performing any additional setup,
+   * like the {@link #subscriberTest(SubscriberWhiteboxVerification.TestStageTestRun)} would.
+   *
+   * Use this method to write tests in which you need full control over when and how the initial {@code subscribe} is signalled.
+   */
+  public void subscriberTestWithoutSetup(TestStageTestRun body) throws Throwable {
+    WhiteboxTestStage stage = new WhiteboxTestStage(env, false);
+    body.run(stage);
+  }
+
+  /**
+   * Test for feature that MAY be implemented. This test will be marked as SKIPPED if it fails.
+   */
+  public void optionalSubscriberTestWithoutSetup(TestStageTestRun body) throws Throwable {
+    try {
+      subscriberTestWithoutSetup(body);
+    } catch (Exception ex) {
+      notVerified("Skipped because tested publisher does NOT implement this OPTIONAL requirement.");
+    }
+  }
+
+  public class WhiteboxTestStage extends ManualPublisher<T> {
+    public Publisher<T> pub;
+    public ManualSubscriber<T> tees; // gives us access to a stream T values
+    public WhiteboxSubscriberProbe<T> probe;
+
+    public T lastT = null;
+
+    public WhiteboxTestStage(TestEnvironment env) throws InterruptedException {
+      this(env, true);
+    }
+
+    public WhiteboxTestStage(TestEnvironment env, boolean runDefaultInit) throws InterruptedException {
+      super(env);
+      if (runDefaultInit) {
+        pub = this.createHelperPublisher(Long.MAX_VALUE);
+        tees = env.newManualSubscriber(pub);
+        probe = new WhiteboxSubscriberProbe<T>(env, subscriber);
+        subscribe(createSubscriber(probe));
+        probe.puppet.expectCompletion(env.defaultTimeoutMillis(), String.format("Subscriber %s did not `registerOnSubscribe`", sub()));
+      }
+    }
+
+    public Subscriber<? super T> sub() {
+      return subscriber.value();
+    }
+
+    public SubscriberPuppet puppet() {
+      return probe.puppet();
+    }
+
+    public WhiteboxSubscriberProbe<T> probe() {
+      return probe;
+    }
+
+    public Publisher<T> createHelperPublisher(long elements) {
+      return SubscriberWhiteboxVerification.this.createHelperPublisher(elements);
+    }
+
+    public WhiteboxSubscriberProbe<T> createWhiteboxSubscriberProbe(TestEnvironment env) {
+      return new WhiteboxSubscriberProbe<T>(env, subscriber);
+    }
+
+    public T signalNext() throws InterruptedException {
+      return signalNext(nextT());
+    }
+
+    private T signalNext(T element) throws InterruptedException {
+      sendNext(element);
+      return element;
+    }
+
+    public T nextT() throws InterruptedException {
+      lastT = tees.requestNextElement();
+      return lastT;
+    }
+
+    public void verifyNoAsyncErrors() {
+      env.verifyNoAsyncErrors();
+    }
+  }
+
+  /**
+   * This class is intented to be used as {@code Subscriber} decorator and should be used in {@code pub.subscriber(...)} calls,
+   * in order to allow intercepting calls on the underlying {@code Subscriber}.
+   * This delegation allows the proxy to implement {@link BlackboxProbe} assertions.
+   */
+  public static class BlackboxSubscriberProxy<T> extends BlackboxProbe<T> implements Subscriber<T> {
+
+    public BlackboxSubscriberProxy(TestEnvironment env, Subscriber<T> subscriber) {
+      super(env, Promise.<Subscriber<? super T>>completed(env, subscriber));
+    }
+
+    @Override
+    public void onSubscribe(Subscription s) {
+      sub().onSubscribe(s);
+    }
+
+    @Override
+    public void onNext(T t) {
+      registerOnNext(t);
+      sub().onNext(t);
+    }
+
+    @Override
+    public void onError(Throwable cause) {
+      registerOnError(cause);
+      sub().onError(cause);
+    }
+
+    @Override
+    public void onComplete() {
+      registerOnComplete();
+      sub().onComplete();
+    }
+  }
+
+  public static class BlackboxProbe<T> implements SubscriberProbe<T> {
+    protected final TestEnvironment env;
+    protected final Promise<Subscriber<? super T>> subscriber;
+
+    protected final Receptacle<T> elements;
+    protected final Promise<Throwable> error;
+
+    public BlackboxProbe(TestEnvironment env, Promise<Subscriber<? super T>> subscriber) {
+      this.env = env;
+      this.subscriber = subscriber;
+      elements = new Receptacle<T>(env);
+      error = new Promise<Throwable>(env);
+    }
+
+    @Override
+    public void registerOnNext(T element) {
+      elements.add(element);
+    }
+
+    @Override
+    public void registerOnComplete() {
+      try {
+        elements.complete();
+      } catch (IllegalStateException ex) {
+        // "Queue full", onComplete was already called
+        env.flop("subscriber::onComplete was called a second time, which is illegal according to Rule 1.7");
+      }
+    }
+
+    @Override
+    public void registerOnError(Throwable cause) {
+      try {
+        error.complete(cause);
+      } catch (IllegalStateException ex) {
+        // "Queue full", onError was already called
+        env.flop("subscriber::onError was called a second time, which is illegal according to Rule 1.7");
+      }
+    }
+
+    public T expectNext() throws InterruptedException {
+      return elements.next(env.defaultTimeoutMillis(), String.format("Subscriber %s did not call `registerOnNext(_)`", sub()));
+    }
+
+    public void expectNext(T expected) throws InterruptedException {
+      expectNext(expected, env.defaultTimeoutMillis());
+    }
+
+    public void expectNext(T expected, long timeoutMillis) throws InterruptedException {
+      T received = elements.next(timeoutMillis, String.format("Subscriber %s did not call `registerOnNext(%s)`", sub(), expected));
+      if (!received.equals(expected)) {
+        env.flop(String.format("Subscriber %s called `registerOnNext(%s)` rather than `registerOnNext(%s)`", sub(), received, expected));
+      }
+    }
+
+    public Subscriber<? super T> sub() {
+      return subscriber.value();
+    }
+
+    public void expectCompletion() throws InterruptedException {
+      expectCompletion(env.defaultTimeoutMillis());
+    }
+
+    public void expectCompletion(long timeoutMillis) throws InterruptedException {
+      expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnComplete()`", sub()));
+    }
+
+    public void expectCompletion(long timeoutMillis, String msg) throws InterruptedException {
+      elements.expectCompletion(timeoutMillis, msg);
+    }
+
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart) throws InterruptedException {
+      final E err = expectError(expected);
+      String message = err.getMessage();
+      assertTrue(message.contains(requiredMessagePart),
+        String.format("Got expected exception %s but missing message [%s], was: %s", err.getClass(), requiredMessagePart, expected));
+    }
+
+    public <E extends Throwable> E expectError(Class<E> expected) throws InterruptedException {
+      return expectError(expected, env.defaultTimeoutMillis());
+    }
+
+    @SuppressWarnings({"unchecked", "ThrowableResultOfMethodCallIgnored"})
+    public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis) throws InterruptedException {
+      error.expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected));
+      if (error.value() == null) {
+        return env.flopAndFail(String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected));
+      } else if (expected.isInstance(error.value())) {
+        return (E) error.value();
+      } else {
+        return env.flopAndFail(String.format("Subscriber %s called `registerOnError(%s)` rather than `registerOnError(%s)`", sub(), error.value(), expected));
+      }
+    }
+
+    public void expectError(Throwable expected) throws InterruptedException {
+      expectError(expected, env.defaultTimeoutMillis());
+    }
+
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    public void expectError(Throwable expected, long timeoutMillis) throws InterruptedException {
+      error.expectCompletion(timeoutMillis, String.format("Subscriber %s did not call `registerOnError(%s)`", sub(), expected));
+      if (error.value() != expected) {
+        env.flop(String.format("Subscriber %s called `registerOnError(%s)` rather than `registerOnError(%s)`", sub(), error.value(), expected));
+      }
+    }
+
+    public void expectNone() throws InterruptedException {
+      expectNone(env.defaultTimeoutMillis());
+    }
+
+    public void expectNone(long withinMillis) throws InterruptedException {
+      elements.expectNone(withinMillis, "Expected nothing");
+    }
+
+  }
+
+  public static class WhiteboxSubscriberProbe<T> extends BlackboxProbe<T> implements SubscriberPuppeteer {
+    protected Promise<SubscriberPuppet> puppet;
+
+    public WhiteboxSubscriberProbe(TestEnvironment env, Promise<Subscriber<? super T>> subscriber) {
+      super(env, subscriber);
+      puppet = new Promise<SubscriberPuppet>(env);
+    }
+
+    private SubscriberPuppet puppet() {
+      return puppet.value();
+    }
+
+    @Override
+    public void registerOnSubscribe(SubscriberPuppet p) {
+      if (!puppet.isCompleted()) {
+        puppet.complete(p);
+      } 
+    }
+
+  }
+
+  public interface SubscriberPuppeteer {
+
+    /**
+     * Must be called by the test subscriber when it has successfully registered a subscription
+     * inside the `onSubscribe` method.
+     */
+    void registerOnSubscribe(SubscriberPuppet puppet);
+  }
+
+  public interface SubscriberProbe<T> {
+
+    /**
+     * Must be called by the test subscriber when it has received an`onNext` event.
+     */
+    void registerOnNext(T element);
+
+    /**
+     * Must be called by the test subscriber when it has received an `onComplete` event.
+     */
+    void registerOnComplete();
+
+    /**
+     * Must be called by the test subscriber when it has received an `onError` event.
+     */
+    void registerOnError(Throwable cause);
+
+  }
+
+  public interface SubscriberPuppet {
+    void triggerRequest(long elements);
+
+    void signalCancel();
+  }
+
+  public void notVerified() {
+    throw new SkipException("Not verified using this TCK.");
+  }
+
+  public void notVerified(String msg) {
+    throw new SkipException(msg);
+  }
+}
diff --git a/tck/src/main/java/org/reactivestreams/tck/TestEnvironment.java b/tck/src/main/java/org/reactivestreams/tck/TestEnvironment.java
new file mode 100644
index 0000000..0e32f5b
--- /dev/null
+++ b/tck/src/main/java/org/reactivestreams/tck/TestEnvironment.java
@@ -0,0 +1,959 @@
+package org.reactivestreams.tck;
+
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import org.reactivestreams.tck.support.SubscriberBufferOverflowException;
+import org.reactivestreams.tck.support.Optional;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+public class TestEnvironment {
+  public static final int TEST_BUFFER_SIZE = 16;
+
+  private static final String DEFAULT_TIMEOUT_MILLIS_ENV = "DEFAULT_TIMEOUT_MILLIS";
+  private static final long DEFAULT_TIMEOUT_MILLIS = 100;
+
+  private final long defaultTimeoutMillis;
+  private final boolean printlnDebug;
+
+  private CopyOnWriteArrayList<Throwable> asyncErrors = new CopyOnWriteArrayList<Throwable>();
+
+  /**
+   * Tests must specify the timeout for expected outcome of asynchronous
+   * interactions. Longer timeout does not invalidate the correctness of
+   * the implementation, but can in some cases result in longer time to
+   * run the tests.
+   *
+   * @param defaultTimeoutMillis default timeout to be used in all expect* methods
+   * @param 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(long defaultTimeoutMillis, boolean printlnDebug) {
+    this.defaultTimeoutMillis = defaultTimeoutMillis;
+    this.printlnDebug = printlnDebug;
+  }
+
+  /**
+   * Tests must specify the timeout for expected outcome of asynchronous
+   * interactions. Longer timeout does not invalidate the correctness of
+   * the implementation, but can in some cases result in longer time to
+   * run the tests.
+   *
+   * @param defaultTimeoutMillis default timeout to be used in all expect* methods
+   */
+  public TestEnvironment(long defaultTimeoutMillis) {
+    this(defaultTimeoutMillis, false);
+  }
+
+  /**
+   * Tests must specify the timeout for expected outcome of asynchronous
+   * interactions. Longer timeout does not invalidate the correctness of
+   * the implementation, but can in some cases result in longer time to
+   * run the tests.
+   *
+   * The default timeout for all expect* methods will be obtained by either the env variable {@code DEFAULT_TIMEOUT_MILLIS}
+   * or the default value ({@link TestEnvironment#DEFAULT_TIMEOUT_MILLIS}) will be used.
+   *
+   * @param 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(boolean printlnDebug) {
+    this(envDefaultTimeoutMillis(), printlnDebug);
+  }
+
+  /**
+   * Tests must specify the timeout for expected outcome of asynchronous
+   * interactions. Longer timeout does not invalidate the correctness of
+   * the implementation, but can in some cases result in longer time to
+   * run the tests.
+   *
+   * The default timeout for all expect* methods will be obtained by either the env variable {@code DEFAULT_TIMEOUT_MILLIS}
+   * or the default value ({@link TestEnvironment#DEFAULT_TIMEOUT_MILLIS}) will be used.
+   */
+  public TestEnvironment() {
+    this(envDefaultTimeoutMillis());
+  }
+
+  public long defaultTimeoutMillis() {
+    return defaultTimeoutMillis;
+  }
+
+  /**
+   * Tries to parse the env variable {@code DEFAULT_TIMEOUT_MILLIS} as long and returns the value if present OR its default value.
+   *
+   * @throws java.lang.IllegalArgumentException when unable to parse the env variable
+   */
+  public static long envDefaultTimeoutMillis() {
+    final String envMillis = System.getenv(DEFAULT_TIMEOUT_MILLIS_ENV);
+    if (envMillis == null) return DEFAULT_TIMEOUT_MILLIS;
+    else try {
+      return Long.parseLong(envMillis);
+    } catch(NumberFormatException ex) {
+      throw new IllegalArgumentException(String.format("Unable to parse %s env value [%s] as long!", DEFAULT_TIMEOUT_MILLIS_ENV, envMillis), ex);
+    }
+  }
+
+  /**
+   * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously.
+   * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required.
+   *
+   * Use {@code 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 {@code expectError} methods or collect the error directly
+   * from the environment using {@code env.dropAsyncError()}.
+   *
+   * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()}
+   */
+  public void flop(String msg) {
+    try {
+      fail(msg);
+    } catch (Throwable t) {
+      asyncErrors.add(t);
+    }
+  }
+
+  /**
+   * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously.
+   * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required.
+   *
+   * This overload keeps the passed in throwable as the asyncError, instead of creating an AssertionError for this.
+   *
+   * Use {@code 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 {@code expectError} methods or collect the error directly
+   * from the environment using {@code env.dropAsyncError()}.
+   *
+   * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()}
+   */
+  public void flop(Throwable thr, String msg) {
+    try {
+      fail(msg, thr);
+    } catch (Throwable t) {
+      asyncErrors.add(thr);
+    }
+  }
+  
+  /**
+   * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously.
+   * This method does *NOT* fail the test - it's up to inspections of the error to fail the test if required.
+   *
+   * This overload keeps the passed in throwable as the asyncError, instead of creating an AssertionError for this.
+   *
+   * Use {@code 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 {@code expectError} methods or collect the error directly
+   * from the environment using {@code env.dropAsyncError()}.
+   *
+   * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()}
+   */
+  public void flop(Throwable thr) {
+    try {
+      fail(thr.getMessage(), thr);
+    } catch (Throwable t) {
+      asyncErrors.add(thr);
+    }
+  }
+
+  /**
+   * To flop means to "fail asynchronously", either by onErroring or by failing some TCK check triggered asynchronously.
+   *
+   * This method DOES fail the test right away (it tries to, by throwing an AssertionException),
+   * in such it is different from {@link org.reactivestreams.tck.TestEnvironment#flop} which only records the error.
+   *
+   * Use {@code 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 {@code expectError} methods or collect the error directly
+   * from the environment using {@code env.dropAsyncError()}.
+   *
+   * To clear asyncErrors you can call {@link org.reactivestreams.tck.TestEnvironment#clearAsyncErrors()}
+   */
+  public <T> T flopAndFail(String msg) {
+    try {
+      fail(msg);
+    } catch (Throwable t) {
+      asyncErrors.add(t);
+      fail(msg, t);
+    }
+    return null; // unreachable, the previous block will always exit by throwing
+  }
+
+
+
+  public <T> void subscribe(Publisher<T> pub, TestSubscriber<T> sub) throws InterruptedException {
+    subscribe(pub, sub, defaultTimeoutMillis);
+  }
+
+  public <T> void subscribe(Publisher<T> pub, TestSubscriber<T> sub, long timeoutMillis) throws InterruptedException {
+    pub.subscribe(sub);
+    sub.subscription.expectCompletion(timeoutMillis, String.format("Could not subscribe %s to Publisher %s", sub, pub));
+    verifyNoAsyncErrorsNoDelay();
+  }
+
+  public <T> ManualSubscriber<T> newBlackholeSubscriber(Publisher<T> pub) throws InterruptedException {
+    ManualSubscriberWithSubscriptionSupport<T> sub = new BlackholeSubscriberWithSubscriptionSupport<T>(this);
+    subscribe(pub, sub, defaultTimeoutMillis());
+    return sub;
+  }
+
+  public <T> ManualSubscriber<T> newManualSubscriber(Publisher<T> pub) throws InterruptedException {
+    return newManualSubscriber(pub, defaultTimeoutMillis());
+  }
+
+  public <T> ManualSubscriber<T> newManualSubscriber(Publisher<T> pub, long timeoutMillis) throws InterruptedException {
+    ManualSubscriberWithSubscriptionSupport<T> sub = new ManualSubscriberWithSubscriptionSupport<T>(this);
+    subscribe(pub, sub, timeoutMillis);
+    return sub;
+  }
+
+  public void clearAsyncErrors() {
+    asyncErrors.clear();
+  }
+
+  public Throwable dropAsyncError() {
+    try {
+      return asyncErrors.remove(0);
+    } catch (IndexOutOfBoundsException ex) {
+      return null;
+    }
+  }
+
+  /**
+   * Waits for {@link TestEnvironment#defaultTimeoutMillis()} and then verifies that no asynchronous errors
+   * were signalled pior to, or during that time (by calling {@code flop()}).
+   */
+  public void verifyNoAsyncErrors() {
+    verifyNoAsyncErrors(defaultTimeoutMillis());
+  }
+
+  /**
+   * This version of {@code verifyNoAsyncErrors} should be used when errors still could be signalled
+   * asynchronously during {@link TestEnvironment#defaultTimeoutMillis()} time.
+   * <p></p>
+   * It will immediatly check if any async errors were signaled (using {@link TestEnvironment#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 verifyNoAsyncErrors(long delay) {
+    try {
+      verifyNoAsyncErrorsNoDelay();
+
+      Thread.sleep(delay);
+      verifyNoAsyncErrorsNoDelay();
+    } catch (InterruptedException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /**
+   * Verifies that no asynchronous errors were signalled pior to calling this method (by calling {@code flop()}).
+   * This version of verifyNoAsyncError <b>does not wait before checking for asynchronous errors</b>, and is to be used
+   * for example in tight loops etc.
+   */
+  public void verifyNoAsyncErrorsNoDelay() {
+    for (Throwable e : asyncErrors) {
+      if (e instanceof AssertionError) {
+        throw (AssertionError) e;
+      } else {
+        fail(String.format("Async error during test execution: %s", e.getMessage()), e);
+      }
+    }
+  }
+
+  /** If {@code TestEnvironment#printlnDebug} is true, print debug message to std out. */
+  public void debug(String msg) {
+    if (printlnDebug)
+      System.out.printf("[TCK-DEBUG] %s%n", msg);
+  }
+
+  /**
+   * Looks for given {@code method} method in stack trace.
+   * Can be used to answer questions like "was this method called from onComplete?".
+   *
+   * @return the caller's StackTraceElement at which he the looked for method was found in the call stack, EMPTY otherwise
+   */
+  public Optional<StackTraceElement> findCallerMethodInStackTrace(String method) {
+    final Throwable thr = new Throwable(); // gets the stacktrace
+
+    for (StackTraceElement stackElement : thr.getStackTrace()) {
+      if (stackElement.getMethodName().equals(method)) {
+        return Optional.of(stackElement);
+      }
+    }
+    return Optional.empty();
+  }
+
+  // ---- classes ----
+
+  /**
+   * {@link Subscriber} implementation which can be steered by test code and asserted on.
+   */
+  public static class ManualSubscriber<T> extends TestSubscriber<T> {
+    Receptacle<T> received;
+
+    public ManualSubscriber(TestEnvironment env) {
+      super(env);
+      received = new Receptacle<T>(this.env);
+    }
+
+    @Override
+    public void onNext(T element) {
+      try {
+        received.add(element);
+      } catch (IllegalStateException ex) {
+          // error message refinement
+          throw new SubscriberBufferOverflowException(
+            String.format("Received more than bufferSize (%d) onNext signals. " +
+                            "The Publisher probably emited more signals than expected!",
+                          received.QUEUE_SIZE), ex);
+      }
+    }
+
+    @Override
+    public void onComplete() {
+      received.complete();
+    }
+
+    public void request(long elements) {
+      subscription.value().request(elements);
+    }
+
+    public T requestNextElement() throws InterruptedException {
+      return requestNextElement(env.defaultTimeoutMillis());
+    }
+
+    public T requestNextElement(long timeoutMillis) throws InterruptedException {
+      return requestNextElement(timeoutMillis, "Did not receive expected element");
+    }
+
+    public T requestNextElement(String errorMsg) throws InterruptedException {
+      return requestNextElement(env.defaultTimeoutMillis(), errorMsg);
+    }
+
+    public T requestNextElement(long timeoutMillis, String errorMsg) throws InterruptedException {
+      request(1);
+      return nextElement(timeoutMillis, errorMsg);
+    }
+
+    public Optional<T> requestNextElementOrEndOfStream(String errorMsg) throws InterruptedException {
+      return requestNextElementOrEndOfStream(env.defaultTimeoutMillis(), errorMsg);
+    }
+
+    public Optional<T> requestNextElementOrEndOfStream(long timeoutMillis) throws InterruptedException {
+      return requestNextElementOrEndOfStream(timeoutMillis, "Did not receive expected stream completion");
+    }
+
+    public Optional<T> requestNextElementOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException {
+      request(1);
+      return nextElementOrEndOfStream(timeoutMillis, errorMsg);
+    }
+
+    public void requestEndOfStream() throws InterruptedException {
+      requestEndOfStream(env.defaultTimeoutMillis(), "Did not receive expected stream completion");
+    }
+
+    public void requestEndOfStream(long timeoutMillis) throws InterruptedException {
+      requestEndOfStream(timeoutMillis, "Did not receive expected stream completion");
+    }
+
+    public void requestEndOfStream(String errorMsg) throws InterruptedException {
+      requestEndOfStream(env.defaultTimeoutMillis(), errorMsg);
+    }
+
+    public void requestEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException {
+      request(1);
+      expectCompletion(timeoutMillis, errorMsg);
+    }
+
+    public List<T> requestNextElements(long elements) throws InterruptedException {
+      request(elements);
+      return nextElements(elements, env.defaultTimeoutMillis());
+    }
+
+    public List<T> requestNextElements(long elements, long timeoutMillis) throws InterruptedException {
+      request(elements);
+      return nextElements(elements, timeoutMillis, String.format("Did not receive %d expected elements", elements));
+    }
+
+    public List<T> requestNextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException {
+      request(elements);
+      return nextElements(elements, timeoutMillis, errorMsg);
+    }
+
+    public T nextElement() throws InterruptedException {
+      return nextElement(env.defaultTimeoutMillis());
+    }
+
+    public T nextElement(long timeoutMillis) throws InterruptedException {
+      return nextElement(timeoutMillis, "Did not receive expected element");
+    }
+
+    public T nextElement(String errorMsg) throws InterruptedException {
+      return nextElement(env.defaultTimeoutMillis(), errorMsg);
+    }
+
+    public T nextElement(long timeoutMillis, String errorMsg) throws InterruptedException {
+      return received.next(timeoutMillis, errorMsg);
+    }
+
+    public Optional<T> nextElementOrEndOfStream() throws InterruptedException {
+      return nextElementOrEndOfStream(env.defaultTimeoutMillis(), "Did not receive expected stream completion");
+    }
+
+    public Optional<T> nextElementOrEndOfStream(long timeoutMillis) throws InterruptedException {
+      return nextElementOrEndOfStream(timeoutMillis, "Did not receive expected stream completion");
+    }
+
+    public Optional<T> nextElementOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException {
+      return received.nextOrEndOfStream(timeoutMillis, errorMsg);
+    }
+
+    public List<T> nextElements(long elements) throws InterruptedException {
+      return nextElements(elements, env.defaultTimeoutMillis(), "Did not receive expected element or completion");
+    }
+
+    public List<T> nextElements(long elements, String errorMsg) throws InterruptedException {
+      return nextElements(elements, env.defaultTimeoutMillis(), errorMsg);
+    }
+
+    public List<T> nextElements(long elements, long timeoutMillis) throws InterruptedException {
+      return nextElements(elements, timeoutMillis, "Did not receive expected element or completion");
+    }
+
+    public List<T> nextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException {
+      return received.nextN(elements, timeoutMillis, errorMsg);
+    }
+
+    public void expectNext(T expected) throws InterruptedException {
+      expectNext(expected, env.defaultTimeoutMillis());
+    }
+
+    public void expectNext(T expected, long timeoutMillis) throws InterruptedException {
+      T received = nextElement(timeoutMillis, "Did not receive expected element on downstream");
+      if (!received.equals(expected)) {
+        env.flop(String.format("Expected element %s on downstream but received %s", expected, received));
+      }
+    }
+
+    public void expectCompletion() throws InterruptedException {
+      expectCompletion(env.defaultTimeoutMillis(), "Did not receive expected stream completion");
+    }
+
+    public void expectCompletion(long timeoutMillis) throws InterruptedException {
+      expectCompletion(timeoutMillis, "Did not receive expected stream completion");
+    }
+
+    public void expectCompletion(String errorMsg) throws InterruptedException {
+      expectCompletion(env.defaultTimeoutMillis(), errorMsg);
+    }
+
+    public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException {
+      received.expectCompletion(timeoutMillis, errorMsg);
+    }
+
+    public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart) throws Exception {
+      expectErrorWithMessage(expected, requiredMessagePart, env.defaultTimeoutMillis());
+    }
+
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart, long timeoutMillis) throws Exception {
+      final E err = expectError(expected, timeoutMillis);
+      final String message = err.getMessage();
+      assertTrue(message.contains(requiredMessagePart),
+                 String.format("Got expected exception [%s] but missing message part [%s], was: %s",
+                               err.getClass(), requiredMessagePart, err.getMessage()));
+    }
+
+    public <E extends Throwable> E expectError(Class<E> expected) throws Exception {
+      return expectError(expected, env.defaultTimeoutMillis());
+    }
+
+    public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis) throws Exception {
+      return expectError(expected, timeoutMillis, String.format("Expected onError(%s)", expected.getName()));
+    }
+
+    public <E extends Throwable> E expectError(Class<E> expected, String errorMsg) throws Exception {
+      return expectError(expected, env.defaultTimeoutMillis(), errorMsg);
+    }
+
+    public <E extends Throwable> E expectError(Class<E> expected, long timeoutMillis, String errorMsg) throws Exception {
+      return received.expectError(expected, timeoutMillis, errorMsg);
+    }
+
+    public void expectNone() throws InterruptedException {
+      expectNone(env.defaultTimeoutMillis());
+    }
+
+    public void expectNone(String errMsgPrefix) throws InterruptedException {
+      expectNone(env.defaultTimeoutMillis(), errMsgPrefix);
+    }
+
+    public void expectNone(long withinMillis) throws InterruptedException {
+      expectNone(withinMillis, "Did not expect an element but got element");
+    }
+
+    public void expectNone(long withinMillis, String errMsgPrefix) throws InterruptedException {
+      received.expectNone(withinMillis, errMsgPrefix);
+    }
+
+  }
+
+  public static class ManualSubscriberWithSubscriptionSupport<T> extends ManualSubscriber<T> {
+
+    public ManualSubscriberWithSubscriptionSupport(TestEnvironment env) {
+      super(env);
+    }
+
+    @Override
+    public void onNext(T element) {
+      env.debug(String.format("%s::onNext(%s)", this, element));
+      if (subscription.isCompleted()) {
+        super.onNext(element);
+      } else {
+        env.flop(String.format("Subscriber::onNext(%s) called before Subscriber::onSubscribe", element));
+      }
+    }
+
+    @Override
+    public void onComplete() {
+      env.debug(this + "::onComplete()");
+      if (subscription.isCompleted()) {
+        super.onComplete();
+      } else {
+        env.flop("Subscriber::onComplete() called before Subscriber::onSubscribe");
+      }
+    }
+
+    @Override
+    public void onSubscribe(Subscription s) {
+      env.debug(String.format("%s::onSubscribe(%s)", this, s));
+      if (!subscription.isCompleted()) {
+        subscription.complete(s);
+      } else {
+        env.flop("Subscriber::onSubscribe called on an already-subscribed Subscriber");
+      }
+    }
+
+    @Override
+    public void onError(Throwable cause) {
+      env.debug(String.format("%s::onError(%s)", this, cause));
+      if (subscription.isCompleted()) {
+        super.onError(cause);
+      } else {
+        env.flop(cause, String.format("Subscriber::onError(%s) called before Subscriber::onSubscribe", cause));
+      }
+    }
+  }
+
+  /**
+   * Similar to {@link org.reactivestreams.tck.TestEnvironment.ManualSubscriberWithSubscriptionSupport}
+   * but does not accumulate values signalled via <code>onNext</code>, thus it can not be used to assert
+   * values signalled to this subscriber. Instead it may be used to quickly drain a given publisher.
+   */
+  public static class BlackholeSubscriberWithSubscriptionSupport<T>
+    extends ManualSubscriberWithSubscriptionSupport<T> {
+
+    public BlackholeSubscriberWithSubscriptionSupport(TestEnvironment env) {
+      super(env);
+    }
+
+    @Override
+    public void onNext(T element) {
+      env.debug(String.format("%s::onNext(%s)", this, element));
+      if (!subscription.isCompleted()) {
+        env.flop(String.format("Subscriber::onNext(%s) called before Subscriber::onSubscribe", element));
+      }
+    }
+
+    @Override
+    public T nextElement(long timeoutMillis, String errorMsg) throws InterruptedException {
+      throw new RuntimeException("Can not expect elements from BlackholeSubscriber, use ManualSubscriber instead!");
+    }
+
+    @Override
+    public List<T> nextElements(long elements, long timeoutMillis, String errorMsg) throws InterruptedException {
+      throw new RuntimeException("Can not expect elements from BlackholeSubscriber, use ManualSubscriber instead!");
+    }
+  }
+
+  public static class TestSubscriber<T> implements Subscriber<T> {
+    final Promise<Subscription> subscription;
+
+    protected final TestEnvironment env;
+
+    public TestSubscriber(TestEnvironment env) {
+      this.env = env;
+      subscription = new Promise<Subscription>(env);
+    }
+
+    @Override
+    public void onError(Throwable cause) {
+      env.flop(cause, String.format("Unexpected Subscriber::onError(%s)", cause));
+    }
+
+    @Override
+    public void onComplete() {
+      env.flop("Unexpected Subscriber::onComplete()");
+    }
+
+    @Override
+    public void onNext(T element) {
+      env.flop(String.format("Unexpected Subscriber::onNext(%s)", element));
+    }
+
+    @Override
+    public void onSubscribe(Subscription subscription) {
+      env.flop(String.format("Unexpected Subscriber::onSubscribe(%s)", subscription));
+    }
+
+    public void cancel() {
+      if (subscription.isCompleted()) {
+        subscription.value().cancel();
+      } else {
+        env.flop("Cannot cancel a subscription before having received it");
+      }
+    }
+  }
+
+  public static class ManualPublisher<T> implements Publisher<T> {
+    protected final TestEnvironment env;
+
+    protected long pendingDemand = 0L;
+    protected Promise<Subscriber<? super T>> subscriber;
+
+    protected final Receptacle<Long> requests;
+
+    protected final Latch cancelled;
+
+    public ManualPublisher(TestEnvironment env) {
+      this.env = env;
+      requests = new Receptacle<Long>(env);
+      cancelled = new Latch(env);
+      subscriber = new Promise<Subscriber<? super T>>(this.env);
+    }
+
+    @Override
+    public void subscribe(Subscriber<? super T> s) {
+      if (!subscriber.isCompleted()) {
+        subscriber.completeImmediatly(s);
+
+        Subscription subs = new Subscription() {
+          @Override
+          public void request(long elements) {
+            requests.add(elements);
+          }
+
+          @Override
+          public void cancel() {
+            cancelled.close();
+          }
+        };
+        s.onSubscribe(subs);
+
+      } else {
+        env.flop("TestPublisher doesn't support more than one Subscriber");
+      }
+    }
+
+    public void sendNext(T element) {
+      if (subscriber.isCompleted()) {
+        subscriber.value().onNext(element);
+      } else {
+        env.flop("Cannot sendNext before having a Subscriber");
+      }
+    }
+
+    public void sendCompletion() {
+      if (subscriber.isCompleted()) {
+        subscriber.value().onComplete();
+      } else {
+        env.flop("Cannot sendCompletion before having a Subscriber");
+      }
+    }
+
+    public void sendError(Throwable cause) {
+      if (subscriber.isCompleted()) {
+        subscriber.value().onError(cause);
+      } else {
+        env.flop("Cannot sendError before having a Subscriber");
+      }
+    }
+
+    public long expectRequest() throws InterruptedException {
+      return expectRequest(env.defaultTimeoutMillis());
+    }
+
+    public long expectRequest(long timeoutMillis) throws InterruptedException {
+      long requested = requests.next(timeoutMillis, "Did not receive expected `request` call");
+      if (requested <= 0) {
+        return env.<Long>flopAndFail(String.format("Requests cannot be zero or negative but received request(%s)", requested));
+      } else {
+        pendingDemand += requested;
+        return requested;
+      }
+    }
+
+    public void expectExactRequest(long expected) throws InterruptedException {
+      expectExactRequest(expected, env.defaultTimeoutMillis());
+    }
+
+    public void expectExactRequest(long expected, long timeoutMillis) throws InterruptedException {
+      long requested = expectRequest(timeoutMillis);
+      if (requested != expected) {
+        env.flop(String.format("Received `request(%d)` on upstream but expected `request(%d)`", requested, expected));
+      }
+      pendingDemand += requested;
+    }
+
+    public void expectNoRequest() throws InterruptedException {
+      expectNoRequest(env.defaultTimeoutMillis());
+    }
+
+    public void expectNoRequest(long timeoutMillis) throws InterruptedException {
+      requests.expectNone(timeoutMillis, "Received an unexpected call to: request: ");
+    }
+
+    public void expectCancelling() throws InterruptedException {
+      expectCancelling(env.defaultTimeoutMillis());
+    }
+
+    public void expectCancelling(long timeoutMillis) throws InterruptedException {
+      cancelled.expectClose(timeoutMillis, "Did not receive expected cancelling of upstream subscription");
+    }
+  }
+
+  /**
+   * Like a CountDownLatch, but resettable and with some convenience methods
+   */
+  public static class Latch {
+    private final TestEnvironment env;
+    volatile private CountDownLatch countDownLatch = new CountDownLatch(1);
+
+    public Latch(TestEnvironment env) {
+      this.env = env;
+    }
+
+    public void reOpen() {
+      countDownLatch = new CountDownLatch(1);
+    }
+
+    public boolean isClosed() {
+      return countDownLatch.getCount() == 0;
+    }
+
+    public void close() {
+      countDownLatch.countDown();
+    }
+
+    public void assertClosed(String openErrorMsg) {
+      if (!isClosed()) {
+        env.flop(new ExpectedClosedLatchException(openErrorMsg));
+      }
+    }
+
+    public void assertOpen(String closedErrorMsg) {
+      if (isClosed()) {
+        env.flop(new ExpectedOpenLatchException(closedErrorMsg));
+      }
+    }
+
+    public void expectClose(String notClosedErrorMsg) throws InterruptedException {
+      expectClose(env.defaultTimeoutMillis(), notClosedErrorMsg);
+    }
+
+    public void expectClose(long timeoutMillis, String notClosedErrorMsg) throws InterruptedException {
+      countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
+      if (countDownLatch.getCount() > 0) {
+        env.flop(String.format("%s within %d ms", notClosedErrorMsg, timeoutMillis));
+      }
+    }
+
+    static final class ExpectedOpenLatchException extends RuntimeException {
+      public ExpectedOpenLatchException(String message) {
+        super(message);
+      }
+    }
+
+    static final class ExpectedClosedLatchException extends RuntimeException {
+      public ExpectedClosedLatchException(String message) {
+        super(message);
+      }
+    }
+
+  }
+
+  // simple promise for *one* value, which cannot be reset
+  public static class Promise<T> {
+    private final TestEnvironment env;
+
+    public static <T> Promise<T> completed(TestEnvironment env, T value) {
+      Promise<T> promise = new Promise<T>(env);
+      promise.completeImmediatly(value);
+      return promise;
+    }
+
+    public Promise(TestEnvironment env) {
+      this.env = env;
+    }
+
+    private ArrayBlockingQueue<T> abq = new ArrayBlockingQueue<T>(1);
+    private volatile T _value = null;
+
+    public T value() {
+      if (isCompleted()) {
+        return _value;
+      } else {
+        env.flop("Cannot access promise value before completion");
+        return null;
+      }
+    }
+
+    public boolean isCompleted() {
+      return _value != null;
+    }
+
+    /**
+     * Allows using expectCompletion to await for completion of the value and complete it _then_
+     */
+    public void complete(T value) {
+      abq.add(value);
+    }
+
+    /**
+     * Completes the promise right away, it is not possible to expectCompletion on a Promise completed this way
+     */
+    public void completeImmediatly(T value) {
+      complete(value); // complete!
+      _value = value;  // immediatly!
+    }
+
+    public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException {
+      if (!isCompleted()) {
+        T val = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS);
+
+        if (val == null) {
+          env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis));
+        } else {
+          _value = val;
+        }
+      }
+    }
+  }
+
+  // a "Promise" for multiple values, which also supports "end-of-stream reached"
+  public static class Receptacle<T> {
+    final int QUEUE_SIZE = 2 * TEST_BUFFER_SIZE;
+    private final TestEnvironment env;
+
+    private final ArrayBlockingQueue<Optional<T>> abq = new ArrayBlockingQueue<Optional<T>>(QUEUE_SIZE);
+
+    private final Latch completedLatch;
+
+    Receptacle(TestEnvironment env) {
+      this.env = env;
+      this.completedLatch = new Latch(env);
+    }
+
+    public void add(T value) {
+      completedLatch.assertOpen(String.format("Unexpected element %s received after stream completed", value));
+
+      abq.add(Optional.of(value));
+    }
+
+    public void complete() {
+      completedLatch.assertOpen("Unexpected additional complete signal received!");
+      completedLatch.close();
+
+      abq.add(Optional.<T>empty());
+    }
+
+    public T next(long timeoutMillis, String errorMsg) throws InterruptedException {
+      Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS);
+
+      if (value == null) {
+        return env.flopAndFail(String.format("%s within %d ms", errorMsg, timeoutMillis));
+      } else if (value.isDefined()) {
+        return value.get();
+      } else {
+        return env.flopAndFail("Expected element but got end-of-stream");
+      }
+    }
+
+    public Optional<T> nextOrEndOfStream(long timeoutMillis, String errorMsg) throws InterruptedException {
+      Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS);
+
+      if (value == null) {
+        env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis));
+        return Optional.empty();
+      }
+
+      return value;
+    }
+
+    /**
+     * @param timeoutMillis total timeout time for awaiting all {@code elements} number of elements
+     */
+    public List<T> nextN(long elements, long timeoutMillis, String errorMsg) throws InterruptedException {
+      List<T> result = new LinkedList<T>();
+      long remaining = elements;
+      long deadline = System.currentTimeMillis() + timeoutMillis;
+      while (remaining > 0) {
+        long remainingMillis = deadline - System.currentTimeMillis();
+
+        result.add(next(remainingMillis, errorMsg));
+        remaining--;
+      }
+
+      return result;
+    }
+
+
+    public void expectCompletion(long timeoutMillis, String errorMsg) throws InterruptedException {
+      Optional<T> value = abq.poll(timeoutMillis, TimeUnit.MILLISECONDS);
+
+      if (value == null) {
+        env.flop(String.format("%s within %d ms", errorMsg, timeoutMillis));
+      } else if (value.isDefined()) {
+        env.flop(String.format("Expected end-of-stream but got element [%s]", value.get()));
+      } // else, ok
+    }
+
+    @SuppressWarnings("unchecked")
+    public <E extends Throwable> E expectError(Class<E> clazz, long timeoutMillis, String errorMsg) throws Exception {
+      Thread.sleep(timeoutMillis);
+
+      if (env.asyncErrors.isEmpty()) {
+        return env.flopAndFail(String.format("%s within %d ms", errorMsg, timeoutMillis));
+      } else {
+        // ok, there was an expected error
+        Throwable thrown = env.asyncErrors.remove(0);
+
+        if (clazz.isInstance(thrown)) {
+          return (E) thrown;
+        } else {
+
+          return env.flopAndFail(String.format("%s within %d ms; Got %s but expected %s",
+                                               errorMsg, timeoutMillis, thrown.getClass().getCanonicalName(), clazz.getCanonicalName()));
+        }
+      }
+    }
+
+    public void expectNone(long withinMillis, String errorMsgPrefix) throws InterruptedException {
+      Thread.sleep(withinMillis);
+      Optional<T> value = abq.poll();
+
+      if (value == null) {
+        // ok
+      } else if (value.isDefined()) {
+        env.flop(String.format("%s [%s]", errorMsgPrefix, value.get()));
+      } else {
+        env.flop("Expected no element but got end-of-stream");
+      }
+    }
+  }
+}
+
diff --git a/tck/src/main/java/org/reactivestreams/tck/WithHelperPublisher.java b/tck/src/main/java/org/reactivestreams/tck/WithHelperPublisher.java
new file mode 100644
index 0000000..a2a1653
--- /dev/null
+++ b/tck/src/main/java/org/reactivestreams/tck/WithHelperPublisher.java
@@ -0,0 +1,69 @@
+package org.reactivestreams.tck;
+
+import org.reactivestreams.Publisher;
+import org.reactivestreams.tck.support.Function;
+import org.reactivestreams.tck.support.HelperPublisher;
+import org.reactivestreams.tck.support.InfiniteHelperPublisher;
+
+import java.util.concurrent.ExecutorService;
+
+/**
+ * Type which is able to create elements based on a seed {@code id} value.
+ * <p>
+ * Simplest implementations will simply return the incoming id as the element.
+ *
+ * @param <T> type of element to be delivered to the Subscriber
+ */
+public abstract class WithHelperPublisher<T> {
+
+  /** ExecutorService to be used by the provided helper {@link org.reactivestreams.Publisher} */
+  public abstract ExecutorService publisherExecutorService();
+
+  /**
+   * Implement this method to match your expected element type.
+   * In case of implementing a simple Subscriber which is able to consume any kind of element simply return the
+   * incoming {@code element} element.
+   * <p>
+   * 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 {@code element} as payload of your custom element, such as appending it to a String or other identifier.
+   * <p>
+   * <b>Warning:</b> This method may be called concurrently by the helper publisher, thus it should be implemented in a
+   * thread-safe manner.
+   *
+   * @return element of the matching type {@code T} that will be delivered to the tested Subscriber
+   */
+  public abstract T createElement(int element);
+
+  /**
+   * Helper method required for creating the Publisher to which the tested Subscriber will be subscribed and tested against.
+   * <p>
+   * By default an <b>asynchronously signalling Publisher</b> is provided, which will use {@link #createElement(int)}
+   * to generate elements type your Subscriber is able to consume.
+   * <p>
+   * 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 {@code elements} parameter) and MUST also must treat the following numbers of elements in these specific ways:
+   * <ul>
+   *   <li>
+   *     If {@code elements} is {@code Long.MAX_VALUE} the produced stream must be infinite.
+   *   </li>
+   *   <li>
+   *     If {@code elements} is {@code 0} the {@code Publisher} should signal {@code onComplete} immediatly.
+   *     In other words, it should represent a "completed stream".
+   *   </li>
+   * </ul>
+   */
+  @SuppressWarnings("unchecked")
+  public Publisher<T> createHelperPublisher(long elements) {
+    final Function<Integer, T> mkElement = new Function<Integer, T>() {
+      @Override public T apply(Integer id) throws Throwable {
+        return createElement(id);
+      }
+    };
+
+    if (elements > Integer.MAX_VALUE) return new InfiniteHelperPublisher(mkElement, publisherExecutorService());
+    else return new HelperPublisher(0, (int) elements, mkElement, publisherExecutorService());
+  }
+
+}
diff --git a/tck/src/main/java/org/reactivestreams/tck/support/Function.java b/tck/src/main/java/org/reactivestreams/tck/support/Function.java
new file mode 100644
index 0000000..d053e5c
--- /dev/null
+++ b/tck/src/main/java/org/reactivestreams/tck/support/Function.java
@@ -0,0 +1,5 @@
+package org.reactivestreams.tck.support;
+
+public interface Function<In, Out> {
+  public Out apply(In in) throws Throwable;
+}
\ No newline at end of file
diff --git a/tck/src/main/java/org/reactivestreams/tck/support/HelperPublisher.java b/tck/src/main/java/org/reactivestreams/tck/support/HelperPublisher.java
new file mode 100644
index 0000000..ba0fce6
--- /dev/null
+++ b/tck/src/main/java/org/reactivestreams/tck/support/HelperPublisher.java
@@ -0,0 +1,33 @@
+package org.reactivestreams.tck.support;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.concurrent.Executor;
+import org.reactivestreams.Subscription;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Publisher;
+import org.reactivestreams.example.unicast.AsyncIterablePublisher;
+
+public class HelperPublisher<T> extends AsyncIterablePublisher<T> {
+  
+    public HelperPublisher(final int from, final int to, final Function<Integer, T> create, final Executor executor) {
+        super(new Iterable<T>() {
+          { if(from > to) throw new IllegalArgumentException("from must be equal or greater than to!"); }
+          @Override public Iterator<T> iterator() {
+            return new Iterator<T>() {
+              private int at = from;
+              @Override public boolean hasNext() { return at < to; }
+              @Override public T next() {
+                if (!hasNext()) return Collections.<T>emptyList().iterator().next();
+                else try {
+                  return create.apply(at++);
+                } catch (Throwable t) {
+                  throw new IllegalStateException(String.format("Failed to create element for id %d!", at - 1), t);
+                }
+              }
+              @Override public void remove() { throw new UnsupportedOperationException(); }
+            };
+          }
+        }, executor);
+    }
+}
\ No newline at end of file
diff --git a/tck/src/main/java/org/reactivestreams/tck/support/InfiniteHelperPublisher.java b/tck/src/main/java/org/reactivestreams/tck/support/InfiniteHelperPublisher.java
new file mode 100644
index 0000000..94897e4
--- /dev/null
+++ b/tck/src/main/java/org/reactivestreams/tck/support/InfiniteHelperPublisher.java
@@ -0,0 +1,30 @@
+package org.reactivestreams.tck.support;
+
+import org.reactivestreams.example.unicast.AsyncIterablePublisher;
+
+import java.util.Iterator;
+import java.util.concurrent.Executor;
+
+public class InfiniteHelperPublisher<T> extends AsyncIterablePublisher<T> {
+
+    public InfiniteHelperPublisher(final Function<Integer, T> create, final Executor executor) {
+        super(new Iterable<T>() {
+          @Override public Iterator<T> iterator() {
+            return new Iterator<T>() {
+              private int at = 0;
+
+              @Override public boolean hasNext() { return true; }
+              @Override public T next() {
+                try {
+                  return create.apply(at++); // Wraps around on overflow
+                } catch (Throwable t) {
+                  throw new IllegalStateException(
+                    String.format("Failed to create element in %s for id %s!", getClass().getSimpleName(), at - 1), t);
+                }
+              }
+              @Override public void remove() { throw new UnsupportedOperationException(); }
+            };
+          }
+        }, executor);
+    }
+}
\ No newline at end of file
diff --git a/tck/src/main/java/org/reactivestreams/tck/support/NonFatal.java b/tck/src/main/java/org/reactivestreams/tck/support/NonFatal.java
new file mode 100644
index 0000000..b6dedd4
--- /dev/null
+++ b/tck/src/main/java/org/reactivestreams/tck/support/NonFatal.java
@@ -0,0 +1,32 @@
+package org.reactivestreams.tck.support;
+
+
+/**
+ * Copy of scala.control.util.NonFatal in order to not depend on scala-library
+ */
+public class NonFatal {
+  private NonFatal() {
+    // no instances, please.
+  }
+
+  /**
+   * Returns true if the provided `Throwable` is to be considered non-fatal, or false if it is to be considered fatal
+   *
+   * @param t throwable to be matched for fatal-ness
+   * @return true if is a non-fatal throwable, false otherwise
+   */
+  public static boolean isNonFatal(Throwable t) {
+    if (t instanceof StackOverflowError) {
+      // StackOverflowError ok even though it is a VirtualMachineError
+      return true;
+    } else if (t instanceof VirtualMachineError ||
+        t instanceof ThreadDeath ||
+        t instanceof InterruptedException ||
+        t instanceof LinkageError) {
+      // VirtualMachineError includes OutOfMemoryError and other fatal errors
+      return false;
+    } else {
+      return true;
+    }
+  }
+}
diff --git a/tck/src/main/java/org/reactivestreams/tck/support/Optional.java b/tck/src/main/java/org/reactivestreams/tck/support/Optional.java
new file mode 100644
index 0000000..82caa98
--- /dev/null
+++ b/tck/src/main/java/org/reactivestreams/tck/support/Optional.java
@@ -0,0 +1,69 @@
+package org.reactivestreams.tck.support;
+
+import java.util.NoSuchElementException;
+
+// simplest possible version of Scala's Option type
+public abstract class Optional<T> {
+
+  private static final Optional<Object> NONE = new Optional<Object>() {
+    @Override
+    public Object get() {
+      throw new NoSuchElementException(".get call on None!");
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return true;
+    }
+  };
+
+  private Optional() {
+  }
+
+  @SuppressWarnings("unchecked")
+  public static <T> Optional<T> empty() {
+    return (Optional<T>) NONE;
+  }
+
+  @SuppressWarnings("unchecked")
+  public static <T> Optional<T> of(T it) {
+    if (it == null) return (Optional<T>) Optional.NONE;
+    else return new Some(it);
+  }
+
+  public abstract T get();
+
+  public abstract boolean isEmpty();
+
+  public boolean isDefined() {
+    return !isEmpty();
+  }
+
+  public static class Some<T> extends Optional<T> {
+    private final T value;
+
+    Some(T value) {
+      this.value = value;
+    }
+
+    @Override
+    public T get() {
+      return value;
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return false;
+    }
+
+    @Override
+    public String toString() {
+      return String.format("Some(%s)", value);
+    }
+  }
+
+  @Override
+  public String toString() {
+    return "None";
+  }
+}
diff --git a/tck/src/main/java/org/reactivestreams/tck/support/PublisherVerificationRules.java b/tck/src/main/java/org/reactivestreams/tck/support/PublisherVerificationRules.java
new file mode 100644
index 0000000..a6c67dc
--- /dev/null
+++ b/tck/src/main/java/org/reactivestreams/tck/support/PublisherVerificationRules.java
@@ -0,0 +1,45 @@
+package org.reactivestreams.tck.support;
+
+
+/**
+ * Internal TCK use only.
+ * Add / Remove tests for PublisherVerification here to make sure that they arre added/removed in the other places.
+ */
+public interface PublisherVerificationRules {
+  void required_validate_maxElementsFromPublisher() throws Exception;
+  void required_validate_boundedDepthOfOnNextAndRequestRecursion() throws Exception;
+  void required_createPublisher1MustProduceAStreamOfExactly1Element() throws Throwable;
+  void required_createPublisher3MustProduceAStreamOfExactly3Elements() throws Throwable;
+  void required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements() throws Throwable;
+  void required_spec102_maySignalLessThanRequestedAndTerminateSubscription() throws Throwable;
+  void stochastic_spec103_mustSignalOnMethodsSequentially() throws Throwable;
+  void optional_spec104_mustSignalOnErrorWhenFails() throws Throwable;
+  void required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates() throws Throwable;
+  void optional_spec105_emptyStreamMustTerminateBySignallingOnComplete() throws Throwable;
+  void untested_spec106_mustConsiderSubscriptionCancelledAfterOnErrorOrOnCompleteHasBeenCalled() throws Throwable;
+  void required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled() throws Throwable;
+  void untested_spec107_mustNotEmitFurtherSignalsOnceOnErrorHasBeenSignalled() throws Throwable;
+  void untested_spec108_possiblyCanceledSubscriptionShouldNotReceiveOnErrorOrOnCompleteSignals() throws Throwable;
+  void required_spec109_mustIssueOnSubscribeForNonNullSubscriber() throws Throwable;
+  void untested_spec109_subscribeShouldNotThrowNonFatalThrowable() throws Throwable;
+  void required_spec109_subscribeThrowNPEOnNullSubscriber() throws Throwable;
+  void required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe() throws Throwable;
+  void untested_spec110_rejectASubscriptionRequestIfTheSameSubscriberSubscribesTwice() throws Throwable;
+  void optional_spec111_maySupportMultiSubscribe() throws Throwable;
+  void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingOneByOne() throws Throwable;
+  void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfront() throws Throwable;
+  void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfrontAndCompleteAsExpected() throws Throwable;
+  void required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe() throws Throwable;
+  void required_spec303_mustNotAllowUnboundedRecursion() throws Throwable;
+  void untested_spec304_requestShouldNotPerformHeavyComputations() throws Exception;
+  void untested_spec305_cancelMustNotSynchronouslyPerformHeavyCompuatation() throws Exception;
+  void required_spec306_afterSubscriptionIsCancelledRequestMustBeNops() throws Throwable;
+  void required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops() throws Throwable;
+  void required_spec309_requestZeroMustSignalIllegalArgumentException() throws Throwable;
+  void required_spec309_requestNegativeNumberMustSignalIllegalArgumentException() throws Throwable;
+  void required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling() throws Throwable;
+  void required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber() throws Throwable;
+  void required_spec317_mustSupportAPendingElementCountUpToLongMaxValue() throws Throwable;
+  void required_spec317_mustSupportACumulativePendingElementCountUpToLongMaxValue() throws Throwable;
+  void required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue() throws Throwable;
+}
diff --git a/tck/src/main/java/org/reactivestreams/tck/support/SubscriberBlackboxVerificationRules.java b/tck/src/main/java/org/reactivestreams/tck/support/SubscriberBlackboxVerificationRules.java
new file mode 100644
index 0000000..463f225
--- /dev/null
+++ b/tck/src/main/java/org/reactivestreams/tck/support/SubscriberBlackboxVerificationRules.java
@@ -0,0 +1,33 @@
+package org.reactivestreams.tck.support;
+
+/**
+ * Internal TCK use only.
+ * Add / Remove tests for SubscriberBlackboxVerification here to make sure that they arre added/removed in the other places.
+ */
+public interface SubscriberBlackboxVerificationRules {
+  void required_spec201_blackbox_mustSignalDemandViaSubscriptionRequest() throws Throwable;
+  void untested_spec202_blackbox_shouldAsynchronouslyDispatch() throws Exception;
+  void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() throws Throwable;
+  void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() throws Throwable;
+  void untested_spec204_blackbox_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() throws Exception;
+  void required_spec205_blackbox_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() throws Exception;
+  void untested_spec206_blackbox_mustCallSubscriptionCancelIfItIsNoLongerValid() throws Exception;
+  void untested_spec207_blackbox_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() throws Exception;
+  void untested_spec208_blackbox_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() throws Throwable;
+  void required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() throws Throwable;
+  void required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() throws Throwable;
+  void required_spec210_blackbox_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() throws Throwable;
+  void untested_spec211_blackbox_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() throws Exception;
+  void untested_spec212_blackbox_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality() throws Throwable;
+  void untested_spec213_blackbox_failingOnSignalInvocation() throws Exception;
+  void required_spec213_blackbox_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable;
+  void required_spec213_blackbox_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable;
+  void required_spec213_blackbox_onError_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable;
+  void untested_spec301_blackbox_mustNotBeCalledOutsideSubscriberContext() throws Exception;
+  void untested_spec308_blackbox_requestMustRegisterGivenNumberElementsToBeProduced() throws Throwable;
+  void untested_spec310_blackbox_requestMaySynchronouslyCallOnNextOnSubscriber() throws Exception;
+  void untested_spec311_blackbox_requestMaySynchronouslyCallOnCompleteOrOnError() throws Exception;
+  void untested_spec314_blackbox_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() throws Exception;
+  void untested_spec315_blackbox_cancelMustNotThrowExceptionAndMustSignalOnError() throws Exception;
+  void untested_spec316_blackbox_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() throws Exception;
+}
\ No newline at end of file
diff --git a/tck/src/main/java/org/reactivestreams/tck/support/SubscriberBufferOverflowException.java b/tck/src/main/java/org/reactivestreams/tck/support/SubscriberBufferOverflowException.java
new file mode 100644
index 0000000..c7150cf
--- /dev/null
+++ b/tck/src/main/java/org/reactivestreams/tck/support/SubscriberBufferOverflowException.java
@@ -0,0 +1,18 @@
+package org.reactivestreams.tck.support;
+
+public final class SubscriberBufferOverflowException extends RuntimeException {
+  public SubscriberBufferOverflowException() {
+  }
+
+  public SubscriberBufferOverflowException(String message) {
+    super(message);
+  }
+
+  public SubscriberBufferOverflowException(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+  public SubscriberBufferOverflowException(Throwable cause) {
+    super(cause);
+  }
+}
diff --git a/tck/src/main/java/org/reactivestreams/tck/support/SubscriberWhiteboxVerificationRules.java b/tck/src/main/java/org/reactivestreams/tck/support/SubscriberWhiteboxVerificationRules.java
new file mode 100644
index 0000000..60d9df2
--- /dev/null
+++ b/tck/src/main/java/org/reactivestreams/tck/support/SubscriberWhiteboxVerificationRules.java
@@ -0,0 +1,35 @@
+package org.reactivestreams.tck.support;
+
+/**
+ * Internal TCK use only.
+ * Add / Remove tests for PublisherVerificaSubscriberWhiteboxVerification here to make sure that they arre added/removed in the other places.
+ */
+public interface SubscriberWhiteboxVerificationRules {
+  void required_exerciseWhiteboxHappyPath() throws Throwable;
+  void required_spec201_mustSignalDemandViaSubscriptionRequest() throws Throwable;
+  void untested_spec202_shouldAsynchronouslyDispatch() throws Exception;
+  void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() throws Throwable;
+  void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() throws Throwable;
+  void untested_spec204_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() throws Exception;
+  void required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() throws Throwable;
+  void untested_spec206_mustCallSubscriptionCancelIfItIsNoLongerValid() throws Exception;
+  void untested_spec207_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() throws Exception;
+  void required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() throws Throwable;
+  void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() throws Throwable;
+  void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() throws Throwable;
+  void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() throws Throwable;
+  void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall() throws Throwable;
+  void untested_spec211_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() throws Exception;
+  void untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation() throws Throwable;
+  void untested_spec213_failingOnSignalInvocation() throws Exception;
+  void required_spec213_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable;
+  void required_spec213_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable;
+  void required_spec213_onError_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable;
+  void untested_spec301_mustNotBeCalledOutsideSubscriberContext() throws Exception;
+  void required_spec308_requestMustRegisterGivenNumberElementsToBeProduced() throws Throwable;
+  void untested_spec310_requestMaySynchronouslyCallOnNextOnSubscriber() throws Exception;
+  void untested_spec311_requestMaySynchronouslyCallOnCompleteOrOnError() throws Exception;
+  void untested_spec314_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() throws Exception;
+  void untested_spec315_cancelMustNotThrowExceptionAndMustSignalOnError() throws Exception;
+  void untested_spec316_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() throws Exception;
+}
\ No newline at end of file
diff --git a/tck/src/main/java/org/reactivestreams/tck/support/TestException.java b/tck/src/main/java/org/reactivestreams/tck/support/TestException.java
new file mode 100644
index 0000000..21e13ca
--- /dev/null
+++ b/tck/src/main/java/org/reactivestreams/tck/support/TestException.java
@@ -0,0 +1,11 @@
+package org.reactivestreams.tck.support;
+
+/**
+ * Exception used by the TCK to signal failures.
+ * May be thrown or signalled through {@link org.reactivestreams.Subscriber#onError(Throwable)}.
+ */
+public final class TestException extends RuntimeException {
+  public TestException() {
+    super("Test Exception: Boom!");
+  }
+}
diff --git a/tck/src/test/java/org/reactivestreams/tck/EmptyLazyPublisherTest.java b/tck/src/test/java/org/reactivestreams/tck/EmptyLazyPublisherTest.java
new file mode 100644
index 0000000..0c22486
--- /dev/null
+++ b/tck/src/test/java/org/reactivestreams/tck/EmptyLazyPublisherTest.java
@@ -0,0 +1,42 @@
+package org.reactivestreams.tck;
+
+import org.reactivestreams.example.unicast.AsyncIterablePublisher;
+import org.reactivestreams.Publisher;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.util.Collections;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+ at Test
+public class EmptyLazyPublisherTest extends PublisherVerification<Integer> {
+
+  private ExecutorService ex;
+
+  public EmptyLazyPublisherTest() {
+    super(new TestEnvironment());
+  }
+
+  @BeforeClass
+  void before() { ex = Executors.newFixedThreadPool(4); }
+
+  @AfterClass
+  void after() { if (ex != null) ex.shutdown(); }
+
+  @Override
+  public Publisher<Integer> createPublisher(long elements) {
+    return new AsyncIterablePublisher<Integer>(Collections.<Integer>emptyList(), ex);
+  }
+
+  @Override
+  public Publisher<Integer> createFailedPublisher() {
+    return null;
+  }
+
+  @Override
+  public long maxElementsFromPublisher() {
+    return 0;
+  }
+}
\ No newline at end of file
diff --git a/tck/src/test/java/org/reactivestreams/tck/IdentityProcessorVerificationDelegationTest.java b/tck/src/test/java/org/reactivestreams/tck/IdentityProcessorVerificationDelegationTest.java
new file mode 100644
index 0000000..e37eca5
--- /dev/null
+++ b/tck/src/test/java/org/reactivestreams/tck/IdentityProcessorVerificationDelegationTest.java
@@ -0,0 +1,75 @@
+package org.reactivestreams.tck;
+
+import org.testng.annotations.Test;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.testng.AssertJUnit.assertTrue;
+
+/**
+ * The {@link org.reactivestreams.tck.IdentityProcessorVerification} must also run all tests from
+ * {@link org.reactivestreams.tck.PublisherVerification} and {@link org.reactivestreams.tck.SubscriberWhiteboxVerification}.
+ *
+ * Since in Java this can be only achieved by delegating, we need to make sure we delegate to each of the tests,
+ * so that if in the future we add more tests to these verifications we're sure to not forget to add the delegating methods.
+ */
+public class IdentityProcessorVerificationDelegationTest {
+
+  @Test
+  public void shouldIncludeAllTestsFromPublisherVerification() throws Exception {
+    // given
+    List<String> processorTests = getTestNames(IdentityProcessorVerification.class);
+    Class<PublisherVerification> delegatedToClass = PublisherVerification.class;
+
+    // when
+    List<String> publisherTests = getTestNames(delegatedToClass);
+
+    // then
+    assertSuiteDelegatedAllTests(IdentityProcessorVerification.class, processorTests, delegatedToClass, publisherTests);
+  }
+
+  @Test
+  public void shouldIncludeAllTestsFromSubscriberVerification() throws Exception {
+    // given
+    List<String> processorTests = getTestNames(IdentityProcessorVerification.class);
+    Class<SubscriberWhiteboxVerification> delegatedToClass = SubscriberWhiteboxVerification.class;
+
+    // when
+    List<String> publisherTests = getTestNames(delegatedToClass);
+
+    // then
+    assertSuiteDelegatedAllTests(IdentityProcessorVerification.class, processorTests, delegatedToClass, publisherTests);
+  }
+
+  private void assertSuiteDelegatedAllTests(Class<?> delegatingFrom, List<String> allTests, Class<?> targetClass, List<String> delegatedToTests) {
+    for (String targetTest : delegatedToTests) {
+      String msg = String.format(
+          "Test '%s' in '%s' has not been properly delegated to in aggregate '%s'! \n" +
+              "You must delegate to this test from %s, like this: \n" +
+              "@Test public void %s() throws Exception { delegate%s.%s(); }",
+          targetTest, targetClass, delegatingFrom,
+          delegatingFrom,
+          targetTest, targetClass.getSimpleName(), targetTest);
+
+      assertTrue(msg, testsInclude(allTests, targetTest));
+    }
+  }
+
+
+  private boolean testsInclude(List<String> processorTests, String publisherTest) {
+    return processorTests.contains(publisherTest);
+  }
+
+  private List<String> getTestNames(Class<?> clazz) {
+    List<String> tests = new ArrayList<String>();
+    for (Method method : clazz.getDeclaredMethods()) {
+      if (method.isAnnotationPresent(Test.class)) {
+        tests.add(method.getName());
+      }
+    }
+
+    return tests;
+  }
+}
diff --git a/tck/src/test/java/org/reactivestreams/tck/IdentityProcessorVerificationTest.java b/tck/src/test/java/org/reactivestreams/tck/IdentityProcessorVerificationTest.java
new file mode 100644
index 0000000..2f4c025
--- /dev/null
+++ b/tck/src/test/java/org/reactivestreams/tck/IdentityProcessorVerificationTest.java
@@ -0,0 +1,162 @@
+package org.reactivestreams.tck;
+
+import org.reactivestreams.Processor;
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import org.reactivestreams.tck.support.TCKVerificationSupport;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+* Validates that the TCK's {@link IdentityProcessorVerification} fails with nice human readable errors.
+* <b>Important: Please note that all Processors implemented in this file are *wrong*!</b>
+*/
+public class IdentityProcessorVerificationTest extends TCKVerificationSupport {
+
+  static final long DEFAULT_TIMEOUT_MILLIS = TestEnvironment.envDefaultTimeoutMillis();
+
+  private ExecutorService ex;
+  @BeforeClass void before() { ex = Executors.newFixedThreadPool(4); }
+  @AfterClass void after() { if (ex != null) ex.shutdown(); }
+
+  @Test
+  public void required_spec104_mustCallOnErrorOnAllItsSubscribersIfItEncountersANonRecoverableError_shouldBeIgnored() throws Throwable {
+    requireTestSkip(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        new IdentityProcessorVerification<Integer>(newTestEnvironment(), DEFAULT_TIMEOUT_MILLIS){
+          @Override public Processor<Integer, Integer> createIdentityProcessor(int bufferSize) {
+            return new NoopProcessor();
+          }
+
+          @Override public ExecutorService publisherExecutorService() { return ex; }
+
+          @Override public Integer createElement(int element) { return element; }
+
+          @Override public Publisher<Integer> createHelperPublisher(long elements) {
+            return SKIP;
+          }
+
+          @Override public Publisher<Integer> createFailedPublisher() {
+            return SKIP;
+          }
+
+          @Override public long maxSupportedSubscribers() {
+            return 1; // can only support 1 subscribe => unable to run this test
+          }
+        }.required_spec104_mustCallOnErrorOnAllItsSubscribersIfItEncountersANonRecoverableError();
+      }
+    }, "The Publisher under test only supports 1 subscribers, while this test requires at least 2 to run");
+  }
+
+  @Test
+  public void required_spec104_mustCallOnErrorOnAllItsSubscribersIfItEncountersANonRecoverableError_shouldFailWhileWaitingForOnError() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        new IdentityProcessorVerification<Integer>(newTestEnvironment(), DEFAULT_TIMEOUT_MILLIS) {
+          @Override public Processor<Integer, Integer> createIdentityProcessor(int bufferSize) {
+            return new Processor<Integer, Integer>() {
+              @Override public void subscribe(final Subscriber<? super Integer> s) {
+                s.onSubscribe(new Subscription() {
+                  @Override public void request(long n) {
+                    s.onNext(0);
+                  }
+
+                  @Override public void cancel() {
+                  }
+                });
+              }
+
+              @Override public void onSubscribe(Subscription s) {
+                s.request(1);
+              }
+
+              @Override public void onNext(Integer integer) {
+                // noop
+              }
+
+              @Override public void onError(Throwable t) {
+                // noop
+              }
+
+              @Override public void onComplete() {
+                // noop
+              }
+            };
+          }
+
+          @Override public ExecutorService publisherExecutorService() { return ex; }
+
+          @Override public Integer createElement(int element) { return element; }
+
+          @Override public Publisher<Integer> createHelperPublisher(long elements) {
+            return new Publisher<Integer>() {
+              @Override public void subscribe(final Subscriber<? super Integer> s) {
+                s.onSubscribe(new NoopSubscription() {
+                  @Override public void request(long n) {
+                    for (int i = 0; i < 10; i++) {
+                      s.onNext(i);
+                    }
+                  }
+                });
+              }
+            };
+          }
+
+          @Override public Publisher<Integer> createFailedPublisher() {
+            return SKIP;
+          }
+        }.required_spec104_mustCallOnErrorOnAllItsSubscribersIfItEncountersANonRecoverableError();
+      }
+    }, "Did not receive expected error on downstream within " + DEFAULT_TIMEOUT_MILLIS);
+  }
+
+  // FAILING IMPLEMENTATIONS //
+
+  final Publisher<Integer> SKIP = null;
+
+  /** Subscription which does nothing. */
+  static class NoopSubscription implements Subscription {
+
+    @Override public void request(long n) {
+      // noop
+    }
+
+    @Override public void cancel() {
+      // noop
+    }
+  }
+
+  static class NoopProcessor implements Processor<Integer, Integer> {
+
+    @Override public void subscribe(Subscriber<? super Integer> s) {
+      s.onSubscribe(new NoopSubscription());
+    }
+
+    @Override public void onSubscribe(Subscription s) {
+      // noop
+    }
+
+    @Override public void onNext(Integer integer) {
+      // noop
+    }
+
+    @Override public void onError(Throwable t) {
+      // noop
+    }
+
+    @Override public void onComplete() {
+      // noop
+    }
+  }
+
+  private TestEnvironment newTestEnvironment() {
+    return new TestEnvironment(DEFAULT_TIMEOUT_MILLIS);
+  }
+
+
+}
diff --git a/tck/src/test/java/org/reactivestreams/tck/PublisherVerificationTest.java b/tck/src/test/java/org/reactivestreams/tck/PublisherVerificationTest.java
new file mode 100644
index 0000000..4b22b58
--- /dev/null
+++ b/tck/src/test/java/org/reactivestreams/tck/PublisherVerificationTest.java
@@ -0,0 +1,829 @@
+package org.reactivestreams.tck;
+
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import org.reactivestreams.tck.support.TCKVerificationSupport;
+import org.reactivestreams.tck.support.TestException;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.util.Random;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+* Validates that the TCK's {@link org.reactivestreams.tck.PublisherVerification} fails with nice human readable errors.
+* <b>Important: Please note that all Publishers implemented in this file are *wrong*!</b>
+*/
+public class PublisherVerificationTest extends TCKVerificationSupport {
+
+  @Test
+  public void required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements_shouldFailBy_ExpectingOnError() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        noopPublisherVerification().required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements();
+      }
+    }, "produced no element after first");
+  }
+
+  @Test
+  public void required_spec102_maySignalLessThanRequestedAndTerminateSubscription_shouldFailBy_notReceivingAnyElement() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        noopPublisherVerification().required_spec102_maySignalLessThanRequestedAndTerminateSubscription();
+      }
+    }, "Did not receive expected element");
+  }
+
+  @Test
+  public void required_spec102_maySignalLessThanRequestedAndTerminateSubscription_shouldFailBy_receivingTooManyElements() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        demandIgnoringSynchronousPublisherVerification().required_spec102_maySignalLessThanRequestedAndTerminateSubscription();
+      }
+    }, "Expected end-of-stream but got element [3]");
+  }
+
+  @Test
+  public void stochastic_spec103_mustSignalOnMethodsSequentially_shouldFailBy_concurrentlyAccessingOnNext() throws Throwable {
+    final AtomicInteger startedSignallingThreads = new AtomicInteger(0);
+    // this is an arbitrary number, we just need "many threads" to try to force an concurrent access scenario
+    final int maxSignallingThreads = 10;
+
+    final ExecutorService signallersPool = Executors.newFixedThreadPool(maxSignallingThreads);
+    final AtomicBoolean concurrentAccessCaused = new AtomicBoolean(false);
+
+    // highly specialised threadpool driven publisher which aims to FORCE concurrent access,
+    // so that we can confirm the test is able to catch this.
+    final Publisher<Integer> concurrentAccessPublisher = new Publisher<Integer>() {
+      @Override public void subscribe(final Subscriber<? super Integer> s) {
+        s.onSubscribe(new NoopSubscription() {
+          @Override public void request(final long n) {
+            Runnable signalling = new Runnable() {
+
+              @Override public void run() {
+                for (long i = 0; i < n; i++) {
+                  try {
+                    // shutdown cleanly in when the threadpool is shutting down
+                    if (Thread.interrupted()) {
+                      return;
+                    }
+
+                    s.onNext((int) i);
+                  } catch (Exception ex) {
+                    // signal others to shut down
+                    signallersPool.shutdownNow();
+
+                    if (ex instanceof TestEnvironment.Latch.ExpectedOpenLatchException) {
+                      if (!concurrentAccessCaused.getAndSet(true)) {
+                        throw new RuntimeException("Concurrent access detected", ex);
+                      } else {
+                        // error signalled once already, stop more errors from propagating
+                        return;
+                      }
+                    } else {
+                      throw new RuntimeException(ex);
+                    }
+                  }
+                }
+              }
+            };
+
+            // must be guarded like this in case a Subscriber triggers request() synchronously from it's onNext()
+            while (startedSignallingThreads.getAndAdd(1) < maxSignallingThreads && !signallersPool.isShutdown()) {
+              try {
+                signallersPool.execute(signalling);
+              } catch (RejectedExecutionException ex) {
+                // ignore, should be safe as it means the pool is shutting down -> which means we triggered the problem we wanted to
+                return;
+              }
+            }
+          }
+        });
+      }
+    };
+
+    try {
+      requireTestFailure(new ThrowingRunnable() {
+        @Override public void run() throws Throwable {
+          customPublisherVerification(concurrentAccessPublisher).stochastic_spec103_mustSignalOnMethodsSequentially();
+        }
+      }, "Illegal concurrent access detected");
+    } finally {
+      signallersPool.shutdownNow();
+      signallersPool.awaitTermination(1, TimeUnit.SECONDS);
+    }
+  }
+
+  @Test
+  public void stochastic_spec103_mustSignalOnMethodsSequentially_shouldPass_forSynchronousPublisher() throws Throwable {
+    customPublisherVerification(new Publisher<Integer>() {
+      @Override public void subscribe(final Subscriber<? super Integer> s) {
+        s.onSubscribe(new NoopSubscription() {
+          int element = 0;
+          @Override public void request(long n) {
+            for (int i = 0; i < n; i++) {
+              s.onNext(element++);
+            }
+            s.onComplete();
+          }
+        });
+      }
+    }).stochastic_spec103_mustSignalOnMethodsSequentially();
+  }
+
+  @Test
+  public void optional_spec104_mustSignalOnErrorWhenFails_shouldFail() throws Throwable {
+    final Publisher<Integer> invalidErrorPublisher = new Publisher<Integer>() {
+      @Override public void subscribe(Subscriber<? super Integer> s) {
+        throw new RuntimeException("It is not valid to throw here!");
+      }
+    };
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customPublisherVerification(SKIP, invalidErrorPublisher).optional_spec104_mustSignalOnErrorWhenFails();
+      }
+    }, "Publisher threw exception (It is not valid to throw here!) instead of signalling error via onError!");
+  }
+
+  @Test
+  public void optional_spec104_mustSignalOnErrorWhenFails_shouldBeSkippedWhenNoErrorPublisherGiven() throws Throwable {
+    requireTestSkip(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        noopPublisherVerification().optional_spec104_mustSignalOnErrorWhenFails();
+      }
+    }, PublisherVerification.SKIPPING_NO_ERROR_PUBLISHER_AVAILABLE);
+  }
+
+  @Test
+  public void required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates_shouldFail() throws Throwable {
+    final Publisher<Integer> forgotToSignalCompletionPublisher = new Publisher<Integer>() {
+      @Override public void subscribe(final Subscriber<? super Integer> s) {
+        s.onSubscribe(new NoopSubscription() {
+          int signal = 0;
+
+          @Override public void request(long n) {
+            for (int i = 0; i < n; i++) {
+              s.onNext(signal);
+              signal += 1;
+            }
+            // intentional omission of onComplete
+          }
+        });
+      }
+    };
+
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customPublisherVerification(forgotToSignalCompletionPublisher).required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates();
+      }
+    }, "Expected end-of-stream but got element [3]");
+  }
+
+  @Test
+  public void optional_spec105_emptyStreamMustTerminateBySignallingOnComplete_shouldNotAllowEagerOnComplete() throws Throwable {
+    final Publisher<Integer> illegalEmptyEagerOnCompletePublisher = new Publisher<Integer>() {
+      @Override public void subscribe(final Subscriber<? super Integer> s) {
+        s.onComplete();
+      }
+    };
+
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        PublisherVerification<Integer> verification = new PublisherVerification<Integer>(newTestEnvironment()) {
+          @Override public Publisher<Integer> createPublisher(long elements) {
+            return illegalEmptyEagerOnCompletePublisher;
+          }
+
+          @Override public long maxElementsFromPublisher() {
+            return 0; // it is an "empty" Publisher
+          }
+
+          @Override public Publisher<Integer> createFailedPublisher() {
+            return null;
+          }
+        };
+
+        verification.optional_spec105_emptyStreamMustTerminateBySignallingOnComplete();
+      }
+    }, "Subscriber::onComplete() called before Subscriber::onSubscribe");
+  }
+
+  @Test
+  public void required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled_shouldFailForNotCompletingPublisher() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        demandIgnoringSynchronousPublisherVerification().required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled();
+      }
+    }, "Expected end-of-stream but got element [" /* element which should not have been signalled */);
+  }
+
+  @Test
+  public void required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled_shouldFailForPublisherWhichCompletesButKeepsServingData() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customPublisherVerification(new Publisher<Integer>() {
+          @Override public void subscribe(final Subscriber<? super Integer> s) {
+            s.onSubscribe(new NoopSubscription() {
+
+              boolean completed = false;
+
+              @Override public void request(long n) {
+                // emit one element
+                s.onNext(0);
+
+                // and "complete"
+                // but keep signalling data if more demand comes in anyway!
+                if (!completed) {
+                  s.onComplete();
+                }
+
+              }
+            });
+          }
+        }).required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled();
+      }
+    }, "Unexpected element 0 received after stream completed");
+  }
+
+  @Test
+  public void required_spec109_subscribeThrowNPEOnNullSubscriber_shouldFailIfDoesntThrowNPE() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customPublisherVerification(new Publisher<Integer>() {
+          @Override public void subscribe(final Subscriber<? super Integer> s) {
+
+          }
+        }).required_spec109_subscribeThrowNPEOnNullSubscriber();
+      }
+    }, "Publisher did not throw a NullPointerException when given a null Subscribe in subscribe");
+  }
+
+  @Test
+  public void required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe_actuallyPass() throws Throwable {
+    customPublisherVerification(SKIP, new Publisher<Integer>() {
+      @Override public void subscribe(Subscriber<? super Integer> s) {
+        s.onSubscribe(new NoopSubscription());
+        s.onError(new RuntimeException("Sorry, I'm busy now. Call me later."));
+      }
+    }).required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe();
+  }
+
+  @Test
+  public void required_spec109_mustIssueOnSubscribeForNonNullSubscriber_mustFailIfOnCompleteHappensFirst() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customPublisherVerification(new Publisher<Integer>() {
+          @Override
+          public void subscribe(Subscriber<? super Integer> s) {
+            s.onComplete();
+          }
+        }).required_spec109_mustIssueOnSubscribeForNonNullSubscriber();
+      }
+    }, "onSubscribe should be called prior to onComplete always");
+  }
+
+  @Test
+  public void required_spec109_mustIssueOnSubscribeForNonNullSubscriber_mustFailIfOnNextHappensFirst() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customPublisherVerification(new Publisher<Integer>() {
+          @Override public void subscribe(Subscriber<? super Integer> s) {
+            s.onNext(1337);
+          }
+        }).required_spec109_mustIssueOnSubscribeForNonNullSubscriber();
+      }
+    }, "onSubscribe should be called prior to onNext always");
+  }
+
+  @Test
+  public void required_spec109_mustIssueOnSubscribeForNonNullSubscriber_mustFailIfOnErrorHappensFirst() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customPublisherVerification(new Publisher<Integer>() {
+          @Override public void subscribe(Subscriber<? super Integer> s) {
+            s.onError(new TestException());
+          }
+        }).required_spec109_mustIssueOnSubscribeForNonNullSubscriber();
+      }
+    }, "onSubscribe should be called prior to onError always");
+  }
+
+  @Test
+  public void required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe_shouldFail() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override
+      public void run() throws Throwable {
+        customPublisherVerification(SKIP, new Publisher<Integer>() {
+          @Override
+          public void subscribe(Subscriber<? super Integer> s) {
+            s.onSubscribe(new NoopSubscription());
+          }
+        }).required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe();
+      }
+    }, "Should have received onError");
+  }
+
+  @Test
+  public void required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe_beSkippedForNoGivenErrorPublisher() throws Throwable {
+    requireTestSkip(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        noopPublisherVerification().required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe();
+      }
+    }, PublisherVerification.SKIPPING_NO_ERROR_PUBLISHER_AVAILABLE);
+  }
+
+  @Test
+  public void untested_spec110_rejectASubscriptionRequestIfTheSameSubscriberSubscribesTwice_shouldFailBy_skippingSinceOptional() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        noopPublisherVerification().untested_spec110_rejectASubscriptionRequestIfTheSameSubscriberSubscribesTwice();
+      }
+    }, "Not verified by this TCK.");
+  }
+
+  @Test
+  public void optional_spec111_maySupportMultiSubscribe_shouldFailBy_actuallyPass() throws Throwable {
+    noopPublisherVerification().optional_spec111_maySupportMultiSubscribe();
+  }
+
+  @Test
+  public void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfront_shouldFailBy_expectingOnError() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customPublisherVerification(new Publisher<Integer>() {
+          @Override public void subscribe(final Subscriber<? super Integer> s) {
+            s.onSubscribe(new Subscription() {
+              final Random rnd = new Random();
+              @Override public void request(long n) {
+                for (int i = 0; i < n; i++) {
+                  s.onNext(rnd.nextInt());
+                }
+              }
+
+              @Override public void cancel() {
+
+              }
+            });
+          }
+        }).optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfront();
+      }
+    }, "Expected elements to be signaled in the same sequence to 1st and 2nd subscribers: Lists differ at element ");
+  }
+
+
+  @Test
+  public void required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe_shouldFailBy_reportingAsyncError() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        onErroringPublisherVerification().required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe();
+      }
+    }, "Async error during test execution: Test Exception: Boom!");
+  }
+
+  @Test
+  public void required_spec303_mustNotAllowUnboundedRecursion_shouldFailBy_informingAboutTooDeepStack() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customPublisherVerification(new Publisher<Integer>() {
+          @Override public void subscribe(final Subscriber<? super Integer> s) {
+            s.onSubscribe(new Subscription() {
+              @Override public void request(long n) {
+                s.onNext(0); // naive reccursive call, would explode with StackOverflowException
+              }
+
+              @Override public void cancel() {
+                // noop
+              }
+            });
+          }
+        }).required_spec303_mustNotAllowUnboundedRecursion();
+      }
+    }, /* Got 2 onNext calls within thread: ... */ "yet expected recursive bound was 1");
+  }
+
+  @Test
+  public void required_spec306_afterSubscriptionIsCancelledRequestMustBeNops_shouldFailBy_unexpectedElement() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        demandIgnoringSynchronousPublisherVerification().required_spec306_afterSubscriptionIsCancelledRequestMustBeNops();
+      }
+    }, "Did not expect an element but got element [0]");
+  }
+
+  @Test
+  public void required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops_shouldPass() throws Throwable {
+    demandIgnoringSynchronousPublisherVerification().required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops();
+  }
+
+  @Test
+  public void required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops_shouldFailBy_unexpectedErrorInCancelling() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customPublisherVerification(new Publisher<Integer>() {
+          @Override public void subscribe(final Subscriber<? super Integer> s) {
+            s.onSubscribe(new Subscription() {
+              @Override public void request(long n) {
+                // noop
+              }
+
+              @Override public void cancel() {
+                s.onError(new TestException()); // illegal error signalling!
+              }
+            });
+          }
+        }).required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops();
+      }
+    }, "Async error during test execution: Test Exception: Boom!");
+  }
+
+  @Test
+  public void required_spec309_requestZeroMustSignalIllegalArgumentException_shouldFailBy_expectingOnError() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        noopPublisherVerification().required_spec309_requestZeroMustSignalIllegalArgumentException();
+      }
+    }, "Expected onError");
+  }
+
+  @Test
+  public void required_spec309_requestNegativeNumberMustSignalIllegalArgumentException_shouldFailBy_expectingOnError() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        noopPublisherVerification().required_spec309_requestNegativeNumberMustSignalIllegalArgumentException();
+      }
+    }, "Expected onError");
+  }
+
+  @Test
+  public void required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling_shouldFailBy_havingEmitedMoreThanRequested() throws Throwable {
+    final ExecutorService pool = Executors.newFixedThreadPool(2);
+
+    try {
+      requireTestFailure(new ThrowingRunnable() {
+        @Override public void run() throws Throwable {
+          demandIgnoringAsynchronousPublisherVerification(pool).required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling();
+        }
+      }, /*Publisher signalled [...] */ ", which is more than the signalled demand: ");
+    } finally {
+      pool.shutdownNow();
+      pool.awaitTermination(1, TimeUnit.SECONDS);
+    }
+  }
+
+  @Test
+  public void required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber_shouldFailBy_keepingTheReferenceLongerThanNeeded() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customPublisherVerification(new Publisher<Integer>() {
+          Subscriber subs = null;
+
+          @Override public void subscribe(final Subscriber<? super Integer> s) {
+            subs = s; // keep the reference
+
+            s.onSubscribe(new Subscription() {
+              @Override public void request(long n) {
+                for (int i = 0; i < n; i++) {
+                  s.onNext((int) n);
+                }
+              }
+
+              @Override public void cancel() {
+                // noop, we still keep the reference!
+              }
+            });
+          }
+        }).required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber();
+      }
+    }, "did not drop reference to test subscriber after subscription cancellation");
+  }
+
+  @Test
+  public void required_spec317_mustSupportAPendingElementCountUpToLongMaxValue_shouldFail_onAsynchDemandIgnoringPublisher() throws Throwable {
+    // 10 is arbitrary here, we just need a "larger number" to get into concurrent access scenarios, anything more than 2
+    // should work, but getting up to 10 should be safer and doesn't hurt to play safe here
+    final ExecutorService pool = Executors.newFixedThreadPool(10);
+
+    try {
+      requireTestFailure(new ThrowingRunnable() {
+        @Override public void run() throws Throwable {
+          demandIgnoringAsynchronousPublisherVerification(pool).required_spec317_mustSupportAPendingElementCountUpToLongMaxValue();
+        }
+      }, "Expected end-of-stream but got");
+    } finally {
+      pool.shutdownNow();
+      pool.awaitTermination(1, TimeUnit.SECONDS);
+    }
+  }
+
+  @Test
+  public void required_spec317_mustSupportAPendingElementCountUpToLongMaxValue_shouldFail_onSynchDemandIgnoringPublisher() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        demandIgnoringSynchronousPublisherVerification().required_spec317_mustSupportAPendingElementCountUpToLongMaxValue();
+      }
+    }, "Received more than bufferSize (32) onNext signals. The Publisher probably emited more signals than expected!");
+  }
+
+  @Test
+  public void required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue_shouldFail_onSynchOverflowingPublisher() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customPublisherVerification(new Publisher<Integer>() {
+          long demand = 0;
+
+          @Override public void subscribe(final Subscriber<? super Integer> s) {
+            s.onSubscribe(new Subscription() {
+              @Override public void request(long n) {
+                // it does not protect from demand overflow!
+                demand += n;
+                if (demand < 0) {
+                  // overflow
+                  s.onError(new IllegalStateException("Illegally signalling onError (violates rule 3.17)")); // Illegally signal error
+                } else {
+                  s.onNext(0);
+                }
+              }
+
+              @Override public void cancel() {
+                // noop
+              }
+            });
+          }
+        }).required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue();
+      }
+    }, "Async error during test execution: Illegally signalling onError (violates rule 3.17)");
+  }
+
+  @Test
+  public void required_spec317_mustSupportACumulativePendingElementCountUpToLongMaxValue_shouldFailWhenErrorSignalledOnceMaxValueReached() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customPublisherVerification(new Publisher<Integer>() {
+          long demand = 0;
+
+          @Override public void subscribe(final Subscriber<? super Integer> s) {
+            s.onSubscribe(new NoopSubscription() {
+              @Override public void request(long n) {
+                demand += n;
+
+                // this is a mistake, it should still be able to accumulate such demand
+                if (demand == Long.MAX_VALUE)
+                  s.onError(new IllegalStateException("Illegally signalling onError too soon! " +
+                                                          "Cumulative demand equal to Long.MAX_VALUE is legal."));
+
+                s.onNext(0);
+              }
+            });
+          }
+        }).required_spec317_mustSupportACumulativePendingElementCountUpToLongMaxValue();
+      }
+    }, "Async error during test execution: Illegally signalling onError too soon!");
+  }
+
+  @Test
+  public void required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue_forSynchronousPublisher() throws Throwable {
+    final AtomicInteger sent = new AtomicInteger();
+
+    customPublisherVerification(new Publisher<Integer>() {
+      @Override
+      public void subscribe(final Subscriber<? super Integer> downstream) {
+        downstream.onSubscribe(new Subscription() {
+          boolean started;
+          boolean cancelled;
+
+          @Override
+          public void request(long n) {
+            if (!started) {
+              started = true;
+              while (!cancelled) {
+                downstream.onNext(sent.getAndIncrement());
+              }
+            }
+          }
+
+          @Override
+          public void cancel() {
+            cancelled = true;
+          }
+        });
+      }
+    }).required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue();
+
+    // 11 due to the implementation of this particular TCK test (see impl)
+    Assert.assertEquals(sent.get(), 11);
+  }
+
+  // FAILING IMPLEMENTATIONS //
+
+  final Publisher<Integer> SKIP = null;
+
+  /** Subscription which does nothing. */
+  static class NoopSubscription implements Subscription {
+
+    @Override public void request(long n) {
+      // noop
+    }
+
+    @Override public void cancel() {
+      // noop
+    }
+  }
+
+  /**
+   * Verification using a Publisher that never publishes any element.
+   * Skips the error state publisher tests.
+   */
+  final PublisherVerification<Integer> noopPublisherVerification() {
+    return new PublisherVerification<Integer>(newTestEnvironment()) {
+      @Override public Publisher<Integer> createPublisher(long elements) {
+
+        return new Publisher<Integer>() {
+          @Override public void subscribe(Subscriber<? super Integer> s) {
+            s.onSubscribe(new NoopSubscription());
+          }
+        };
+
+      }
+
+      @Override public Publisher<Integer> createFailedPublisher() {
+        return SKIP;
+      }
+    };
+  }
+
+  /**
+   * Verification using a Publisher that never publishes any element
+   */
+  final PublisherVerification<Integer> onErroringPublisherVerification() {
+    return new PublisherVerification<Integer>(newTestEnvironment()) {
+      @Override public Publisher<Integer> createPublisher(long elements) {
+
+        return new Publisher<Integer>() {
+          @Override public void subscribe(final Subscriber<? super Integer> s) {
+            s.onSubscribe(new NoopSubscription() {
+              @Override public void request(long n) {
+                s.onError(new TestException());
+              }
+            });
+          }
+        };
+
+      }
+
+      @Override public Publisher<Integer> createFailedPublisher() {
+        return SKIP;
+      }
+    };
+  }
+
+  /**
+   * Custom Verification using given Publishers
+   */
+  final PublisherVerification<Integer> customPublisherVerification(final Publisher<Integer> pub) {
+    return customPublisherVerification(pub, SKIP);
+  }
+
+  /**
+   * Custom Verification using given Publishers
+   */
+  final PublisherVerification<Integer> customPublisherVerification(final Publisher<Integer> pub, final Publisher<Integer> errorPub) {
+    return new PublisherVerification<Integer>(newTestEnvironment()) {
+      @Override public Publisher<Integer> createPublisher(long elements) {
+        return pub;
+      }
+
+      @Override public Publisher<Integer> createFailedPublisher() {
+        return errorPub;
+      }
+    };
+  }
+
+  /**
+   * Verification using a Publisher that publishes elements even with no demand available
+   */
+  final PublisherVerification<Integer> demandIgnoringSynchronousPublisherVerification() {
+    return new PublisherVerification<Integer>(newTestEnvironment()) {
+      @Override public Publisher<Integer> createPublisher(long elements) {
+
+        return new Publisher<Integer>() {
+          @Override public void subscribe(final Subscriber<? super Integer> s) {
+            s.onSubscribe(new NoopSubscription() {
+              @Override public void request(long n) {
+
+                for (long i = 0; i <= n; i++) {
+                  // one too much
+                  s.onNext((int) i);
+                }
+              }
+            });
+          }
+        };
+
+      }
+
+      @Override public Publisher<Integer> createFailedPublisher() {
+        return SKIP;
+      }
+    };
+  }
+
+  /**
+   * Verification using a Publisher that publishes elements even with no demand available, from multiple threads (!).
+   *
+   * Please note that exceptions thrown from onNext *will be swallowed* – reason being this verification is used to check
+   * very specific things about error reporting - from the "TCK Tests", we do not have any assertions on thrown exceptions.
+   */
+  final PublisherVerification<Integer> demandIgnoringAsynchronousPublisherVerification(final ExecutorService signallersPool) {
+    return demandIgnoringAsynchronousPublisherVerification(signallersPool, true);
+  }
+
+  /**
+   * Verification using a Publisher that publishes elements even with no demand available, from multiple threads (!).
+   */
+  final PublisherVerification<Integer> demandIgnoringAsynchronousPublisherVerification(final ExecutorService signallersPool, final boolean swallowOnNextExceptions) {
+    final AtomicInteger startedSignallingThreads = new AtomicInteger(0);
+    final int maxSignallingThreads = 2;
+
+    final AtomicBoolean concurrentAccessCaused = new AtomicBoolean(false);
+
+    return new PublisherVerification<Integer>(newTestEnvironment()) {
+      @Override public Publisher<Integer> createPublisher(long elements) {
+
+        return new Publisher<Integer>() {
+          @Override public void subscribe(final Subscriber<? super Integer> s) {
+            s.onSubscribe(new NoopSubscription() {
+              @Override public void request(final long n) {
+                Runnable signalling = new Runnable() {
+
+                  @Override public void run() {
+                    for (long i = 0; i <= n; i++) {
+                      // one signal too much
+
+                      try {
+                        final long signal = i;
+                        signallersPool.execute(new Runnable() {
+                          @Override public void run() {
+                            try {
+                              s.onNext((int) signal);
+                            } catch (Exception ex) {
+                              if (!swallowOnNextExceptions) {
+                                throw new RuntimeException("onNext threw an exception!", ex);
+                              } else {
+                                // yes, swallow the exception, we're not asserting and they'd just end up being logged (stdout),
+                                // which we do not need in this specific PublisherVerificationTest
+                              }
+                            }
+                          }
+                        });
+                      } catch (Exception ex) {
+                        if (ex instanceof TestEnvironment.Latch.ExpectedOpenLatchException) {
+                          if (concurrentAccessCaused.compareAndSet(false, true)) {
+                            throw new RuntimeException("Concurrent access detected", ex);
+                          } else {
+                            // error signalled once already, stop more errors from propagating
+                            return;
+                          }
+                        } else if (ex instanceof RejectedExecutionException) {
+                          // ignore - this may happen since one thread may have already gotten into a concurrent access
+                          // problem and initiated the pool's shutdown. It will then throw RejectedExecutionException.
+                        } else {
+                          if (concurrentAccessCaused.get()) {
+                            return;
+                          } else {
+                            throw new RuntimeException(ex);
+                          }
+                        }
+                      }
+                    }
+                  }
+                };
+
+                // must be guarded like this in case a Subscriber triggers request() synchronously from it's onNext()
+                while (startedSignallingThreads.getAndAdd(1) < maxSignallingThreads) {
+                  signallersPool.execute(signalling);
+                }
+              }
+            });
+          }
+        };
+
+      }
+
+      @Override public Publisher<Integer> createFailedPublisher() {
+        return SKIP;
+      }
+    };
+  }
+
+  private TestEnvironment newTestEnvironment() {
+    return new TestEnvironment();
+  }
+
+
+}
diff --git a/tck/src/test/java/org/reactivestreams/tck/SingleElementPublisherTest.java b/tck/src/test/java/org/reactivestreams/tck/SingleElementPublisherTest.java
new file mode 100644
index 0000000..f847b11
--- /dev/null
+++ b/tck/src/test/java/org/reactivestreams/tck/SingleElementPublisherTest.java
@@ -0,0 +1,42 @@
+package org.reactivestreams.tck;
+
+import org.reactivestreams.Publisher;
+import org.reactivestreams.example.unicast.AsyncIterablePublisher;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.util.Collections;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+ at Test
+public class SingleElementPublisherTest extends PublisherVerification<Integer> {
+
+  private ExecutorService ex;
+
+  public SingleElementPublisherTest() {
+    super(new TestEnvironment());
+  }
+
+  @BeforeClass
+  void before() { ex = Executors.newFixedThreadPool(4); }
+
+  @AfterClass
+  void after() { if (ex != null) ex.shutdown(); }
+
+  @Override
+  public Publisher<Integer> createPublisher(long elements) {
+    return new AsyncIterablePublisher<Integer>(Collections.singleton(1), ex);
+  }
+
+  @Override
+  public Publisher<Integer> createFailedPublisher() {
+    return null;
+  }
+
+  @Override
+  public long maxElementsFromPublisher() {
+    return 1;
+  }
+}
\ No newline at end of file
diff --git a/tck/src/test/java/org/reactivestreams/tck/SubscriberBlackboxVerificationTest.java b/tck/src/test/java/org/reactivestreams/tck/SubscriberBlackboxVerificationTest.java
new file mode 100644
index 0000000..4e31ffc
--- /dev/null
+++ b/tck/src/test/java/org/reactivestreams/tck/SubscriberBlackboxVerificationTest.java
@@ -0,0 +1,260 @@
+package org.reactivestreams.tck;
+
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import org.reactivestreams.tck.support.TCKVerificationSupport;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+* Validates that the TCK's {@link org.reactivestreams.tck.SubscriberBlackboxVerification} fails with nice human readable errors.
+* <b>Important: Please note that all Subscribers implemented in this file are *wrong*!</b>
+*/
+public class SubscriberBlackboxVerificationTest extends TCKVerificationSupport {
+
+  private ExecutorService ex;
+  @BeforeClass void before() { ex = Executors.newFixedThreadPool(4); }
+  @AfterClass void after() { if (ex != null) ex.shutdown(); }
+
+  @Test
+  public void required_spec201_blackbox_mustSignalDemandViaSubscriptionRequest_shouldFailBy_notGettingRequestCall() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        noopSubscriberVerification().required_spec201_blackbox_mustSignalDemandViaSubscriptionRequest();
+      }
+    }, "Did not receive expected `request` call within");
+  }
+
+  @Test
+  public void required_spec201_blackbox_mustSignalDemandViaSubscriptionRequest_shouldPass() throws Throwable {
+    simpleSubscriberVerification().required_spec201_blackbox_mustSignalDemandViaSubscriptionRequest();
+  }
+
+  @Test
+  public void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_shouldFail_dueToCallingRequest() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customSubscriberVerification(new KeepSubscriptionSubscriber() {
+          @Override public void onComplete() {
+            subscription.request(1);
+          }
+        }).required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete();
+      }
+    }, "Subscription::request MUST NOT be called from Subscriber::onComplete (Rule 2.3)!");
+  }
+
+  @Test
+  public void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_shouldFail_dueToCallingCancel() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customSubscriberVerification(new KeepSubscriptionSubscriber() {
+          @Override public void onComplete() {
+            subscription.cancel();
+          }
+        }).required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete();
+      }
+    }, "Subscription::cancel MUST NOT be called from Subscriber::onComplete (Rule 2.3)!");
+  }
+
+  @Test
+  public void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError_shouldFail_dueToCallingRequest() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customSubscriberVerification(new KeepSubscriptionSubscriber() {
+          @Override public void onError(Throwable t) {
+            subscription.request(1);
+          }
+        }).required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError();
+      }
+    }, "Subscription::request MUST NOT be called from Subscriber::onError (Rule 2.3)!");
+  }
+  @Test
+  public void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError_shouldFail_dueToCallingCancel() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customSubscriberVerification(new KeepSubscriptionSubscriber() {
+          @Override public void onError(Throwable t) {
+            subscription.cancel();
+          }
+        }).required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError();
+      }
+    }, "Subscription::cancel MUST NOT be called from Subscriber::onError (Rule 2.3)!");
+  }
+
+  @Test
+  public void required_spec205_blackbox_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal_shouldFail() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customSubscriberVerification(new KeepSubscriptionSubscriber() {
+          @Override public void onSubscribe(Subscription s) {
+            super.onSubscribe(s);
+
+            s.request(1); // this is wrong, as one should always check if should accept or reject the subscription
+          }
+        }).required_spec205_blackbox_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal();
+      }
+    }, "illegally called `subscription.request(1)");
+  }
+
+  @Test
+  public void required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall_shouldFail() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customSubscriberVerification(new NoopSubscriber() {
+          // don't even request()
+        }).required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall();
+      }
+    }, "did not call `registerOnComplete()`");
+  }
+
+  @Test
+  public void required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall_shouldPass_withNoopSubscriber() throws Throwable {
+    customSubscriberVerification(new NoopSubscriber() {
+      // don't even request()
+    }).required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall();
+  }
+
+  @Test
+  public void required_spec210_blackbox_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall_shouldFail() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+
+        customSubscriberVerification(new NoopSubscriber() {
+          @Override public void onError(Throwable t) {
+            // this is wrong in many ways (incl. spec violation), but aims to simulate user code which "blows up" when handling the onError signal
+            throw new RuntimeException("Wrong, don't do this!", t); // don't do this
+          }
+        }).required_spec210_blackbox_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall();
+      }
+    }, "Test Exception: Boom!"); // checks that the expected exception was delivered to onError, we don't expect anyone to implement onError so weirdly
+  }
+
+  @Test
+  public void required_spec213_blackbox_mustThrowNullPointerExceptionWhenParametersAreNull_mustFailOnIgnoredNull_onSubscribe() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+
+        customSubscriberVerification(new NoopSubscriber())
+          .required_spec213_blackbox_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull();
+      }
+    }, "onSubscribe(null) did not throw NullPointerException");
+  }
+  
+  @Test
+  public void required_spec213_blackbox_mustThrowNullPointerExceptionWhenParametersAreNull_mustFailOnIgnoredNull_onNext() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+
+        customSubscriberVerification(new NoopSubscriber())
+          .required_spec213_blackbox_onNext_mustThrowNullPointerExceptionWhenParametersAreNull();
+      }
+    }, "onNext(null) did not throw NullPointerException");
+  }
+  
+  @Test
+  public void required_spec213_blackbox_mustThrowNullPointerExceptionWhenParametersAreNull_mustFailOnIgnoredNull_onError() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+
+        customSubscriberVerification(new NoopSubscriber())
+          .required_spec213_blackbox_onError_mustThrowNullPointerExceptionWhenParametersAreNull();
+      }
+    }, "onError(null) did not throw NullPointerException");
+  }
+
+  // FAILING IMPLEMENTATIONS //
+
+  /**
+   * Verification using a Subscriber that doesn't do anything on any of the callbacks
+   */
+  final SubscriberBlackboxVerification<Integer> noopSubscriberVerification() throws Exception {
+    return new SubscriberBlackboxVerification<Integer>(newTestEnvironment()) {
+      @Override public Subscriber<Integer> createSubscriber() {
+        return new NoopSubscriber();
+      }
+
+      @Override public Integer createElement(int element) {
+        return element;
+      }
+
+      @Override public ExecutorService publisherExecutorService() { return ex; }
+    };
+  }
+
+  /**
+   * Verification using a Subscriber that doesn't do anything on any of the callbacks
+   */
+  final SubscriberBlackboxVerification<Integer> simpleSubscriberVerification() {
+    return new SubscriberBlackboxVerification<Integer>(newTestEnvironment()) {
+      @Override public Subscriber<Integer> createSubscriber() {
+        return new NoopSubscriber() {
+          volatile Subscription subscription;
+
+          @Override public void onSubscribe(Subscription s) {
+            this.subscription = s;
+            s.request(1);
+          }
+
+          @Override public void onNext(Integer element) {
+            subscription.request(1);
+          }
+        };
+      }
+
+      @Override public Integer createElement(int element) { return element; }
+
+      @Override public ExecutorService publisherExecutorService() { return ex; }
+    };
+  }
+
+  /**
+   * Custom Verification using given Subscriber
+   */
+  final SubscriberBlackboxVerification<Integer> customSubscriberVerification(final Subscriber<Integer> sub) {
+    return new SubscriberBlackboxVerification<Integer>(newTestEnvironment()) {
+      @Override public Subscriber<Integer> createSubscriber() {
+        return sub;
+      }
+
+      @Override public Integer createElement(int element) { return element; }
+
+      @Override public ExecutorService publisherExecutorService() { return ex; }
+    };
+  }
+
+  static class NoopSubscriber implements Subscriber<Integer> {
+
+    @Override public void onSubscribe(Subscription s) {
+      // noop
+    }
+
+    @Override public void onNext(Integer element) {
+      // noop
+    }
+
+    @Override public void onError(Throwable t) {
+      // noop
+    }
+
+    @Override public void onComplete() {
+      // noop
+    }
+  }
+
+  static class KeepSubscriptionSubscriber extends NoopSubscriber {
+    volatile Subscription subscription;
+
+    @Override public void onSubscribe(Subscription s) {
+      this.subscription = s;
+    }
+  }
+
+  private TestEnvironment newTestEnvironment() {
+    return new TestEnvironment();
+  }
+
+}
diff --git a/tck/src/test/java/org/reactivestreams/tck/SubscriberWhiteboxVerificationTest.java b/tck/src/test/java/org/reactivestreams/tck/SubscriberWhiteboxVerificationTest.java
new file mode 100644
index 0000000..ed4b7e9
--- /dev/null
+++ b/tck/src/test/java/org/reactivestreams/tck/SubscriberWhiteboxVerificationTest.java
@@ -0,0 +1,415 @@
+package org.reactivestreams.tck;
+
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import org.reactivestreams.tck.SubscriberWhiteboxVerification.SubscriberPuppet;
+import org.reactivestreams.tck.SubscriberWhiteboxVerification.WhiteboxSubscriberProbe;
+import org.reactivestreams.tck.support.Function;
+import org.reactivestreams.tck.support.TCKVerificationSupport;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Validates that the TCK's {@link SubscriberWhiteboxVerification} fails with nice human readable errors.
+ * <b>Important: Please note that all Subscribers implemented in this file are *wrong*!</b>
+ */
+public class SubscriberWhiteboxVerificationTest extends TCKVerificationSupport {
+
+  private ExecutorService ex;
+  @BeforeClass void before() { ex = Executors.newFixedThreadPool(4); }
+  @AfterClass void after() { if (ex != null) ex.shutdown(); }
+
+  @Test
+  public void required_spec201_mustSignalDemandViaSubscriptionRequest_shouldFailBy_notGettingRequestCall() throws Throwable {
+    // this mostly verifies the probe is injected correctly
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customSubscriberVerification(new Function<WhiteboxSubscriberProbe<Integer>, Subscriber<Integer>>() {
+          @Override public Subscriber<Integer> apply(final WhiteboxSubscriberProbe<Integer> probe) throws Throwable {
+            return new SimpleSubscriberWithProbe(probe) {
+              @Override public void onSubscribe(final Subscription s) {
+                probe.registerOnSubscribe(new SubscriberPuppet() {
+                  @Override public void triggerRequest(long elements) {
+                    // forgot to implement request triggering properly!
+                  }
+
+                  @Override public void signalCancel() {
+                    s.cancel();
+                  }
+                });
+              }
+            };
+          }
+        }).required_spec201_mustSignalDemandViaSubscriptionRequest();
+      }
+    }, "Did not receive expected `request` call within");
+  }
+
+  @Test
+  public void required_spec201_mustSignalDemandViaSubscriptionRequest_shouldPass() throws Throwable {
+    simpleSubscriberVerification().required_spec201_mustSignalDemandViaSubscriptionRequest();
+  }
+
+  @Test
+  public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_shouldFail_dueToCallingRequest() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customSubscriberVerification(new Function<WhiteboxSubscriberProbe<Integer>, Subscriber<Integer>>() {
+          @Override public Subscriber<Integer> apply(final WhiteboxSubscriberProbe<Integer> probe) throws Throwable {
+            return new SimpleSubscriberWithProbe(probe) {
+              @Override public void onSubscribe(final Subscription s) {
+                this.subscription = s;
+                probe.registerOnSubscribe(newSimpleSubscriberPuppet(s));
+              }
+
+              @Override public void onComplete() {
+                subscription.request(1);
+                probe.registerOnComplete();
+              }
+            };
+          }
+        }).required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete();
+      }
+    }, "Subscription::request MUST NOT be called from Subscriber::onComplete (Rule 2.3)!");
+  }
+
+  @Test
+  public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_shouldFail_dueToCallingCancel() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customSubscriberVerification(new Function<WhiteboxSubscriberProbe<Integer>, Subscriber<Integer>>() {
+          @Override public Subscriber<Integer> apply(final WhiteboxSubscriberProbe<Integer> probe) throws Throwable {
+            return new SimpleSubscriberWithProbe(probe) {
+              @Override public void onSubscribe(final Subscription s) {
+                this.subscription = s;
+                probe.registerOnSubscribe(newSimpleSubscriberPuppet(s));
+              }
+
+              @Override public void onComplete() {
+                subscription.cancel();
+                probe.registerOnComplete();
+              }
+            };
+          }
+        }).required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete();
+      }
+    }, "Subscription::cancel MUST NOT be called from Subscriber::onComplete (Rule 2.3)!");
+  }
+
+  @Test
+  public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError_shouldFail_dueToCallingRequest() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customSubscriberVerification(new Function<WhiteboxSubscriberProbe<Integer>, Subscriber<Integer>>() {
+          @Override public Subscriber<Integer> apply(final WhiteboxSubscriberProbe<Integer> probe) throws Throwable {
+            return new SimpleSubscriberWithProbe(probe) {
+              @Override public void onSubscribe(final Subscription s) {
+                this.subscription = s;
+                probe.registerOnSubscribe(newSimpleSubscriberPuppet(s));
+              }
+
+              @Override public void onError(Throwable t) {
+                subscription.request(1);
+              }
+            };
+          }
+        }).required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError();
+      }
+    }, "Subscription::request MUST NOT be called from Subscriber::onError (Rule 2.3)!");
+  }
+
+  @Test
+  public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError_shouldFail_dueToCallingCancel() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customSubscriberVerification(new Function<WhiteboxSubscriberProbe<Integer>, Subscriber<Integer>>() {
+          @Override public Subscriber<Integer> apply(final WhiteboxSubscriberProbe<Integer> probe) throws Throwable {
+            return new SimpleSubscriberWithProbe(probe) {
+              @Override public void onSubscribe(final Subscription s) {
+                this.subscription = s;
+                probe.registerOnSubscribe(newSimpleSubscriberPuppet(s));
+              }
+
+              @Override public void onError(Throwable t) {
+                subscription.cancel();
+              }
+            };
+          }
+        }).required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError();
+      }
+    }, "Subscription::cancel MUST NOT be called from Subscriber::onError (Rule 2.3)!");
+  }
+
+  @Test
+  public void required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal_shouldFail() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customSubscriberVerification(new Function<WhiteboxSubscriberProbe<Integer>, Subscriber<Integer>>() {
+          @Override
+          public Subscriber<Integer> apply(WhiteboxSubscriberProbe<Integer> probe) throws Throwable {
+            return new SimpleSubscriberWithProbe(probe) {
+              @Override public void onSubscribe(Subscription s) {
+                super.onSubscribe(s);
+
+                s.request(1); // this is wrong, as one should always check if should accept or reject the subscription
+              }
+            };
+          }
+        }).required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal();
+      }
+    }, "Expected 2nd Subscription given to subscriber to be cancelled");
+  }
+
+  @Test
+  public void required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel_shouldFail() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customSubscriberVerification(new Function<WhiteboxSubscriberProbe<Integer>, Subscriber<Integer>>() {
+          @Override
+          public Subscriber<Integer> apply(WhiteboxSubscriberProbe<Integer> probe) throws Throwable {
+
+            final AtomicBoolean subscriptionCancelled = new AtomicBoolean(false);
+
+            return new SimpleSubscriberWithProbe(probe) {
+              @Override public void onSubscribe(final Subscription s) {
+                this.subscription = s;
+                probe.registerOnSubscribe(new SubscriberPuppet() {
+                  @Override public void triggerRequest(long elements) {
+                    s.request(elements);
+                  }
+
+                  @Override public void signalCancel() {
+                    subscriptionCancelled.set(true);
+                    s.cancel();
+                  }
+                });
+              }
+
+              @Override public void onNext(Integer element) {
+                if (subscriptionCancelled.get()) {
+                  // this is wrong for many reasons, firstly onNext should never throw,
+                  // but this test aims to simulate a Subscriber where someone got it's internals wrong and "blows up".
+                  throw new RuntimeException("But I thought it's cancelled!");
+                } else {
+                  probe.registerOnNext(element);
+                }
+              }
+            };
+          }
+        }).required_spec208_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel();
+      }
+    }, "But I thought it's cancelled!");
+  }
+
+  @Test
+  public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall_shouldFail() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customSubscriberVerification(new Function<WhiteboxSubscriberProbe<Integer>, Subscriber<Integer>>() {
+          @Override
+          public Subscriber<Integer> apply(WhiteboxSubscriberProbe<Integer> probe) throws Throwable {
+            return new SimpleSubscriberWithProbe(probe) {
+              @Override public void onComplete() {
+                // forgot to call the probe here
+              }
+            };
+          }
+        }).required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall();
+      }
+    }, "did not call `registerOnComplete()`");
+  }
+
+  @Test
+  public void required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall_shouldPass_withNoopSubscriber() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+        customSubscriberVerification(new Function<WhiteboxSubscriberProbe<Integer>, Subscriber<Integer>>() {
+          @Override
+          public Subscriber<Integer> apply(WhiteboxSubscriberProbe<Integer> probe) throws Throwable {
+            return new SimpleSubscriberWithProbe(probe) {
+              @Override public void onSubscribe(Subscription s) {
+                // intentional omission of probe registration
+              }
+            };
+          }
+        }).required_spec209_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall();
+
+      }
+    }, "did not `registerOnSubscribe`");
+  }
+
+  @Test
+  public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall_shouldFail() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+
+        customSubscriberVerification(new Function<WhiteboxSubscriberProbe<Integer>, Subscriber<Integer>>() {
+          @Override
+          public Subscriber<Integer> apply(WhiteboxSubscriberProbe<Integer> probe) throws Throwable {
+            return new SimpleSubscriberWithProbe(probe) {
+              @Override public void onError(Throwable t) {
+                // this is wrong in many ways (incl. spec violation), but aims to simulate user code which "blows up" when handling the onError signal
+                throw new RuntimeException("Wrong, don't do this!", t); // intentional spec violation
+              }
+            };
+          }
+        }).required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall();
+      }
+    }, "Test Exception: Boom!"); // checks that the expected exception was delivered to onError, we don't expect anyone to implement onError so weirdly
+  }
+
+  @Test
+  public void required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall_shouldFail() throws Throwable {
+    requireTestFailure(new ThrowingRunnable() {
+      @Override public void run() throws Throwable {
+
+        customSubscriberVerification(new Function<WhiteboxSubscriberProbe<Integer>, Subscriber<Integer>>() {
+          @Override
+          public Subscriber<Integer> apply(WhiteboxSubscriberProbe<Integer> probe) throws Throwable {
+            return new SimpleSubscriberWithProbe(probe) {
+              @Override public void onSubscribe(Subscription s) {
+                super.onSubscribe(s);
+              }
+
+              @Override public void onError(Throwable t) {
+                // this is wrong in many ways (incl. spec violation), but aims to simulate user code which "blows up" when handling the onError signal
+                throw new RuntimeException("Wrong, don't do this!", t);
+              }
+            };
+          }
+        }).required_spec210_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall();
+      }
+    }, "Test Exception: Boom!"); // checks that the expected exception was delivered to onError, we don't expect anyone to implement onError so weirdly
+  }
+  
+  @Test
+  public void required_spec308_requestMustRegisterGivenNumberElementsToBeProduced_shouldFail() throws Throwable {
+    // sanity checks the "happy path", that triggerRequest() propagates the right demand
+    customSubscriberVerification(new Function<WhiteboxSubscriberProbe<Integer>, Subscriber<Integer>>() {
+      @Override
+      public Subscriber<Integer> apply(WhiteboxSubscriberProbe<Integer> probe) throws Throwable {
+        return new SimpleSubscriberWithProbe(probe) {};
+      }
+    }).required_spec308_requestMustRegisterGivenNumberElementsToBeProduced();
+  }
+
+  // FAILING IMPLEMENTATIONS //
+
+  /**
+   * Verification using a Subscriber that doesn't do anything on any of the callbacks.
+   *
+   * The {@link org.reactivestreams.tck.SubscriberWhiteboxVerification.WhiteboxSubscriberProbe} is properly installed in this subscriber.
+   *
+   * This verification can be used in the "simples case, subscriber which does basically nothing case" validation.
+   */
+  final SubscriberWhiteboxVerification<Integer> simpleSubscriberVerification() {
+    return new SubscriberWhiteboxVerification<Integer>(newTestEnvironment()) {
+      @Override
+      public Subscriber<Integer> createSubscriber(final WhiteboxSubscriberProbe<Integer> probe) {
+        return new Subscriber<Integer>() {
+          @Override public void onSubscribe(final Subscription s) {
+            probe.registerOnSubscribe(new SubscriberPuppet() {
+              @Override public void triggerRequest(long elements) {
+                s.request(elements);
+              }
+
+              @Override public void signalCancel() {
+                s.cancel();
+              }
+            });
+          }
+
+          @Override public void onNext(Integer element) {
+            probe.registerOnNext(element);
+          }
+
+          @Override public void onError(Throwable t) {
+            probe.registerOnError(t);
+          }
+
+          @Override public void onComplete() {
+            probe.registerOnComplete();
+          }
+        };
+      }
+
+      @Override public Integer createElement(int element) { return element; }
+
+      @Override public ExecutorService publisherExecutorService() { return ex; }
+    };
+  }
+
+  /**
+   * Verification using a Subscriber that can be fine tuned by the TCK implementer
+   */
+  final SubscriberWhiteboxVerification<Integer> customSubscriberVerification(final Function<WhiteboxSubscriberProbe<Integer>, Subscriber<Integer>> newSubscriber) {
+    return new SubscriberWhiteboxVerification<Integer>(newTestEnvironment()) {
+      @Override
+      public Subscriber<Integer> createSubscriber(WhiteboxSubscriberProbe<Integer> probe) {
+        try {
+          return newSubscriber.apply(probe);
+        } catch (Throwable t) {
+          throw new RuntimeException("Unable to create subscriber!", t);
+        }
+      }
+
+      @Override public Integer createElement(int element) { return element; }
+
+      @Override public ExecutorService publisherExecutorService() { return ex; }
+    };
+  }
+
+  private SubscriberPuppet newSimpleSubscriberPuppet(final Subscription subscription) {
+    return new SubscriberPuppet() {
+      @Override public void triggerRequest(long elements) {
+        subscription.request(elements);
+      }
+
+      @Override public void signalCancel() {
+        subscription.cancel();
+      }
+    };
+  }
+
+  /**
+   * Simplest possible implementation of Subscriber which calls the WhiteboxProbe in all apropriate places.
+   * Override it to save some lines of boilerplate, and then break behaviour in specific places.
+   */
+  private abstract class SimpleSubscriberWithProbe implements Subscriber<Integer> {
+
+    volatile Subscription subscription;
+
+    final WhiteboxSubscriberProbe<Integer> probe;
+
+    public SimpleSubscriberWithProbe(WhiteboxSubscriberProbe<Integer> probe) {
+      this.probe = probe;
+    }
+
+    @Override public void onSubscribe(final Subscription s) {
+      this.subscription = s;
+      probe.registerOnSubscribe(newSimpleSubscriberPuppet(s));
+    }
+
+    @Override public void onNext(Integer element) {
+      probe.registerOnNext(element);
+    }
+
+    @Override public void onError(Throwable t) {
+      probe.registerOnError(t);
+    }
+
+    @Override public void onComplete() {
+      probe.registerOnComplete();
+    }
+  }
+
+  private TestEnvironment newTestEnvironment() {
+    return new TestEnvironment();
+  }
+
+}
diff --git a/tck/src/test/java/org/reactivestreams/tck/SyncTriggeredDemandSubscriberTest.java b/tck/src/test/java/org/reactivestreams/tck/SyncTriggeredDemandSubscriberTest.java
new file mode 100644
index 0000000..0b099fc
--- /dev/null
+++ b/tck/src/test/java/org/reactivestreams/tck/SyncTriggeredDemandSubscriberTest.java
@@ -0,0 +1,47 @@
+package org.reactivestreams.tck;
+
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import org.reactivestreams.tck.SubscriberBlackboxVerification;
+import org.reactivestreams.tck.TestEnvironment;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import org.reactivestreams.tck.support.SyncTriggeredDemandSubscriber;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+ at Test // Must be here for TestNG to find and run this, do not remove
+public class SyncTriggeredDemandSubscriberTest extends SubscriberBlackboxVerification<Integer> {
+
+  private ExecutorService e;
+  @BeforeClass void before() { e = Executors.newFixedThreadPool(4); }
+  @AfterClass void after() { if (e != null) e.shutdown(); }
+
+  public SyncTriggeredDemandSubscriberTest() {
+    super(new TestEnvironment());
+  }
+
+  @Override public void triggerRequest(final Subscriber<? super Integer> subscriber) {
+    ((SyncTriggeredDemandSubscriber<? super Integer>)subscriber).triggerDemand(1);
+  }
+
+  @Override public Subscriber<Integer> createSubscriber() {
+    return new SyncTriggeredDemandSubscriber<Integer>() {
+      private long acc;
+      @Override protected long foreach(final Integer element) {
+        acc += element;
+        return 1;
+      }
+
+      @Override public void onComplete() {
+      }
+    };
+  }
+
+  @Override public Integer createElement(int element) {
+    return element;
+  }
+}
diff --git a/tck/src/test/java/org/reactivestreams/tck/SyncTriggeredDemandSubscriberWhiteboxTest.java b/tck/src/test/java/org/reactivestreams/tck/SyncTriggeredDemandSubscriberWhiteboxTest.java
new file mode 100644
index 0000000..fb85a43
--- /dev/null
+++ b/tck/src/test/java/org/reactivestreams/tck/SyncTriggeredDemandSubscriberWhiteboxTest.java
@@ -0,0 +1,77 @@
+package org.reactivestreams.tck;
+
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import org.reactivestreams.tck.SubscriberBlackboxVerification;
+import org.reactivestreams.tck.SubscriberWhiteboxVerification;
+import org.reactivestreams.tck.TestEnvironment;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import org.reactivestreams.tck.support.SyncTriggeredDemandSubscriber;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+ at Test // Must be here for TestNG to find and run this, do not remove
+public class SyncTriggeredDemandSubscriberWhiteboxTest extends SubscriberWhiteboxVerification<Integer> {
+
+  private ExecutorService e;
+  @BeforeClass void before() { e = Executors.newFixedThreadPool(4); }
+  @AfterClass void after() { if (e != null) e.shutdown(); }
+
+  public SyncTriggeredDemandSubscriberWhiteboxTest() {
+    super(new TestEnvironment());
+  }
+
+  @Override
+  public Subscriber<Integer> createSubscriber(final WhiteboxSubscriberProbe<Integer> probe) {
+    return new SyncTriggeredDemandSubscriber<Integer>() {
+      @Override
+      public void onSubscribe(final Subscription s) {
+        super.onSubscribe(s);
+
+        probe.registerOnSubscribe(new SubscriberPuppet() {
+          @Override
+          public void triggerRequest(long elements) {
+            s.request(elements);
+          }
+
+          @Override
+          public void signalCancel() {
+            s.cancel();
+          }
+        });
+      }
+
+      @Override
+      public void onNext(Integer element) {
+        super.onNext(element);
+        probe.registerOnNext(element);
+      }
+
+      @Override
+      public void onError(Throwable cause) {
+        super.onError(cause);
+        probe.registerOnError(cause);
+      }
+
+      @Override
+      public void onComplete() {
+        super.onComplete();
+        probe.registerOnComplete();
+      }
+
+      @Override
+      protected long foreach(Integer element) {
+        return 1;
+      }
+    };
+  }
+
+  @Override public Integer createElement(int element) {
+    return element;
+  }
+
+}
diff --git a/tck/src/test/java/org/reactivestreams/tck/support/SyncTriggeredDemandSubscriber.java b/tck/src/test/java/org/reactivestreams/tck/support/SyncTriggeredDemandSubscriber.java
new file mode 100644
index 0000000..00a3ea6
--- /dev/null
+++ b/tck/src/test/java/org/reactivestreams/tck/support/SyncTriggeredDemandSubscriber.java
@@ -0,0 +1,123 @@
+package org.reactivestreams.tck.support;
+
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+
+/**
+ * SyncTriggeredDemandSubscriber is an implementation of Reactive Streams `Subscriber`,
+ * it runs synchronously (on the Publisher's thread) and requests demand triggered from
+ * "the outside" using its `triggerDemand` method and from "the inside" using the return
+ * value of its user-defined `foreach` method which is invoked to process each element.
+ *
+ * NOTE: The code below uses a lot of try-catches to show the reader where exceptions can be expected, and where they are forbidden.
+ */
+public abstract class SyncTriggeredDemandSubscriber<T> implements Subscriber<T> {
+  private Subscription subscription; // Obeying rule 3.1, we make this private!
+  private boolean done = false;
+
+  @Override public void onSubscribe(final Subscription s) {
+    // As per rule 2.13, we need to throw a `java.lang.NullPointerException` if the `Subscription` is `null`
+    if (s == null) throw null;
+
+    if (subscription != null) { // If someone has made a mistake and added this Subscriber multiple times, let's handle it gracefully
+      try {
+        s.cancel(); // Cancel the additional subscription
+      } catch(final Throwable t) {
+        //Subscription.cancel is not allowed to throw an exception, according to rule 3.15
+        (new IllegalStateException(s + " violated the Reactive Streams rule 3.15 by throwing an exception from cancel.", t)).printStackTrace(System.err);
+      }
+    } else {
+      // We have to assign it locally before we use it, if we want to be a synchronous `Subscriber`
+      // Because according to rule 3.10, the Subscription is allowed to call `onNext` synchronously from within `request`
+      subscription = s;
+    }
+  }
+
+  /**
+   * Requests the provided number of elements from the `Subscription` of this `Subscriber`.
+   * NOTE: This makes no attempt at thread safety so only invoke it once from the outside to initiate the demand.
+   * @return `true` if successful and `false` if not (either due to no `Subscription` or due to exceptions thrown)
+   */
+  public boolean triggerDemand(final long n) {
+    final Subscription s = subscription;
+    if (s == null) return false;
+    else {
+      try {
+        s.request(n);
+      } catch(final Throwable t) {
+        // Subscription.request is not allowed to throw according to rule 3.16
+        (new IllegalStateException(s + " violated the Reactive Streams rule 3.16 by throwing an exception from request.", t)).printStackTrace(System.err);
+        return false;
+      }
+      return true;
+    }
+  }
+
+  @Override public void onNext(final T element) {
+    if (subscription == null) { // Technically this check is not needed, since we are expecting Publishers to conform to the spec
+      (new IllegalStateException("Publisher violated the Reactive Streams rule 1.09 signalling onNext prior to onSubscribe.")).printStackTrace(System.err);
+    } else {
+      // As per rule 2.13, we need to throw a `java.lang.NullPointerException` if the `element` is `null`
+      if (element == null) throw null;
+
+      if (!done) { // If we aren't already done
+        try {
+          final long need = foreach(element);
+          if (need > 0) triggerDemand(need);
+          else if (need == 0) {}
+          else {
+            done();
+          }
+        } catch (final Throwable t) {
+          done();
+          try {
+            onError(t);
+          } catch (final Throwable t2) {
+            //Subscriber.onError is not allowed to throw an exception, according to rule 2.13
+            (new IllegalStateException(this + " violated the Reactive Streams rule 2.13 by throwing an exception from onError.", t2)).printStackTrace(System.err);
+          }
+        }
+      }
+    }
+  }
+
+  // Showcases a convenience method to idempotently marking the Subscriber as "done", so we don't want to process more elements
+  // herefor we also need to cancel our `Subscription`.
+  private void done() {
+    //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.
+    done = true; // If we `foreach` throws an exception, let's consider ourselves done (not accepting more elements)
+    try {
+      subscription.cancel(); // Cancel the subscription
+    } catch(final Throwable t) {
+      //Subscription.cancel is not allowed to throw an exception, according to rule 3.15
+      (new IllegalStateException(subscription + " violated the Reactive Streams rule 3.15 by throwing an exception from cancel.", t)).printStackTrace(System.err);
+    }
+  }
+
+  // This method is left as an exercise to the reader/extension point
+  // Don't forget to call `triggerDemand` at the end if you are interested in more data,
+  // a return value of < 0 indicates that the subscription should be cancelled,
+  // a value of 0 indicates that there is no current need,
+  // a value of > 0 indicates the current need.
+  protected abstract long foreach(final T element);
+
+  @Override public void onError(final Throwable t) {
+    if (subscription == null) { // Technically this check is not needed, since we are expecting Publishers to conform to the spec
+      (new IllegalStateException("Publisher violated the Reactive Streams rule 1.09 signalling onError prior to onSubscribe.")).printStackTrace(System.err);
+    } else {
+      // As per rule 2.13, we need to throw a `java.lang.NullPointerException` if the `Throwable` is `null`
+      if (t == null) throw null;
+      // Here we are not allowed to call any methods on the `Subscription` or the `Publisher`, as per rule 2.3
+      // And anyway, the `Subscription` is considered to be cancelled if this method gets called, as per rule 2.4
+    }
+  }
+
+  @Override public void onComplete() {
+    if (subscription == null) { // Technically this check is not needed, since we are expecting Publishers to conform to the spec
+      (new IllegalStateException("Publisher violated the Reactive Streams rule 1.09 signalling onComplete prior to onSubscribe.")).printStackTrace(System.err);
+    } else {
+      // Here we are not allowed to call any methods on the `Subscription` or the `Publisher`, as per rule 2.3
+      // And anyway, the `Subscription` is considered to be cancelled if this method gets called, as per rule 2.4
+    }
+  }
+}
\ No newline at end of file
diff --git a/tck/src/test/java/org/reactivestreams/tck/support/TCKVerificationSupport.java b/tck/src/test/java/org/reactivestreams/tck/support/TCKVerificationSupport.java
new file mode 100644
index 0000000..8aa396e
--- /dev/null
+++ b/tck/src/test/java/org/reactivestreams/tck/support/TCKVerificationSupport.java
@@ -0,0 +1,129 @@
+package org.reactivestreams.tck.support;
+
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import org.testng.SkipException;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.testng.Assert.fail;
+
+/**
+ * Provides assertions to validate the TCK tests themselves,
+ * with the goal of guaranteeing proper error messages when an implementation does not pass a given TCK test.
+ *
+ * "Quis custodiet ipsos custodes?" -- Iuvenalis
+ */
+public class TCKVerificationSupport {
+
+  // INTERNAL ASSERTION METHODS //
+
+  /**
+   * Runs given code block and expects it to fail with an "Expected onError" failure.
+   * Use this method to validate that TCK tests fail with meaningful errors instead of NullPointerExceptions etc.
+   *
+   * @param run     encapsulates test case which we expect to fail
+   * @param msgPart the exception failing the test (inside the run parameter) must contain this message part in one of it's causes
+   */
+  public void requireTestFailure(ThrowingRunnable run, String msgPart) {
+    try {
+      run.run();
+    } catch (Throwable throwable) {
+      if (findDeepErrorMessage(throwable, msgPart)) {
+        return;
+      } else {
+        throw new RuntimeException(
+          String.format("Expected TCK to fail with '... %s ...', " +
+                          "yet `%s(%s)` was thrown and test would fail with not useful error message!",
+                        msgPart, throwable.getClass().getName(), throwable.getMessage()), throwable);
+      }
+    }
+    throw new RuntimeException(String.format("Expected TCK to fail with '... %s ...', " +
+                                               "yet no exception was thrown and test would pass unexpectedly!",
+                                             msgPart));
+  }
+
+  /**
+   * Runs given code block and expects it fail with an {@code org.testng.SkipException}
+   *
+   * @param run encapsulates test case which we expect to be skipped
+   * @param msgPart the exception failing the test (inside the run parameter) must contain this message part in one of it's causes
+   */
+  public void requireTestSkip(ThrowingRunnable run, String msgPart) {
+    try {
+      run.run();
+    } catch (SkipException skip) {
+        if (skip.getMessage().contains(msgPart)) {
+          return;
+        } else {
+          throw new RuntimeException(
+            String.format("Expected TCK to skip this test with '... %s ...', yet it skipped with (%s) instead!",
+                          msgPart, skip.getMessage()), skip);
+        }
+    } catch (Throwable throwable) {
+        throw new RuntimeException(
+          String.format("Expected TCK to skip this test, yet it threw %s(%s) instead!",
+                        throwable.getClass().getName(), throwable.getMessage()), throwable);
+    }
+    throw new RuntimeException("Expected TCK to SKIP this test, instead if PASSed!");
+  }
+
+  /**
+   * This publisher does NOT fulfil all Publisher spec requirements.
+   * It's just the bare minimum to enable this test to fail the Subscriber tests.
+   */
+  public Publisher<Integer> newSimpleIntsPublisher(final long maxElementsToEmit) {
+    return new Publisher<Integer>() {
+      @Override public void subscribe(final Subscriber<? super Integer> s) {
+        s.onSubscribe(new Subscription() {
+          private AtomicLong nums = new AtomicLong();
+          private AtomicBoolean active = new AtomicBoolean(true);
+
+          @Override public void request(long n) {
+            long thisDemand = n;
+            while (active.get() && thisDemand > 0 && nums.get() < maxElementsToEmit) {
+              s.onNext((int) nums.getAndIncrement());
+              thisDemand--;
+            }
+
+            if (nums.get() == maxElementsToEmit) {
+              s.onComplete();
+            }
+          }
+
+          @Override public void cancel() {
+            active.set(false);
+          }
+        });
+      }
+    };
+  }
+
+  /**
+   * Looks for expected error message prefix inside of causes of thrown throwable.
+   *
+   * @return true if one of the causes indeed contains expected error, false otherwise
+   */
+  public boolean findDeepErrorMessage(Throwable throwable, String msgPart) {
+    return findDeepErrorMessage(throwable, msgPart, 5);
+  }
+
+  private boolean findDeepErrorMessage(Throwable throwable, String msgPart, int depth) {
+    if (throwable instanceof NullPointerException) {
+      fail(String.format("%s was thrown, definitely not a helpful error!", NullPointerException.class.getName()), throwable);
+      return false;
+    } else if (throwable == null || depth == 0) {
+      return false;
+    } else {
+      final String message = throwable.getMessage();
+      return (message != null && message.contains(msgPart)) || findDeepErrorMessage(throwable.getCause(), msgPart, depth - 1);
+    }
+  }
+
+  /** Like {@link java.lang.Runnable} but allows throwing {@link java.lang.Throwable}, useful for wrapping test execution */
+  public static interface ThrowingRunnable {
+    void run() throws Throwable;
+  }
+}
diff --git a/tck/src/test/resources/testng.yaml b/tck/src/test/resources/testng.yaml
new file mode 100644
index 0000000..854a0ba
--- /dev/null
+++ b/tck/src/test/resources/testng.yaml
@@ -0,0 +1,10 @@
+name: TCKSuite
+threadCount: 1
+
+tests:
+  - name: TCK
+    classes:
+      - org.reactivestreams.tck.IdentityProcessorVerificationDelegationTest
+      - org.reactivestreams.tck.PublisherVerificationTest
+      - org.reactivestreams.tck.SubscriberBlackboxVerificationTest
+      - org.reactivestreams.tck.SubscriberWhiteboxVerificationTest

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/reactive-streams.git



More information about the pkg-java-commits mailing list