diff --git a/tck/src/main/java/org/reactivestreams/tck/IdentityProcessorVerification.java b/tck/src/main/java/org/reactivestreams/tck/IdentityProcessorVerification.java index ed972aa0..5efe6e27 100644 --- a/tck/src/main/java/org/reactivestreams/tck/IdentityProcessorVerification.java +++ b/tck/src/main/java/org/reactivestreams/tck/IdentityProcessorVerification.java @@ -370,6 +370,11 @@ public void required_spec309_requestZeroMustSignalIllegalArgumentException() thr 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 { diff --git a/tck/src/main/java/org/reactivestreams/tck/PublisherVerification.java b/tck/src/main/java/org/reactivestreams/tck/PublisherVerification.java index e9011b9d..eb6f9344 100644 --- a/tck/src/main/java/org/reactivestreams/tck/PublisherVerification.java +++ b/tck/src/main/java/org/reactivestreams/tck/PublisherVerification.java @@ -896,7 +896,7 @@ public void required_spec309_requestZeroMustSignalIllegalArgumentException() thr @Override public void run(Publisher pub) throws Throwable { final ManualSubscriber sub = env.newManualSubscriber(pub); sub.request(0); - sub.expectErrorWithMessage(IllegalArgumentException.class, "3.9"); // we do require implementations to mention the rule number at the very least + sub.expectError(IllegalArgumentException.class); } }); } @@ -909,7 +909,22 @@ public void run(Publisher pub) throws Throwable { final ManualSubscriber sub = env.newManualSubscriber(pub); final Random r = new Random(); sub.request(-r.nextInt(Integer.MAX_VALUE) - 1); - sub.expectErrorWithMessage(IllegalArgumentException.class, "3.9"); // we do require implementations to mention the rule number at the very least + // 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() { + @Override + public void run(Publisher pub) throws Throwable { + final ManualSubscriber 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")); } }); } diff --git a/tck/src/main/java/org/reactivestreams/tck/TestEnvironment.java b/tck/src/main/java/org/reactivestreams/tck/TestEnvironment.java index 86649058..e7474d59 100644 --- a/tck/src/main/java/org/reactivestreams/tck/TestEnvironment.java +++ b/tck/src/main/java/org/reactivestreams/tck/TestEnvironment.java @@ -17,6 +17,7 @@ import org.reactivestreams.tck.support.SubscriberBufferOverflowException; import org.reactivestreams.tck.support.Optional; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; @@ -513,14 +514,24 @@ public void expectCompletion(long timeoutMillis, String errorMsg) throws Interru public void expectErrorWithMessage(Class expected, String requiredMessagePart) throws Exception { expectErrorWithMessage(expected, requiredMessagePart, env.defaultTimeoutMillis()); } + public void expectErrorWithMessage(Class expected, List requiredMessagePartAlternatives) throws Exception { + expectErrorWithMessage(expected, requiredMessagePartAlternatives, env.defaultTimeoutMillis()); + } @SuppressWarnings("ThrowableResultOfMethodCallIgnored") public void expectErrorWithMessage(Class expected, String requiredMessagePart, long timeoutMillis) throws Exception { + expectErrorWithMessage(expected, Collections.singletonList(requiredMessagePart), timeoutMillis); + } + public void expectErrorWithMessage(Class expected, List requiredMessagePartAlternatives, long timeoutMillis) throws Exception { final E err = expectError(expected, timeoutMillis); final String message = err.getMessage(); - assertTrue(message.contains(requiredMessagePart), + + 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(), requiredMessagePart, err.getMessage())); + err.getClass(), "anyOf: " + requiredMessagePartAlternatives, err.getMessage())); } public E expectError(Class expected) throws Exception { diff --git a/tck/src/main/java/org/reactivestreams/tck/support/PublisherVerificationRules.java b/tck/src/main/java/org/reactivestreams/tck/support/PublisherVerificationRules.java index cd3cf032..e429ec30 100644 --- a/tck/src/main/java/org/reactivestreams/tck/support/PublisherVerificationRules.java +++ b/tck/src/main/java/org/reactivestreams/tck/support/PublisherVerificationRules.java @@ -471,7 +471,7 @@ public interface PublisherVerificationRules { 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} and the message containing the string "3.9" (reference to the rule number). + * with an {@code IllegalArgumentException}. *

* Verifies rule: 3.9 *

@@ -495,8 +495,8 @@ public interface PublisherVerificationRules { */ 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} and the message containing the string "3.9" (reference to the rule number). + * 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}. *

* Verifies rule: 3.9 *

@@ -519,6 +519,31 @@ public interface PublisherVerificationRules { * */ 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}. + *

+ * Verifies rule: 3.9 + *

+ * The test is not executed if {@link org.reactivestreams.tck.PublisherVerification#maxElementsFromPublisher()} is less than 10. + *

+ * 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). + *

+ * Note also that requesting and emission may happen concurrently and honoring this rule may require extra coordination within + * the {@code Publisher}. + *

+ * If this test fails, the following could be checked within the {@code Publisher} implementation: + *

    + *
  • the {@code TestEnvironment} has large enough timeout specified in case the {@code Publisher} has some time-delay behavior,
  • + *
  • make sure the {@link #required_createPublisher1MustProduceAStreamOfExactly1Element()} and {@link #required_createPublisher3MustProduceAStreamOfExactly3Elements()} tests pass,
  • + *
  • 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. + *
+ */ + 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). diff --git a/tck/src/test/java/org/reactivestreams/tck/PublisherVerificationTest.java b/tck/src/test/java/org/reactivestreams/tck/PublisherVerificationTest.java index 87ef07ca..52270518 100644 --- a/tck/src/test/java/org/reactivestreams/tck/PublisherVerificationTest.java +++ b/tck/src/test/java/org/reactivestreams/tck/PublisherVerificationTest.java @@ -481,6 +481,27 @@ public void required_spec309_requestZeroMustSignalIllegalArgumentException_shoul }, "Expected onError"); } + @Test + public void required_spec309_requestZeroMustSignalIllegalArgumentException_shouldPass() throws Throwable { + customPublisherVerification(new Publisher() { + @Override + public void subscribe(final Subscriber s) { + s.onSubscribe(new Subscription() { + @Override + public void request(long n) { + // we error out with any message, it does not have to contain any specific wording + if (n <= 0) s.onError(new IllegalArgumentException("Illegal request value detected!")); + } + + @Override + public void cancel() { + // noop + } + }); + } + }).required_spec309_requestZeroMustSignalIllegalArgumentException(); + } + @Test public void required_spec309_requestNegativeNumberMustSignalIllegalArgumentException_shouldFailBy_expectingOnError() throws Throwable { requireTestFailure(new ThrowingRunnable() {