/* * Copyright (c) 2011-Present VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package reactor.core.publisher; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import java.util.stream.Stream; import org.awaitility.Awaitility; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DynamicContainer; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import reactor.test.StepVerifier; import reactor.test.StepVerifierOptions; import reactor.test.subscriber.AssertSubscriber; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; import static org.junit.jupiter.api.DynamicTest.dynamicTest; /** * @author Simon Baslé */ class SinksTest { @Nested class MulticastNoWarmup { final Supplier<Sinks.StandaloneFluxSink<Integer>> supplier = Sinks::multicastNoWarmup; @TestFactory Stream<DynamicContainer> checkSemantics() { return Stream.of( expectMulticast(supplier), expectReplay(supplier, NONE), expectBufferingBeforeFirstSubscriber(supplier, NONE) ); } } @Nested class Multicast { //TODO Multicast has slightly different behavior with early onNext + onError : doesn't buffer elements for benefit of 1st subscriber //(this is a behavioral difference in EmitterProcessor) final Supplier<Sinks.StandaloneFluxSink<Integer>> supplier = Sinks::multicast; @TestFactory Stream<DynamicContainer> checkSemantics() { return Stream.of( expectMulticast(supplier), expectReplay(supplier, NONE), dynamicContainer("buffers all before 1st subscriber, except for errors", expectBufferingBeforeFirstSubscriber(supplier, ALL) .getChildren().filter(dn -> !dn.getDisplayName().equals("replayAndErrorFirstSubscriber"))) ); } @Test void noReplayBeforeFirstSubscriberIfEarlyError() { Sinks.StandaloneFluxSink<Integer> sink = supplier.get(); Flux<Integer> flux = sink.asFlux(); AssertSubscriber<Integer> first = AssertSubscriber.create(); sink.next(1).next(2).next(3).error(new IllegalStateException("boom")); flux.subscribe(first); first.assertNoValues().assertErrorMessage("boom"); } } @Nested class MulticastReplayAll { final Supplier<Sinks.StandaloneFluxSink<Integer>> supplier = Sinks::replayAll; @TestFactory Stream<DynamicContainer> checkSemantics() { return Stream.of( expectMulticast(supplier), expectReplay(supplier, ALL), expectBufferingBeforeFirstSubscriber(supplier, ALL) ); } } @Nested class MulticastReplayN { @TestFactory Stream<DynamicContainer> checkSemanticsSize5() { final int historySize = 5; final Supplier<Sinks.StandaloneFluxSink<Integer>> supplier = () -> Sinks.replay(historySize); return Stream.of( expectMulticast(supplier), expectReplay(supplier, historySize), expectBufferingBeforeFirstSubscriber(supplier, historySize) ); } @TestFactory Stream<DynamicContainer> checkSemanticsSize0() { final int historySize = 0; final Supplier<Sinks.StandaloneFluxSink<Integer>> supplier = () -> Sinks.replay(historySize); return Stream.of( expectMulticast(supplier), expectReplay(supplier, historySize), expectBufferingBeforeFirstSubscriber(supplier, historySize) ); } @TestFactory Stream<DynamicContainer> checkSemanticsSize100() { final int historySize = 100; final Supplier<Sinks.StandaloneFluxSink<Integer>> supplier = () -> Sinks.replay(historySize); return Stream.of( expectMulticast(supplier), expectReplay(supplier, historySize), expectBufferingBeforeFirstSubscriber(supplier, historySize) ); } } @Nested class Unicast { final Supplier<Sinks.StandaloneFluxSink<Integer>> supplier = Sinks::unicast; @TestFactory Stream<DynamicContainer> checkSemantics() { return Stream.of( expectUnicast(supplier), expectBufferingBeforeFirstSubscriber(supplier, ALL) ); } } @Nested class Promise { Sinks.StandaloneMonoSink<Integer> promise; Mono<Integer> mono; @BeforeEach void createPromise() { promise = Sinks.promise(); mono = promise.asMono(); } //TODO racing dual completions ? @Test void promiseIsCompletableOnlyOnce_emptyVsValued() { StepVerifier.create(mono) .then(() -> { promise.success(); promise.success(-1); }) .expectComplete() .verifyThenAssertThat() .hasDropped(-1); } @Test void promiseIsCompletableOnlyOnce_emptyVsError() { StepVerifier.create(mono) .then(() -> { promise.success(); promise.error(new IllegalStateException("boom")); }) .expectComplete() .verifyThenAssertThat() .hasDroppedErrorWithMessage("boom"); } @Test void promiseIsCompletableOnlyOnce_valuedVsEmpty() { StepVerifier.create(mono) .then(() -> { promise.success(1); promise.success(); }) .expectNext(1) .expectComplete() .verifyThenAssertThat() .hasNotDroppedElements(); } @Test void promiseIsCompletableOnlyOnce_valuedVsError() { StepVerifier.create(mono) .then(() -> { promise.success(1); promise.error(new IllegalStateException("boom")); }) .expectNext(1) .expectComplete() .verifyThenAssertThat() .hasDroppedErrorWithMessage("boom"); } @Test void promiseIsCompletableOnlyOnce_errorVsValued() { StepVerifier.create(mono) .then(() -> { promise.error(new IllegalStateException("boom")); promise.success(-1); }) .expectErrorMessage("boom") .verifyThenAssertThat() .hasDropped(-1); } @Test void promiseIsCompletableOnlyOnce_errorVsEmpty() { StepVerifier.create(mono) .then(() -> { promise.error(new IllegalStateException("boom")); promise.success(); }) .expectErrorMessage("boom") .verifyThenAssertThat() .hasNotDroppedElements() .hasNotDroppedErrors(); } @Test void canBeValuedEarly() { promise.success(1); StepVerifier.create(mono) .expectNext(1) .verifyComplete(); } @Test void canBeCompletedEarly() { promise.success(); StepVerifier.create(mono) .verifyComplete(); } @Test void canBeErroredEarly() { promise.error(new IllegalStateException("boom")); StepVerifier.create(mono) .verifyErrorMessage("boom"); } @Test void canBeValuedLate() { StepVerifier.create(mono) .expectSubscription() .expectNoEvent(Duration.ofMillis(100)) .then(() -> promise.success(1)) .expectNext(1) .verifyComplete(); } @Test void canBeCompletedLate() { StepVerifier.create(mono) .expectSubscription() .expectNoEvent(Duration.ofMillis(100)) .then(() -> promise.success()) .verifyComplete(); } @Test void canBeErroredLate() { StepVerifier.create(mono) .expectSubscription() .expectNoEvent(Duration.ofMillis(100)) .then(() -> promise.error(new IllegalStateException("boom"))) .verifyErrorMessage("boom"); } @Test void replaysValuedCompletionToLateSubscribersWithBackpressure() { promise.success(1); mono.subscribe(); //first subscriber StepVerifier.create(mono, StepVerifierOptions.create().scenarioName("second subscriber, no backpressure")) .expectNext(1) .verifyComplete(); StepVerifier.create(mono, StepVerifierOptions.create() .scenarioName("third subscriber, backpressure") .initialRequest(0)) .expectSubscription() .expectNoEvent(Duration.ofMillis(100)) .thenRequest(1) .expectNext(1) .verifyComplete(); } @Test void replaysEmptyCompletionToLateSubscribersEvenWithoutRequest() { promise.success(); mono.subscribe(); //first subscriber StepVerifier.create(mono, StepVerifierOptions.create().scenarioName("second subscriber, no backpressure")) .verifyComplete(); StepVerifier.create(mono, StepVerifierOptions.create() .scenarioName("third subscriber, 0 request") .initialRequest(0)) .expectSubscription() //notice no expectNoEvent / request here .verifyComplete(); } @Test void replaysErrorCompletionToLateSubscribers() { promise.error(new IllegalStateException("boom")); mono.subscribe(); //first subscriber StepVerifier.create(mono, StepVerifierOptions.create().scenarioName("second subscriber, no backpressure")) .verifyErrorMessage("boom"); StepVerifier.create(mono, StepVerifierOptions.create() .scenarioName("third subscriber, 0 request") .initialRequest(0)) .expectSubscription() //notice no expectNoEvent / request here .verifyErrorMessage("boom"); } } private static final int NONE = 0; private static final int ALL = Integer.MAX_VALUE; DynamicContainer expectMulticast(Supplier<Sinks.StandaloneFluxSink<Integer>> sinkSupplier) { return dynamicContainer("multicast", Stream.of( dynamicTest("fluxViewReturnsSameInstance", () -> { Sinks.StandaloneFluxSink<Integer> sink = sinkSupplier.get(); Flux<Integer> flux = sink.asFlux(); assertThat(flux).isSameAs(sink.asFlux()); }), dynamicTest("acceptsMoreThanOneSubscriber", () -> { Flux<Integer> flux = sinkSupplier.get().asFlux(); assertThatCode(() -> { flux.subscribe(); flux.subscribe(); }).doesNotThrowAnyException(); }), dynamicTest("honorsMultipleSubscribersBackpressure", () -> { Sinks.StandaloneFluxSink<Integer> sink = sinkSupplier.get(); Flux<Integer> flux = sink.asFlux(); ExecutorService es = Executors.newFixedThreadPool(2); try { CountDownLatch requestLatch = new CountDownLatch(2); final Future<?> f1 = es.submit(() -> { AssertSubscriber<Integer> test1 = AssertSubscriber.create(2); flux.subscribe(test1); test1.assertNoValues(); requestLatch.countDown(); test1.awaitAndAssertNextValues(1, 2); try { Awaitility.await().atMost(2, TimeUnit.SECONDS) .with().pollDelay(1, TimeUnit.SECONDS) .untilAsserted(() -> test1.assertValueCount(2)); } finally { test1.cancel(); } }); final Future<?> f2 = es.submit(() -> { AssertSubscriber<Integer> test2 = AssertSubscriber.create(1); flux.subscribe(test2); requestLatch.countDown(); test2.awaitAndAssertNextValues(1); try { Awaitility.await().atMost(2, TimeUnit.SECONDS) .with().pollDelay(1, TimeUnit.SECONDS) .untilAsserted(() -> test2.assertValueCount(1)); } finally { test2.cancel(); } }); requestLatch.await(1, TimeUnit.SECONDS); sink.next(1) .next(2) .next(3) .next(4) .complete(); f1.get(); f2.get(); } finally { es.shutdownNow(); } }) )); } DynamicContainer expectUnicast(Supplier<Sinks.StandaloneFluxSink<Integer>> sinkSupplier) { return dynamicContainer("unicast", Stream.of( dynamicTest("fluxViewReturnsSameInstance", () -> { Sinks.StandaloneFluxSink<Integer> sink = sinkSupplier.get(); Flux<Integer> flux = sink.asFlux(); assertThat(flux).isSameAs(sink.asFlux()); }), dynamicTest("acceptsOnlyOneSubscriber", () -> { Sinks.StandaloneFluxSink<Integer> sink = sinkSupplier.get(); Flux<Integer> flux = sink.asFlux(); sink.complete(); assertThatCode(flux::subscribe).doesNotThrowAnyException(); StepVerifier.create(flux) .verifyErrorSatisfies(e -> assertThat(e).hasMessageEndingWith("allows only a single Subscriber")); }), dynamicTest("honorsSubscriberBackpressure", () -> { Sinks.StandaloneFluxSink<Integer> sink = sinkSupplier.get(); Flux<Integer> flux = sink.asFlux(); ExecutorService es = Executors.newFixedThreadPool(2); try { CountDownLatch requestLatch = new CountDownLatch(1); final Future<?> future = es.submit(() -> { AssertSubscriber<Integer> test = AssertSubscriber.create(2); flux.subscribe(test); test.assertNoValues(); requestLatch.countDown(); test.awaitAndAssertNextValues(1, 2); try { Awaitility.await().atMost(2, TimeUnit.SECONDS) .with().pollDelay(1, TimeUnit.SECONDS) .untilAsserted(() -> test.assertValueCount(2)); } finally { test.cancel(); } }); requestLatch.await(1, TimeUnit.SECONDS); sink.next(1) .next(2) .next(3) .next(4) .complete(); future.get(); } finally { es.shutdownNow(); } }) )); } DynamicContainer expectReplay(Supplier<Sinks.StandaloneFluxSink<Integer>> sinkSupplier, int expectedReplay) { if (expectedReplay == NONE) { return dynamicContainer("no replay", Stream.of( dynamicTest("doesNotReplayToLateSubscribers", () -> { Sinks.StandaloneFluxSink<Integer> sink = sinkSupplier.get(); Flux<Integer> flux = sink.asFlux(); AssertSubscriber<Integer> s1 = AssertSubscriber.create(); AssertSubscriber<Integer> s2 = AssertSubscriber.create(); flux.subscribe(s1); sink.next(1).next(2).next(3); s1.assertValues(1, 2, 3); flux.subscribe(s2); s2.assertNoValues().assertNotComplete(); sink.complete(); s1.assertValueCount(3).assertComplete(); s2.assertNoValues().assertComplete(); }), dynamicTest("immediatelyCompleteLateSubscriber", () -> { Sinks.StandaloneFluxSink<Integer> sink = sinkSupplier.get(); Flux<Integer> flux = sink.asFlux(); flux.subscribe(); //first subscriber AssertSubscriber<Integer> late = AssertSubscriber.create(); sink.next(1).complete(); flux.subscribe(late); late.assertNoValues().assertComplete(); }), dynamicTest("immediatelyErrorLateSubscriber", () -> { Sinks.StandaloneFluxSink<Integer> sink = sinkSupplier.get(); Flux<Integer> flux = sink.asFlux(); flux.onErrorReturn(-1).subscribe(); //first subscriber, ignore errors AssertSubscriber<Integer> late = AssertSubscriber.create(); sink.next(1).error(new IllegalStateException("boom")); flux.subscribe(late); late.assertNoValues().assertErrorMessage("boom"); }) )); } else if (expectedReplay == ALL) { return dynamicContainer("replays all", Stream.of( dynamicTest("doesReplayAllToLateSubscribers", () -> { Sinks.StandaloneFluxSink<Integer> sink = sinkSupplier.get(); Flux<Integer> flux = sink.asFlux(); AssertSubscriber<Integer> s1 = AssertSubscriber.create(); AssertSubscriber<Integer> s2 = AssertSubscriber.create(); flux.subscribe(s1); sink.next(1).next(2).next(3); s1.assertValues(1, 2, 3); flux.subscribe(s2); s2.assertValues(1, 2, 3).assertNotComplete(); sink.complete(); s1.assertValueCount(3).assertComplete(); s2.assertValues(1, 2, 3).assertComplete(); }), dynamicTest("immediatelyCompleteLateSubscriber", () -> { Sinks.StandaloneFluxSink<Integer> sink = sinkSupplier.get(); Flux<Integer> flux = sink.asFlux(); }), dynamicTest("immediatelyErrorLateSubscriber", () -> { Sinks.StandaloneFluxSink<Integer> sink = sinkSupplier.get(); Flux<Integer> flux = sink.asFlux(); }) )); } else { return dynamicContainer("replays " + expectedReplay, Stream.of( dynamicTest("doesReplay" + expectedReplay + "ToLateSubscribers", () -> { Sinks.StandaloneFluxSink<Integer> sink = sinkSupplier.get(); Flux<Integer> flux = sink.asFlux(); AssertSubscriber<Integer> s1 = AssertSubscriber.create(); AssertSubscriber<Integer> s2 = AssertSubscriber.create(); flux.subscribe(s1); List<Integer> expected = new ArrayList<>(); for (int i = 0; i < expectedReplay + 10; i++) { sink.next(i); if (i >= 10) expected.add(i); } s1.assertValueCount(expectedReplay + 10); flux.subscribe(s2); s2.assertValueSequence(expected).assertNotComplete(); sink.complete(); s1.assertValueCount(expectedReplay + 10).assertComplete(); s2.assertValueSequence(expected).assertComplete(); }), dynamicTest("replay" + expectedReplay + "AndCompleteLateSubscriber", () -> { Sinks.StandaloneFluxSink<Integer> sink = sinkSupplier.get(); Flux<Integer> flux = sink.asFlux(); flux.subscribe(); //first AssertSubscriber<Integer> late = AssertSubscriber.create(); List<Integer> expected = new ArrayList<>(); for (int i = 0; i < expectedReplay + 10; i++) { sink.next(i); if (i >= 10) expected.add(i); } sink.complete(); flux.subscribe(late); late.assertValueSequence(expected).assertComplete(); }), dynamicTest("replay" + expectedReplay + "AndErrorLateSubscriber", () -> { Sinks.StandaloneFluxSink<Integer> sink = sinkSupplier.get(); Flux<Integer> flux = sink.asFlux(); flux.onErrorReturn(-1).subscribe(); //first subscriber, ignore errors AssertSubscriber<Integer> late = AssertSubscriber.create(); List<Integer> expected = new ArrayList<>(); for (int i = 0; i < expectedReplay + 10; i++) { sink.next(i); if (i >= 10) expected.add(i); } sink.error(new IllegalStateException("boom")); flux.subscribe(late); late.assertValueSequence(expected).assertErrorMessage("boom"); }) )); } } DynamicContainer expectBufferingBeforeFirstSubscriber(Supplier<Sinks.StandaloneFluxSink<Integer>> sinkSupplier, int expectedBuffering) { if (expectedBuffering == NONE) { return dynamicContainer("no buffering before 1st subscriber", Stream.of( dynamicTest("doesNotBufferBeforeFirstSubscriber", () -> { Sinks.StandaloneFluxSink<Integer> sink = sinkSupplier.get(); Flux<Integer> flux = sink.asFlux(); AssertSubscriber<Integer> first = AssertSubscriber.create(); sink.next(1).next(2).next(3); flux.subscribe(first); first.assertNoValues().assertNotComplete(); sink.complete(); first.assertNoValues().assertComplete(); }), dynamicTest("immediatelyCompleteFirstSubscriber", () -> { Sinks.StandaloneFluxSink<Integer> sink = sinkSupplier.get(); Flux<Integer> flux = sink.asFlux(); AssertSubscriber<Integer> first = AssertSubscriber.create(); sink.next(1).complete(); flux.subscribe(first); first.assertNoValues().assertComplete(); }), dynamicTest("immediatelyErrorFirstSubscriber", () -> { Sinks.StandaloneFluxSink<Integer> sink = sinkSupplier.get(); Flux<Integer> flux = sink.asFlux(); AssertSubscriber<Integer> first = AssertSubscriber.create(); sink.next(1).error(new IllegalStateException("boom")); flux.subscribe(first); first.assertNoValues().assertErrorMessage("boom"); }) )); } else if (expectedBuffering == ALL) { return dynamicContainer("buffers all before 1st subscriber", Stream.of( dynamicTest("doesBufferBeforeFirstSubscriber", () -> { Sinks.StandaloneFluxSink<Integer> sink = sinkSupplier.get(); Flux<Integer> flux = sink.asFlux(); AssertSubscriber<Integer> first = AssertSubscriber.create(); sink.next(1).next(2).next(3); flux.subscribe(first); first.assertValues(1, 2, 3).assertNotComplete(); sink.complete(); first.assertComplete(); }), dynamicTest("replayAndCompleteFirstSubscriber", () -> { Sinks.StandaloneFluxSink<Integer> sink = sinkSupplier.get(); Flux<Integer> flux = sink.asFlux(); AssertSubscriber<Integer> first = AssertSubscriber.create(); sink.next(1).complete(); flux.subscribe(first); first.assertValues(1).assertComplete(); }), dynamicTest("replayAndErrorFirstSubscriber", () -> { Sinks.StandaloneFluxSink<Integer> sink = sinkSupplier.get(); Flux<Integer> flux = sink.asFlux(); AssertSubscriber<Integer> first = AssertSubscriber.create(); sink.next(1).next(2).next(3).error(new IllegalStateException("boom")); flux.subscribe(first); first.assertValues(1, 2, 3).assertErrorMessage("boom"); }) )); } else { return dynamicContainer("buffers " + expectedBuffering + " before 1st subscriber", Stream.of( dynamicTest("doesBuffer" + expectedBuffering + "BeforeFirstSubscriber", () -> { Sinks.StandaloneFluxSink<Integer> sink = sinkSupplier.get(); Flux<Integer> flux = sink.asFlux(); AssertSubscriber<Integer> first = AssertSubscriber.create(); List<Integer> expected = new ArrayList<>(); for (int i = 0; i < 10 + expectedBuffering; i++) { sink.next(i); if (i >= 10) { expected.add(i); } } flux.subscribe(first); first.assertValueSequence(expected).assertNotComplete(); sink.complete(); first.assertValueSequence(expected).assertComplete(); }), dynamicTest("replayLimitedHistoryAndCompleteFirstSubscriber", () -> { Sinks.StandaloneFluxSink<Integer> sink = sinkSupplier.get(); Flux<Integer> flux = sink.asFlux(); AssertSubscriber<Integer> first = AssertSubscriber.create(); List<Integer> expected = new ArrayList<>(); for (int i = 0; i < 10 + expectedBuffering; i++) { sink.next(i); if (i >= 10) { expected.add(i); } } sink.complete(); flux.subscribe(first); first.assertValueSequence(expected).assertComplete(); }), dynamicTest("replayLimitedHistoryAndErrorFirstSubscriber", () -> { Sinks.StandaloneFluxSink<Integer> sink = sinkSupplier.get(); Flux<Integer> flux = sink.asFlux(); AssertSubscriber<Integer> first = AssertSubscriber.create(); List<Integer> expected = new ArrayList<>(); for (int i = 0; i < 10 + expectedBuffering; i++) { sink.next(i); if (i >= 10) { expected.add(i); } } sink.error(new IllegalStateException("boom")); flux.subscribe(first); first.assertValueSequence(expected).assertErrorMessage("boom"); }) )); } } }