/*
* Copyright (c) 2011-2017 Pivotal Software Inc, 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
*
* http://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.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Assert;
import org.junit.Test;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import reactor.core.Exceptions;
import reactor.core.Scannable;
import reactor.core.scheduler.Schedulers;
import reactor.test.StepVerifier;
import reactor.test.publisher.TestPublisher;
import reactor.test.subscriber.AssertSubscriber;
import reactor.util.concurrent.QueueSupplier;
import static org.assertj.core.api.Assertions.assertThat;
public class FluxFlatMapTest {
/*@Test
public void constructors() {
ConstructorTestBuilder ctb = new ConstructorTestBuilder(FluxFlatMap.class);
ctb.addRef("source", Flux.never());
ctb.addRef("mapper", (Function<Object, Publisher<Object>>)v -> Flux.never());
ctb.addInt("prefetch", 1, Integer.MAX_VALUE);
ctb.addInt("maxConcurrency", 1, Integer.MAX_VALUE);
ctb.addRef("mainQueueSupplier", (Supplier<Queue<Object>>)() -> new ConcurrentLinkedQueue<>());
ctb.addRef("innerQueueSupplier", (Supplier<Queue<Object>>)() -> new ConcurrentLinkedQueue<>());
ctb.test();
}*/
@Test
public void normal() {
AssertSubscriber<Integer> ts = AssertSubscriber.create();
Flux.range(1, 1000).flatMap(v -> Flux.range(v, 2)).subscribe(ts);
ts.assertValueCount(2000)
.assertNoError()
.assertComplete();
}
@Test
public void normalBackpressured() {
AssertSubscriber<Integer> ts = AssertSubscriber.create(0);
Flux.range(1, 1000).flatMap(v -> Flux.range(v, 2)).subscribe(ts);
ts.assertNoValues()
.assertNoError()
.assertNotComplete();
ts.request(1000);
ts.assertValueCount(1000)
.assertNoError()
.assertNotComplete();
ts.request(1000);
ts.assertValueCount(2000)
.assertNoError()
.assertComplete();
}
@Test
public void mainError() {
AssertSubscriber<Integer> ts = AssertSubscriber.create(0);
Flux.<Integer>error(new RuntimeException("forced failure"))
.flatMap(v -> Flux.just(v)).subscribe(ts);
ts.assertNoValues()
.assertError(RuntimeException.class)
.assertErrorWith( e -> Assert.assertTrue(e.getMessage().contains("forced failure")))
.assertNotComplete();
}
@Test
public void innerError() {
AssertSubscriber<Object> ts = AssertSubscriber.create(0);
Flux.just(1).flatMap(v -> Flux.error(new RuntimeException("forced failure"))).subscribe(ts);
ts.assertNoValues()
.assertError(RuntimeException.class)
.assertErrorWith( e -> Assert.assertTrue(e.getMessage().contains("forced failure")))
.assertNotComplete();
}
@Test
public void normalQueueOpt() {
AssertSubscriber<Integer> ts = AssertSubscriber.create();
Flux.range(1, 1000).flatMap(v -> Flux.just(v, v + 1)).subscribe(ts);
ts.assertValueCount(2000)
.assertNoError()
.assertComplete();
}
@Test
public void normalQueueOptBackpressured() {
AssertSubscriber<Integer> ts = AssertSubscriber.create(0);
Flux.range(1, 1000).flatMap(v -> Flux.just(v, v + 1)).subscribe(ts);
ts.assertNoValues()
.assertNoError()
.assertNotComplete();
ts.request(1000);
ts.assertValueCount(1000)
.assertNoError()
.assertNotComplete();
ts.request(1000);
ts.assertValueCount(2000)
.assertNoError()
.assertComplete();
}
@Test
public void nullValue() {
AssertSubscriber<Integer> ts = AssertSubscriber.create();
Flux.range(1, 1000).flatMap(v -> Flux.just((Integer)null)).subscribe(ts);
ts.assertNoValues()
.assertError(NullPointerException.class)
.assertNotComplete();
}
@Test
public void mainEmpty() {
AssertSubscriber<Integer> ts = AssertSubscriber.create(0);
Flux.<Integer>empty().flatMap(v -> Flux.just(v)).subscribe(ts);
ts.assertNoValues()
.assertNoError()
.assertComplete();
}
@Test
public void innerEmpty() {
AssertSubscriber<Integer> ts = AssertSubscriber.create(0);
Flux.range(1, 1000).flatMap(v -> Flux.<Integer>empty()).subscribe(ts);
ts.assertNoValues()
.assertNoError()
.assertComplete();
}
@Test
public void flatMapOfJust() {
AssertSubscriber<Integer> ts = AssertSubscriber.create();
Flux.range(1, 1000).flatMap(Flux::just).subscribe(ts);
ts.assertValueCount(1000)
.assertNoError()
.assertComplete();
}
@Test
public void flatMapOfMixed() {
AssertSubscriber<Integer> ts = AssertSubscriber.create();
Flux.range(1, 1000).flatMap(
v -> v % 2 == 0 ? Flux.just(v) : Flux.fromIterable(Arrays.asList(v)))
.subscribe(ts);
ts.assertValueCount(1000)
.assertNoError()
.assertComplete();
}
@Test
public void flatMapOfMixedBackpressured() {
AssertSubscriber<Integer> ts = AssertSubscriber.create(0);
Flux.range(1, 1000).flatMap(v -> v % 2 == 0 ? Flux.just(v) : Flux.fromIterable(Arrays.asList(v))).subscribe(ts);
ts.assertNoValues()
.assertNoError()
.assertNotComplete();
ts.request(500);
ts.assertValueCount(500)
.assertNoError()
.assertNotComplete();
ts.request(500);
ts.assertValueCount(1000)
.assertNoError()
.assertComplete();
}
@Test
public void flatMapOfMixedBackpressured1() {
AssertSubscriber<Integer> ts = AssertSubscriber.create(0);
Flux.range(1, 1000).flatMap(v -> v % 2 == 0 ? Flux.just(v) : Flux.fromIterable(Arrays.asList(v))).subscribe(ts);
ts.assertNoValues()
.assertNoError()
.assertNotComplete();
ts.request(500);
ts.assertValueCount(500)
.assertNoError()
.assertNotComplete();
ts.request(501);
ts.assertValueCount(1000)
.assertNoError()
.assertComplete();
}
@Test
public void flatMapOfJustBackpressured() {
AssertSubscriber<Integer> ts = AssertSubscriber.create(0);
Flux.range(1, 1000).flatMap(Flux::just).subscribe(ts);
ts.assertNoValues()
.assertNoError()
.assertNotComplete();
ts.request(500);
ts.assertValueCount(500)
.assertNoError()
.assertNotComplete();
ts.request(500);
ts.assertValueCount(1000)
.assertNoError()
.assertComplete();
}
@Test
public void flatMapOfJustBackpressured1() {
AssertSubscriber<Integer> ts = AssertSubscriber.create(0);
Flux.range(1, 1000).flatMap(Flux::just).subscribe(ts);
ts.assertNoValues()
.assertNoError()
.assertNotComplete();
ts.request(500);
ts.assertValueCount(500)
.assertNoError()
.assertNotComplete();
ts.request(501);
ts.assertValueCount(1000)
.assertNoError()
.assertComplete();
}
@Test
public void testMaxConcurrency1() {
AssertSubscriber<Integer> ts = AssertSubscriber.create();
Flux.range(1, 1_000_000).flatMap(Flux::just, 1, 32).subscribe(ts);
ts.assertValueCount(1_000_000)
.assertNoError()
.assertComplete();
}
@Test
public void testMaxConcurrency2() {
AssertSubscriber<Integer> ts = AssertSubscriber.create();
Flux.range(1, 1_000_000).flatMap(Flux::just, 64).subscribe(ts);
ts.assertValueCount(1_000_000)
.assertNoError()
.assertComplete();
}
@Test
public void singleSubscriberOnly() {
AssertSubscriber<Integer> ts = AssertSubscriber.create();
AtomicInteger emission = new AtomicInteger();
Flux<Integer> source = Flux.range(1, 2).doOnNext(v -> emission.getAndIncrement());
EmitterProcessor<Integer> source1 = EmitterProcessor.create();
EmitterProcessor<Integer> source2 = EmitterProcessor.create();
source.flatMap(v -> v == 1 ? source1 : source2, 1, 32).subscribe(ts);
Assert.assertEquals(1, emission.get());
ts.assertNoValues()
.assertNoError()
.assertNotComplete();
Assert.assertTrue("source1 no subscribers?", source1.downstreamCount() != 0);
Assert.assertFalse("source2 has subscribers?", source2.downstreamCount() != 0);
source1.onNext(1);
source2.onNext(10);
source1.onComplete();
source2.onNext(2);
source2.onComplete();
ts.assertValues(1, 2)
.assertNoError()
.assertComplete();
}
@Test
public void flatMapUnbounded() {
AssertSubscriber<Integer> ts = AssertSubscriber.create();
AtomicInteger emission = new AtomicInteger();
Flux<Integer> source = Flux.range(1, 1000).doOnNext(v -> emission.getAndIncrement());
EmitterProcessor<Integer> source1 = EmitterProcessor.create();
EmitterProcessor<Integer> source2 = EmitterProcessor.create();
source.flatMap(v -> v == 1 ? source1 : source2, Integer.MAX_VALUE, 32).subscribe(ts);
Assert.assertEquals(1000, emission.get());
ts.assertNoValues()
.assertNoError()
.assertNotComplete();
Assert.assertTrue("source1 no subscribers?", source1.downstreamCount() != 0);
Assert.assertTrue("source2 no subscribers?", source2.downstreamCount() != 0);
source1.onNext(1);
source1.onComplete();
source2.onNext(2);
source2.onComplete();
ts.assertValueCount(1000)
.assertNoError()
.assertComplete();
}
@Test
public void syncFusionIterable() {
AssertSubscriber<Integer> ts = AssertSubscriber.create();
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
list.add(i);
}
Flux.range(1, 1000).flatMap(v -> Flux.fromIterable(list)).subscribe(ts);
ts.assertValueCount(1_000_000)
.assertNoError()
.assertComplete();
}
@Test
public void syncFusionRange() {
AssertSubscriber<Integer> ts = AssertSubscriber.create();
Flux.range(1, 1000).flatMap(v -> Flux.range(v, 1000)).subscribe(ts);
ts.assertValueCount(1_000_000)
.assertNoError()
.assertComplete();
}
@Test
public void syncFusionArray() {
AssertSubscriber<Integer> ts = AssertSubscriber.create();
Integer[] array = new Integer[1000];
Arrays.fill(array, 777);
Flux.range(1, 1000).flatMap(v -> Flux.fromArray(array)).subscribe(ts);
ts.assertValueCount(1_000_000)
.assertNoError()
.assertComplete();
}
@Test
public void innerMapSyncFusion() {
AssertSubscriber<Integer> ts = AssertSubscriber.create();
Flux.range(1, 1000).flatMap(v -> Flux.range(1, 1000).map(w -> w + 1)).subscribe(ts);
ts.assertValueCount(1_000_000)
.assertNoError()
.assertComplete();
}
@Test
public void defaultPrefetch() {
assertThat(Flux.just(1, 2, 3)
.flatMap(Flux::just)
.getPrefetch()).isEqualTo(QueueSupplier.XS_BUFFER_SIZE);
}
@Test(expected = IllegalArgumentException.class)
public void failMaxConcurrency() {
Flux.just(1, 2, 3)
.flatMap(Flux::just, -1);
}
@Test(expected = IllegalArgumentException.class)
public void failPrefetch() {
Flux.just(1, 2, 3)
.flatMap(Flux::just, 128, -1);
}
@Test
public void failCallable() {
StepVerifier.create(Flux.just(1, 2, 3)
.flatMap(d -> Mono.fromCallable(() -> {
throw new Exception("test");
})))
.verifyErrorMessage("test");
}
@Test
public void failMap() {
StepVerifier.create(Flux.just(1, 2, 3)
.flatMap(d -> {
throw new RuntimeException("test");
}))
.verifyErrorMessage("test");
}
@Test
public void failPublisher() {
StepVerifier.create(Flux.just(1, 2, 3)
.flatMap(d -> Flux.error(new Exception("test"))))
.verifyErrorMessage("test");
}
@Test
public void failPublisherDelay() {
StepVerifier.create(Flux.just(1, 2, 3)
.flatMapDelayError(d -> Flux.error(new Exception("test")),
1,
1))
.verifyErrorMessage("Multiple exceptions");
}
@Test
public void completePublisherDelayCancel() {
StepVerifier.create(Flux.just(1, 2, 3)
.flatMapDelayError(d -> Flux.<Integer>empty().hide(),
1,
1))
.thenCancel()
.verify();
}
@Test
public void completePublisherDelay() {
StepVerifier.create(Flux.just(1, 2, 3)
.flatMapDelayError(d -> Flux.<Integer>empty().hide(),
1,
1))
.verifyComplete();
}
@Test
public void failNull() {
StepVerifier.create(Flux.just(1, 2, 3)
.flatMap(d -> null))
.verifyError(NullPointerException.class);
}
@Test
public void failScalarCallable() {
StepVerifier.create(Mono.fromCallable(() -> {
throw new Exception("test");
})
.flatMapMany(Flux::just))
.verifyErrorMessage("test");
}
@Test
public void failScalarMap() {
StepVerifier.create(Mono.just(1)
.flatMapMany(f -> {
throw new RuntimeException("test");
}))
.verifyErrorMessage("test");
}
@Test
public void failScalarMapNull() {
StepVerifier.create(Mono.just(1)
.flatMapMany(f -> null))
.verifyError(NullPointerException.class);
}
@Test
public void failScalarMapCallableError() {
StepVerifier.create(Mono.just(1)
.flatMapMany(f -> Mono.fromCallable(() -> {
throw new Exception("test");
})))
.verifyErrorMessage("test");
}
@Test
public void failMapCallableNullError() {
StepVerifier.create(Mono.just(1)
.flatMapMany(f -> Mono.fromCallable(() -> null)))
.verifyError(NullPointerException.class);
}
@Test
public void prematureScalarMapCallableNullComplete() {
StepVerifier.create(Mono.just(1)
.flatMapMany(f -> Mono.empty()))
.verifyComplete();
}
@Test
public void prematureScalarMapCallableJust() {
StepVerifier.create(Mono.just(1)
.flatMapMany(f -> Mono.fromCallable(() -> 2)))
.expectNext(2)
.verifyComplete();
}
@Test
public void prematureCompleteMaxPrefetch() {
StepVerifier.create(Flux.just(1, 2, 3)
.flatMap(f -> Flux.empty(), Integer.MAX_VALUE))
.verifyComplete();
}
@Test
public void prematureCancel() {
StepVerifier.create(Flux.just(1, 2, 3)
.flatMap(Flux::just))
.expectNext(1, 2, 3)
.thenCancel()
.verify();
}
@Test
public void prematureCancel2() {
StepVerifier.create(Flux.range(1, 10000)
.flatMap(Flux::just, 2)
.cancelOn(Schedulers.single()), 1)
.expectNext(1)
.thenRequest(2)
.expectNext(2, 3)
.thenCancel()
.verify();
}
@Test //FIXME use Violation.NO_CLEANUP_ON_TERMINATE
public void failNextOnTerminated() {
UnicastProcessor<Integer> up = UnicastProcessor.create();
Hooks.onNextDropped(c -> {
assertThat(c).isEqualTo(2);
});
StepVerifier.create(up.flatMap(Flux::just))
.then(() -> {
up.onNext(1);
Subscriber<? super Integer> a = up.actual;
up.onComplete();
a.onNext(2);
})
.expectNext(1)
.verifyComplete();
Hooks.resetOnNextDropped();
}
@Test
public void ignoreDoubleComplete() {
StepVerifier.create(Flux.from(s -> {
s.onSubscribe(Operators.emptySubscription());
s.onComplete();
s.onComplete();
}).flatMap(Flux::just))
.verifyComplete();
}
@Test
public void ignoreConcurrentHiddenComplete() {
StepVerifier.create(Flux.from(s -> {
s.onSubscribe(Operators.emptySubscription());
s.onNext(1);
}).flatMap(f -> Flux.just(f).hide()))
.thenCancel()
.verify();
}
@Test
public void ignoreDoubleOnSubscribe() {
StepVerifier.create(Flux.from(s -> {
s.onSubscribe(Operators.emptySubscription());
s.onSubscribe(Operators.emptySubscription());
s.onComplete();
}).flatMap(Flux::just))
.verifyComplete();
}
@Test
public void ignoreDoubleOnSubscribeInner() {
StepVerifier.create(Flux.just(1).hide()
.flatMap(f -> Flux.from(s -> {
s.onSubscribe(Operators.emptySubscription());
s.onSubscribe(Operators.emptySubscription());
s.onComplete();
})))
.verifyComplete();
}
@Test
public void failDoubleError() {
try {
StepVerifier.create(Flux.from(s -> {
s.onSubscribe(Operators.emptySubscription());
s.onError(new Exception("test"));
s.onError(new Exception("test2"));
}).flatMap(Flux::just))
.verifyErrorMessage("test");
Assert.fail();
}
catch (Exception e) {
assertThat(Exceptions.unwrap(e)).hasMessage("test2");
}
}
@Test //FIXME use Violation.NO_CLEANUP_ON_TERMINATE
public void failDoubleErrorTerminated() {
try {
StepVerifier.create(Flux.from(s -> {
s.onSubscribe(Operators.emptySubscription());
Exceptions.terminate(FluxFlatMap.FlatMapMain.ERROR, (FluxFlatMap.FlatMapMain) s);
s.onError(new Exception("test"));
}).flatMap(Flux::just))
.verifyErrorMessage("test");
Assert.fail();
}
catch (Exception e) {
assertThat(Exceptions.unwrap(e)).hasMessage("test");
}
}
@Test //FIXME use Violation.NO_CLEANUP_ON_TERMINATE
public void failDoubleErrorTerminatedInner() {
try {
StepVerifier.create(Flux.just(1).hide().flatMap(f -> Flux.from(s -> {
s.onSubscribe(Operators.emptySubscription());
Exceptions.terminate(FluxFlatMap.FlatMapMain.ERROR,( (FluxFlatMap
.FlatMapInner) s).parent);
s.onError(new Exception("test"));
})))
.verifyErrorMessage("test");
Assert.fail();
}
catch (Exception e) {
assertThat(Exceptions.unwrap(e)).hasMessage("test");
}
}
@Test
public void failOverflowScalar() {
TestPublisher<Integer> ts =
TestPublisher.createNoncompliant(TestPublisher.Violation.REQUEST_OVERFLOW);
StepVerifier.create(ts.flux()
.flatMap(Flux::just, 1), 0)
.then(() -> ts.emit(1, 2))
.verifyErrorMatches(Exceptions::isOverflow);
}
@Test
public void failOverflow() {
TestPublisher<Integer> ts =
TestPublisher.createNoncompliant(TestPublisher.Violation.REQUEST_OVERFLOW);
StepVerifier.create(Flux.just(1)
.hide()
.flatMap(f -> ts, 2, 1), 0)
.then(() -> ts.emit(1, 2))
.verifyErrorMatches(Exceptions::isOverflow);
}
@Test
@SuppressWarnings("unchecked")
public void failOverflowCancelledWhileActive() {
TestPublisher<Integer> ts =
TestPublisher.createNoncompliant(TestPublisher.Violation.REQUEST_OVERFLOW);
AtomicReference<FluxFlatMap.FlatMapMain> ref = new AtomicReference<>();
StepVerifier.create(Flux.just(1)
.hide()
.flatMap(f -> ts, 2, 1), 0)
.consumeSubscriptionWith(s -> ref.set((FluxFlatMap.FlatMapMain)s))
.then(() -> {
ref.get().wip = 1;
ts.emit(1, 2);
ref.get().drainLoop();
})
.verifyErrorMatches(Exceptions::isOverflow);
}
@Test
public void failOverflowScalarThenError() {
AtomicBoolean set = new AtomicBoolean();
Hooks.onErrorDropped(e -> {
assertThat(Exceptions.isOverflow(e)).isTrue();
set.set(true);
});
StepVerifier.create(Flux.from(s -> {
s.onSubscribe(Operators.emptySubscription());
s.onNext(1);
Exceptions.terminate(FluxFlatMap.FlatMapMain.ERROR, (FluxFlatMap.FlatMapMain) s);
s.onNext(2);
((FluxFlatMap.FlatMapMain)s).error = null;
s.onError(new Exception("test"));
})
.flatMap(Flux::just, 1), 0)
.verifyErrorMessage("test");
Hooks.resetOnErrorDropped();
assertThat(set.get()).isTrue();
}
@Test
public void failOverflowWhileActiveScalar() {
StepVerifier.create(Flux.from(s -> {
s.onSubscribe(Operators.emptySubscription());
s.onNext(1);
((FluxFlatMap.FlatMapMain)s).wip = 1; //simulate concurrent active
s.onNext(2);
((FluxFlatMap.FlatMapMain)s).drainLoop();
})
.flatMap(Flux::just, 1), 0)
.verifyErrorMatches(Exceptions::isOverflow);
}
@Test
public void failOverflowWhileActiveScalarThenError() {
AtomicBoolean set = new AtomicBoolean();
Hooks.onErrorDropped(e -> {
assertThat(Exceptions.isOverflow(e)).isTrue();
set.set(true);
});
StepVerifier.create(Flux.from(s -> {
s.onSubscribe(Operators.emptySubscription());
s.onNext(1);
Exceptions.terminate(FluxFlatMap.FlatMapMain.ERROR, (FluxFlatMap.FlatMapMain) s);
((FluxFlatMap.FlatMapMain)s).wip = 1; //simulate concurrent active
s.onNext(2);
s.onNext(3);
((FluxFlatMap.FlatMapMain)s).error = null;
((FluxFlatMap.FlatMapMain)s).drainLoop();
s.onError(new Exception("test"));
})
.flatMap(Flux::just, 1), 1)
.expectNext(1)
.verifyErrorMessage("test");
Hooks.resetOnErrorDropped();
assertThat(set.get()).isTrue();
}
@Test //FIXME use Violation.NO_CLEANUP_ON_TERMINATE
public void failDoubleErrorSilent() {
Hooks.onErrorDropped(e -> {
assertThat(e).hasMessage("test2");
});
StepVerifier.create(Flux.from(s -> {
s.onSubscribe(Operators.emptySubscription());
s.onError(new Exception("test"));
s.onError(new Exception("test2"));
}).flatMap(Flux::just))
.verifyErrorMessage("test");
Hooks.resetOnErrorDropped();
}
@Test
public void suppressFusionIfExpected() {
StepVerifier.create(Mono.just(1)
.flatMap(d -> Mono.just(d)
.hide()))
.consumeSubscriptionWith(s -> {
assertThat(s).isInstanceOf(FluxHide.SuppressFuseableSubscriber.class);
})
.expectNext(1)
.verifyComplete();
}
@Test
public void ignoreRequestZeroThenRequestOneByOne() {
StepVerifier.create(Flux.just(1, 2, 3)
.flatMap(f -> Flux.just(f * 2)), 0)
.consumeSubscriptionWith(s -> s.request(0))
.thenRequest(1)
.expectNext(2)
.thenRequest(1)
.expectNext(4)
.thenRequest(1)
.expectNext(6)
.verifyComplete();
}
@Test
public void suppressFusionIfExpectedError() {
StepVerifier.create(Mono.just(1)
.flatMap(d -> Mono.error(new Exception("test"))
.hide()))
.consumeSubscriptionWith(s -> {
assertThat(s).isInstanceOf(FluxHide.SuppressFuseableSubscriber.class);
})
.verifyErrorMessage("test");
}
@Test
public void backpressuredScalarThenCancel(){
StepVerifier.create(Flux.just(1, 2, 3).flatMap(Flux::just), 0)
.thenRequest(2)
.expectNext(1, 2)
.thenCancel()
.verify();
}
@Test
public void delayedUnboundedScalarThenCancel() {
StepVerifier.create(Flux.just(1, 2, 3)
.flatMap(Flux::just), 0)
.thenAwait()
.thenRequest(Long.MAX_VALUE)
.expectNext(1, 2, 3)
.thenCancel()
.verify();
}
@Test
public void delayedBackpressuredScalar() {
StepVerifier.create(Flux.range(1, 1000)
.flatMap(Flux::just), 0)
.thenAwait()
.thenRequest(2)
.expectNext(1, 2)
.thenRequest(1)
.expectNext(3)
.thenRequest(Long.MAX_VALUE)
.expectNextCount(997)
.verifyComplete();
}
Flux<Integer> scenario_backpressuredThenCancel() {
return Flux.just(1, 2, 3)
.flatMap(f -> Flux.range(1, 10)
.delayElements(Duration.ofMillis(10L)))
.hide();
}
@Test
public void backpressuredThenCancel() {
StepVerifier.withVirtualTime(this::scenario_backpressuredThenCancel, 0)
.thenRequest(2)
.thenAwait(Duration.ofSeconds(1))
.expectNext(1, 1)
.thenAwait(Duration.ofSeconds(1))
.thenRequest(1)
.expectNext(2)
.thenCancel()
.verify();
}
void assertBeforeOnSubscribeState(InnerOperator s) {
assertThat(s.scan(Scannable.Attr.REQUESTED_FROM_DOWNSTREAM)).isEqualTo(0L);
assertThat(s.scan(Scannable.Attr.PREFETCH)).isEqualTo(1);
assertThat(s.scan(Scannable.Attr.TERMINATED, Boolean.class)).isFalse();
assertThat(s.scanOrDefault(Scannable.Attr.CANCELLED, false)).isFalse();
}
void assertAfterOnSubscribeState(InnerOperator s) {
assertThat(s.scan(Scannable.Attr.ERROR)).isNull();
assertThat(s.scan(Scannable.Attr.REQUESTED_FROM_DOWNSTREAM)).isEqualTo(1L);
assertThat(s.scan(Scannable.Attr.TERMINATED, Boolean.class)).isFalse();
assertThat(s.scanOrDefault(Scannable.Attr.CANCELLED, false)).isFalse();
}
void assertAfterOnNextInnerState(InnerConsumer s) {
assertThat(s.scan(Scannable.Attr.BUFFERED)).isEqualTo(1);
}
void assertAfterOnNextInnerState2(InnerConsumer s) {
assertThat(s.scan(Scannable.Attr.BUFFERED)).isEqualTo(0);
}
void assertAfterOnCompleteInnerState(InnerConsumer s) {
assertThat(s.scan(Scannable.Attr.TERMINATED, Boolean.class)).isFalse();
}
void assertAfterOnCompleteInnerState2(InnerConsumer s) {
assertThat(s.scan(Scannable.Attr.TERMINATED, Boolean.class)).isTrue();
assertThat(s.scan(Scannable.Attr.BUFFERED)).isEqualTo(0);
}
@SuppressWarnings("unchecked")
void assertAfterOnSubscribeInnerState(InnerProducer s) {
assertThat(s.inners()).hasSize(1);
}
void assertBeforeOnSubscribeInnerState(InnerConsumer s) {
assertThat(s.scan(Scannable.Attr.TERMINATED, Boolean.class)).isFalse();
assertThat(s.scan(Scannable.Attr.PREFETCH)).isEqualTo(32);
assertThat(s.scan(Scannable.Attr.BUFFERED)).isEqualTo(0);
}
@Test
public void assertOnSubscribeStateMainAndInner() {
StepVerifier.create(Flux.from(s -> {
assertBeforeOnSubscribeState((FluxFlatMap.FlatMapMain) s);
s.onSubscribe(Operators.emptySubscription());
assertAfterOnSubscribeState((FluxFlatMap.FlatMapMain) s);
s.onNext(1);
assertThat(((FluxFlatMap.FlatMapMain) s).actual()).isNotNull();
assertThat(((FluxFlatMap.FlatMapMain) s).scan(Scannable.Attr.CANCELLED, Boolean.class)).isTrue();
s.onComplete();
})
.flatMap(f -> Flux.from(s -> {
assertBeforeOnSubscribeInnerState((FluxFlatMap.FlatMapInner) s);
s.onSubscribe(Operators.emptySubscription());
assertAfterOnSubscribeInnerState(((FluxFlatMap
.FlatMapInner) s).parent);
s.onNext(f);
s.onNext(f);
assertAfterOnNextInnerState(((FluxFlatMap.FlatMapInner) s));
assertAfterOnCompleteInnerState(((FluxFlatMap.FlatMapInner) s));
assertThat(((FluxFlatMap.FlatMapInner)s).scan(Scannable.Attr.BUFFERED)).isEqualTo(1);
s.onComplete();
assertAfterOnCompleteInnerState(((FluxFlatMap.FlatMapInner) s));
}), 1), 1)
.expectNext(1)
.thenCancel()
.verify();
}
@Test
public void assertOnSubscribeStateMainAndInner2() {
StepVerifier.create(Flux.from(s -> {
assertBeforeOnSubscribeState((FluxFlatMap.FlatMapMain) s);
s.onSubscribe(Operators.emptySubscription());
assertAfterOnSubscribeState((FluxFlatMap.FlatMapMain) s);
s.onNext(1);
assertThat(((FluxFlatMap.FlatMapMain) s).actual()).isNotNull();
assertThat(((FluxFlatMap.FlatMapMain) s).scan(Scannable.Attr.CANCELLED, Boolean.class)).isFalse();
s.onComplete();
assertThat(((FluxFlatMap.FlatMapMain) s).scan(Scannable.Attr.TERMINATED, Boolean.class)).isTrue();
})
.flatMap(f -> Flux.from(s -> {
assertBeforeOnSubscribeInnerState((FluxFlatMap.FlatMapInner) s);
s.onSubscribe(Operators.emptySubscription());
assertAfterOnSubscribeInnerState(((FluxFlatMap
.FlatMapInner) s).parent);
s.onNext(f);
assertAfterOnNextInnerState2(((FluxFlatMap
.FlatMapInner) s));
s.onComplete();
assertAfterOnCompleteInnerState2(((FluxFlatMap.FlatMapInner) s));
}), 1), 1)
.expectNext(1)
.verifyComplete();
}
@Test
public void assertOnSubscribeStateMainAndInner3() {
StepVerifier.create(Flux.just(1)
.hide()
.flatMap(f -> Flux.from(s -> {
s.onSubscribe(Operators.emptySubscription());
s.onComplete();
assertAfterOnCompleteInnerState2(((FluxFlatMap.FlatMapInner) s));
}), 1), 1)
.verifyComplete();
}
@Test
@SuppressWarnings("unchecked")
public void assertOnSubscribeStateMainScalar() {
AtomicReference<FluxFlatMap.FlatMapMain> ref = new AtomicReference<>();
StepVerifier.create(Flux.from(s -> {
assertBeforeOnSubscribeState((FluxFlatMap.FlatMapMain) s);
s.onSubscribe(Operators.emptySubscription());
assertAfterOnSubscribeState((FluxFlatMap.FlatMapMain) s);
s.onNext(1);
})
.flatMap(Flux::just, 1), 1)
.consumeSubscriptionWith(s -> ref.set((FluxFlatMap.FlatMapMain)s))
.expectNext(1)
.then(() -> {
FluxFlatMap.FlatMapMain s = ref.get();
assertThat(s.scan(Scannable.Attr.BUFFERED)).isEqualTo(0);
s.onNext(2);
assertThat(s.actual()).isNotNull();
assertThat(s.scan(Scannable.Attr.CANCELLED, Boolean.class)).isFalse();
assertThat(s.scan(Scannable.Attr.BUFFERED)).isEqualTo(1);
assertThat(s.scan(Scannable.Attr.TERMINATED, Boolean.class)).isFalse();
s.onComplete();
assertThat(s.scan(Scannable.Attr.BUFFERED)).isEqualTo(1);
assertThat(s.scan(Scannable.Attr.TERMINATED, Boolean.class)).isFalse();
})
.thenRequest(1)
.then(() -> {
FluxFlatMap.FlatMapMain s = ref.get();
assertThat(s.scan(Scannable.Attr.TERMINATED, Boolean.class)).isTrue();
assertThat(s.scan(Scannable.Attr.BUFFERED)).isEqualTo(0);
})
.expectNext(2)
.verifyComplete();
}
@Test
public void ignoreSyncFusedRequest() {
StepVerifier.create(Flux.just(1, 2, 3)
.flatMap(f -> Flux.range(f, 2), 1), 0)
.thenRequest(1)
.thenCancel()
.verify();
}
@Test
public void asyncInnerFusion() {
UnicastProcessor<Integer> up = UnicastProcessor.create();
StepVerifier.create(Flux.just(1)
.hide()
.flatMap(f -> up, 1))
.then(() -> up.onNext(1))
.then(() -> up.onNext(2))
.then(() -> up.onNext(3))
.then(() -> up.onNext(4))
.then(() -> up.onComplete())
.expectNext(1, 2, 3, 4)
.verifyComplete();
}
@Test
public void failAsyncInnerFusion() {
UnicastProcessor<Integer> up = UnicastProcessor.create();
StepVerifier.create(Flux.just(1)
.hide()
.flatMap(f -> up, 1))
.then(() -> up.onNext(1))
.then(() -> up.onNext(2))
.then(() -> up.onError(new Exception("test")))
.expectNext(1, 2)
.verifyErrorMessage("test");
}
@Test
public void failsyncInnerFusion() {
StepVerifier.create(Flux.just(1)
.hide()
.flatMap(f -> Flux.just(1, 2, null), 1))
.expectNext(1, 2)
.verifyError(NullPointerException.class);
}
@Test
public void prematureInnerCancel() {
StepVerifier.create(Flux.just(1, 2, 3, 4, 5)
.hide()
.flatMap(f -> Flux.from(s -> {
s.onSubscribe(Operators.emptySubscription());
s.onNext(1);
s.onNext(2);
if(f > 1) {
s.onComplete();
}
})))
.expectNext(1, 2)
.expectNext(1, 2)
.thenCancel()
.verify();
}
@Test
public void normalInnerArrayMoreThanDefaultArraySize4() {
TestPublisher<Integer> ts = TestPublisher.create();
TestPublisher<Integer> ts2 = TestPublisher.create();
StepVerifier.create(Flux.just(1, 2, 3, 4, 5, 6, 7, 8)
.hide()
.flatMap(f -> f < 5 ? ts : ts2), 0)
.then(() -> ts.next(1, 2))
.thenRequest(2)
.expectNext(1, 2)
.thenRequest(2)
.expectNext(1, 2)
.thenRequest(2)
.expectNext(1, 2)
.thenRequest(2)
.expectNext(1, 2)
.then(() -> ts2.complete())
.then(() -> ts.emit(3))
.thenRequest(4)
.expectNext(3, 3, 3, 3)
.verifyComplete();
}
@Test
public void prematureCancelInnerArrayMoreThanDefaultArraySize4() {
AtomicReference<Subscription> x = new AtomicReference<>();
StepVerifier.create(Flux.just(1, 2, 3, 4)
.hide()
.flatMap(f -> Flux.from(s -> {
s.onSubscribe(Operators.emptySubscription());
s.onNext(1);
s.onNext(2);
})), 0)
.consumeSubscriptionWith(x::set)
.thenAwait()
.thenRequest(5)
.expectNext(1, 2)
.expectNext(1, 2)
.expectNext(1)
.thenAwait()
.thenRequest(2)
.expectNext(2, 1)
.thenRequest(1)
.thenCancel()
.verify();
}
@Test
public void flatMapDelayErrors() throws Exception {
AtomicInteger onNextSignals = new AtomicInteger();
StepVerifier.create(Flux.range(0, 5).hide()
.flatMapDelayError(i -> Mono.just(i)
.doOnNext(e -> onNextSignals.incrementAndGet())
.handle((s1, sink) -> {
if (s1 == 1 || s1 == 3) {
sink.error(new RuntimeException("Error: " + s1));
}
else {
sink.next(s1);
}
})
.subscribeOn(Schedulers.parallel()),
4, 4)
.retry(1))
.recordWith(ArrayList::new)
.expectNextCount(3)
.consumeRecordedWith(c -> {
assertThat(c).containsExactlyInAnyOrder(0, 2, 4);
c.clear();
})
.expectNextCount(3)
.consumeRecordedWith(c -> assertThat(c).containsExactlyInAnyOrder(0, 2, 4))
.verifyError();
assertThat(onNextSignals.get()).isEqualTo(10);
}
}