diff --git a/tck/src/main/java/org/reactivestreams/tck/SubscriberBlackboxVerification.java b/tck/src/main/java/org/reactivestreams/tck/SubscriberBlackboxVerification.java index 8931ec93..402ef879 100644 --- a/tck/src/main/java/org/reactivestreams/tck/SubscriberBlackboxVerification.java +++ b/tck/src/main/java/org/reactivestreams/tck/SubscriberBlackboxVerification.java @@ -128,10 +128,12 @@ public void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublishe blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() { @Override public void run(BlackboxTestStage stage) throws Throwable { + final String onCompleteMethod = "required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_call"; + final Subscription subs = new Subscription() { @Override public void request(long n) { - final Optional onCompleteStackTraceElement = env.findCallerMethodInStackTrace("onComplete"); + final Optional onCompleteStackTraceElement = env.findCallerMethodInStackTrace(onCompleteMethod); 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)", @@ -141,7 +143,7 @@ public void request(long n) { @Override public void cancel() { - final Optional onCompleteStackElement = env.findCallerMethodInStackTrace("onComplete"); + final Optional onCompleteStackElement = env.findCallerMethodInStackTrace(onCompleteMethod); 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)", @@ -152,10 +154,15 @@ public void cancel() { final Subscriber sub = createSubscriber(); sub.onSubscribe(subs); - sub.onComplete(); + required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_call(sub); env.verifyNoAsyncErrorsNoDelay(); } + + /** Makes sure the onComplete is initiated with a recognizable stacktrace element on the current thread. */ + void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_call(Subscriber sub) { + sub.onComplete(); + } }); } @@ -164,36 +171,41 @@ public void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublishe blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() { @Override public void run(BlackboxTestStage stage) throws Throwable { + final String onErrorMethod = "required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError_call"; + 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())); - } + final Optional onCompleteStackElement = env.findCallerMethodInStackTrace(onErrorMethod); + if (onCompleteStackElement.isDefined()) { + final StackTraceElement stackElem = onCompleteStackElement.get(); + 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 Optional onCompleteStackElement = env.findCallerMethodInStackTrace(onErrorMethod); + if (onCompleteStackElement.isDefined()) { + final StackTraceElement stackElem = onCompleteStackElement.get(); + 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 sub = createSubscriber(); sub.onSubscribe(subs); - sub.onError(new TestException()); + required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError_call(sub); env.verifyNoAsyncErrorsNoDelay(); } + + /** Makes sure the onError is initiated with a recognizable stacktrace element on the current thread. */ + void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError_call(Subscriber sub) { + sub.onError(new TestException()); + } }); } diff --git a/tck/src/main/java/org/reactivestreams/tck/SubscriberWhiteboxVerification.java b/tck/src/main/java/org/reactivestreams/tck/SubscriberWhiteboxVerification.java index 3b14691f..8493a435 100644 --- a/tck/src/main/java/org/reactivestreams/tck/SubscriberWhiteboxVerification.java +++ b/tck/src/main/java/org/reactivestreams/tck/SubscriberWhiteboxVerification.java @@ -133,10 +133,12 @@ public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComp subscriberTestWithoutSetup(new TestStageTestRun() { @Override public void run(WhiteboxTestStage stage) throws Throwable { + final String onCompleteMethod = "required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_call"; + final Subscription subs = new Subscription() { @Override public void request(long n) { - final Optional onCompleteStackTraceElement = env.findCallerMethodInStackTrace("onComplete"); + final Optional onCompleteStackTraceElement = env.findCallerMethodInStackTrace(onCompleteMethod); 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)", @@ -146,7 +148,7 @@ public void request(long n) { @Override public void cancel() { - final Optional onCompleteStackElement = env.findCallerMethodInStackTrace("onComplete"); + final Optional onCompleteStackElement = env.findCallerMethodInStackTrace(onCompleteMethod); 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)", @@ -159,10 +161,15 @@ public void cancel() { final Subscriber sub = createSubscriber(stage.probe); sub.onSubscribe(subs); - sub.onComplete(); + required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_call(sub); env.verifyNoAsyncErrorsNoDelay(); } + + /** Makes sure the onComplete is initiated with a recognizable stacktrace element on the current thread. */ + void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_call(Subscriber sub) { + sub.onComplete(); + } }); } @@ -172,26 +179,26 @@ public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnErro subscriberTestWithoutSetup(new TestStageTestRun() { @Override public void run(WhiteboxTestStage stage) throws Throwable { + final String onErrorMethod = "required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError_call"; + 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)", + final Optional onCompleteStackElement = env.findCallerMethodInStackTrace(onErrorMethod); + if (onCompleteStackElement.isDefined()) { + final StackTraceElement stackElem = onCompleteStackElement.get(); + 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 Optional onCompleteStackElement = env.findCallerMethodInStackTrace(onErrorMethod); + if (onCompleteStackElement.isDefined()) { + final StackTraceElement stackElem = onCompleteStackElement.get(); + 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())); } } }; @@ -200,10 +207,15 @@ public void cancel() { final Subscriber sub = createSubscriber(stage.probe); sub.onSubscribe(subs); - sub.onError(new TestException()); + required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError_call(sub); env.verifyNoAsyncErrorsNoDelay(); } + + /** Makes sure the onError is initiated with a recognizable stacktrace element on the current thread. */ + void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError_call(Subscriber sub) { + sub.onError(new TestException()); + } }); } diff --git a/tck/src/test/java/org/reactivestreams/tck/SubscriberBlackboxVerificationTest.java b/tck/src/test/java/org/reactivestreams/tck/SubscriberBlackboxVerificationTest.java index 9f5055bf..a00a12dd 100644 --- a/tck/src/test/java/org/reactivestreams/tck/SubscriberBlackboxVerificationTest.java +++ b/tck/src/test/java/org/reactivestreams/tck/SubscriberBlackboxVerificationTest.java @@ -11,17 +11,11 @@ package org.reactivestreams.tck; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; -import org.reactivestreams.tck.flow.support.TCKVerificationSupport; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; + +import org.reactivestreams.*; +import org.reactivestreams.tck.flow.support.*; +import org.testng.annotations.*; /** * Validates that the TCK's {@link org.reactivestreams.tck.SubscriberBlackboxVerification} fails with nice human readable errors. @@ -98,6 +92,166 @@ public void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublishe }, "Subscription::cancel MUST NOT be called from Subscriber::onError (Rule 2.3)!"); } + @Test + public void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_shouldPass_unrelatedCancelFromOnComplete() throws Throwable { + customSubscriberVerification(new Subscriber() { + @Override + public void onSubscribe(final Subscription s) { + // emulate unrelated calls by issuing them from a method named `onComplete` + new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + } + + @Override + public void onNext(Object t) { + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + s.cancel(); + } + }.onComplete(); + } + + @Override + public void onNext(Integer t) { + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + } + }).required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete(); + } + + @Test + public void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_shouldPass_unrelatedRequestFromOnComplete() throws Throwable { + customSubscriberVerification(new Subscriber() { + @Override + public void onSubscribe(final Subscription s) { + // emulate unrelated calls by issuing them from a method named `onComplete` + new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + } + + @Override + public void onNext(Object t) { + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + s.request(1); + } + }.onComplete(); + } + + @Override + public void onNext(Integer t) { + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + } + }).required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete(); + } + + @Test + public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_shouldPass_unrelatedCancelFromOnError() throws Throwable { + customSubscriberVerification(new Subscriber() { + @Override + public void onSubscribe(final Subscription s) { + // emulate unrelated calls by issuing them from a method named `onComplete` + new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + } + + @Override + public void onNext(Object t) { + } + + @Override + public void onError(Throwable t) { + s.cancel(); + } + + @Override + public void onComplete() { + } + }.onError(null); + } + + @Override + public void onNext(Integer t) { + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + } + }).required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError(); + } + + @Test + public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_shouldPass_unrelatedRequestFromOnError() throws Throwable { + customSubscriberVerification(new Subscriber() { + @Override + public void onSubscribe(final Subscription s) { + // emulate unrelated calls by issuing them from a method named `onComplete` + new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + } + + @Override + public void onNext(Object t) { + } + + @Override + public void onError(Throwable t) { + s.request(1); + } + + @Override + public void onComplete() { + } + }.onError(null); + } + + @Override + public void onNext(Integer t) { + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + } + }).required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError(); + } + @Test public void required_spec205_blackbox_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal_shouldFail() throws Throwable { requireTestFailure(new ThrowingRunnable() { diff --git a/tck/src/test/java/org/reactivestreams/tck/SubscriberWhiteboxVerificationTest.java b/tck/src/test/java/org/reactivestreams/tck/SubscriberWhiteboxVerificationTest.java index 0441e6ed..e7e9e6fd 100644 --- a/tck/src/test/java/org/reactivestreams/tck/SubscriberWhiteboxVerificationTest.java +++ b/tck/src/test/java/org/reactivestreams/tck/SubscriberWhiteboxVerificationTest.java @@ -159,6 +159,142 @@ public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnErro }, "Subscription::cancel MUST NOT be called from Subscriber::onError (Rule 2.3)!"); } + @Test + public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_shouldPass_unrelatedCancelFromOnComplete() throws Throwable { + customSubscriberVerification(new Function, Subscriber>() { + @Override + public Subscriber apply(WhiteboxSubscriberProbe probe) throws Throwable { + return new SimpleSubscriberWithProbe(probe) { + @Override + public void onSubscribe(final Subscription s) { + super.onSubscribe(s); + // emulate unrelated calls by issuing them from a method named `onComplete` + new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + } + + @Override + public void onNext(Object t) { + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + s.cancel(); + } + }.onComplete(); + } + }; + } + }).required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete(); + } + + @Test + public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_shouldPass_unrelatedRequestFromOnComplete() throws Throwable { + customSubscriberVerification(new Function, Subscriber>() { + @Override + public Subscriber apply(WhiteboxSubscriberProbe probe) throws Throwable { + return new SimpleSubscriberWithProbe(probe) { + @Override + public void onSubscribe(final Subscription s) { + super.onSubscribe(s); + // emulate unrelated calls by issuing them from a method named `onComplete` + new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + } + + @Override + public void onNext(Object t) { + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + s.request(1); + } + }.onComplete(); + } + }; + } + }).required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete(); + } + + @Test + public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_shouldPass_unrelatedCancelFromOnError() throws Throwable { + customSubscriberVerification(new Function, Subscriber>() { + @Override + public Subscriber apply(WhiteboxSubscriberProbe probe) throws Throwable { + return new SimpleSubscriberWithProbe(probe) { + @Override + public void onSubscribe(final Subscription s) { + super.onSubscribe(s); + // emulate unrelated calls by issuing them from a method named `onComplete` + new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + } + + @Override + public void onNext(Object t) { + } + + @Override + public void onError(Throwable t) { + s.cancel(); + } + + @Override + public void onComplete() { + } + }.onError(null); + } + }; + } + }).required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError(); + } + + @Test + public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_shouldPass_unrelatedRequestFromOnError() throws Throwable { + customSubscriberVerification(new Function, Subscriber>() { + @Override + public Subscriber apply(WhiteboxSubscriberProbe probe) throws Throwable { + return new SimpleSubscriberWithProbe(probe) { + @Override + public void onSubscribe(final Subscription s) { + super.onSubscribe(s); + // emulate unrelated calls by issuing them from a method named `onComplete` + new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + } + + @Override + public void onNext(Object t) { + } + + @Override + public void onError(Throwable t) { + s.request(1); + } + + @Override + public void onComplete() { + } + }.onError(null); + } + }; + } + }).required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError(); + } + @Test public void required_spec205_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal_shouldFail() throws Throwable { requireTestFailure(new ThrowingRunnable() {