Search in sources :

Example 1 with Latch

use of org.reactivestreams.tck.TestEnvironment.Latch in project reactive-streams-jvm by reactive-streams.

the class PublisherVerification method optional_spec104_mustSignalOnErrorWhenFails.

// Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.4
@Override
@Test
public void optional_spec104_mustSignalOnErrorWhenFails() throws Throwable {
    try {
        whenHasErrorPublisherTest(new PublisherTestRun<T>() {

            @Override
            public void run(final Publisher<T> pub) throws InterruptedException {
                final Latch onErrorlatch = new Latch(env);
                final Latch onSubscribeLatch = new Latch(env);
                pub.subscribe(new TestEnvironment.TestSubscriber<T>(env) {

                    @Override
                    public void onSubscribe(Subscription subs) {
                        onSubscribeLatch.assertOpen("Only one onSubscribe call expected");
                        onSubscribeLatch.close();
                    }

                    @Override
                    public void onError(Throwable cause) {
                        onSubscribeLatch.assertClosed("onSubscribe should be called prior to onError always");
                        onErrorlatch.assertOpen(String.format("Error-state Publisher %s called `onError` twice on new Subscriber", pub));
                        onErrorlatch.close();
                    }
                });
                onSubscribeLatch.expectClose("Should have received onSubscribe");
                onErrorlatch.expectClose(String.format("Error-state Publisher %s did not call `onError` on new Subscriber", pub));
                env.verifyNoAsyncErrors();
            }
        });
    } catch (SkipException se) {
        throw se;
    } catch (Throwable ex) {
        // 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);
    }
}
Also used : Latch(org.reactivestreams.tck.TestEnvironment.Latch) SkipException(org.testng.SkipException) Subscription(org.reactivestreams.Subscription) Override(java.lang.Override) Test(org.testng.annotations.Test) Override(java.lang.Override)

Example 2 with Latch

use of org.reactivestreams.tck.TestEnvironment.Latch in project reactive-streams-jvm by reactive-streams.

the class PublisherVerification method stochastic_spec103_mustSignalOnMethodsSequentially.

// Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.3
@Override
@Test
public void stochastic_spec103_mustSignalOnMethodsSequentially() throws Throwable {
    final int iterations = 100;
    final int elements = 10;
    stochasticTest(iterations, new Function<Integer, Void>() {

        @Override
        public Void apply(final Integer runNumber) throws Throwable {
            activePublisherTest(elements, true, new PublisherTestRun<T>() {

                @Override
                public void run(Publisher<T> pub) throws Throwable {
                    final Latch completionLatch = new Latch(env);
                    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 (// requesting one more than we know are in the stream (some Publishers need this)
                            gotElements.incrementAndGet() <= elements)
                                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;
        }
    });
}
Also used : AtomicReference(java.util.concurrent.atomic.AtomicReference) Publisher(org.reactivestreams.Publisher) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) Latch(org.reactivestreams.tck.TestEnvironment.Latch) Subscription(org.reactivestreams.Subscription) Override(java.lang.Override) Test(org.testng.annotations.Test) Override(java.lang.Override)

Example 3 with Latch

use of org.reactivestreams.tck.TestEnvironment.Latch in project reactive-streams-jvm by reactive-streams.

the class PublisherVerification method required_spec303_mustNotAllowUnboundedRecursion.

// Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#3.3
@Override
@Test
public void required_spec303_mustNotAllowUnboundedRecursion() throws Throwable {
    final long oneMoreThanBoundedLimit = boundedDepthOfOnNextAndRequestRecursion() + 1;
    activePublisherTest(oneMoreThanBoundedLimit, false, new PublisherTestRun<T>() {

        @Override
        public void run(Publisher<T> pub) throws Throwable {
            final ThreadLocal<Long> stackDepthCounter = new ThreadLocal<Long>() {

                @Override
                protected Long initialValue() {
                    return 0L;
                }
            };
            final Latch runCompleted = new Latch(env);
            final ManualSubscriber<T> sub = new ManualSubscriberWithSubscriptionSupport<T>(env) {

                // counts the number of signals received, used to break out from possibly infinite request/onNext loops
                long signalsReceived = 0L;

                @Override
                public void onNext(T element) {
                    // NOT calling super.onNext as this test only cares about stack depths, not the actual values of elements
                    // which also simplifies this test as we do not have to drain the test buffer, which would otherwise be in danger of overflowing
                    signalsReceived += 1;
                    stackDepthCounter.set(stackDepthCounter.get() + 1);
                    env.debug(String.format("%s(recursion depth: %d)::onNext(%s)", this, stackDepthCounter.get(), element));
                    final long callsUntilNow = stackDepthCounter.get();
                    if (callsUntilNow > boundedDepthOfOnNextAndRequestRecursion()) {
                        env.flop(String.format("Got %d onNext calls within thread: %s, yet expected recursive bound was %d", callsUntilNow, Thread.currentThread(), boundedDepthOfOnNextAndRequestRecursion()));
                        // stop the recursive call chain
                        runCompleted.close();
                        return;
                    } else if (signalsReceived >= oneMoreThanBoundedLimit) {
                        // since max number of signals reached, and recursion depth not exceeded, we judge this as a success and
                        // stop the recursive call chain
                        runCompleted.close();
                        return;
                    }
                    // request more right away, the Publisher must break the recursion
                    subscription.value().request(1);
                    stackDepthCounter.set(stackDepthCounter.get() - 1);
                }

                @Override
                public void onComplete() {
                    super.onComplete();
                    runCompleted.close();
                }

                @Override
                public void onError(Throwable cause) {
                    super.onError(cause);
                    runCompleted.close();
                }
            };
            try {
                env.subscribe(pub, sub);
                // kick-off the `request -> onNext -> request -> onNext -> ...`
                sub.request(1);
                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();
            }
        }
    });
}
Also used : ManualSubscriber(org.reactivestreams.tck.TestEnvironment.ManualSubscriber) Latch(org.reactivestreams.tck.TestEnvironment.Latch) Override(java.lang.Override) Test(org.testng.annotations.Test) Override(java.lang.Override)

Example 4 with Latch

use of org.reactivestreams.tck.TestEnvironment.Latch in project reactive-streams-jvm by reactive-streams.

the class PublisherVerification method required_spec109_mustIssueOnSubscribeForNonNullSubscriber.

// Verifies rule: https://github.com/reactive-streams/reactive-streams-jvm#1.9
@Override
@Test
public void required_spec109_mustIssueOnSubscribeForNonNullSubscriber() throws Throwable {
    activePublisherTest(0, false, new PublisherTestRun<T>() {

        @Override
        public void run(Publisher<T> pub) throws Throwable {
            final Latch onSubscribeLatch = new Latch(env);
            pub.subscribe(new Subscriber<T>() {

                @Override
                public void onError(Throwable cause) {
                    onSubscribeLatch.assertClosed("onSubscribe should be called prior to onError always");
                }

                @Override
                public void onSubscribe(Subscription subs) {
                    onSubscribeLatch.assertOpen("Only one onSubscribe call expected");
                    onSubscribeLatch.close();
                }

                @Override
                public void onNext(T elem) {
                    onSubscribeLatch.assertClosed("onSubscribe should be called prior to onNext always");
                }

                @Override
                public void onComplete() {
                    onSubscribeLatch.assertClosed("onSubscribe should be called prior to onComplete always");
                }
            });
            onSubscribeLatch.expectClose("Should have received onSubscribe");
            env.verifyNoAsyncErrorsNoDelay();
        }
    });
}
Also used : Subscriber(org.reactivestreams.Subscriber) ManualSubscriber(org.reactivestreams.tck.TestEnvironment.ManualSubscriber) Latch(org.reactivestreams.tck.TestEnvironment.Latch) Subscription(org.reactivestreams.Subscription) Override(java.lang.Override) Test(org.testng.annotations.Test) Override(java.lang.Override)

Aggregations

Override (java.lang.Override)4 Latch (org.reactivestreams.tck.TestEnvironment.Latch)4 Test (org.testng.annotations.Test)4 Subscription (org.reactivestreams.Subscription)3 ManualSubscriber (org.reactivestreams.tck.TestEnvironment.ManualSubscriber)2 AtomicInteger (java.util.concurrent.atomic.AtomicInteger)1 AtomicReference (java.util.concurrent.atomic.AtomicReference)1 Publisher (org.reactivestreams.Publisher)1 Subscriber (org.reactivestreams.Subscriber)1 SkipException (org.testng.SkipException)1