8226602: Test convenience reactive primitives from java.net.http with RS TCK
Reviewed-by: chegar, dfuchs
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/BodyPublishersFromPublisher.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.FlowPublisherVerification;
+
+import java.net.http.HttpRequest.BodyPublishers;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Flow.Publisher;
+import java.util.stream.Stream;
+
+/* See TckDriver.java for more information */
+public class BodyPublishersFromPublisher
+ extends FlowPublisherVerification<ByteBuffer> {
+
+ public BodyPublishersFromPublisher() {
+ super(new TestEnvironment(450L));
+ }
+
+ @Override
+ public Publisher<ByteBuffer> createFlowPublisher(long nElements) {
+ Stream<ByteBuffer> buffers =
+ Stream.generate(() -> S.bufferOfNRandomBytes(1024))
+ .limit(nElements);
+ Publisher<ByteBuffer> pub = S.publisherOfStream(buffers);
+ return BodyPublishers.fromPublisher(pub);
+ }
+
+ @Override
+ public Publisher<ByteBuffer> createFailedFlowPublisher() {
+ return BodyPublishers.fromPublisher(S.newErroredPublisher());
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/BodyPublishersNoBody.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.FlowPublisherVerification;
+
+import java.net.http.HttpRequest.BodyPublishers;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Flow.Publisher;
+
+/* See TckDriver.java for more information */
+public class BodyPublishersNoBody
+ extends FlowPublisherVerification<ByteBuffer> {
+
+ public BodyPublishersNoBody() {
+ super(new TestEnvironment(450L));
+ }
+
+ @Override
+ public Publisher<ByteBuffer> createFlowPublisher(long nElements) {
+ return BodyPublishers.noBody();
+ }
+
+ @Override
+ public Publisher<ByteBuffer> createFailedFlowPublisher() {
+ return null;
+ }
+
+ @Override
+ public long maxElementsFromPublisher() {
+ return 0;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/BodyPublishersOfByteArray.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.FlowPublisherVerification;
+
+import java.net.http.HttpRequest.BodyPublishers;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Flow.Publisher;
+
+/* See TckDriver.java for more information */
+public class BodyPublishersOfByteArray
+ extends FlowPublisherVerification<ByteBuffer> {
+
+ private static final int ELEMENT_SIZE = 16 * 1024;
+
+ public BodyPublishersOfByteArray() {
+ super(new TestEnvironment(450L));
+ }
+
+ @Override
+ public Publisher<ByteBuffer> createFlowPublisher(long nElements) {
+ byte[] b = S.arrayOfNRandomBytes(nElements * ELEMENT_SIZE);
+ return BodyPublishers.ofByteArray(b);
+ }
+
+ @Override
+ public Publisher<ByteBuffer> createFailedFlowPublisher() {
+ return null;
+ }
+
+ @Override
+ public long maxElementsFromPublisher() {
+ return 21;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/BodyPublishersOfByteArrays.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.FlowPublisherVerification;
+
+import java.net.http.HttpRequest.BodyPublishers;
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.concurrent.Flow.Publisher;
+
+/* See TckDriver.java for more information */
+public class BodyPublishersOfByteArrays
+ extends FlowPublisherVerification<ByteBuffer> {
+
+ private static final int ELEMENT_SIZE = 16 * 1024;
+
+ public BodyPublishersOfByteArrays() {
+ super(new TestEnvironment(450L));
+ }
+
+ @Override
+ public Publisher<ByteBuffer> createFlowPublisher(long nElements) {
+ byte[] bytes = S.arrayOfNRandomBytes(ELEMENT_SIZE);
+ return BodyPublishers.ofByteArrays(
+ Collections.nCopies((int) nElements, bytes));
+ }
+
+ @Override
+ public Publisher<ByteBuffer> createFailedFlowPublisher() {
+ return null;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/BodyPublishersOfFile.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.FlowPublisherVerification;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.concurrent.Flow.Publisher;
+import java.util.concurrent.atomic.AtomicLong;
+
+/* See TckDriver.java for more information */
+public class BodyPublishersOfFile
+ extends FlowPublisherVerification<ByteBuffer> {
+
+ private static final int ELEMENT_SIZE = 16 * 1024;
+ private static final AtomicLong UNIQUE_NUMBERS = new AtomicLong();
+
+ public BodyPublishersOfFile() {
+ super(new TestEnvironment(450L));
+ }
+
+ @Override
+ public Publisher<ByteBuffer> createFlowPublisher(long nElements) {
+ try {
+ Path f = createFile(nElements * ELEMENT_SIZE);
+ return BodyPublishers.ofFile(f);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private static Path createFile(long nBytes) throws IOException {
+ String name = "f" + UNIQUE_NUMBERS.getAndIncrement();
+ Path f = Files.createFile(Path.of(name));
+ return Files.write(f, S.arrayOfNRandomBytes(nBytes));
+ }
+
+ @Override
+ public Publisher<ByteBuffer> createFailedFlowPublisher() {
+ return null;
+ }
+
+ @Override
+ public long maxElementsFromPublisher() {
+ return 21;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/BodyPublishersOfInputStream.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.FlowPublisherVerification;
+
+import java.io.InputStream;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Flow.Publisher;
+import java.util.function.Supplier;
+
+/* See TckDriver.java for more information */
+public class BodyPublishersOfInputStream
+ extends FlowPublisherVerification<ByteBuffer> {
+
+ public BodyPublishersOfInputStream() {
+ super(new TestEnvironment(450L));
+ }
+
+ @Override
+ public Publisher<ByteBuffer> createFlowPublisher(long nElements) {
+ Supplier<InputStream> s = () -> S.inputStreamOfNReads((int) nElements);
+ return BodyPublishers.ofInputStream(s);
+ }
+
+ @Override
+ public Publisher<ByteBuffer> createFailedFlowPublisher() {
+ return null;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/BodyPublishersOfSubByteArray.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.FlowPublisherVerification;
+
+import java.net.http.HttpRequest.BodyPublishers;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Flow.Publisher;
+
+/* See TckDriver.java for more information */
+public class BodyPublishersOfSubByteArray
+ extends FlowPublisherVerification<ByteBuffer> {
+
+ private static final int ELEMENT_SIZE = 16 * 1024;
+
+ public BodyPublishersOfSubByteArray() {
+ super(new TestEnvironment(450L));
+ }
+
+ @Override
+ public Publisher<ByteBuffer> createFlowPublisher(long nElements) {
+ int prefixLen = S.randomIntUpTo(13);
+ int postfixLen = S.randomIntUpTo(17);
+ byte[] b = S.arrayOfNRandomBytes(nElements * ELEMENT_SIZE);
+ byte[] contents = new byte[prefixLen + b.length + postfixLen];
+ System.arraycopy(b, 0, contents, prefixLen, b.length);
+ return BodyPublishers.ofByteArray(contents, prefixLen, b.length);
+ }
+
+ @Override
+ public Publisher<ByteBuffer> createFailedFlowPublisher() {
+ return null;
+ }
+
+ @Override
+ public long maxElementsFromPublisher() {
+ return 21;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/BodySubscribersBuffering.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.FlowSubscriberBlackboxVerification;
+
+import java.net.http.HttpResponse.BodySubscribers;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.Flow.Subscriber;
+
+/* See TckDriver.java for more information */
+public class BodySubscribersBuffering
+ extends FlowSubscriberBlackboxVerification<List<ByteBuffer>> {
+
+ public BodySubscribersBuffering() {
+ super(new TestEnvironment(450L));
+ }
+
+ @Override
+ public Subscriber<List<ByteBuffer>> createFlowSubscriber() {
+ return BodySubscribers.buffering(BodySubscribers.discarding(),
+ S.randomIntUpTo(1024) + 1);
+ }
+
+ @Override
+ public List<ByteBuffer> createElement(int element) {
+ return S.listOfBuffersFromBufferOfNBytes(element % 17);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/BodySubscribersDiscarding.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.FlowSubscriberBlackboxVerification;
+
+import java.net.http.HttpResponse.BodySubscribers;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.Flow.Subscriber;
+
+/* See TckDriver.java for more information */
+public class BodySubscribersDiscarding
+ extends FlowSubscriberBlackboxVerification<List<ByteBuffer>> {
+
+ public BodySubscribersDiscarding() {
+ super(new TestEnvironment(450L));
+ }
+
+ @Override
+ public Subscriber<List<ByteBuffer>> createFlowSubscriber() {
+ return BodySubscribers.discarding();
+ }
+
+ @Override
+ public List<ByteBuffer> createElement(int element) {
+ return S.listOfBuffersFromBufferOfNBytes(element % 17);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/BodySubscribersFromLineSubscriber.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.FlowSubscriberBlackboxVerification;
+
+import java.net.http.HttpResponse.BodySubscribers;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.Flow.Subscriber;
+
+/* See TckDriver.java for more information */
+public class BodySubscribersFromLineSubscriber
+ extends FlowSubscriberBlackboxVerification<List<ByteBuffer>> {
+
+ public BodySubscribersFromLineSubscriber() {
+ super(new TestEnvironment(450L));
+ }
+
+ @Override
+ public Subscriber<List<ByteBuffer>> createFlowSubscriber() {
+ return BodySubscribers.fromLineSubscriber(
+ S.nonCompliantSubscriber());
+ }
+
+ @Override
+ public List<ByteBuffer> createElement(int element) {
+ return S.scatterBuffer(
+ S.bufferOfNRandomASCIIBytes(element % 17));
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/BodySubscribersFromSubscriber.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.FlowSubscriberBlackboxVerification;
+
+import java.net.http.HttpResponse.BodySubscribers;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.Flow.Subscriber;
+
+/* See TckDriver.java for more information */
+public class BodySubscribersFromSubscriber
+ extends FlowSubscriberBlackboxVerification<List<ByteBuffer>> {
+
+ public BodySubscribersFromSubscriber() {
+ super(new TestEnvironment(450L));
+ }
+
+ @Override
+ public Subscriber<List<ByteBuffer>> createFlowSubscriber() {
+ Subscriber<List<ByteBuffer>> sub = S.nonCompliantSubscriber();
+ return BodySubscribers.fromSubscriber(sub);
+ }
+
+ @Override
+ public List<ByteBuffer> createElement(int element) {
+ return S.scatterBuffer(
+ S.bufferOfNRandomASCIIBytes(element % 17));
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/BodySubscribersMapping.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.FlowSubscriberBlackboxVerification;
+
+import java.net.http.HttpResponse.BodySubscribers;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.Flow.Subscriber;
+
+/* See TckDriver.java for more information */
+public class BodySubscribersMapping
+ extends FlowSubscriberBlackboxVerification<List<ByteBuffer>> {
+
+ public BodySubscribersMapping() {
+ super(new TestEnvironment(450L));
+ }
+
+ @Override
+ public Subscriber<List<ByteBuffer>> createFlowSubscriber() {
+ return BodySubscribers.mapping(BodySubscribers.ofByteArray(),
+ bytes -> bytes.length);
+ }
+
+ @Override
+ public List<ByteBuffer> createElement(int element) {
+ return S.listOfBuffersFromBufferOfNBytes(element % 17);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/BodySubscribersOfByteArray.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.FlowSubscriberBlackboxVerification;
+
+import java.net.http.HttpResponse.BodySubscribers;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.Flow.Subscriber;
+
+/* See TckDriver.java for more information */
+public class BodySubscribersOfByteArray
+ extends FlowSubscriberBlackboxVerification<List<ByteBuffer>> {
+
+ public BodySubscribersOfByteArray() {
+ super(new TestEnvironment(450L));
+ }
+
+ @Override
+ public Subscriber<List<ByteBuffer>> createFlowSubscriber() {
+ return BodySubscribers.ofByteArray();
+ }
+
+ @Override
+ public List<ByteBuffer> createElement(int element) {
+ return S.listOfBuffersFromBufferOfNBytes(element % 17);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/BodySubscribersOfByteArrayConsumer.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.FlowSubscriberBlackboxVerification;
+
+import java.net.http.HttpResponse.BodySubscribers;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.Flow.Subscriber;
+
+/* See TckDriver.java for more information */
+public class BodySubscribersOfByteArrayConsumer
+ extends FlowSubscriberBlackboxVerification<List<ByteBuffer>> {
+
+ public BodySubscribersOfByteArrayConsumer() {
+ super(new TestEnvironment(450L));
+ }
+
+ @Override
+ public Subscriber<List<ByteBuffer>> createFlowSubscriber() {
+ return BodySubscribers.ofByteArrayConsumer(bytes -> { });
+ }
+
+ @Override
+ public List<ByteBuffer> createElement(int element) {
+ return S.listOfBuffersFromBufferOfNBytes(element % 17);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/BodySubscribersOfFile.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.FlowSubscriberBlackboxVerification;
+
+import java.net.http.HttpResponse.BodySubscribers;
+import java.nio.ByteBuffer;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.concurrent.Flow.Subscriber;
+
+/* See TckDriver.java for more information */
+public class BodySubscribersOfFile
+ extends FlowSubscriberBlackboxVerification<List<ByteBuffer>> {
+
+ public BodySubscribersOfFile() {
+ super(new TestEnvironment(450L));
+ }
+
+ @Override
+ public Subscriber<List<ByteBuffer>> createFlowSubscriber() {
+ return BodySubscribers.ofFile(Path.of("f1.bin"));
+ }
+
+ @Override
+ public List<ByteBuffer> createElement(int element) {
+ return S.listOfBuffersFromBufferOfNBytes(element % 17);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/BodySubscribersOfInputStream.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.FlowSubscriberBlackboxVerification;
+
+import java.net.http.HttpResponse.BodySubscribers;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.Flow.Subscriber;
+
+/* See TckDriver.java for more information */
+public class BodySubscribersOfInputStream
+ extends FlowSubscriberBlackboxVerification<List<ByteBuffer>> {
+
+ public BodySubscribersOfInputStream() {
+ super(new TestEnvironment(450L));
+ }
+
+ @Override
+ public Subscriber<List<ByteBuffer>> createFlowSubscriber() {
+ return BodySubscribers.ofInputStream();
+ }
+
+ @Override
+ public List<ByteBuffer> createElement(int element) {
+ return S.listOfBuffersFromBufferOfNBytes(element % 17);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/BodySubscribersOfLines.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.FlowSubscriberBlackboxVerification;
+
+import java.net.http.HttpResponse.BodySubscribers;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.concurrent.Flow.Subscriber;
+
+/* See TckDriver.java for more information */
+public class BodySubscribersOfLines
+ extends FlowSubscriberBlackboxVerification<List<ByteBuffer>> {
+
+ public BodySubscribersOfLines() {
+ super(new TestEnvironment(450L));
+ }
+
+ @Override
+ public Subscriber<List<ByteBuffer>> createFlowSubscriber() {
+ return BodySubscribers.ofLines(StandardCharsets.UTF_8);
+ }
+
+ @Override
+ public List<ByteBuffer> createElement(int element) {
+ return S.scatterBuffer(
+ S.bufferOfNRandomASCIIBytes(element % 17));
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/BodySubscribersOfPublisher.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.FlowSubscriberBlackboxVerification;
+
+import java.net.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpResponse.BodySubscribers;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Flow.Publisher;
+import java.util.concurrent.Flow.Subscriber;
+import java.util.concurrent.Flow.Subscription;
+
+/* See TckDriver.java for more information */
+public class BodySubscribersOfPublisher
+ extends FlowSubscriberBlackboxVerification<List<ByteBuffer>> {
+
+ public BodySubscribersOfPublisher() {
+ super(new TestEnvironment(450L));
+ }
+
+ /* The reason for overriding this method is that BodySubscribers.ofPublisher
+ is somewhat tricky. It is not an independent Subscriber, but rather
+ an adaptor from Subscriber to Publisher. Until the Subscriber that
+ subscribed to that resulting Publisher requests anything, nothing
+ happens. */
+ @Override
+ public void triggerFlowRequest(
+ Subscriber<? super List<ByteBuffer>> subscriber)
+ {
+ BodySubscriber<Publisher<List<ByteBuffer>>> sub =
+ (BodySubscriber<Publisher<List<ByteBuffer>>>) subscriber;
+ CompletionStage<Publisher<List<ByteBuffer>>> body = sub.getBody();
+ Publisher<List<ByteBuffer>> pub = body.toCompletableFuture().join();
+ pub.subscribe(new Subscriber<>() {
+
+ @Override
+ public void onSubscribe(Subscription subscription) {
+ subscription.request(Integer.MAX_VALUE);
+ }
+
+ @Override public void onNext(List<ByteBuffer> item) { }
+ @Override public void onError(Throwable throwable) { }
+ @Override public void onComplete() { }
+ });
+ }
+
+ @Override
+ public Subscriber<List<ByteBuffer>> createFlowSubscriber() {
+ return BodySubscribers.ofPublisher();
+ }
+
+ @Override
+ public List<ByteBuffer> createElement(int element) {
+ return S.listOfBuffersFromBufferOfNBytes(element % 17);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/BodySubscribersOfPublisher1.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.FlowSubscriberBlackboxVerification;
+
+import java.net.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpResponse.BodySubscribers;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Flow.Publisher;
+import java.util.concurrent.Flow.Subscriber;
+import java.util.concurrent.Flow.Subscription;
+
+/* See TckDriver.java for more information */
+public class BodySubscribersOfPublisher1
+ extends FlowSubscriberBlackboxVerification<List<ByteBuffer>> {
+
+ public BodySubscribersOfPublisher1() {
+ super(new TestEnvironment(450L));
+ }
+
+ /* The reason for overriding this method is that BodySubscribers.ofPublisher
+ is somewhat tricky. It is not an independent Subscriber, but rather
+ an adaptor from Subscriber to Publisher. Until the Subscriber that
+ subscribed to that resulting Publisher requests anything, nothing
+ happens. */
+ @Override
+ public void triggerFlowRequest(
+ Subscriber<? super List<ByteBuffer>> subscriber)
+ {
+ BodySubscriber<Publisher<List<ByteBuffer>>> sub =
+ (BodySubscriber<Publisher<List<ByteBuffer>>>) subscriber;
+ CompletionStage<Publisher<List<ByteBuffer>>> body = sub.getBody();
+ Publisher<List<ByteBuffer>> pub = body.toCompletableFuture().join();
+ pub.subscribe(new Subscriber<>() {
+
+ Subscription sub;
+
+ @Override
+ public void onSubscribe(Subscription subscription) {
+ (sub = subscription).request(1);
+ }
+
+ @Override public void onNext(List<ByteBuffer> item) {
+ sub.request(1);
+ }
+
+ @Override public void onError(Throwable throwable) { }
+ @Override public void onComplete() { }
+ });
+ }
+
+ @Override
+ public Subscriber<List<ByteBuffer>> createFlowSubscriber() {
+ return BodySubscribers.ofPublisher();
+ }
+
+ @Override
+ public List<ByteBuffer> createElement(int element) {
+ return S.listOfBuffersFromBufferOfNBytes(element % 17);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/BodySubscribersOfPublisherPublisher.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.FlowPublisherVerification;
+
+import java.net.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpResponse.BodySubscribers;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.Flow.Publisher;
+import java.util.stream.Stream;
+
+/* See TckDriver.java for more information */
+public class BodySubscribersOfPublisherPublisher
+ extends FlowPublisherVerification<List<ByteBuffer>> {
+
+ public BodySubscribersOfPublisherPublisher() {
+ super(new TestEnvironment(450L));
+ }
+
+ @Override
+ public Publisher<List<ByteBuffer>> createFlowPublisher(long nElements) {
+ BodySubscriber<Publisher<List<ByteBuffer>>> sub =
+ BodySubscribers.ofPublisher();
+ Stream<List<ByteBuffer>> buffers =
+ Stream.generate(() -> S.listOfBuffersFromBufferOfNBytes(1024))
+ .limit(nElements);
+ Publisher<List<ByteBuffer>> pub = S.publisherOfStream(buffers);
+ pub.subscribe(sub);
+ return sub.getBody().toCompletableFuture().join();
+ }
+
+ @Override
+ public Publisher<List<ByteBuffer>> createFailedFlowPublisher() {
+ BodySubscriber<Publisher<List<ByteBuffer>>> sub =
+ BodySubscribers.ofPublisher();
+ Publisher<List<ByteBuffer>> pub = S.newErroredPublisher();
+ pub.subscribe(sub);
+ return sub.getBody().toCompletableFuture().join();
+ }
+
+ @Override
+ public long maxElementsFromPublisher() {
+ return 21;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/BodySubscribersOfString.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.FlowSubscriberBlackboxVerification;
+
+import java.net.http.HttpResponse.BodySubscribers;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.concurrent.Flow.Subscriber;
+
+/* See TckDriver.java for more information */
+public class BodySubscribersOfString
+ extends FlowSubscriberBlackboxVerification<List<ByteBuffer>> {
+
+ public BodySubscribersOfString() {
+ super(new TestEnvironment(450L));
+ }
+
+ @Override
+ public Subscriber<List<ByteBuffer>> createFlowSubscriber() {
+ return BodySubscribers.ofString(StandardCharsets.UTF_8);
+ }
+
+ @Override
+ public List<ByteBuffer> createElement(int element) {
+ return S.scatterBuffer(
+ S.bufferOfNRandomASCIIBytes(element % 17));
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/BodySubscribersReplacing.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.FlowSubscriberBlackboxVerification;
+
+import java.net.http.HttpResponse.BodySubscribers;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.Flow.Subscriber;
+
+/* See TckDriver.java for more information */
+public class BodySubscribersReplacing
+ extends FlowSubscriberBlackboxVerification<List<ByteBuffer>> {
+
+ public BodySubscribersReplacing() {
+ super(new TestEnvironment(450L));
+ }
+
+ @Override
+ public Subscriber<List<ByteBuffer>> createFlowSubscriber() {
+ /* it doesn't matter what we are replacing with */
+ return BodySubscribers.replacing(Boolean.TRUE);
+ }
+
+ @Override
+ public List<ByteBuffer> createElement(int element) {
+ return S.listOfBuffersFromBufferOfNBytes(element % 17);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/S.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,277 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Random;
+import java.util.concurrent.Flow.Publisher;
+import java.util.concurrent.Flow.Subscriber;
+import java.util.concurrent.Flow.Subscription;
+import java.util.stream.Stream;
+
+/*
+ * S for Support.
+ *
+ * Auxiliary methods for tests that check conformance with reactive streams
+ * specification.
+ *
+ * Short name is for the sake of convenience calling this class' static methods.
+ * It could've been called Support or TckSupport, but then we would need to
+ * place this class in its own package so as to use "import static".
+ */
+public class S {
+
+ private static final Random RANDOM = new SecureRandom();
+
+ private S() { }
+
+ public static List<ByteBuffer> listOfBuffersFromBufferOfNBytes(int nBytes) {
+ return scatterBuffer(bufferOfNRandomBytes(nBytes));
+ }
+
+ /*
+ * Spreads the remaining contents of the given byte buffer across a number
+ * of buffers put into a list.
+ */
+ public static List<ByteBuffer> scatterBuffer(ByteBuffer src) {
+ List<ByteBuffer> buffers = new ArrayList<>();
+ while (src.hasRemaining()) {
+ // We do not allow empty buffers ~~~~~~~~~~~~~~~~v
+ int capacity = RANDOM.nextInt(src.remaining()) + 1;
+ ByteBuffer b = ByteBuffer.allocate(capacity);
+ for (int i = 0; i < capacity; i++) {
+ b.put(src.get());
+ }
+ b.flip();
+ buffers.add(b);
+ }
+ return List.copyOf(buffers);
+ }
+
+ public static ByteBuffer bufferOfNRandomBytes(int capacity) {
+ return ByteBuffer.wrap(arrayOfNRandomBytes(capacity));
+ }
+
+ public static byte[] arrayOfNRandomBytes(int nBytes) {
+ byte[] contents = new byte[nBytes];
+ RANDOM.nextBytes(contents);
+ return contents;
+ }
+
+ public static InputStream inputStreamOfNReads(long n) {
+ return new NReadsInputStream(n);
+ }
+
+ /*
+ * Convenience method for testing publishers.
+ */
+ public static byte[] arrayOfNRandomBytes(long nBytes) {
+ return arrayOfNRandomBytes((int) nBytes);
+ }
+
+ public static ByteBuffer bufferOfNRandomASCIIBytes(int capacity) {
+ String alphaNumeric = "abcdefghijklmnopqrstuvwxyz1234567890";
+ StringBuilder builder = new StringBuilder(capacity);
+ for (int i = 0; i < capacity; i++) {
+ int idx = RANDOM.nextInt(alphaNumeric.length());
+ builder.append(alphaNumeric.charAt(idx));
+ }
+ return ByteBuffer.wrap(builder.toString().getBytes(
+ StandardCharsets.US_ASCII));
+ }
+
+ /*
+ * Returns a simple non-compliant Subscriber.
+ *
+ * This Subscriber is useful for testing our adaptors and wrappers, to make
+ * sure they do not delegate RS compliance to the underlying (and foreign to
+ * java.net.http codebase) Subscribers, but rather comply themselves.
+ *
+ * Here's an example:
+ *
+ * public void onSubscribe(Subscription s) {
+ * delegate.onSubscribe(s);
+ * }
+ *
+ * The snippet above cannot be considered a good implementation of a
+ * Subscriber if `delegate` is an unknown Subscriber. In this case the
+ * implementation should independently check all the rules from the RS spec
+ * related to subscribers.
+ */
+ public static <T> Subscriber<T> nonCompliantSubscriber() {
+ return new Subscriber<>() {
+
+ @Override
+ public void onSubscribe(Subscription subscription) {
+ subscription.request(Long.MAX_VALUE);
+ }
+
+ @Override
+ public void onNext(T item) { }
+
+ @Override
+ public void onError(Throwable throwable) { }
+
+ @Override
+ public void onComplete() { }
+ };
+ }
+
+ public static int randomIntUpTo(int bound) {
+ return RANDOM.nextInt(bound);
+ }
+
+ /*
+ * Signals an error to its subscribers immediately after subscription.
+ */
+ public static <T> Publisher<T> newErroredPublisher() {
+ return subscriber -> {
+ subscriber.onSubscribe(new Subscription() {
+ @Override
+ public void request(long n) { }
+
+ @Override
+ public void cancel() { }
+ });
+ subscriber.onError(new IOException());
+ };
+ }
+
+ /*
+ * Publishes the elements obtained from the stream and signals completion.
+ * Can be cancelled, but cannot signal an error.
+ *
+ * This trivial ad-hoc implementation of Publisher was created so as to
+ * publish lists of byte buffers. We can publish ByteBuffer, but we can't
+ * seem to publish List<ByteBuffer> since there's no readily available
+ * publisher of those, nor there's a simple adaptor.
+ */
+ public static <T> Publisher<T> publisherOfStream(Stream<? extends T> stream)
+ {
+ if (stream == null) {
+ throw new NullPointerException();
+ }
+ return new Publisher<T>() {
+ @Override
+ public void subscribe(Subscriber<? super T> subscriber) {
+ if (subscriber == null) {
+ throw new NullPointerException();
+ }
+ Subscription subscription = new Subscription() {
+
+ boolean inOnNext; // recursion control
+ volatile boolean cancelled;
+ long demand;
+ final Iterator<? extends T> supply = stream.iterator();
+
+ @Override
+ public void request(long n) {
+ demand = demand + n < 0 ? Long.MAX_VALUE : demand + n;
+ if (inOnNext) {
+ return;
+ }
+ if (cancelled)
+ return;
+ if (n <= 0) {
+ cancelled = true;
+ subscriber.onError(new IllegalArgumentException(
+ "non-positive subscription request"));
+ return;
+ }
+ while (supply.hasNext() && demand > 0 && !cancelled) {
+ demand--;
+ inOnNext = true;
+ try {
+ T item = supply.next();
+ subscriber.onNext(item);
+ } finally {
+ inOnNext = false;
+ }
+ }
+ if (!supply.hasNext()) {
+ cancelled = true;
+ subscriber.onComplete();
+ }
+ }
+
+ @Override
+ public void cancel() {
+ cancelled = true;
+ }
+ };
+ subscriber.onSubscribe(subscription);
+ }
+ };
+ }
+
+ static final class NReadsInputStream extends InputStream {
+
+ private static final int EOF = -1;
+ private long readsLeft;
+
+ NReadsInputStream(long n) {
+ if (n < 0) {
+ throw new IllegalArgumentException(String.valueOf(n));
+ }
+ this.readsLeft = n;
+ }
+
+ @Override
+ public int read() {
+ if (readsLeft == 0L) {
+ return EOF;
+ }
+ readsLeft--;
+ return S.randomIntUpTo(256);
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) {
+ Objects.checkFromIndexSize(off, len, b.length);
+ // Must return 0 if len == 0,
+ // even if there are no more reads left
+ if (len == 0) {
+ return 0;
+ }
+ if (readsLeft == 0L) {
+ return EOF;
+ }
+ readsLeft--;
+ // At least one byte MUST be read, but we can read
+ // less than `len` bytes
+ int r = RANDOM.nextInt(len) + 1;
+ for (int i = 0; i < r; i++) {
+ b[i] = (byte) randomIntUpTo(256);
+ }
+ return r;
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/SPublisherOfStream.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.FlowPublisherVerification;
+
+import java.util.concurrent.Flow.Publisher;
+import java.util.stream.LongStream;
+import java.util.stream.Stream;
+
+/* See TckDriver.java for more information */
+public class SPublisherOfStream
+ extends FlowPublisherVerification<Long> {
+
+ public SPublisherOfStream() {
+ super(new TestEnvironment(450L));
+ }
+
+ @Override
+ public Publisher<Long> createFlowPublisher(long nElements) {
+ Stream<Long> s = LongStream.range(0, nElements).boxed();
+ return S.publisherOfStream(s);
+ }
+
+ @Override
+ public Publisher<Long> createFailedFlowPublisher() {
+ return null;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/STest.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+public class STest {
+
+ @DataProvider(name = "bufferSizes")
+ public static Object[][] bufferSizes() {
+ return new Object[][]{
+ { 1},
+ { 2},
+ { 3},
+ { 4},
+ {16},
+ {17},
+ };
+ }
+
+ @DataProvider
+ public static Object[][] inputStream() {
+ return new Object[][] {
+ { 0, 1},
+ { 1, 2},
+ { 1, 3},
+ { 1, 4},
+ { 2, 1},
+ { 2, 2},
+ { 2, 3},
+ { 2, 4},
+ { 2, 13},
+ { 3, 1},
+ { 3, 2},
+ { 3, 3},
+ { 3, 4},
+ { 3, 17},
+ { 4, 1},
+ { 4, 2},
+ { 4, 3},
+ { 4, 4},
+ { 4, 5},
+ { 13, 1},
+ { 13, 2},
+ { 13, 13},
+ { 16, 18},
+ { 17, 2},
+ {255, 1},
+ {256, 255},
+ {257, 267},
+ };
+ }
+
+ @Test
+ public void testScatter0() {
+ List<ByteBuffer> buffers = S.scatterBuffer(
+ ByteBuffer.allocate(0));
+ assertEquals(buffers.size(), 0);
+ }
+
+ @Test(dataProvider = "bufferSizes")
+ public void testScatterN(int n) {
+ final ByteBuffer src = S.bufferOfNRandomBytes(n);
+ final int srcLength = src.remaining();
+ ByteBuffer copy = ByteBuffer.wrap(Arrays.copyOf(src.array(),
+ src.array().length));
+ List<ByteBuffer> buffers = S.scatterBuffer(src);
+ int m = 0;
+ for (ByteBuffer b : buffers) {
+ m += b.remaining();
+ while (b.hasRemaining() & copy.hasRemaining()) {
+ assertEquals(b.get(), copy.get());
+ }
+ }
+ assertEquals(m, srcLength);
+ }
+
+ @Test(dataProvider = "inputStream")
+ public void testInputStreamOfNReads(int n, int capacity) throws IOException {
+ InputStream s = S.inputStreamOfNReads(n);
+ int count = 0;
+ byte[] b = new byte[capacity];
+ while (s.read(b) != -1) {
+ count++;
+ }
+ assertEquals(count, n);
+ assertTrue(s.read() == -1);
+ assertTrue(s.read(b) == -1);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck-tests/TckDriver.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8226602
+ * @summary Tests convenience reactive primitives with RS TCK
+ *
+ * @library ../reactivestreams-tck
+ * @build S
+ *
+ * @compile -encoding UTF-8 SPublisherOfStream.java
+ *
+ * @compile -encoding UTF-8 BodyPublishersFromPublisher.java
+ * @compile -encoding UTF-8 BodyPublishersNoBody.java
+ * @compile -encoding UTF-8 BodyPublishersOfByteArray.java
+ * @compile -encoding UTF-8 BodyPublishersOfByteArrays.java
+ * @compile -encoding UTF-8 BodyPublishersOfFile.java
+ * @compile -encoding UTF-8 BodyPublishersOfInputStream.java
+ * @compile -encoding UTF-8 BodyPublishersOfSubByteArray.java
+ *
+ * @compile -encoding UTF-8 BodySubscribersBuffering.java
+ * @compile -encoding UTF-8 BodySubscribersDiscarding.java
+ * @compile -encoding UTF-8 BodySubscribersFromLineSubscriber.java
+ * @compile -encoding UTF-8 BodySubscribersFromSubscriber.java
+ * @compile -encoding UTF-8 BodySubscribersMapping.java
+ * @compile -encoding UTF-8 BodySubscribersOfByteArray.java
+ * @compile -encoding UTF-8 BodySubscribersOfByteArrayConsumer.java
+ * @compile -encoding UTF-8 BodySubscribersOfFile.java
+ * @compile -encoding UTF-8 BodySubscribersOfInputStream.java
+ * @compile -encoding UTF-8 BodySubscribersOfLines.java
+ * @compile -encoding UTF-8 BodySubscribersOfPublisher.java
+ * @compile -encoding UTF-8 BodySubscribersOfPublisher1.java
+ * @compile -encoding UTF-8 BodySubscribersOfPublisherPublisher.java
+ * @compile -encoding UTF-8 BodySubscribersOfString.java
+ * @compile -encoding UTF-8 BodySubscribersReplacing.java
+ *
+ * @run testng/othervm STest
+ * @run testng/othervm SPublisherOfStream
+ *
+ * @run testng/othervm BodyPublishersFromPublisher
+ * @run testng/othervm BodyPublishersNoBody
+ * @run testng/othervm BodyPublishersOfByteArray
+ * @run testng/othervm BodyPublishersOfByteArrays
+ * @run testng/othervm BodyPublishersOfFile
+ * @run testng/othervm BodyPublishersOfInputStream
+ * @run testng/othervm BodyPublishersOfSubByteArray
+ *
+ * @run testng/othervm BodySubscribersBuffering
+ * @run testng/othervm BodySubscribersDiscarding
+ * @run testng/othervm BodySubscribersFromLineSubscriber
+ * @run testng/othervm BodySubscribersFromSubscriber
+ * @run testng/othervm BodySubscribersMapping
+ * @run testng/othervm BodySubscribersOfByteArray
+ * @run testng/othervm BodySubscribersOfByteArrayConsumer
+ * @run testng/othervm BodySubscribersOfFile
+ * @run testng/othervm BodySubscribersOfInputStream
+ * @run testng/othervm BodySubscribersOfLines
+ * @run testng/othervm BodySubscribersOfPublisher
+ * @run testng/othervm BodySubscribersOfPublisher1
+ * @run testng/othervm BodySubscribersOfPublisherPublisher
+ * @run testng/othervm BodySubscribersOfString
+ * @run testng/othervm BodySubscribersReplacing
+ *
+ * @key randomness
+ */
+public class TckDriver {
+ /*
+ #### General Information
+
+ 1. This JTREG test aggregates multiple TestNG tests. This is because
+ these tests share a common library (reactivestreams-tck), and we don't
+ want this library to be compiled separately for each of those tests.
+
+ 2. Tests that use RS TCK are compiled with the UTF-8 encoding. This is
+ performed for the sake of reactivestreams-tck. We don't want to patch
+ the TCK because of the extra merging work in the future, should we bring
+ update(s) from the RS repo.
+
+ #### Tests
+
+ 1. The purpose of each test should be easily digestible. The name of the
+ test is derived from the very entity the test exercises. For example,
+
+ the BodyPublishersOfFile test exercises the BodyPublisher obtained
+ by calling BodyPublishers.ofFile(Path)
+
+ the BodySubscribersOfFile test exercises the BodySubscriber obtained
+ by calling BodySubscribers.ofFile(Path)
+
+ 2. RS TCK requires PublisherVerification tests to produce publishers
+ capable of emitting a certain number of elements. In order to achieve
+ this, we use some knowledge of the internal workings of our publishers.
+ An example would be a chunk size a publisher uses to deliver a portion
+ of data. Without knowing that it is not possible to guarantee that the
+ publisher will emit a particular number of elements.
+
+ 3. Typically our publishers cannot be created in a known failed state.
+ In this case the corresponding `createFailedFlowPublisher` method
+ returns `null`.
+
+ 4. SubscriberBlackBoxVerification uses the `createElement(int element)`
+ method. Our implementations usually cap the amount of data created by
+ this method, because it's not known beforehand how big the `element`
+ value is. Hence, sometimes there's code like as follows:
+
+ @Override
+ public List<ByteBuffer> createElement(int element) {
+ return scatterBuffer(
+ bufferOfNRandomASCIIBytes(element % 17));
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
+ }
+
+ 5. The amount of testing RS TCK performs on a publisher seems to depend
+ on the number of elements this publisher reports it can emit. Sometimes
+ a code like the following can be seen in the tests:
+
+ @Override public long maxElementsFromPublisher() {
+ return 21;
+ ~~~~~~~~~~~^
+ }
+
+ This magic number is a result of trial and error and seems to unlock
+ most of the tests. Reporting big values (e.g. Long.MAX_VALUE - 1) is
+ not an option for most of our publishers because they require to have
+ all the elements upfront.
+
+ 6. It doesn't seem currently feasible to provide SubscriberWhiteboxVerification
+ tests as a) it's not clear how much better the coverage is and b) it's
+ significantly harder to code that.
+
+ #### S (Support)
+
+ Support utilities are being tested (STest) too.
+ */
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/FlowAdapters.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,389 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.reactivestreams;
+
+import java.util.concurrent.Flow;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Bridge between Reactive Streams API and the Java 9 {@link java.util.concurrent.Flow} API.
+ */
+public final class FlowAdapters {
+ /** Utility class. */
+ private FlowAdapters() {
+ throw new IllegalStateException("No instances!");
+ }
+
+ /**
+ * Converts a Flow Publisher into a Reactive Streams Publisher.
+ * @param <T> the element type
+ * @param flowPublisher the source Flow Publisher to convert
+ * @return the equivalent Reactive Streams Publisher
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> org.reactivestreams.Publisher<T> toPublisher(
+ Flow.Publisher<? extends T> flowPublisher) {
+ requireNonNull(flowPublisher, "flowPublisher");
+ final org.reactivestreams.Publisher<T> publisher;
+ if (flowPublisher instanceof FlowPublisherFromReactive) {
+ publisher = (org.reactivestreams.Publisher<T>)(((FlowPublisherFromReactive<T>)flowPublisher).reactiveStreams);
+ } else if (flowPublisher instanceof org.reactivestreams.Publisher) {
+ publisher = (org.reactivestreams.Publisher<T>)flowPublisher;
+ } else {
+ publisher = new ReactivePublisherFromFlow<T>(flowPublisher);
+ }
+ return publisher;
+ }
+
+ /**
+ * Converts a Reactive Streams Publisher into a Flow Publisher.
+ * @param <T> the element type
+ * @param reactiveStreamsPublisher the source Reactive Streams Publisher to convert
+ * @return the equivalent Flow Publisher
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> Flow.Publisher<T> toFlowPublisher(
+ org.reactivestreams.Publisher<? extends T> reactiveStreamsPublisher
+ ) {
+ requireNonNull(reactiveStreamsPublisher, "reactiveStreamsPublisher");
+ final Flow.Publisher<T> flowPublisher;
+ if (reactiveStreamsPublisher instanceof ReactivePublisherFromFlow) {
+ flowPublisher = (Flow.Publisher<T>)(((ReactivePublisherFromFlow<T>)reactiveStreamsPublisher).flow);
+ } else if (reactiveStreamsPublisher instanceof Flow.Publisher) {
+ flowPublisher = (Flow.Publisher<T>)reactiveStreamsPublisher;
+ } else {
+ flowPublisher = new FlowPublisherFromReactive<T>(reactiveStreamsPublisher);
+ }
+ return flowPublisher;
+ }
+
+ /**
+ * Converts a Flow Processor into a Reactive Streams Processor.
+ * @param <T> the input value type
+ * @param <U> the output value type
+ * @param flowProcessor the source Flow Processor to convert
+ * @return the equivalent Reactive Streams Processor
+ */
+ @SuppressWarnings("unchecked")
+ public static <T, U> org.reactivestreams.Processor<T, U> toProcessor(
+ Flow.Processor<? super T, ? extends U> flowProcessor
+ ) {
+ requireNonNull(flowProcessor, "flowProcessor");
+ final org.reactivestreams.Processor<T, U> processor;
+ if (flowProcessor instanceof FlowToReactiveProcessor) {
+ processor = (org.reactivestreams.Processor<T, U>)(((FlowToReactiveProcessor<T, U>)flowProcessor).reactiveStreams);
+ } else if (flowProcessor instanceof org.reactivestreams.Processor) {
+ processor = (org.reactivestreams.Processor<T, U>)flowProcessor;
+ } else {
+ processor = new ReactiveToFlowProcessor<T, U>(flowProcessor);
+ }
+ return processor;
+ }
+
+ /**
+ * Converts a Reactive Streams Processor into a Flow Processor.
+ * @param <T> the input value type
+ * @param <U> the output value type
+ * @param reactiveStreamsProcessor the source Reactive Streams Processor to convert
+ * @return the equivalent Flow Processor
+ */
+ @SuppressWarnings("unchecked")
+ public static <T, U> Flow.Processor<T, U> toFlowProcessor(
+ org.reactivestreams.Processor<? super T, ? extends U> reactiveStreamsProcessor
+ ) {
+ requireNonNull(reactiveStreamsProcessor, "reactiveStreamsProcessor");
+ final Flow.Processor<T, U> flowProcessor;
+ if (reactiveStreamsProcessor instanceof ReactiveToFlowProcessor) {
+ flowProcessor = (Flow.Processor<T, U>)(((ReactiveToFlowProcessor<T, U>)reactiveStreamsProcessor).flow);
+ } else if (reactiveStreamsProcessor instanceof Flow.Processor) {
+ flowProcessor = (Flow.Processor<T, U>)reactiveStreamsProcessor;
+ } else {
+ flowProcessor = new FlowToReactiveProcessor<T, U>(reactiveStreamsProcessor);
+ }
+ return flowProcessor;
+ }
+
+ /**
+ * Converts a Reactive Streams Subscriber into a Flow Subscriber.
+ * @param <T> the input and output value type
+ * @param reactiveStreamsSubscriber the Reactive Streams Subscriber instance to convert
+ * @return the equivalent Flow Subscriber
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> Flow.Subscriber<T> toFlowSubscriber(org.reactivestreams.Subscriber<T> reactiveStreamsSubscriber) {
+ requireNonNull(reactiveStreamsSubscriber, "reactiveStreamsSubscriber");
+ final Flow.Subscriber<T> flowSubscriber;
+ if (reactiveStreamsSubscriber instanceof ReactiveToFlowSubscriber) {
+ flowSubscriber = (Flow.Subscriber<T>)((ReactiveToFlowSubscriber<T>)reactiveStreamsSubscriber).flow;
+ } else if (reactiveStreamsSubscriber instanceof Flow.Subscriber) {
+ flowSubscriber = (Flow.Subscriber<T>)reactiveStreamsSubscriber;
+ } else {
+ flowSubscriber = new FlowToReactiveSubscriber<T>(reactiveStreamsSubscriber);
+ }
+ return flowSubscriber;
+ }
+
+ /**
+ * Converts a Flow Subscriber into a Reactive Streams Subscriber.
+ * @param <T> the input and output value type
+ * @param flowSubscriber the Flow Subscriber instance to convert
+ * @return the equivalent Reactive Streams Subscriber
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> org.reactivestreams.Subscriber<T> toSubscriber(Flow.Subscriber<T> flowSubscriber) {
+ requireNonNull(flowSubscriber, "flowSubscriber");
+ final org.reactivestreams.Subscriber<T> subscriber;
+ if (flowSubscriber instanceof FlowToReactiveSubscriber) {
+ subscriber = (org.reactivestreams.Subscriber<T>)((FlowToReactiveSubscriber<T>)flowSubscriber).reactiveStreams;
+ } else if (flowSubscriber instanceof org.reactivestreams.Subscriber) {
+ subscriber = (org.reactivestreams.Subscriber<T>)flowSubscriber;
+ } else {
+ subscriber = new ReactiveToFlowSubscriber<T>(flowSubscriber);
+ }
+ return subscriber;
+ }
+
+ /**
+ * Wraps a Reactive Streams Subscription and converts the calls to a Flow Subscription.
+ */
+ static final class FlowToReactiveSubscription implements Flow.Subscription {
+ final org.reactivestreams.Subscription reactiveStreams;
+
+ public FlowToReactiveSubscription(org.reactivestreams.Subscription reactive) {
+ this.reactiveStreams = reactive;
+ }
+
+ @Override
+ public void request(long n) {
+ reactiveStreams.request(n);
+ }
+
+ @Override
+ public void cancel() {
+ reactiveStreams.cancel();
+ }
+
+ }
+
+ /**
+ * Wraps a Flow Subscription and converts the calls to a Reactive Streams Subscription.
+ */
+ static final class ReactiveToFlowSubscription implements org.reactivestreams.Subscription {
+ final Flow.Subscription flow;
+
+ public ReactiveToFlowSubscription(Flow.Subscription flow) {
+ this.flow = flow;
+ }
+
+ @Override
+ public void request(long n) {
+ flow.request(n);
+ }
+
+ @Override
+ public void cancel() {
+ flow.cancel();
+ }
+
+
+ }
+
+ /**
+ * Wraps a Reactive Streams Subscriber and forwards methods of the Flow Subscriber to it.
+ * @param <T> the element type
+ */
+ static final class FlowToReactiveSubscriber<T> implements Flow.Subscriber<T> {
+ final org.reactivestreams.Subscriber<? super T> reactiveStreams;
+
+ public FlowToReactiveSubscriber(org.reactivestreams.Subscriber<? super T> reactive) {
+ this.reactiveStreams = reactive;
+ }
+
+ @Override
+ public void onSubscribe(Flow.Subscription subscription) {
+ reactiveStreams.onSubscribe((subscription == null) ? null : new ReactiveToFlowSubscription(subscription));
+ }
+
+ @Override
+ public void onNext(T item) {
+ reactiveStreams.onNext(item);
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ reactiveStreams.onError(throwable);
+ }
+
+ @Override
+ public void onComplete() {
+ reactiveStreams.onComplete();
+ }
+
+ }
+
+ /**
+ * Wraps a Flow Subscriber and forwards methods of the Reactive Streams Subscriber to it.
+ * @param <T> the element type
+ */
+ static final class ReactiveToFlowSubscriber<T> implements org.reactivestreams.Subscriber<T> {
+ final Flow.Subscriber<? super T> flow;
+
+ public ReactiveToFlowSubscriber(Flow.Subscriber<? super T> flow) {
+ this.flow = flow;
+ }
+
+ @Override
+ public void onSubscribe(org.reactivestreams.Subscription subscription) {
+ flow.onSubscribe((subscription == null) ? null : new FlowToReactiveSubscription(subscription));
+ }
+
+ @Override
+ public void onNext(T item) {
+ flow.onNext(item);
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ flow.onError(throwable);
+ }
+
+ @Override
+ public void onComplete() {
+ flow.onComplete();
+ }
+
+ }
+
+ /**
+ * Wraps a Flow Processor and forwards methods of the Reactive Streams Processor to it.
+ * @param <T> the input type
+ * @param <U> the output type
+ */
+ static final class ReactiveToFlowProcessor<T, U> implements org.reactivestreams.Processor<T, U> {
+ final Flow.Processor<? super T, ? extends U> flow;
+
+ public ReactiveToFlowProcessor(Flow.Processor<? super T, ? extends U> flow) {
+ this.flow = flow;
+ }
+
+ @Override
+ public void onSubscribe(org.reactivestreams.Subscription subscription) {
+ flow.onSubscribe((subscription == null) ? null : new FlowToReactiveSubscription(subscription));
+ }
+
+ @Override
+ public void onNext(T t) {
+ flow.onNext(t);
+ }
+
+ @Override
+ public void onError(Throwable t) {
+ flow.onError(t);
+ }
+
+ @Override
+ public void onComplete() {
+ flow.onComplete();
+ }
+
+ @Override
+ public void subscribe(org.reactivestreams.Subscriber<? super U> s) {
+ flow.subscribe((s == null) ? null : new FlowToReactiveSubscriber<U>(s));
+ }
+ }
+
+ /**
+ * Wraps a Reactive Streams Processor and forwards methods of the Flow Processor to it.
+ * @param <T> the input type
+ * @param <U> the output type
+ */
+ static final class FlowToReactiveProcessor<T, U> implements Flow.Processor<T, U> {
+ final org.reactivestreams.Processor<? super T, ? extends U> reactiveStreams;
+
+ public FlowToReactiveProcessor(org.reactivestreams.Processor<? super T, ? extends U> reactive) {
+ this.reactiveStreams = reactive;
+ }
+
+ @Override
+ public void onSubscribe(Flow.Subscription subscription) {
+ reactiveStreams.onSubscribe((subscription == null) ? null : new ReactiveToFlowSubscription(subscription));
+ }
+
+ @Override
+ public void onNext(T t) {
+ reactiveStreams.onNext(t);
+ }
+
+ @Override
+ public void onError(Throwable t) {
+ reactiveStreams.onError(t);
+ }
+
+ @Override
+ public void onComplete() {
+ reactiveStreams.onComplete();
+ }
+
+ @Override
+ public void subscribe(Flow.Subscriber<? super U> s) {
+ reactiveStreams.subscribe((s == null) ? null : new ReactiveToFlowSubscriber<U>(s));
+ }
+ }
+
+ /**
+ * Reactive Streams Publisher that wraps a Flow Publisher.
+ * @param <T> the element type
+ */
+ static final class ReactivePublisherFromFlow<T> implements org.reactivestreams.Publisher<T> {
+ final Flow.Publisher<? extends T> flow;
+
+ public ReactivePublisherFromFlow(Flow.Publisher<? extends T> flowPublisher) {
+ this.flow = flowPublisher;
+ }
+
+ @Override
+ public void subscribe(org.reactivestreams.Subscriber<? super T> reactive) {
+ flow.subscribe((reactive == null) ? null : new FlowToReactiveSubscriber<T>(reactive));
+ }
+ }
+
+ /**
+ * Flow Publisher that wraps a Reactive Streams Publisher.
+ * @param <T> the element type
+ */
+ static final class FlowPublisherFromReactive<T> implements Flow.Publisher<T> {
+
+ final org.reactivestreams.Publisher<? extends T> reactiveStreams;
+
+ public FlowPublisherFromReactive(org.reactivestreams.Publisher<? extends T> reactivePublisher) {
+ this.reactiveStreams = reactivePublisher;
+ }
+
+ @Override
+ public void subscribe(Flow.Subscriber<? super T> flow) {
+ reactiveStreams.subscribe((flow == null) ? null : new ReactiveToFlowSubscriber<T>(flow));
+ }
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/Processor.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+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> {
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/Publisher.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+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);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/Subscriber.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+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();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/Subscription.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+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 if the outstanding cumulative demand ever becomes Long.MAX_VALUE or more,
+ * it 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.
+ */
+ public void cancel();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/example/unicast/AsyncIterablePublisher.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,281 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+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);
+ }
+ };
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/example/unicast/AsyncSubscriber.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,261 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+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 `whenNext` 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 OnComplete, 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);
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/example/unicast/InfiniteIncrementNumberPublisher.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+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);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/example/unicast/NumberIterablePublisher.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+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);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/example/unicast/RangePublisher.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.reactivestreams.example.unicast;
+
+import org.reactivestreams.*;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * A synchronous implementation of the {@link Publisher} that can
+ * be subscribed to multiple times and each individual subscription
+ * will receive range of monotonically increasing integer values on demand.
+ */
+public final class RangePublisher implements Publisher<Integer> {
+
+ /** The starting value of the range. */
+ final int start;
+
+ /** The number of items to emit. */
+ final int count;
+
+ /**
+ * Constructs a RangePublisher instance with the given start and count values
+ * that yields a sequence of [start, start + count).
+ * @param start the starting value of the range
+ * @param count the number of items to emit
+ */
+ public RangePublisher(int start, int count) {
+ this.start = start;
+ this.count = count;
+ }
+
+ @Override
+ public void subscribe(Subscriber<? super Integer> subscriber) {
+ // As per rule 1.11, we have decided to support multiple subscribers
+ // in a unicast configuration for this `Publisher` implementation.
+
+ // As per rule 1.09, we need to throw a `java.lang.NullPointerException`
+ // if the `Subscriber` is `null`
+ if (subscriber == null) throw null;
+
+ // As per 2.13, this method must return normally (i.e. not throw).
+ try {
+ subscriber.onSubscribe(new RangeSubscription(subscriber, start, start + count));
+ } catch (Throwable ex) {
+ new IllegalStateException(subscriber + " violated the Reactive Streams rule 2.13 " +
+ "by throwing an exception from onSubscribe.", ex)
+ // When onSubscribe fails this way, we don't know what state the
+ // subscriber is thus calling onError may cause more crashes.
+ .printStackTrace();
+ }
+ }
+
+ /**
+ * A Subscription implementation that holds the current downstream
+ * requested amount and responds to the downstream's request() and
+ * cancel() calls.
+ */
+ static final class RangeSubscription
+ // We are using this `AtomicLong` to make sure that this `Subscription`
+ // doesn't run concurrently with itself, which would violate rule 1.3
+ // among others (no concurrent notifications).
+ // The atomic transition from 0L to N > 0L will ensure this.
+ extends AtomicLong implements Subscription {
+
+ private static final long serialVersionUID = -9000845542177067735L;
+
+ /** The Subscriber we are emitting integer values to. */
+ final Subscriber<? super Integer> downstream;
+
+ /** The end index (exclusive). */
+ final int end;
+
+ /**
+ * The current index and within the [start, start + count) range that
+ * will be emitted as downstream.onNext().
+ */
+ int index;
+
+ /**
+ * Indicates the emission should stop.
+ */
+ volatile boolean cancelled;
+
+ /**
+ * Holds onto the IllegalArgumentException (containing the offending stacktrace)
+ * indicating there was a non-positive request() call from the downstream.
+ */
+ volatile Throwable invalidRequest;
+
+ /**
+ * Constructs a stateful RangeSubscription that emits signals to the given
+ * downstream from an integer range of [start, end).
+ * @param downstream the Subscriber receiving the integer values and the completion signal.
+ * @param start the first integer value emitted, start of the range
+ * @param end the end of the range, exclusive
+ */
+ RangeSubscription(Subscriber<? super Integer> downstream, int start, int end) {
+ this.downstream = downstream;
+ this.index = start;
+ this.end = end;
+ }
+
+ // This method will register inbound demand from our `Subscriber` and
+ // validate it against rule 3.9 and rule 3.17
+ @Override
+ public void request(long n) {
+ // Non-positive requests should be honored with IllegalArgumentException
+ if (n <= 0L) {
+ invalidRequest = new IllegalArgumentException("§3.9: non-positive requests are not allowed!");
+ n = 1;
+ }
+ // Downstream requests are cumulative and may come from any thread
+ for (;;) {
+ long requested = get();
+ long update = requested + n;
+ // As governed by rule 3.17, when demand overflows `Long.MAX_VALUE`
+ // we treat the signalled demand as "effectively unbounded"
+ if (update < 0L) {
+ update = Long.MAX_VALUE;
+ }
+ // atomically update the current requested amount
+ if (compareAndSet(requested, update)) {
+ // if there was no prior request amount, we start the emission loop
+ if (requested == 0L) {
+ emit(update);
+ }
+ break;
+ }
+ }
+ }
+
+ // This handles cancellation requests, and is idempotent, thread-safe and not
+ // synchronously performing heavy computations as specified in rule 3.5
+ @Override
+ public void cancel() {
+ // Indicate to the emission loop it should stop.
+ cancelled = true;
+ }
+
+ void emit(long currentRequested) {
+ // Load fields to avoid re-reading them from memory due to volatile accesses in the loop.
+ Subscriber<? super Integer> downstream = this.downstream;
+ int index = this.index;
+ int end = this.end;
+ int emitted = 0;
+
+ try {
+ for (; ; ) {
+ // Check if there was an invalid request and then report its exception
+ // as mandated by rule 3.9. The stacktrace in it should
+ // help locate the faulty logic in the Subscriber.
+ Throwable invalidRequest = this.invalidRequest;
+ if (invalidRequest != null) {
+ // When we signal onError, the subscription must be considered as cancelled, as per rule 1.6
+ cancelled = true;
+
+ downstream.onError(invalidRequest);
+ return;
+ }
+
+ // Loop while the index hasn't reached the end and we haven't
+ // emitted all that's been requested
+ while (index != end && emitted != currentRequested) {
+ // to make sure that we follow rule 1.8, 3.6 and 3.7
+ // We stop if cancellation was requested.
+ if (cancelled) {
+ return;
+ }
+
+ downstream.onNext(index);
+
+ // Increment the index for the next possible emission.
+ index++;
+ // Increment the emitted count to prevent overflowing the downstream.
+ emitted++;
+ }
+
+ // If the index reached the end, we complete the downstream.
+ if (index == end) {
+ // to make sure that we follow rule 1.8, 3.6 and 3.7
+ // Unless cancellation was requested by the last onNext.
+ if (!cancelled) {
+ // We need to consider this `Subscription` as cancelled as per rule 1.6
+ // Note, however, that this state is not observable from the outside
+ // world and since we leave the loop with requested > 0L, any
+ // further request() will never trigger the loop.
+ cancelled = true;
+
+ downstream.onComplete();
+ }
+ return;
+ }
+
+ // Did the requested amount change while we were looping?
+ long freshRequested = get();
+ if (freshRequested == currentRequested) {
+ // Save where the loop has left off: the next value to be emitted
+ this.index = index;
+ // Atomically subtract the previously requested (also emitted) amount
+ currentRequested = addAndGet(-currentRequested);
+ // If there was no new request in between get() and addAndGet(), we simply quit
+ // The next 0 to N transition in request() will trigger the next emission loop.
+ if (currentRequested == 0L) {
+ break;
+ }
+ // Looks like there were more async requests, reset the emitted count and continue.
+ emitted = 0;
+ } else {
+ // Yes, avoid the atomic subtraction and resume.
+ // emitted != currentRequest in this case and index
+ // still points to the next value to be emitted
+ currentRequested = freshRequested;
+ }
+ }
+ } catch (Throwable ex) {
+ // We can only get here if `onNext`, `onError` or `onComplete` threw, and they
+ // are not allowed to according to 2.13, so we can only cancel and log here.
+ // If `onError` throws an exception, this is a spec violation according to rule 1.9,
+ // and all we can do is to log it.
+
+ // Make sure that we are cancelled, since we cannot do anything else
+ // since the `Subscriber` is faulty.
+ cancelled = true;
+
+ // We can't report the failure to onError as the Subscriber is unreliable.
+ (new IllegalStateException(downstream + " violated the Reactive Streams rule 2.13 by " +
+ "throwing an exception from onNext, onError or onComplete.", ex))
+ .printStackTrace();
+ }
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/example/unicast/SyncSubscriber.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+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 (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();
+ }
+ } 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 `whenNext` 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 whenNext(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
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/tck/IdentityProcessorVerification.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,896 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+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.flow.support.Function;
+import org.reactivestreams.tck.flow.support.SubscriberWhiteboxVerificationRules;
+import org.reactivestreams.tck.flow.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 {@link Processor}, 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 want to 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}.
+ * Stochastic in this case 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.
+ */
+ 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;
+ }
+
+ /**
+ * Override this method and return {@code true} if the {@link Processor} returned by the
+ * {@link #createIdentityProcessor(int)} coordinates its {@link Subscriber}s
+ * request amounts and only delivers onNext signals if all Subscribers have
+ * indicated (via their Subscription#request(long)) they are ready to receive elements.
+ */
+ public boolean doesCoordinatedEmission() {
+ return false;
+ }
+
+ ////////////////////// 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_registeredSubscribersMustReceiveOnNextOrOnCompleteSignals() throws Throwable {
+ publisherVerification.optional_spec111_registeredSubscribersMustReceiveOnNextOrOnCompleteSignals();
+ }
+
+ @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_cancelMustNotSynchronouslyPerformHeavyComputation() throws Exception {
+ publisherVerification.untested_spec305_cancelMustNotSynchronouslyPerformHeavyComputation();
+ }
+
+ @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 optional_spec309_requestNegativeNumberMaySignalIllegalArgumentExceptionWithSpecificMessage() throws Throwable {
+ publisherVerification.optional_spec309_requestNegativeNumberMaySignalIllegalArgumentExceptionWithSpecificMessage();
+ }
+
+ @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();
+ }
+
+
+ /**
+ * Asks for a {@code Processor} that supports at least 2 {@code Subscriber}s at once and checks if two {@code Subscriber}s
+ * receive the same items and a terminal {@code Exception}.
+ * <p>
+ * If the {@code Processor} requests and/or emits items only when all of its {@code Subscriber}s have requested,
+ * override {@link #doesCoordinatedEmission()} and return {@code true} to indicate this property.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#1.4'>1.4</a> with multiple
+ * {@code Subscriber}s.
+ * <p>
+ * The test is not executed if {@link IdentityProcessorVerification#maxSupportedSubscribers()} is less than 2.
+ * <p>
+ * If this test fails, the following could be checked within the {@code Processor} implementation:
+ * <ul>
+ * <li>The {@code TestEnvironment} has large enough timeout specified in case the {@code Processor} has some time-delay behavior.</li>
+ * <li>The {@code Processor} is able to fulfill requests of its {@code Subscriber}s independently of each other's requests or
+ * else override {@link #doesCoordinatedEmission()} and return {@code true} to indicate the test {@code Subscriber}s
+ * both have to request first.</li>
+ * </ul>
+ */
+ @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);
+
+ final Exception ex = new RuntimeException("Test exception");
+
+ if (doesCoordinatedEmission()) {
+ sub1.request(1);
+ sub2.request(1);
+
+ expectRequest();
+
+ final T x = sendNextTFromUpstream();
+
+ expectNextElement(sub1, x);
+ expectNextElement(sub2, x);
+
+ sub1.request(1);
+ sub2.request(1);
+ } else {
+ sub1.request(1);
+
+ expectRequest(env.defaultTimeoutMillis(),
+ "If the Processor coordinates requests/emissions when having multiple Subscribers"
+ + " at once, please override doesCoordinatedEmission() to return true in this "
+ + "IdentityProcessorVerification to allow this test to pass.");
+
+ final T x = sendNextTFromUpstream();
+ expectNextElement(sub1, x,
+ "If the Processor coordinates requests/emissions when having multiple Subscribers"
+ + " at once, please override doesCoordinatedEmission() to return true in this "
+ + "IdentityProcessorVerification to allow this test to pass.");
+
+ sub1.request(1);
+
+ // sub1 has received one element, and has one demand pending
+ // sub2 has not yet requested anything
+ }
+ 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) {
+ if (env.debugEnabled()) {
+ 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) {
+ if (env.debugEnabled()) {
+ env.debug(String.format("whiteboxSubscriber::onNext(%s)", element));
+ }
+ probe.registerOnNext(element);
+ }
+
+ @Override
+ public void onComplete() {
+ if (env.debugEnabled()) {
+ env.debug("whiteboxSubscriber::onComplete()");
+ }
+ probe.registerOnComplete();
+ }
+
+ @Override
+ public void onError(Throwable cause) {
+ if (env.debugEnabled()) {
+ 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 //////////////////////
+
+ /**
+ * Asks for a {@code Processor} that supports at least 2 {@code Subscriber}s at once and checks requests
+ * from {@code Subscriber}s will eventually lead to requests towards the upstream of the {@code Processor}.
+ * <p>
+ * If the {@code Processor} requests and/or emits items only when all of its {@code Subscriber}s have requested,
+ * override {@link #doesCoordinatedEmission()} and return {@code true} to indicate this property.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#1.4'>2.1</a> with multiple
+ * {@code Subscriber}s.
+ * <p>
+ * The test is not executed if {@link IdentityProcessorVerification#maxSupportedSubscribers()} is less than 2.
+ * <p>
+ * If this test fails, the following could be checked within the {@code Processor} implementation:
+ * <ul>
+ * <li>The {@code TestEnvironment} has large enough timeout specified in case the {@code Processor} has some time-delay behavior.</li>
+ * <li>The {@code Processor} is able to fulfill requests of its {@code Subscriber}s independently of each other's requests or
+ * else override {@link #doesCoordinatedEmission()} and return {@code true} to indicate the test {@code Subscriber}s
+ * both have to request first.</li>
+ * </ul>
+ */
+ @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
+
+ if (doesCoordinatedEmission()) {
+ sub2.expectNone(); // since sub2 hasn't requested anything yet
+
+ sub2.request(1);
+
+ final T z = sendNextTFromUpstream();
+ expectNextElement(sub1, z);
+ expectNextElement(sub2, z);
+ } else {
+ final T z = sendNextTFromUpstream();
+ expectNextElement(sub1, z,
+ "If the Processor coordinates requests/emissions when having multiple Subscribers"
+ + " at once, please override doesCoordinatedEmission() to return true in this "
+ + "IdentityProcessorVerification to allow this test to pass.");
+ 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 void expectNextElement(ManualSubscriber<T> sub, T expected, String errorMessageAddendum) throws InterruptedException {
+ final T elem = sub.nextElement(String.format("timeout while awaiting %s. %s", expected, errorMessageAddendum));
+ 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()));
+ }
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/tck/PublisherVerification.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,1245 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+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.flow.support.Function;
+import org.reactivestreams.tck.flow.support.Optional;
+import org.reactivestreams.tck.flow.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.AtomicInteger;
+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}.
+ * Stochastic in this case 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.
+ */
+ 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 ///////////////////////////////
+
+ @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);
+ try {
+ 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));
+ } finally {
+ sub.cancel();
+ }
+ }
+ });
+ }
+
+ @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();
+ }
+ });
+ }
+
+ @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);
+
+ final AtomicInteger gotElements = new AtomicInteger(0);
+ pub.subscribe(new Subscriber<T>() {
+ private Subscription subs;
+
+ 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);
+
+ if (gotElements.incrementAndGet() <= 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(),
+ String.format("Failed in iteration %d of %d. Expected completion signal after signalling %d elements (signalled %d), yet did not receive it",
+ runNumber, iterations, elements, gotElements.get()));
+ }
+ });
+ return null;
+ }
+ });
+ }
+
+ @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();
+ }
+ });
+ } 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);
+ }
+ }
+
+ @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();
+ }
+ });
+ }
+
+ @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();
+ }
+ });
+ }
+
+ @Override @Test
+ public void untested_spec106_mustConsiderSubscriptionCancelledAfterOnErrorOrOnCompleteHasBeenCalled() throws Throwable {
+ notVerified(); // not really testable without more control over the Publisher
+ }
+
+ @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();
+ }
+ });
+ }
+
+ @Override @Test
+ public void untested_spec107_mustNotEmitFurtherSignalsOnceOnErrorHasBeenSignalled() throws Throwable {
+ notVerified(); // can we meaningfully test this, without more control over the publisher?
+ }
+
+ @Override @Test
+ public void untested_spec108_possiblyCanceledSubscriptionShouldNotReceiveOnErrorOrOnCompleteSignals() throws Throwable {
+ notVerified(); // can we meaningfully test this?
+ }
+
+ @Override @Test
+ public void untested_spec109_subscribeShouldNotThrowNonFatalThrowable() throws Throwable {
+ notVerified(); // can we meaningfully test this?
+ }
+
+ @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();
+ }
+ });
+ }
+
+ @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);
+ final AtomicReference<Subscription> cancel = new AtomicReference<Subscription>();
+ try {
+ 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) {
+ cancel.set(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();
+ } finally {
+ Subscription s = cancel.getAndSet(null);
+ if (s != null) {
+ s.cancel();
+ }
+ }
+ }
+ });
+ }
+
+ @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();
+ }
+ });
+ }
+
+ @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);
+
+ try {
+ env.verifyNoAsyncErrors();
+ } finally {
+ try {
+ sub1.cancel();
+ } finally {
+ sub2.cancel();
+ }
+ }
+ }
+ });
+ }
+
+ @Override @Test
+ public void optional_spec111_registeredSubscribersMustReceiveOnNextOrOnCompleteSignals() 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);
+ // Since we're testing the case when the Publisher DOES support the optional multi-subscribers scenario,
+ // and decides if it handles them uni-cast or multi-cast, we don't know which subscriber will receive an
+ // onNext (and optional onComplete) signal(s) and which just onComplete signal.
+ // Plus, even if subscription assumed to be unicast, it's implementation choice, which one will be signalled
+ // with onNext.
+ sub1.requestNextElementOrEndOfStream();
+ sub2.requestNextElementOrEndOfStream();
+ try {
+ env.verifyNoAsyncErrors();
+ } finally {
+ try {
+ sub1.cancel();
+ } finally {
+ sub2.cancel();
+ }
+ }
+ }
+ });
+ }
+
+ @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));
+ }
+ });
+ }
+
+ @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"));
+ }
+ });
+ }
+
+ @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 //////////////////////////////////
+
+ @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);
+
+ env.verifyNoAsyncErrors();
+ }
+ });
+ }
+
+ @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);
+ if (env.debugEnabled()) {
+ 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();
+ }
+ }
+ });
+ }
+
+ @Override @Test
+ public void untested_spec304_requestShouldNotPerformHeavyComputations() throws Exception {
+ notVerified(); // cannot be meaningfully tested, or can it?
+ }
+
+ @Override @Test
+ public void untested_spec305_cancelMustNotSynchronouslyPerformHeavyComputation() throws Exception {
+ notVerified(); // cannot be meaningfully tested, or can it?
+ }
+
+ @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();
+ }
+ });
+ }
+
+ @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();
+ }
+ });
+ }
+
+ @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.expectError(IllegalArgumentException.class);
+ }
+ });
+ }
+
+ @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) - 1);
+ // we do require implementations to mention the rule number at the very least, or mentioning that the non-negative request is the problem
+ sub.expectError(IllegalArgumentException.class);
+ }
+ });
+ }
+
+ @Override @Test
+ public void optional_spec309_requestNegativeNumberMaySignalIllegalArgumentExceptionWithSpecificMessage() throws Throwable {
+ optionalActivePublisherTest(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) - 1);
+ // we do require implementations to mention the rule number at the very least, or mentioning that the non-negative request is the problem
+ sub.expectErrorWithMessage(IllegalArgumentException.class, Arrays.asList("3.9", "non-positive subscription request", "negative subscription request"));
+ }
+ });
+ }
+
+ @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();
+ }
+
+ @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();
+ }
+ });
+ }
+
+ @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();
+ }
+ });
+ }
+
+ @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();
+ }
+
+ }
+ });
+ }
+
+ @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) {
+ if (env.debugEnabled()) {
+ 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();
+ } 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);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/tck/SubscriberBlackboxVerification.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,516 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+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.flow.support.Optional;
+import org.reactivestreams.tck.flow.support.SubscriberBlackboxVerificationRules;
+import org.reactivestreams.tck.flow.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) {
+ // this method is intentionally left blank
+ }
+
+ // 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 ///////////////////////////////
+
+ @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 requested = stage.expectRequest();// assuming subscriber wants to consume elements...
+ final long signalsToEmit = Math.min(requested, 512); // protecting against Subscriber which sends ridiculous large demand
+
+ // should cope with up to requested number of elements
+ for (int i = 0; i < signalsToEmit && sampleIsCancelled(stage, i, 10); i++)
+ stage.signalNext();
+
+ // we complete after `signalsToEmit` (which can be less than `requested`),
+ // which is legal under https://github.com/reactive-streams/reactive-streams-jvm#1.2
+ stage.sendCompletion();
+ }
+
+ /**
+ * In order to allow some "skid" and not check state on each iteration,
+ * only check {@code stage.isCancelled} every {@code checkInterval}'th iteration.
+ */
+ private boolean sampleIsCancelled(BlackboxTestStage stage, int i, int checkInterval) throws InterruptedException {
+ if (i % checkInterval == 0) return stage.isCancelled();
+ else return false;
+ }
+ });
+ }
+
+ @Override @Test
+ public void untested_spec202_blackbox_shouldAsynchronouslyDispatch() throws Exception {
+ notVerified(); // cannot be meaningfully tested, or can it?
+ }
+
+ @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();
+ }
+ });
+ }
+
+ @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();
+ }
+ });
+ }
+
+ @Override @Test
+ public void untested_spec204_blackbox_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() throws Exception {
+ notVerified(); // cannot be meaningfully tested, or can it?
+ }
+
+ @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();
+ sendCompletion(); // we're done, complete the subscriber under test
+ }};
+ }
+
+ @Override @Test
+ public void untested_spec206_blackbox_mustCallSubscriptionCancelIfItIsNoLongerValid() throws Exception {
+ notVerified(); // cannot be meaningfully tested, or can it?
+ }
+
+ @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?
+ }
+
+ @Override @Test
+ public void untested_spec208_blackbox_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() throws Throwable {
+ notVerified(); // cannot be meaningfully tested as black box, or can it?
+ }
+
+ @Override @Test
+ public void required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() throws Throwable {
+ blackboxSubscriberTest(new BlackboxTestStageTestRun() {
+ @Override @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+ public void run(BlackboxTestStage stage) throws Throwable {
+ triggerRequest(stage.subProxy().sub());
+ final long notUsed = stage.expectRequest(); // received request signal
+ stage.sub().onComplete();
+ stage.subProxy().expectCompletion();
+ }
+ });
+ }
+
+ @Override @Test
+ public void required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() throws Throwable {
+ blackboxSubscriberTest(new BlackboxTestStageTestRun() {
+ @Override @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+ public void run(BlackboxTestStage stage) throws Throwable {
+ final Subscriber<? super T> sub = stage.sub();
+ sub.onComplete();
+ stage.subProxy().expectCompletion();
+ }
+ });
+ }
+
+ @Override @Test
+ public void required_spec210_blackbox_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() throws Throwable {
+ blackboxSubscriberTest(new BlackboxTestStageTestRun() {
+ @Override @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+ public void run(BlackboxTestStage stage) throws Throwable {
+ triggerRequest(stage.subProxy().sub());
+ final long notUsed = stage.expectRequest(); // received request signal
+ stage.sub().onError(new TestException()); // in response to that, we fail
+ stage.subProxy().expectError(Throwable.class);
+ }
+ });
+ }
+
+ // Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#2.10
+ @Override @Test
+ public void required_spec210_blackbox_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall() 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);
+ }
+ });
+ }
+
+ @Override @Test
+ public void untested_spec211_blackbox_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() throws Exception {
+ notVerified(); // cannot be meaningfully tested, or can it?
+ }
+
+ @Override @Test
+ public void untested_spec212_blackbox_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality() throws Throwable {
+ notVerified(); // cannot be meaningfully tested as black box, or can it?
+ }
+
+ @Override @Test
+ public void untested_spec213_blackbox_failingOnSignalInvocation() throws Exception {
+ notVerified(); // cannot be meaningfully tested, or can it?
+ }
+
+ @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();
+ }
+ });
+ }
+
+ @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();
+ }
+ });
+ }
+
+ @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 //////////////////
+
+ @Override @Test
+ public void untested_spec301_blackbox_mustNotBeCalledOutsideSubscriberContext() throws Exception {
+ notVerified(); // cannot be meaningfully tested, or can it?
+ }
+
+ @Override @Test
+ public void untested_spec308_blackbox_requestMustRegisterGivenNumberElementsToBeProduced() throws Throwable {
+ notVerified(); // cannot be meaningfully tested as black box, or can it?
+ }
+
+ @Override @Test
+ public void untested_spec310_blackbox_requestMaySynchronouslyCallOnNextOnSubscriber() throws Exception {
+ notVerified(); // cannot be meaningfully tested, or can it?
+ }
+
+ @Override @Test
+ public void untested_spec311_blackbox_requestMaySynchronouslyCallOnCompleteOrOnError() throws Exception {
+ notVerified(); // cannot be meaningfully tested, or can it?
+ }
+
+ @Override @Test
+ public void untested_spec314_blackbox_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() throws Exception {
+ notVerified(); // cannot be meaningfully tested, or can it?
+ }
+
+ @Override @Test
+ public void untested_spec315_blackbox_cancelMustNotThrowExceptionAndMustSignalOnError() throws Exception {
+ notVerified(); // cannot be meaningfully tested, or can it?
+ }
+
+ @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.");
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/tck/SubscriberWhiteboxVerification.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,843 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+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.flow.support.Optional;
+import org.reactivestreams.tck.flow.support.SubscriberWhiteboxVerificationRules;
+import org.reactivestreams.tck.flow.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 whitebox style 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.expectRequest();
+ 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);
+ long requestedElements = stage.expectRequest();
+ stage.probe.expectNext(stage.signalNext());
+ // Some subscribers may only request one element at a time.
+ if (requestedElements < 2) {
+ stage.expectRequest();
+ }
+ 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()));
+ env.verifyNoAsyncErrorsNoDelay();
+ }
+ }
+
+ 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.defaultNoSignalsTimeoutMillis());
+ }
+
+ 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);
+
+ }
+
+ /**
+ * Implement this puppet in your Whitebox style tests.
+ * The test suite will invoke the specific trigger/signal methods requesting you to execute the specific action.
+ * Since this is a whitebox style test, you're allowed and expected to use knowladge about your implementation to
+ * make implement these calls.
+ */
+ public interface SubscriberPuppet {
+
+ /**
+ * Ensure that at least {@code elements} are eventually requested by your {@link Subscriber}, if it hasn't already
+ * requested that many elements.
+ * <p>
+ * This does not necessarily have to correlate 1:1 with a {@code Subscription.request(elements)} call, but the sum
+ * of the elements requested by your {@code Subscriber} must eventually be at least the the sum of the elements
+ * triggered to be requested by all the invocations of this method.
+ * <p>
+ * Additionally, subscribers are permitted to delay requesting elements until previous requests for elements have
+ * been fulfilled. For example, a subscriber that only requests one element at a time may fulfill the request made
+ * by this method by requesting one element {@code elements} times, waiting for each element to arrive before the
+ * next request is made.
+ * <p>
+ * Before sending any element to the subscriber, the TCK must wait for the subscriber to request that element, and
+ * must be prepared for the subscriber to only request one element at a time, it is not enough for the TCK to
+ * simply invoke this method before sending elements.
+ */
+ void triggerRequest(long elements);
+
+ /**
+ * Trigger {@code cancel()} on your {@link Subscriber}
+ */
+ void signalCancel();
+ }
+
+ public void notVerified() {
+ throw new SkipException("Not verified using this TCK.");
+ }
+
+ public void notVerified(String msg) {
+ throw new SkipException(msg);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/tck/TestEnvironment.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,1168 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.reactivestreams.tck;
+
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import org.reactivestreams.tck.flow.support.Optional;
+import org.reactivestreams.tck.flow.support.SubscriberBufferOverflowException;
+
+import java.util.Collections;
+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 java.util.concurrent.atomic.AtomicReference;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+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 static final String DEFAULT_NO_SIGNALS_TIMEOUT_MILLIS_ENV = "DEFAULT_NO_SIGNALS_TIMEOUT_MILLIS";
+ private static final String DEFAULT_POLL_TIMEOUT_MILLIS_ENV = "DEFAULT_POLL_TIMEOUT_MILLIS_ENV";
+
+ private final long defaultTimeoutMillis;
+ private final long defaultPollTimeoutMillis;
+ private final long defaultNoSignalsTimeoutMillis;
+ 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 defaultNoSignalsTimeoutMillis default timeout to be used when no further signals are expected anymore
+ * @param defaultPollTimeoutMillis default amount of time to poll for events if {@code defaultTimeoutMillis} isn't
+ * preempted by an asynchronous event.
+ * @param printlnDebug if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output,
+ */
+ public TestEnvironment(long defaultTimeoutMillis, long defaultNoSignalsTimeoutMillis, long defaultPollTimeoutMillis,
+ boolean printlnDebug) {
+ this.defaultTimeoutMillis = defaultTimeoutMillis;
+ this.defaultPollTimeoutMillis = defaultPollTimeoutMillis;
+ this.defaultNoSignalsTimeoutMillis = defaultNoSignalsTimeoutMillis;
+ 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
+ * @param defaultNoSignalsTimeoutMillis default timeout to be used when no further signals are expected anymore
+ * @param printlnDebug if true, signals such as OnNext / Request / OnComplete etc will be printed to standard output,
+ */
+ public TestEnvironment(long defaultTimeoutMillis, long defaultNoSignalsTimeoutMillis, boolean printlnDebug) {
+ this(defaultTimeoutMillis, defaultNoSignalsTimeoutMillis, defaultTimeoutMillis, 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
+ * @param defaultNoSignalsTimeoutMillis default timeout to be used when no further signals are expected anymore
+ * @param defaultPollTimeoutMillis default amount of time to poll for events if {@code defaultTimeoutMillis} isn't
+ * preempted by an asynchronous event.
+ */
+ public TestEnvironment(long defaultTimeoutMillis, long defaultNoSignalsTimeoutMillis, long defaultPollTimeoutMillis) {
+ this(defaultTimeoutMillis, defaultNoSignalsTimeoutMillis, defaultPollTimeoutMillis, 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.
+ *
+ * @param defaultTimeoutMillis default timeout to be used in all expect* methods
+ * @param defaultNoSignalsTimeoutMillis default timeout to be used when no further signals are expected anymore
+ */
+ public TestEnvironment(long defaultTimeoutMillis, long defaultNoSignalsTimeoutMillis) {
+ this(defaultTimeoutMillis, defaultTimeoutMillis, defaultNoSignalsTimeoutMillis);
+ }
+
+ /**
+ * 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, defaultTimeoutMillis, defaultTimeoutMillis);
+ }
+
+ /**
+ * 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(), envDefaultNoSignalsTimeoutMillis(), envDefaultPollTimeoutMillis(), 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(), envDefaultNoSignalsTimeoutMillis());
+ }
+
+ /** This timeout is used when waiting for a signal to arrive. */
+ public long defaultTimeoutMillis() {
+ return defaultTimeoutMillis;
+ }
+
+ /**
+ * This timeout is used when asserting that no further signals are emitted.
+ * Note that this timeout default
+ */
+ public long defaultNoSignalsTimeoutMillis() {
+ return defaultNoSignalsTimeoutMillis;
+ }
+
+ /**
+ * The default amount of time to poll for events if {@code defaultTimeoutMillis} isn't preempted by an asynchronous
+ * event.
+ */
+ public long defaultPollTimeoutMillis() {
+ return defaultPollTimeoutMillis;
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+ /**
+ * Tries to parse the env variable {@code DEFAULT_NO_SIGNALS_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 envDefaultNoSignalsTimeoutMillis() {
+ final String envMillis = System.getenv(DEFAULT_NO_SIGNALS_TIMEOUT_MILLIS_ENV);
+ if (envMillis == null) return envDefaultTimeoutMillis();
+ else try {
+ return Long.parseLong(envMillis);
+ } catch (NumberFormatException ex) {
+ throw new IllegalArgumentException(String.format("Unable to parse %s env value [%s] as long!", DEFAULT_NO_SIGNALS_TIMEOUT_MILLIS_ENV, envMillis), ex);
+ }
+ }
+
+ /**
+ * Tries to parse the env variable {@code DEFAULT_POLL_TIMEOUT_MILLIS_ENV} 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 envDefaultPollTimeoutMillis() {
+ final String envMillis = System.getenv(DEFAULT_POLL_TIMEOUT_MILLIS_ENV);
+ if (envMillis == null) return envDefaultTimeoutMillis();
+ else try {
+ return Long.parseLong(envMillis);
+ } catch (NumberFormatException ex) {
+ throw new IllegalArgumentException(String.format("Unable to parse %s env value [%s] as long!", DEFAULT_POLL_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#defaultNoSignalsTimeoutMillis()} and then verifies that no asynchronous errors
+ * were signalled pior to, or during that time (by calling {@code flop()}).
+ */
+ public void verifyNoAsyncErrors() {
+ verifyNoAsyncErrors(defaultNoSignalsTimeoutMillis());
+ }
+
+ /**
+ * 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 (debugEnabled()) {
+ System.out.printf("[TCK-DEBUG] %s%n", msg);
+ }
+ }
+
+ public final boolean debugEnabled() {
+ return printlnDebug;
+ }
+
+ /**
+ * 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() throws InterruptedException {
+ return requestNextElementOrEndOfStream(env.defaultTimeoutMillis(), "Did not receive expected stream completion");
+ }
+
+ 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, Collections.singletonList(requiredMessagePart), env.defaultTimeoutMillis(), env.defaultPollTimeoutMillis());
+ }
+ public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, List<String> requiredMessagePartAlternatives) throws Exception {
+ expectErrorWithMessage(expected, requiredMessagePartAlternatives, env.defaultTimeoutMillis(), env.defaultPollTimeoutMillis());
+ }
+
+ @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+ public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, String requiredMessagePart, long timeoutMillis) throws Exception {
+ expectErrorWithMessage(expected, Collections.singletonList(requiredMessagePart), timeoutMillis);
+ }
+
+ public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, List<String> requiredMessagePartAlternatives, long timeoutMillis) throws Exception {
+ expectErrorWithMessage(expected, requiredMessagePartAlternatives, timeoutMillis, timeoutMillis);
+ }
+
+ public <E extends Throwable> void expectErrorWithMessage(Class<E> expected, List<String> requiredMessagePartAlternatives,
+ long totalTimeoutMillis, long pollTimeoutMillis) throws Exception {
+ final E err = expectError(expected, totalTimeoutMillis, pollTimeoutMillis);
+ final String message = err.getMessage();
+
+ boolean contains = false;
+ for (String requiredMessagePart : requiredMessagePartAlternatives)
+ if (message.contains(requiredMessagePart)) contains = true; // not short-circuting loop, it is expected to
+ assertTrue(contains,
+ String.format("Got expected exception [%s] but missing message part [%s], was: %s",
+ err.getClass(), "anyOf: " + requiredMessagePartAlternatives, 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, env.defaultPollTimeoutMillis());
+ }
+
+ 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 expectError(expected, timeoutMillis, env.defaultPollTimeoutMillis(), errorMsg);
+ }
+
+ public <E extends Throwable> E expectError(Class<E> expected, long totalTimeoutMillis, long pollTimeoutMillis) throws Exception {
+ return expectError(expected, totalTimeoutMillis, pollTimeoutMillis, String.format("Expected onError(%s)", expected.getName()));
+ }
+
+ public <E extends Throwable> E expectError(Class<E> expected, long totalTimeoutMillis, long pollTimeoutMillis,
+ String errorMsg) throws Exception {
+ return received.expectError(expected, totalTimeoutMillis, pollTimeoutMillis, errorMsg);
+ }
+
+ public void expectNone() throws InterruptedException {
+ expectNone(env.defaultNoSignalsTimeoutMillis());
+ }
+
+ public void expectNone(String errMsgPrefix) throws InterruptedException {
+ expectNone(env.defaultNoSignalsTimeoutMillis(), 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) {
+ if (env.debugEnabled()) {
+ 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() {
+ if (env.debugEnabled()) {
+ env.debug(this + "::onComplete()");
+ }
+ if (subscription.isCompleted()) {
+ super.onComplete();
+ } else {
+ env.flop("Subscriber::onComplete() called before Subscriber::onSubscribe");
+ }
+ }
+
+ @Override
+ public void onSubscribe(Subscription s) {
+ if (env.debugEnabled()) {
+ 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) {
+ if (env.debugEnabled()) {
+ 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) {
+ if (env.debugEnabled()) {
+ 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 long expectRequest(long timeoutMillis, String errorMessageAddendum) throws InterruptedException {
+ long requested = requests.next(timeoutMillis, String.format("Did not receive expected `request` call. %s", errorMessageAddendum));
+ 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");
+ }
+
+ public boolean isCancelled() throws InterruptedException {
+ return cancelled.isClosed();
+ }
+ }
+
+ /**
+ * 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 AtomicReference<T> _value = new AtomicReference<T>();
+
+ public T value() {
+ final T value = _value.get();
+ if (value != null) {
+ return value;
+ } else {
+ env.flop("Cannot access promise value before completion");
+ return null;
+ }
+ }
+
+ public boolean isCompleted() {
+ return _value.get() != null;
+ }
+
+ /**
+ * Allows using expectCompletion to await for completion of the value and complete it _then_
+ */
+ public void complete(T value) {
+ if (_value.compareAndSet(null, value)) {
+ // we add the value to the queue such to wake up any expectCompletion which was triggered before complete() was called
+ abq.add(value);
+ } else {
+ env.flop(String.format("Cannot complete a promise more than once! Present value: %s, attempted to set: %s", _value.get(), value));
+ }
+ }
+
+ /**
+ * Same as complete.
+ *
+ * Keeping this method for binary compatibility.
+ */
+ public void completeImmediatly(T value) {
+ complete(value);
+ }
+
+ 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));
+ }
+ }
+ }
+ }
+
+ // 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
+ }
+
+ /**
+ * @deprecated Deprecated in favor of {@link #expectError(Class, long, long, String)}.
+ */
+ @Deprecated
+ public <E extends Throwable> E expectError(Class<E> clazz, long timeoutMillis, String errorMsg) throws Exception {
+ return expectError(clazz, timeoutMillis, timeoutMillis, errorMsg);
+ }
+
+ @SuppressWarnings("unchecked")
+ final <E extends Throwable> E expectError(Class<E> clazz, final long totalTimeoutMillis,
+ long pollTimeoutMillis,
+ String errorMsg) throws Exception {
+ long totalTimeoutRemainingNs = MILLISECONDS.toNanos(totalTimeoutMillis);
+ long timeStampANs = System.nanoTime();
+ long timeStampBNs;
+
+ for (;;) {
+ Thread.sleep(Math.min(pollTimeoutMillis, NANOSECONDS.toMillis(totalTimeoutRemainingNs)));
+
+ if (env.asyncErrors.isEmpty()) {
+ timeStampBNs = System.nanoTime();
+ totalTimeoutRemainingNs =- timeStampBNs - timeStampANs;
+ timeStampANs = timeStampBNs;
+
+ if (totalTimeoutRemainingNs <= 0) {
+ return env.flopAndFail(String.format("%s within %d ms", errorMsg, totalTimeoutMillis));
+ }
+ } 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, totalTimeoutMillis, 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");
+ }
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/tck/WithHelperPublisher.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.reactivestreams.tck;
+
+import org.reactivestreams.Publisher;
+import org.reactivestreams.tck.flow.support.Function;
+import org.reactivestreams.tck.flow.support.HelperPublisher;
+import org.reactivestreams.tck.flow.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());
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/tck/flow/FlowPublisherVerification.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.reactivestreams.tck.flow;
+
+import org.reactivestreams.Publisher;
+import org.reactivestreams.FlowAdapters;
+import org.reactivestreams.tck.PublisherVerification;
+import org.reactivestreams.tck.TestEnvironment;
+
+import java.util.concurrent.Flow;
+
+/**
+ * Provides tests for verifying a Java 9+ {@link java.util.concurrent.Flow.Publisher} specification rules.
+ *
+ * @see java.util.concurrent.Flow.Publisher
+ */
+public abstract class FlowPublisherVerification<T> extends PublisherVerification<T> {
+
+ public FlowPublisherVerification(TestEnvironment env, long publisherReferenceGCTimeoutMillis) {
+ super(env, publisherReferenceGCTimeoutMillis);
+ }
+
+ public FlowPublisherVerification(TestEnvironment env) {
+ super(env);
+ }
+
+ @Override
+ final public Publisher<T> createPublisher(long elements) {
+ final Flow.Publisher<T> flowPublisher = createFlowPublisher(elements);
+ return FlowAdapters.toPublisher(flowPublisher);
+ }
+ /**
+ * 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 Flow.Publisher<T> createFlowPublisher(long elements);
+
+ @Override
+ final public Publisher<T> createFailedPublisher() {
+ final Flow.Publisher<T> failed = createFailedFlowPublisher();
+ if (failed == null) return null; // because `null` means "SKIP" in createFailedPublisher
+ else return FlowAdapters.toPublisher(failed);
+ }
+ /**
+ * By implementing this method, additional TCK tests concerning a "failed" publishers will be run.
+ *
+ * The expected behaviour of the {@link Flow.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 Flow.Publisher<T> createFailedFlowPublisher();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/tck/flow/FlowSubscriberBlackboxVerification.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.reactivestreams.tck.flow;
+
+import org.reactivestreams.FlowAdapters;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import org.reactivestreams.tck.SubscriberBlackboxVerification;
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.support.SubscriberBlackboxVerificationRules;
+
+import java.util.concurrent.Flow;
+
+/**
+ * Provides tests for verifying {@link java.util.concurrent.Flow.Subscriber} and {@link java.util.concurrent.Flow.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 java.util.concurrent.Flow.Subscriber
+ * @see java.util.concurrent.Flow.Subscription
+ */
+public abstract class FlowSubscriberBlackboxVerification<T> extends SubscriberBlackboxVerification<T>
+ implements SubscriberBlackboxVerificationRules {
+
+ protected FlowSubscriberBlackboxVerification(TestEnvironment env) {
+ super(env);
+ }
+
+ @Override
+ public final void triggerRequest(Subscriber<? super T> subscriber) {
+ triggerFlowRequest(FlowAdapters.toFlowSubscriber(subscriber));
+ }
+ /**
+ * Override this method if the {@link java.util.concurrent.Flow.Subscriber} implementation you are verifying
+ * needs an external signal before it signals demand to its Publisher.
+ *
+ * By default this method does nothing.
+ */
+ public void triggerFlowRequest(Flow.Subscriber<? super T> subscriber) {
+ // this method is intentionally left blank
+ }
+
+ @Override
+ public final Subscriber<T> createSubscriber() {
+ return FlowAdapters.<T>toSubscriber(createFlowSubscriber());
+ }
+ /**
+ * This is the main method you must implement in your test incarnation.
+ * It must create a new {@link Flow.Subscriber} instance to be subjected to the testing logic.
+ */
+ abstract public Flow.Subscriber<T> createFlowSubscriber();
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/tck/flow/FlowSubscriberWhiteboxVerification.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.reactivestreams.tck.flow;
+
+import org.reactivestreams.FlowAdapters;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.tck.SubscriberWhiteboxVerification;
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.support.SubscriberWhiteboxVerificationRules;
+
+import java.util.concurrent.Flow;
+
+/**
+ * Provides whitebox style tests for verifying {@link java.util.concurrent.Flow.Subscriber}
+ * and {@link java.util.concurrent.Flow.Subscription} specification rules.
+ *
+ * @see java.util.concurrent.Flow.Subscriber
+ * @see java.util.concurrent.Flow.Subscription
+ */
+public abstract class FlowSubscriberWhiteboxVerification<T> extends SubscriberWhiteboxVerification<T>
+ implements SubscriberWhiteboxVerificationRules {
+
+ protected FlowSubscriberWhiteboxVerification(TestEnvironment env) {
+ super(env);
+ }
+
+ @Override
+ final public Subscriber<T> createSubscriber(WhiteboxSubscriberProbe<T> probe) {
+ return FlowAdapters.toSubscriber(createFlowSubscriber(probe));
+ }
+ /**
+ * 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.
+ */
+ protected abstract Flow.Subscriber<T> createFlowSubscriber(WhiteboxSubscriberProbe<T> probe);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/tck/flow/IdentityFlowProcessorVerification.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.reactivestreams.tck.flow;
+
+import org.reactivestreams.*;
+import org.reactivestreams.tck.IdentityProcessorVerification;
+import org.reactivestreams.tck.TestEnvironment;
+import org.reactivestreams.tck.flow.support.SubscriberWhiteboxVerificationRules;
+import org.reactivestreams.tck.flow.support.PublisherVerificationRules;
+
+import java.util.concurrent.Flow;
+
+public abstract class IdentityFlowProcessorVerification<T> extends IdentityProcessorVerification<T>
+ implements SubscriberWhiteboxVerificationRules, PublisherVerificationRules {
+
+ public IdentityFlowProcessorVerification(TestEnvironment env) {
+ super(env);
+ }
+
+ public IdentityFlowProcessorVerification(TestEnvironment env, long publisherReferenceGCTimeoutMillis) {
+ super(env, publisherReferenceGCTimeoutMillis);
+ }
+
+ public IdentityFlowProcessorVerification(TestEnvironment env, long publisherReferenceGCTimeoutMillis, int processorBufferSize) {
+ super(env, publisherReferenceGCTimeoutMillis, processorBufferSize);
+ }
+
+ /**
+ * By implementing this method, additional TCK tests concerning a "failed" Flow publishers will be run.
+ *
+ * The expected behaviour of the {@link Flow.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 want to ignore these additional tests, return {@code null} from this method.
+ */
+ protected abstract Flow.Publisher<T> createFailedFlowPublisher();
+
+ /**
+ * This is the main method you must implement in your test incarnation.
+ * It must create a {@link Flow.Processor}, 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.
+ */
+ protected abstract Flow.Processor<T,T> createIdentityFlowProcessor(int bufferSize);
+
+ @Override
+ public final Processor<T, T> createIdentityProcessor(int bufferSize) {
+ return FlowAdapters.toProcessor(createIdentityFlowProcessor(bufferSize));
+ }
+
+ @Override
+ public final Publisher<T> createFailedPublisher() {
+ Flow.Publisher<T> failed = createFailedFlowPublisher();
+ if (failed == null) return null; // because `null` means "SKIP" in createFailedPublisher
+ else return FlowAdapters.toPublisher(failed);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/tck/flow/support/Function.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.reactivestreams.tck.flow.support;
+
+public interface Function<In, Out> {
+ public Out apply(In in) throws Throwable;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/tck/flow/support/HelperPublisher.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.reactivestreams.tck.flow.support;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.concurrent.Executor;
+
+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);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/tck/flow/support/InfiniteHelperPublisher.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.reactivestreams.tck.flow.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);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/tck/flow/support/NonFatal.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.reactivestreams.tck.flow.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;
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/tck/flow/support/Optional.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.reactivestreams.tck.flow.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";
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/tck/flow/support/PublisherVerificationRules.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,658 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.reactivestreams.tck.flow.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 {
+ /**
+ * Validates that the override of {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()}
+ * returns a non-negative value.
+ */
+ void required_validate_maxElementsFromPublisher() throws Exception;
+ /**
+ * Validates that the override of {@link org.reactivestreams.tck.PublisherVerification#boundedDepthOfOnNextAndRequestRecursion()}
+ * returns a positive value.
+ */
+ void required_validate_boundedDepthOfOnNextAndRequestRecursion() throws Exception;
+ /**
+ * Asks for a {@code Publisher} that should emit exactly one item and complete (both within a
+ * timeout specified by {@link org.reactivestreams.tck.TestEnvironment#defaultTimeoutMillis()})
+ * in response to a request(1).
+ * <p>
+ * The test is not executed if {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()} returns zero.
+ * If this test fails, the following could be checked within the {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code Publisher.subscribe(Subscriber)} method has actual implementation,</li>
+ * <li>in the {@code Publisher.subscribe(Subscriber)} method, if there is an upstream {@code Publisher},
+ * that {@code Publisher} is actually subscribed to,</li>
+ * <li>if the {@code Publisher} is part of a chain, all elements actually issue a {@code request()} call
+ * in response to the test subscriber or by default to their upstream,</li>
+ * <li>in the {@code Publisher.subscribe(Subscriber)} method, the {@code Subscriber.onSubscribe} is called
+ * as part of the preparation process (usually before subscribing to other {@code Publisher}s),</li>
+ * <li>if the {@code Publisher} implementation works for a consumer that calls {@code request(1)},</li>
+ * <li>if the {@code Publisher} implementation is able to emit an {@code onComplete} without requests,</li>
+ * <li>that the {@code Publisher} implementation does not emit more than the allowed elements (exactly one).</li>
+ * </ul>
+ */
+ void required_createPublisher1MustProduceAStreamOfExactly1Element() throws Throwable;
+ /**
+ * Asks for a {@code Publisher} that should emit exactly three items and complete (all within a
+ * timeout specified by {@link org.reactivestreams.tck.TestEnvironment#defaultTimeoutMillis()}).
+ * <p>
+ * The test is not executed if {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()} is less than 3.
+ * <p>
+ * The tests requests one-by-one and verifies each single response item arrives in time.
+ * <p>
+ * If this test fails, the following could be checked within the {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code Publisher.subscribe(Subscriber)} method has actual implementation,</li>
+ * <li>in the {@code Publisher.subscribe(Subscriber)} method, if there is an upstream {@code Publisher},
+ * that {@code Publisher} is actually subscribed to,</li>
+ * <li>if the {@code Publisher} is part of a chain, all elements actually issue a {@code request()} call
+ * in response to the test subscriber or by default to their upstream,</li>
+ * <li>in the {@code Publisher.subscribe(Subscriber)} method, the {@code Subscriber.onSubscribe} is called
+ * as part of the preparation process (usually before subscribing to other {@code Publisher}s),</li>
+ * <li>if the {@code Publisher} implementation works for a subscriber that calls {@code request(1)} after consuming an item,</li>
+ * <li>if the {@code Publisher} implementation is able to emit an {@code onComplete} without requests.</li>
+ * </ul>
+ */
+ void required_createPublisher3MustProduceAStreamOfExactly3Elements() throws Throwable;
+ /**
+ * Asks for a {@code Publisher} that responds to a request pattern of 0 (not requesting upfront), 1, 1 and 2
+ * in a timely manner.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#1.1'>1.1</a>
+ * <p>
+ * The test is not executed if {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()} is less than 5.
+ * <p>
+ * This test ensures that the {@code Publisher} implementation correctly responds to {@code request()} calls that in
+ * total are less than the number of elements this {@code Publisher} could emit (thus the completion event won't be emitted).
+ * <p>
+ * If this test fails, the following could be checked within the {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code TestEnvironment} has large enough timeout specified in case the {@code Publisher} has some time-delay behavior,</li>
+ * <li>make sure the {@link #required_createPublisher1MustProduceAStreamOfExactly1Element()} and {@link #required_createPublisher3MustProduceAStreamOfExactly3Elements()} tests pass,</li>
+ * <li>if the {@code Publisher} implementation considers the cumulative request amount it receives,</li>
+ * <li>if the {@code Publisher} doesn't lose any {@code request()} signal and the state transition from idle -> emitting or emitting -> keep emitting works properly.</li>
+ * </ul>
+ */
+ void required_spec101_subscriptionRequestMustResultInTheCorrectNumberOfProducedElements() throws Throwable;
+ /**
+ * Asks for a short {@code Publisher} and verifies that requesting once and with more than the length (but bounded) results in the
+ * correct number of items to be emitted (i.e., length 3 and request 10) followed by an {@code onComplete} signal.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#1.2'>1.2</a>
+ * <p>
+ * The test is not executed if {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()} is less than 3.
+ * <p>
+ * This test ensures that the {@code Publisher} implementation can deal with larger requests than the number of items it can produce.
+ * <p>
+ * If this test fails, the following could be checked within the {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code TestEnvironment} has large enough timeout specified in case the {@code Publisher} has some time-delay behavior,</li>
+ * <li>make sure the {@link #required_createPublisher1MustProduceAStreamOfExactly1Element()} and {@link #required_createPublisher3MustProduceAStreamOfExactly3Elements()} tests pass.</li>
+ * </ul>
+ */
+ void required_spec102_maySignalLessThanRequestedAndTerminateSubscription() throws Throwable;
+ /**
+ * Asks for a short {@code Publisher} (i.e., length 10), repeatedly subscribes to this {@code Publisher}, requests items
+ * one by one and verifies the {@code Publisher} calls the {@code onXXX} methods non-overlappingly.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#1.3'>1.3</a>
+ * <p>
+ * The test is not executed if {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()} is less than 10.
+ * <p>
+ * Note that this test is probabilistic, that is, may not capture any concurrent invocation in a {code Publisher} implementation.
+ * Note also that this test is sensitive to cases when a {@code request()} call in {@code onSubscribe()} triggers an asynchronous
+ * call to the other {@code onXXX} methods. In contrast, the test allows synchronous call chain of
+ * {@code onSubscribe -> request -> onNext}.
+ * <p>
+ * If this test fails, the following could be checked within the {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code TestEnvironment} has large enough timeout specified in case the {@code Publisher} has some time-delay behavior,</li>
+ * <li>make sure the {@link #required_createPublisher1MustProduceAStreamOfExactly1Element()} and {@link #required_createPublisher3MustProduceAStreamOfExactly3Elements()} tests pass,</li>
+ * <li>if a {@code request()} call from {@code onSubscribe()} could trigger an asynchronous call to {@code onNext()} and if so, make sure
+ * such {@code request()} calls are deferred until the call to {@code onSubscribe()} returns normally.</li>
+ * </ul>
+ */
+ void stochastic_spec103_mustSignalOnMethodsSequentially() throws Throwable;
+ /**
+ * Asks for an error {@code Publisher} that should call {@code onSubscribe} exactly once
+ * followed by a single call to {@code onError()} without receiving any requests and otherwise
+ * not throwing any exception.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#1.4'>1.4</a>
+ * <p>
+ * The test is not executed if {@code PublisherVerification.createErrorPublisher()} returns null.
+ * <p>
+ * If this test fails, the following could be checked within the error {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code Publisher.subscribe(Subscriber)} method has actual implementation,</li>
+ * <li>in the {@code Publisher.subscribe(Subscriber)} method, if there is an upstream {@code Publisher},
+ * that {@code Publisher} is actually subscribed to,</li>
+ * <li>if the {@code Publisher} implementation does signal an {@code onSubscribe} before signalling {@code onError},</li>
+ * <li>if the {@code Publisher} implementation is able to emit an {@code onError} without requests,</li>
+ * <li>if the {@code Publisher} is non-empty as this test requires a {@code Publisher} to signal an
+ * {@code onError} eagerly.</li>
+ * </ul>
+ */
+ void optional_spec104_mustSignalOnErrorWhenFails() throws Throwable;
+ /**
+ * Asks for a short {@code Publisher} (i.e., length 3) and verifies, after requesting one by one, the sequence
+ * completes normally.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#1.5'>1.5</a>
+ * <p>
+ * The test is not executed if {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()} is less than 3.
+ * <p>
+ * Note that the tests requests 1 after the items have been received and before expecting an {@code onComplete} signal.
+ * <p>
+ * If this test fails, the following could be checked within the {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code TestEnvironment} has large enough timeout specified in case the {@code Publisher} has some time-delay behavior,</li>
+ * <li>make sure the {@link #required_createPublisher1MustProduceAStreamOfExactly1Element()} and {@link #required_createPublisher3MustProduceAStreamOfExactly3Elements()} tests pass,</li>
+ * </ul>
+ */
+ void required_spec105_mustSignalOnCompleteWhenFiniteStreamTerminates() throws Throwable;
+ /**
+ * Asks for an empty {@code Publisher} (i.e., length 0) and verifies it completes in a timely manner.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#1.5'>1.5</a>
+ * <p>
+ * Note that the tests requests 1 before expecting an {@code onComplete} signal.
+ * <p>
+ * If this test fails, the following could be checked within the {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code TestEnvironment} has large enough timeout specified in case the {@code Publisher} has some time-delay behavior,</li>
+ * <li>if the {@code Publisher} is non-empty as this test requires a {@code Publisher} without items.</li>
+ * </ul>
+ */
+ void optional_spec105_emptyStreamMustTerminateBySignallingOnComplete() throws Throwable;
+ /**
+ * Currently, this test is skipped because it is unclear this rule can be effectively checked
+ * on a {@code Publisher} instance without looking into or hooking into the implementation of it.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#1.6'>1.6</a>
+ */
+ void untested_spec106_mustConsiderSubscriptionCancelledAfterOnErrorOrOnCompleteHasBeenCalled() throws Throwable;
+ /**
+ * Asks for a single-element {@code Publisher} and checks if requesting after the terminal event doesn't
+ * lead to more items or terminal signals to be emitted.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#1.7'>1.7</a>
+ * <p>
+ * The test is not executed if {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()} is less than 1.
+ * <p>
+ * The tests requests more items than the expected {@code Publisher} length upfront and some more items after its completion.
+ * <p>
+ * If this test fails, the following could be checked within the {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code TestEnvironment} has large enough timeout specified in case the {@code Publisher} has some time-delay behavior,</li>
+ * <li>the indication for the terminal state is properly persisted and a request call can't trigger emission of more items or another
+ * terminal signal.</li>
+ * </ul>
+ */
+ void required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled() throws Throwable;
+ /**
+ * Currently, this test is skipped, although it is possible to validate an error {@code Publisher} along
+ * the same lines as {@link #required_spec107_mustNotEmitFurtherSignalsOnceOnCompleteHasBeenSignalled()}.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#1.7'>1.7</a>
+ */
+ void untested_spec107_mustNotEmitFurtherSignalsOnceOnErrorHasBeenSignalled() throws Throwable;
+ /**
+ * Currently, this test is skipped because there was no agreement on how to verify its "eventually" requirement.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#1.8'>1.8</a>
+ */
+ void untested_spec108_possiblyCanceledSubscriptionShouldNotReceiveOnErrorOrOnCompleteSignals() throws Throwable;
+ /**
+ * Asks for an empty {@code Publisher} and verifies if {@code onSubscribe} signal was emitted before
+ * any other {@code onNext}, {@code onError} or {@code onComplete} signal.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#1.9'>1.9</a>
+ * <p>
+ * Note that this test doesn't request anything, however, an {@code onNext} is not considered as a failure.
+ * <p>
+ * If this test fails, the following could be checked within the {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code TestEnvironment} has large enough timeout specified in case the {@code Publisher} has some time-delay behavior,</li>
+ * <li>the {@code Publisher.subscribe(Subscriber)} method has actual implementation,</li>
+ * <li>in the {@code Publisher.subscribe(Subscriber)} method, if there is an upstream {@code Publisher},
+ * that {@code Publisher} is actually subscribed to,</li>
+ * <li>in the {@code Publisher.subscribe(Subscriber)} method, the {@code Subscriber.onSubscribe} is called
+ * as part of the preparation process (usually before subscribing to other {@code Publisher}s).</li>
+ * </ul>
+ */
+ void required_spec109_mustIssueOnSubscribeForNonNullSubscriber() throws Throwable;
+ /**
+ * Currently, this test is skipped because there is no common agreement on what is to be considered a fatal exception and
+ * besides, {@code Publisher.subscribe} is only allowed throw a {@code NullPointerException} and any other
+ * exception would require looking into or hooking into the implementation of the {@code Publisher}.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#1.9'>1.9</a>
+ */
+ void untested_spec109_subscribeShouldNotThrowNonFatalThrowable() throws Throwable;
+ /**
+ * Asks for an empty {@code Publisher} and calls {@code subscribe} on it with {@code null} that should result in
+ * a {@code NullPointerException} to be thrown.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#1.9'>1.9</a>
+ * <p>
+ * If this test fails, check if the {@code subscribe()} implementation has an explicit null check (or a method dereference
+ * on the {@code Subscriber}), especially if the incoming {@code Subscriber} is wrapped or stored to be used later.
+ */
+ void required_spec109_subscribeThrowNPEOnNullSubscriber() throws Throwable;
+ /**
+ * Asks for an error {@code Publisher} that should call {@code onSubscribe} exactly once
+ * followed by a single call to {@code onError()} without receiving any requests.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#1.9'>1.9</a>
+ * <p>
+ * The test is not executed if {@code PublisherVerification.createErrorPublisher()} returns null.
+ * <p>
+ * The difference between this test and {@link #optional_spec104_mustSignalOnErrorWhenFails()} is that there is
+ * no explicit verification if exceptions were thrown in addition to the regular {@code onSubscribe+onError} signal pair.
+ * <p>
+ * If this test fails, the following could be checked within the error {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code Publisher.subscribe(Subscriber)} method has actual implementation,</li>
+ * <li>in the {@code Publisher.subscribe(Subscriber)} method, if there is an upstream {@code Publisher},
+ * that {@code Publisher} is actually subscribed to,</li>
+ * <li>if the {@code Publisher} implementation is able to emit an {@code onError} without requests,</li>
+ * <li>if the {@code Publisher} is non-empty as this test expects a {@code Publisher} without items.</li>
+ * </ul>
+ */
+ void required_spec109_mayRejectCallsToSubscribeIfPublisherIsUnableOrUnwillingToServeThemRejectionMustTriggerOnErrorAfterOnSubscribe() throws Throwable;
+ /**
+ * Currently, this test is skipped because enforcing rule §1.10 requires unlimited retention and reference-equal checks on
+ * all incoming {@code Subscriber} which is generally infeasible, plus reusing the same {@code Subscriber} instance is
+ * better detected (or ignored) inside {@code Subscriber.onSubscribe} when the method is called multiple times.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#1.10'>1.10</a>
+ */
+ void untested_spec110_rejectASubscriptionRequestIfTheSameSubscriberSubscribesTwice() throws Throwable;
+ /**
+ * Asks for a single-element {@code Publisher} and subscribes to it twice, without consuming with either
+ * {@code Subscriber} instance
+ * (i.e., no requests are issued).
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#1.11'>1.11</a>
+ * <p>
+ * The test is not executed if {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()} is less than 1.
+ * <p>
+ * Note that this test ignores what signals the {@code Publisher} emits. Any exception thrown through non-regular
+ * means will indicate a skipped test.
+ */
+ void optional_spec111_maySupportMultiSubscribe() throws Throwable;
+ /**
+ * Asks for a single-element {@code Publisher} and subscribes to it twice.
+ * Each {@code Subscriber} requests for 1 element and checks if onNext or onComplete signals was received.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#1.11'>1.11</a>,
+ * and depends on valid implementation of rule <a href='https://github.com/reactive-streams/reactive-streams-jvm#1.5'>1.5</a>
+ * in order to verify this.
+ * <p>
+ * The test is not executed if {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()} is less than 1.
+ * <p>
+ * Any exception thrown through non-regular means will indicate a skipped test.
+ */
+ void optional_spec111_registeredSubscribersMustReceiveOnNextOrOnCompleteSignals() throws Throwable;
+ /**
+ * Asks for a short {@code Publisher} (length 5), subscribes 3 {@code Subscriber}s to it, requests with different
+ * patterns and checks if all 3 received the same events in the same order.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#1.11'>1.11</a>
+ * <p>
+ * The test is not executed if {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()} is less than 5.
+ * <p>
+ * The request pattern for the first {@code Subscriber} is (1, 1, 2, 1); for the second is (2, 3) and for the third is (3, 1, 1).
+ * <p>
+ * Note that this test requires a {@code Publisher} that always emits the same signals to any {@code Subscriber}, regardless of
+ * when they subscribe and how they request elements. I.e., a "live" {@code Publisher} emitting the current time would not pass this test.
+ * <p>
+ * Note that this test is optional and may appear skipped even if the behavior should be actually supported by the {@code Publisher},
+ * see the skip message for an indication of this.
+ * <p>
+ * If this test fails, the following could be checked within the {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code TestEnvironment} has large enough timeout specified in case the {@code Publisher} has some time-delay behavior,</li>
+ * <li>make sure the {@link #required_createPublisher1MustProduceAStreamOfExactly1Element()} and {@link #required_createPublisher3MustProduceAStreamOfExactly3Elements()} tests pass,</li>
+ * <li>if the {@code Publisher} implementation considers the cumulative request amount it receives,</li>
+ * <li>if the {@code Publisher} doesn't lose any {@code request()} signal and the state transition from idle -> emitting or emitting -> keep emitting works properly.</li>
+ * </ul>
+ */
+ void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingOneByOne() throws Throwable;
+ /**
+ * Asks for a short {@code Publisher} (length 3), subscribes 3 {@code Subscriber}s to it, requests more than the length items
+ * upfront with each and verifies they all received the same items in the same order (but does not verify they all complete).
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#1.11'>1.11</a>
+ * <p>
+ * The test is not executed if {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()} is less than 3.
+ * <p>
+ * Note that this test requires a {@code Publisher} that always emits the same signals to any {@code Subscriber}, regardless of
+ * when they subscribe and how they request elements. I.e., a "live" {@code Publisher} emitting the current time would not pass this test.
+ * <p>
+ * Note that this test is optional and may appear skipped even if the behavior should be actually supported by the {@code Publisher},
+ * see the skip message for an indication of this.
+ * <p>
+ * If this test fails, the following could be checked within the {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code TestEnvironment} has large enough timeout specified in case the {@code Publisher} has some time-delay behavior,</li>
+ * <li>make sure the {@link #required_createPublisher1MustProduceAStreamOfExactly1Element()} and {@link #required_createPublisher3MustProduceAStreamOfExactly3Elements()} tests pass,</li>
+ * <li>if the {@code Publisher} implementation considers the cumulative request amount it receives,</li>
+ * <li>if the {@code Publisher} doesn't lose any {@code request()} signal and the state transition from idle -> emitting or emitting -> keep emitting works properly.</li>
+ * </ul>
+ */
+ void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfront() throws Throwable;
+ /**
+ * Asks for a short {@code Publisher} (length 3), subscribes 3 {@code Subscriber}s to it, requests more than the length items
+ * upfront with each and verifies they all received the same items in the same order followed by an {@code onComplete} signal.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#1.11'>1.11</a>
+ * <p>
+ * The test is not executed if {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()} is less than 3.
+ * <p>
+ * Note that this test requires a {@code Publisher} that always emits the same signals to any {@code Subscriber}, regardless of
+ * when they subscribe and how they request elements. I.e., a "live" {@code Publisher} emitting the current time would not pass this test.
+ * <p>
+ * Note that this test is optional and may appear skipped even if the behavior should be actually supported by the {@code Publisher},
+ * see the skip message for an indication of this.
+ * <p>
+ * If this test fails, the following could be checked within the {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code TestEnvironment} has large enough timeout specified in case the {@code Publisher} has some time-delay behavior,</li>
+ * <li>make sure the {@link #required_createPublisher1MustProduceAStreamOfExactly1Element()} and {@link #required_createPublisher3MustProduceAStreamOfExactly3Elements()} tests pass,</li>
+ * <li>if the {@code Publisher} implementation considers the cumulative request amount it receives,</li>
+ * <li>if the {@code Publisher} doesn't lose any {@code request()} signal and the state transition from idle -> emitting or emitting -> keep emitting works properly.</li>
+ * </ul>
+ */
+ void optional_spec111_multicast_mustProduceTheSameElementsInTheSameSequenceToAllOfItsSubscribersWhenRequestingManyUpfrontAndCompleteAsExpected() throws Throwable;
+ /**
+ * Asks for a short {@code Publisher} (length 6), requests several times from within {@code onSubscribe} and then requests
+ * one-by-one from {@code onNext}.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#3.2'>3.2</a>
+ * <p>
+ * The test is not executed if {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()} is less than 6.
+ * <p>
+ * The request pattern is 3 x 1 from within {@code onSubscribe} and one from within each {@code onNext} invocation.
+ * <p>
+ * The test consumes the {@code Publisher} but otherwise doesn't verify the {@code Publisher} completes (however, it checks
+ * for errors).
+ * <p>
+ * If this test fails, the following could be checked within the {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code TestEnvironment} has large enough timeout specified in case the {@code Publisher} has some time-delay behavior,</li>
+ * <li>make sure the {@link #required_createPublisher1MustProduceAStreamOfExactly1Element()} and {@link #required_createPublisher3MustProduceAStreamOfExactly3Elements()} tests pass,</li>
+ * <li>if the {@code Publisher} implementation considers the cumulative request amount it receives,</li>
+ * <li>if the {@code Publisher} doesn't lose any {@code request()} signal and the state transition from idle -> emitting or emitting -> keep emitting works properly.</li>
+ * </ul>
+ */
+ void required_spec302_mustAllowSynchronousRequestCallsFromOnNextAndOnSubscribe() throws Throwable;
+ /**
+ * Asks for a {@code Publisher} with length equal to the value returned by {@link #required_validate_boundedDepthOfOnNextAndRequestRecursion()} plus 1,
+ * calls {@code request(1)} externally and then from within {@code onNext} and checks if the stack depth did not increase beyond the
+ * amount permitted by {@link #required_validate_boundedDepthOfOnNextAndRequestRecursion()}.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#3.3'>3.3</a>
+ * <p>
+ * The test is not executed if {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()} is less than
+ * {@link #required_validate_boundedDepthOfOnNextAndRequestRecursion()} plus 1.
+ * <p>
+ * If this test fails, the following could be checked within the {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code TestEnvironment} has large enough timeout specified in case the {@code Publisher} has some time-delay behavior,</li>
+ * <li>make sure the {@link #required_createPublisher1MustProduceAStreamOfExactly1Element()} and {@link #required_createPublisher3MustProduceAStreamOfExactly3Elements()} tests pass,</li>
+ * <li>the implementation doesn't allow unbounded recursion when {@code request()} is called from within {@code onNext}, i.e., the lack of
+ * reentrant-safe state machine around the request amount (such as a for loop with a bound on the parameter {@code n} that calls {@code onNext}).
+ * </ul>
+ */
+ void required_spec303_mustNotAllowUnboundedRecursion() throws Throwable;
+ /**
+ * Currently, this test is skipped because a {@code request} could enter into a synchronous computation via {@code onNext}
+ * legally and otherwise there is no common agreement how to detect such heavy computation reliably.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#3.4'>3.4</a>
+ */
+ void untested_spec304_requestShouldNotPerformHeavyComputations() throws Exception;
+ /**
+ * Currently, this test is skipped because there is no reliable agreed upon way to detect a heavy computation.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#3.5'>3.5</a>
+ */
+ void untested_spec305_cancelMustNotSynchronouslyPerformHeavyComputation() throws Exception;
+ /**
+ * Asks for a short {@code Publisher} (length 3) and verifies that cancelling without requesting anything, then requesting
+ * items should result in no signals to be emitted.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#3.6'>3.6</a>
+ * <p>
+ * The test is not executed if {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()} is less than 3.
+ * <p>
+ * The post-cancellation request pattern is (1, 1, 1).
+ * <p>
+ * If this test fails, the following could be checked within the {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code TestEnvironment} has large enough timeout specified in case the {@code Publisher} has some time-delay behavior,</li>
+ * <li>make sure the {@link #required_createPublisher1MustProduceAStreamOfExactly1Element()} and {@link #required_createPublisher3MustProduceAStreamOfExactly3Elements()} tests pass,</li>
+ * <li>the cancellation indicator flag is properly persisted (may require volatile) and checked as part of the signal emission process.</li>
+ * </ul>
+ */
+ void required_spec306_afterSubscriptionIsCancelledRequestMustBeNops() throws Throwable;
+ /**
+ * Asks for a single-element {@code Publisher} and verifies that without requesting anything, cancelling the sequence
+ * multiple times should result in no signals to be emitted and should result in an thrown exception.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#3.7'>3.7</a>
+ * <p>
+ * The test is not executed if {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()} is less than 1.
+ * <p>
+ * If this test fails, the following could be checked within the {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code TestEnvironment} has large enough timeout specified in case the {@code Publisher} has some time-delay behavior,</li>
+ * <li>make sure the {@link #required_createPublisher1MustProduceAStreamOfExactly1Element()} and {@link #required_createPublisher3MustProduceAStreamOfExactly3Elements()} tests pass,</li>
+ * <li>the cancellation indicator flag is properly persisted (may require volatile) and checked as part of the signal emission process.</li>
+ * </ul>
+ */
+ void required_spec307_afterSubscriptionIsCancelledAdditionalCancelationsMustBeNops() throws Throwable;
+ /**
+ * Asks for a short {@code Publisher} (length 10) and issues a {@code request(0)} which should trigger an {@code onError} call
+ * with an {@code IllegalArgumentException}.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#3.9'>3.9</a>
+ * <p>
+ * The test is not executed if {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()} is less than 10.
+ * <p>
+ * Note that this test expects the {@code IllegalArgumentException} being signalled through {@code onError}, not by
+ * throwing from {@code request()} (which is also forbidden) or signalling the error by any other means (i.e., through the
+ * {@code Thread.currentThread().getUncaughtExceptionHandler()} for example).
+ * <p>
+ * Note also that requesting and emission may happen concurrently and honoring this rule may require extra coordination within
+ * the {@code Publisher}.
+ * <p>
+ * If this test fails, the following could be checked within the {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code TestEnvironment} has large enough timeout specified in case the {@code Publisher} has some time-delay behavior,</li>
+ * <li>make sure the {@link #required_createPublisher1MustProduceAStreamOfExactly1Element()} and {@link #required_createPublisher3MustProduceAStreamOfExactly3Elements()} tests pass,</li>
+ * <li>the {@code Publisher} can emit an {@code onError} in this particular case, even if there was no prior and legal
+ * {@code request} call and even if the {@code Publisher} would like to emit items first before emitting an {@code onError}
+ * in general.
+ * </ul>
+ */
+ void required_spec309_requestZeroMustSignalIllegalArgumentException() throws Throwable;
+ /**
+ * Asks for a short {@code Publisher} (length 10) and issues a random, negative {@code request()} call which should
+ * trigger an {@code onError} call with an {@code IllegalArgumentException}.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#3.9'>3.9</a>
+ * <p>
+ * The test is not executed if {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()} is less than 10.
+ * <p>
+ * Note that this test expects the {@code IllegalArgumentException} being signalled through {@code onError}, not by
+ * throwing from {@code request()} (which is also forbidden) or signalling the error by any other means (i.e., through the
+ * {@code Thread.currentThread().getUncaughtExceptionHandler()} for example).
+ * <p>
+ * Note also that requesting and emission may happen concurrently and honoring this rule may require extra coordination within
+ * the {@code Publisher}.
+ * <p>
+ * If this test fails, the following could be checked within the {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code TestEnvironment} has large enough timeout specified in case the {@code Publisher} has some time-delay behavior,</li>
+ * <li>make sure the {@link #required_createPublisher1MustProduceAStreamOfExactly1Element()} and {@link #required_createPublisher3MustProduceAStreamOfExactly3Elements()} tests pass,</li>
+ * <li>the {@code Publisher} can emit an {@code onError} in this particular case, even if there was no prior and legal
+ * {@code request} call and even if the {@code Publisher} would like to emit items first before emitting an {@code onError}
+ * in general.
+ * </ul>
+ */
+ void required_spec309_requestNegativeNumberMustSignalIllegalArgumentException() throws Throwable;
+ /**
+ * Asks for a short {@code Publisher} (length 10) and issues a random, negative {@code request()} call which should
+ * trigger an {@code onError} call with an {@code IllegalArgumentException}.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#3.9'>3.9</a>
+ * <p>
+ * The test is not executed if {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()} is less than 10.
+ * <p>
+ * Note that this test expects the {@code IllegalArgumentException} being signalled through {@code onError}, not by
+ * throwing from {@code request()} (which is also forbidden) or signalling the error by any other means (i.e., through the
+ * {@code Thread.currentThread().getUncaughtExceptionHandler()} for example).
+ * <p>
+ * Note also that requesting and emission may happen concurrently and honoring this rule may require extra coordination within
+ * the {@code Publisher}.
+ * <p>
+ * If this test fails, the following could be checked within the {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code TestEnvironment} has large enough timeout specified in case the {@code Publisher} has some time-delay behavior,</li>
+ * <li>make sure the {@link #required_createPublisher1MustProduceAStreamOfExactly1Element()} and {@link #required_createPublisher3MustProduceAStreamOfExactly3Elements()} tests pass,</li>
+ * <li>the {@code Publisher} can emit an {@code onError} in this particular case, even if there was no prior and legal
+ * {@code request} call and even if the {@code Publisher} would like to emit items first before emitting an {@code onError}
+ * in general.
+ * </ul>
+ */
+ void optional_spec309_requestNegativeNumberMaySignalIllegalArgumentExceptionWithSpecificMessage() throws Throwable;
+ /**
+ * Asks for a short {@code Publisher} (length 20), requests some items (less than the length), consumes one item then
+ * cancels the sequence and verifies the publisher emitted at most the requested amount and stopped emitting (or terminated).
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#3.12'>3.12</a>
+ * <p>
+ * The test is not executed if {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()} is less than 20.
+ * <p>
+ * If this test fails, the following could be checked within the {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code TestEnvironment} has large enough timeout specified in case the {@code Publisher} has some time-delay behavior,</li>
+ * <li>make sure the {@link #required_createPublisher1MustProduceAStreamOfExactly1Element()} and {@link #required_createPublisher3MustProduceAStreamOfExactly3Elements()} tests pass,</li>
+ * <li>the cancellation indicator flag is properly persisted (may require volatile) and checked as part of the signal emission process.</li>
+ * </ul>
+ */
+ void required_spec312_cancelMustMakeThePublisherToEventuallyStopSignaling() throws Throwable;
+ /**
+ * Asks for a short {@code Publisher} (length 3) requests and consumes one element from it, cancels the {@code Subscription}
+ * , calls {@code System.gc()} and then checks if all references to the test {@code Subscriber} has been dropped (by checking
+ * the {@code WeakReference} has been emptied).
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#3.13'>3.13</a>
+ * <p>
+ * The test is not executed if {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()} is less than 3.
+ * <p>
+ * If this test fails, the following could be checked within the {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code TestEnvironment} has large enough timeout specified in case the {@code Publisher} has some time-delay behavior,</li>
+ * <li>make sure the {@link #required_createPublisher1MustProduceAStreamOfExactly1Element()} and {@link #required_createPublisher3MustProduceAStreamOfExactly3Elements()} tests pass,</li>
+ * <li>the cancellation indicator flag is properly persisted (may require volatile) and checked as part of the signal emission process.</li>
+ * <li>the {@code Publisher} stores the {@code Subscriber} reference somewhere which is then not cleaned up when the {@code Subscriber} is cancelled.
+ * Note that this may happen on many code paths in a {@code Publisher}, for example in an emission loop that terminates because of the
+ * {@code cancel} signal or because reaching a terminal state. Note also that eagerly nulling {@code Subscriber} references may not be necessary
+ * for this test to pass in case there is a self-contained chain of them (i.e., {@code Publisher.subscribe()} creates a chain of fresh
+ * {@code Subscriber} instances where each of them only references their downstream {@code Subscriber} thus the chain can get GC'd
+ * when the reference to the final {@code Subscriber} is dropped).
+ * </ul>
+ */
+ void required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber() throws Throwable;
+ /**
+ * Asks for a short {@code Publisher} (length 3) and requests {@code Long.MAX_VALUE} from it, verifying that the
+ * {@code Publisher} emits all of its items and completes normally
+ * and does not keep spinning attempting to fulfill the {@code Long.MAX_VALUE} demand by some means.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#3.17'>3.17</a>
+ * <p>
+ * The test is not executed if {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()} is less than 3.
+ * <p>
+ * If this test fails, the following could be checked within the {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code TestEnvironment} has large enough timeout specified in case the {@code Publisher} has some time-delay behavior,</li>
+ * <li>make sure the {@link #required_createPublisher1MustProduceAStreamOfExactly1Element()} and {@link #required_createPublisher3MustProduceAStreamOfExactly3Elements()} tests pass,</li>
+ * <li>if the {@code Publisher} implementation considers the cumulative request amount it receives,</li>
+ * <li>if the {@code Publisher} doesn't lose any {@code request()} signal and the state transition from idle -> emitting or emitting -> keep emitting works properly.</li>
+ * </ul>
+ */
+ void required_spec317_mustSupportAPendingElementCountUpToLongMaxValue() throws Throwable;
+ /**
+ * Asks for a short {@code Publisher} (length 3) and requests {@code Long.MAX_VALUE} from it in total (split across
+ * two {@code Long.MAX_VALUE / 2} and one {@code request(1)}), verifying that the
+ * {@code Publisher} emits all of its items and completes normally.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#3.17'>3.17</a>
+ * <p>
+ * The test is not executed if {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()} is less than 3.
+ * <p>
+ * If this test fails, the following could be checked within the {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code TestEnvironment} has large enough timeout specified in case the {@code Publisher} has some time-delay behavior,</li>
+ * <li>make sure the {@link #required_createPublisher1MustProduceAStreamOfExactly1Element()} and {@link #required_createPublisher3MustProduceAStreamOfExactly3Elements()} tests pass,</li>
+ * <li>if the {@code Publisher} implementation considers the cumulative request amount it receives,</li>
+ * <li>if the {@code Publisher} implements adding individual request amounts together properly (not overflowing into zero or negative pending request amounts)
+ * or not properly deducing the number of emitted items from the pending amount,</li>
+ * <li>if the {@code Publisher} doesn't lose any {@code request()} signal and the state transition from idle -> emitting or emitting -> keep emitting works properly.</li>
+ * </ul>
+ */
+ void required_spec317_mustSupportACumulativePendingElementCountUpToLongMaxValue() throws Throwable;
+ /**
+ * Asks for a very long {@code Publisher} (up to {@code Integer.MAX_VALUE}), requests {@code Long.MAX_VALUE - 1} after
+ * each received item and expects no failure due to a potential overflow in the pending emission count while consuming
+ * 10 items and cancelling the sequence.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#3.17'>3.17</a>
+ * <p>
+ * The test is not executed if {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()} is less than {@code Integer.MAX_VALUE}.
+ * <p>
+ * The request pattern is one {@code request(1)} upfront and ten {@code request(Long.MAX_VALUE - 1)} after.
+ * <p>
+ * If this test fails, the following could be checked within the {@code Publisher} implementation:
+ * <ul>
+ * <li>the {@code TestEnvironment} has large enough timeout specified in case the {@code Publisher} has some time-delay behavior,</li>
+ * <li>make sure the {@link #required_createPublisher1MustProduceAStreamOfExactly1Element()} and {@link #required_createPublisher3MustProduceAStreamOfExactly3Elements()} tests pass,</li>
+ * <li>if the {@code Publisher} implementation considers the cumulative request amount it receives,</li>
+ * <li>if the {@code Publisher} implements adding individual request amounts together properly (not overflowing into zero or negative pending request amounts)
+ * or not properly deducing the number of emitted items from the pending amount,</li>
+ * <li>if the {@code Publisher} doesn't lose any {@code request()} signal and the state transition from idle -> emitting or emitting -> keep emitting works properly.</li>
+ * </ul>
+ */
+ void required_spec317_mustNotSignalOnErrorWhenPendingAboveLongMaxValue() throws Throwable;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/tck/flow/support/SubscriberBlackboxVerificationRules.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,395 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.reactivestreams.tck.flow.support;
+
+import org.reactivestreams.tck.SubscriberBlackboxVerification;
+
+/**
+ * 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 {
+ /**
+ * Asks for a {@code Subscriber} instance, expects it to call {@code request()} in
+ * a timely manner and signals as many {@code onNext} items as the very first request
+ * amount specified by the {@code Subscriber}.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#2.1'>2.1</a>
+ * <p>
+ * Notes:
+ * <ul>
+ * <li>This test emits the number of items requested thus the {@code Subscriber} implementation
+ * should not request too much.</li>
+ * <li>Only the very first {@code request} amount is considered.</li>
+ * <li>This test doesn't signal {@code onComplete} after the first set of {@code onNext} signals
+ * has been emitted and may cause resource leak in
+ * {@code Subscriber}s that expect a finite {@code Publisher}.</li>
+ * <li>The test ignores cancellation from the {@code Subscriber} and emits the requested amount regardless.</li>
+ * </ul>
+ * <p>
+ * If this test fails, the following could be checked within the {@code Subscriber} implementation:
+ * <ul>
+ * <li>if the {@code Subscriber} requires external stimulus to begin requesting; override the
+ * {@link SubscriberBlackboxVerification#triggerRequest(org.reactivestreams.Subscriber)} method
+ * in this case,</li>
+ * <li>the {@code TestEnvironment} has large enough timeout specified in case the {@code Subscriber} has some time-delay behavior,</li>
+ * <li>if the {@code Subscriber} requests zero or a negative value in some circumstances,</li>
+ * <li>if the {@code Subscriber} throws an unchecked exception from its {@code onSubscribe} or
+ * {@code onNext} methods.
+ * </ul>
+ */
+ void required_spec201_blackbox_mustSignalDemandViaSubscriptionRequest() throws Throwable;
+ /**
+ * Currently, this test is skipped because there is no agreed upon approach how
+ * to detect if the {@code Subscriber} really goes async or just responds in
+ * a timely manner.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#2.2'>2.2</a>
+ */
+ void untested_spec202_blackbox_shouldAsynchronouslyDispatch() throws Exception;
+ /**
+ * Asks for a {@code Subscriber}, signals an {@code onSubscribe} followed by an {@code onComplete} synchronously,
+ * and checks if neither {@code request} nor {@code cancel} was called from within the {@code Subscriber}'s
+ * {@code onComplete} implementation.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#2.3'>2.3</a>
+ * <p>
+ * Notes:
+ * <ul>
+ * <li>The test checks for the presensce of method named "onComplete" in the current stacktrace when handling
+ * the {@code request} or {@code cancel} calls in the test's own {@code Subscription}.
+ * </ul>
+ * <p>
+ * If this test fails, the following could be checked within the {@code Subscriber} implementation:
+ * <ul>
+ * <li>no calls happen to {@code request} or {@code cancel} in response to an {@code onComplete}
+ * directly or indirectly,</li>
+ * <li>if the {@code Subscriber} throws an unchecked exception from its {@code onSubscribe} or
+ * {@code onComplete} methods.
+ * </ul>
+ */
+ void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete() throws Throwable;
+ /**
+ * Asks for a {@code Subscriber}, signals an {@code onSubscribe} followed by an {@code onError} synchronously,
+ * and checks if neither {@code request} nor {@code cancel} was called from within the {@code Subscriber}'s
+ * {@code onComplete} implementation.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#2.3'>2.3</a>
+ * <p>
+ * Notes:
+ * <ul>
+ * <li>The test checks for the presensce of method named "onError" in the current stacktrace when handling
+ * the {@code request} or {@code cancel} calls in the test's own {@code Subscription}.
+ * </ul>
+ * <p>
+ * If this test fails, the following could be checked within the {@code Subscriber} implementation:
+ * <ul>
+ * <li>no calls happen to {@code request} or {@code cancel} in response to an {@code onError}
+ * directly or indirectly,</li>
+ * <li>if the {@code Subscriber} throws an unchecked exception from its {@code onSubscribe} or
+ * {@code onError} methods.
+ * </ul>
+ */
+ void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError() throws Throwable;
+ /**
+ * Currently, this test is skipped because there is no way to check what the {@code Subscriber} "considers"
+ * since rule §2.3 forbids interaction from within the {@code onError} and {@code onComplete} methods.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#2.4'>2.4</a>
+ * <p>
+ * Notes:
+ * <ul>
+ * <li>It would be possible to check if there was an async interaction with the test's {@code Subscription}
+ * within a grace period but such check is still not generally decisive.</li>
+ * </ul>
+ */
+ void untested_spec204_blackbox_mustConsiderTheSubscriptionAsCancelledInAfterRecievingOnCompleteOrOnError() throws Exception;
+ /**
+ * Asks for a {@code Subscriber}, signals {@code onSubscribe} twice synchronously and expects the second {@code Subscription} gets
+ * cancelled in a timely manner and without any calls to its {@code request} method.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#2.5'>2.5</a>
+ * <p>
+ * Notes:
+ * <ul>
+ * <li>The test doesn't signal any other events than {@code onSubscribe} and may cause resource leak in
+ * {@code Subscriber}s that expect a finite {@code Publisher}.
+ * </ul>
+ * <p>
+ * If this test fails, the following could be checked within the {@code Subscriber} implementation:
+ * <ul>
+ * <li>if the {@code Subscribe.onSubscribe} implementation actually tries to detect multiple calls to it,</li>
+ * <li>if the second {@code Subscription} is cancelled asynchronously and that takes longer time than
+ * the {@code TestEnvironment}'s timeout permits.</li>
+ * </ul>
+ */
+ void required_spec205_blackbox_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal() throws Exception;
+
+ /**
+ * Currently, this test is skipped because it requires more control over the {@code Subscriber} implementation
+ * to make it cancel the {@code Subscription} for some external condition.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#2.6'>2.6</a>
+ */
+ void untested_spec206_blackbox_mustCallSubscriptionCancelIfItIsNoLongerValid() throws Exception;
+ /**
+ * Currently, this test is skipped because it requires more control over the {@code Subscriber} implementation
+ * to issue requests based on external stimulus.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#2.7'>2.7</a>
+ */
+ void untested_spec207_blackbox_mustEnsureAllCallsOnItsSubscriptionTakePlaceFromTheSameThreadOrTakeCareOfSynchronization() throws Exception;
+ /**
+ * Currently, this test is skipped because there is no way to make the {@code Subscriber} implementation
+ * cancel the test's {@code Subscription} and check the outcome of sending {@code onNext}s after such
+ * cancel.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#2.8'>2.8</a>
+ */
+ void untested_spec208_blackbox_mustBePreparedToReceiveOnNextSignalsAfterHavingCalledSubscriptionCancel() throws Throwable;
+ /**
+ * Asks for a {@code Subscriber}, expects it to request some amount and in turn be able to receive an {@code onComplete}
+ * synchronously from the {@code request} call without any {@code onNext} signals before that.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#2.9'>2.9</a>
+ * <p>
+ * Notes:
+ * <ul>
+ * <li>The test ignores cancellation from the {@code Subscriber}.</li>
+ * <li>Invalid request amounts are ignored by this test.</li>
+ * <li>Concurrent calls to the test's {@code Subscription.request()} must be externally synchronized, otherwise
+ * such case results probabilistically in multiple {@code onComplete} calls by the test.</li>
+ * </ul>
+ * <p>
+ * If this test fails, the following could be checked within the {@code Subscriber} implementation:
+ * <ul>
+ * <li>if the {@code Subscriber} throws an unchecked exception from its {@code onSubscribe} or
+ * {@code onComplete} methods.
+ * <li>if the {@code Subscriber} requires external stimulus to begin requesting; override the
+ * {@link SubscriberBlackboxVerification#triggerRequest(org.reactivestreams.Subscriber)} method
+ * in this case,</li>
+ * </ul>
+ */
+ void required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() throws Throwable;
+ /**
+ * Asks for a {@code Subscriber} and expects it to handle {@code onComplete} independent of whether the {@code Subscriber}
+ * requests items or not.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#2.9'>2.9</a>
+ * <p>
+ * Notes:
+ * <ul>
+ * <li>Currently, the test doesn't call {@code onSubscribe} on the {@code Subscriber} which violates §1.9.
+ * </ul>
+ * <p>
+ * If this test fails, the following could be checked within the {@code Subscriber} implementation:
+ * <ul>
+ * <li>if the {@code Subscriber} throws an unchecked exception from its {@code onSubscribe} or
+ * {@code onComplete} methods.
+ * </ul>
+ */
+ void required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithoutPrecedingRequestCall() throws Throwable;
+ /**
+ * Asks for a {@code Subscriber}, signals {@code onSubscribe} followed by an {@code onError} synchronously.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#2.10'>2.10</a>
+ * <p>
+ * Notes:
+ * <ul>
+ * <li>Despite the method name, the test doesn't expect a request signal from {@code Subscriber} and emits the
+ * {@code onError} signal anyway.
+ * </ul>
+ * <p>
+ * If this test fails, the following could be checked within the {@code Subscriber} implementation:
+ * <ul>
+ * <li>if the {@code Subscriber} throws an unchecked exception from its {@code onSubscribe} or
+ * {@code onError} methods.
+ * </ul>
+ */
+ void required_spec210_blackbox_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() throws Throwable;
+
+ /**
+ * Asks for a {@code Subscriber}, signals {@code onSubscribe} followed by an {@code onError} synchronously.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#2.10'>2.10</a>
+ * <p>
+ * If this test fails, the following could be checked within the {@code Subscriber} implementation:
+ * <ul>
+ * <li>if the {@code Subscriber} throws an unchecked exception from its {@code onSubscribe} or
+ * {@code onError} methods.
+ * </ul>
+ */
+ void required_spec210_blackbox_mustBePreparedToReceiveAnOnErrorSignalWithoutPrecedingRequestCall() throws Throwable;
+
+ /**
+ * Currently, this test is skipped because it would require analyzing what the {@code Subscriber} implementation
+ * does.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#2.11'>2.11</a>
+ */
+ void untested_spec211_blackbox_mustMakeSureThatAllCallsOnItsMethodsHappenBeforeTheProcessingOfTheRespectiveEvents() throws Exception;
+ /**
+ * Currently, this test is skipped because the test for
+ * {@link #required_spec205_blackbox_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal §2.5}
+ * is in a better position to test for handling the reuse of the same {@code Subscriber}.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#2.12'>2.12</a>
+ * <p>
+ * Notes:
+ * <ul>
+ * <li>In addition to §2.5, this rule could be better verified when testing a {@code Publisher}'s subscription behavior.
+ * </ul>
+ */
+ void untested_spec212_blackbox_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality() throws Throwable;
+ /**
+ * Currently, this test is skipped because it would require more control over the {@code Subscriber} to
+ * fail internally in response to a set of legal event emissions, not throw any exception from the {@code Subscriber}
+ * methods and have it cancel the {@code Subscription}.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#2.13'>2.13</a>
+ */
+ void untested_spec213_blackbox_failingOnSignalInvocation() throws Exception;
+ /**
+ * Asks for a {@code Subscriber} and signals an {@code onSubscribe} event with {@code null} as a parameter and
+ * expects an immediate {@code NullPointerException} to be thrown by the {@code Subscriber.onSubscribe} method.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#2.13'>2.13</a>
+ * <p>
+ * If this test fails, the following could be checked within the {@code Subscriber} implementation:
+ * <ul>
+ * <li>if the {@code Subscriber} throws a {@code NullPointerException} from its {@code onSubscribe} method
+ * in response to a {@code null} parameter and not some other unchecked exception or no exception at all.
+ * </ul>
+ */
+ void required_spec213_blackbox_onSubscribe_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable;
+ /**
+ * Asks for a {@code Subscriber}, signals an {@code onSubscribe} event followed by a
+ * {@code onNext} with {@code null} as a parameter and
+ * expects an immediate {@code NullPointerException} to be thrown by the {@code Subscriber.onNext} method.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#2.13'>2.13</a>
+ * <p>
+ * Notes:
+ * <ul>
+ * <li>The test ignores cancellation and requests from the {@code Subscriber} and emits the {@code onNext}
+ * signal with a {@code null} parameter anyway.</li>
+ * </ul>
+ * <p>
+ * If this test fails, the following could be checked within the {@code Subscriber} implementation:
+ * <ul>
+ * <li>if the {@code Subscriber} throws a {@code NullPointerException} from its {@code onNext} method
+ * in response to a {@code null} parameter and not some other unchecked exception or no exception at all.
+ * </ul>
+ */
+ void required_spec213_blackbox_onNext_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable;
+ /**
+ * Asks for a {@code Subscriber}, signals an {@code onSubscribe} event followed by a
+ * {@code onError} with {@code null} as a parameter and
+ * expects an immediate {@code NullPointerException} to be thrown by the {@code Subscriber.onError} method.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#2.13'>2.13</a>
+ * <p>
+ * Notes:
+ * <ul>
+ * <li>The test ignores cancellation from the {@code Subscriber} and emits the {@code onError}
+ * signal with a {@code null} parameter anyway.</li>
+ * </ul>
+ * <p>
+ * If this test fails, the following could be checked within the {@code Subscriber} implementation:
+ * <ul>
+ * <li>if the {@code Subscriber} throws a {@code NullPointerException} from its {@code onNext} method
+ * in response to a {@code null} parameter and not some other unchecked exception or no exception at all.
+ * </ul>
+ */
+ void required_spec213_blackbox_onError_mustThrowNullPointerExceptionWhenParametersAreNull() throws Throwable;
+ /**
+ * Currently, this test is skipped because there is no agreed upon way for specifying, enforcing and testing
+ * a {@code Subscriber} with an arbitrary context.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#3.1'>3.1</a>
+ */
+ void untested_spec301_blackbox_mustNotBeCalledOutsideSubscriberContext() throws Exception;
+ /**
+ * Currently, this test is skipped because element production is the responsibility of the {@code Publisher} and
+ * a {@code Subscription} is not expected to be the active element in an established subscription.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#3.8'>3.8</a>
+ */
+ void untested_spec308_blackbox_requestMustRegisterGivenNumberElementsToBeProduced() throws Throwable;
+ /**
+ * Currently, this test is skipped because element production is the responsibility of the {@code Publisher} and
+ * a {@code Subscription} is not expected to be the active element in an established subscription.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#3.10'>3.10</a>
+ * <p>
+ * Notes:
+ * <ul>
+ * <li>This could be tested with a synchronous source currently not available within the TCK.</li>
+ * </ul>
+ */
+ void untested_spec310_blackbox_requestMaySynchronouslyCallOnNextOnSubscriber() throws Exception;
+ /**
+ * Currently, this test is skipped because signal production is the responsibility of the {@code Publisher} and
+ * a {@code Subscription} is not expected to be the active element in an established subscription.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#3.11'>3.11</a>
+ * <p>
+ * Notes:
+ * <ul>
+ * <li>Tests {@link #required_spec209_blackbox_mustBePreparedToReceiveAnOnCompleteSignalWithPrecedingRequestCall() §2.9}
+ * and {@link #required_spec210_blackbox_mustBePreparedToReceiveAnOnErrorSignalWithPrecedingRequestCall() §2.10} are
+ * supposed to cover this case from the {@code Subscriber's} perspective.</li>
+ * </ul>
+ */
+ void untested_spec311_blackbox_requestMaySynchronouslyCallOnCompleteOrOnError() throws Exception;
+ /**
+ * Currently, this test is skipped because it is the responsibility of the {@code Publisher} deal with the case
+ * that all subscribers have cancelled their subscription.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#3.14'>3.14</a>
+ * <p>
+ * Notes:
+ * <ul>
+ * <li>The specification lists this as an optional behavior because only some {@code Publisher} implementations
+ * (most likely {@code Processor}s) would coordinate with multiple {@code Subscriber}s.</li>
+ * </ul>
+ */
+ void untested_spec314_blackbox_cancelMayCauseThePublisherToShutdownIfNoOtherSubscriptionExists() throws Exception;
+ /**
+ * Currently, this test is skipped because it requires more control over the {@code Subscriber} implementation
+ * thus there is no way to detect that the {@code Subscriber} called its own {@code onError} method in response
+ * to an exception thrown from {@code Subscription.cancel}.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#3.15'>3.15</a>
+ */
+ void untested_spec315_blackbox_cancelMustNotThrowExceptionAndMustSignalOnError() throws Exception;
+ /**
+ * Currently, this test is skipped because it requires more control over the {@code Subscriber} implementation
+ * thus there is no way to detect that the {@code Subscriber} called its own {@code onError} method in response
+ * to an exception thrown from {@code Subscription.request}.
+ * <p>
+ * <b>Verifies rule:</b> <a href='https://github.com/reactive-streams/reactive-streams-jvm#3.16'>3.16</a>
+ */
+ void untested_spec316_blackbox_requestMustNotThrowExceptionAndMustOnErrorTheSubscriber() throws Exception;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/tck/flow/support/SubscriberBufferOverflowException.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.reactivestreams.tck.flow.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);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/tck/flow/support/SubscriberWhiteboxVerificationRules.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.reactivestreams.tck.flow.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;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/reactivestreams-tck/org/reactivestreams/tck/flow/support/TestException.java Tue Jul 02 13:25:51 2019 +0100
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.reactivestreams.tck.flow.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!");
+ }
+}