/******************************************************************************* * Copyright (c) 2018 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. * * 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 org.eclipse.microprofile.reactive.streams.operators.tck.spi; import org.eclipse.microprofile.reactive.streams.operators.ProcessorBuilder; import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; import org.reactivestreams.Processor; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import org.testng.annotations.Test; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import static org.testng.Assert.assertEquals; public class FlatMapStageVerification extends AbstractStageVerification { FlatMapStageVerification(ReactiveStreamsSpiVerification.VerificationDeps deps) { super(deps); } @Test public void flatMapStageShouldMapElements() { assertEquals(await(rs.of(1, 2, 3) .flatMap(n -> rs.of(n, n, n)) .toList() .run(getEngine())), Arrays.asList(1, 1, 1, 2, 2, 2, 3, 3, 3)); } @Test public void flatMapStageShouldAllowEmptySubStreams() { assertEquals(await(rs.of(rs.empty(), rs.of(1, 2)) .flatMap(Function.identity()) .toList() .run(getEngine())), Arrays.asList(1, 2)); } @Test(expectedExceptions = QuietRuntimeException.class, expectedExceptionsMessageRegExp = "failed") public void flatMapStageShouldHandleExceptions() { CompletableFuture<Void> cancelled = new CompletableFuture<>(); CompletionStage<List<Object>> result = infiniteStream() .onTerminate(() -> cancelled.complete(null)) .flatMap(foo -> { throw new QuietRuntimeException("failed"); }) .toList() .run(getEngine()); await(cancelled); await(result); } @Test(expectedExceptions = QuietRuntimeException.class, expectedExceptionsMessageRegExp = "failed") public void flatMapStageShouldPropagateUpstreamExceptions() { await(rs.failed(new QuietRuntimeException("failed")) .flatMap(rs::of) .toList() .run(getEngine())); } @Test(expectedExceptions = QuietRuntimeException.class, expectedExceptionsMessageRegExp = "failed") public void flatMapStageShouldPropagateSubstreamExceptions() { CompletableFuture<Void> cancelled = new CompletableFuture<>(); CompletionStage<List<Object>> result = infiniteStream() .onTerminate(() -> cancelled.complete(null)) .flatMap(f -> rs.failed(new QuietRuntimeException("failed"))) .toList() .run(getEngine()); await(cancelled); await(result); } @Test public void flatMapStageShouldOnlySubscribeToOnePublisherAtATime() throws Exception { AtomicInteger activePublishers = new AtomicInteger(); CompletionStage<List<Integer>> result = rs.of(1, 2, 3, 4, 5) .flatMap(id -> rs.fromPublisher(new ScheduledPublisher(id, activePublishers, this::getExecutorService))) .toList() .run(getEngine()); assertEquals(result.toCompletableFuture().get(2, TimeUnit.SECONDS), Arrays.asList(1, 2, 3, 4, 5)); } @Test public void flatMapStageShouldPropgateCancelToSubstreams() { CompletableFuture<Void> outerCancelled = new CompletableFuture<>(); CompletableFuture<Void> innerCancelled = new CompletableFuture<>(); await(infiniteStream() .onTerminate(() -> outerCancelled.complete(null)) .flatMap(i -> infiniteStream().onTerminate(() -> innerCancelled.complete(null))) .limit(5) .toList() .run(getEngine())); await(outerCancelled); await(innerCancelled); } @Test public void flatMapStageBuilderShouldBeReusable() { ProcessorBuilder<PublisherBuilder<Integer>, Integer> flatMap = rs.<PublisherBuilder<Integer>>builder().flatMap(Function.identity()); assertEquals(await(rs.of(rs.of(1, 2)).via(flatMap).toList().run(getEngine())), Arrays.asList(1, 2)); assertEquals(await(rs.of(rs.of(3, 4)).via(flatMap).toList().run(getEngine())), Arrays.asList(3, 4)); } @Override List<Object> reactiveStreamsTckVerifiers() { return Arrays.asList(new OuterProcessorVerification(), new InnerSubscriberVerification()); } /** * Verifies the outer processor. */ public class OuterProcessorVerification extends StageProcessorVerification<Integer> { @Override public Processor<Integer, Integer> createIdentityProcessor(int bufferSize) { return rs.<Integer>builder().flatMap(rs::of).buildRs(getEngine()); } @Override public Publisher<Integer> createFailedPublisher() { return rs.<Integer>failed(new RuntimeException("failed")) .flatMap(rs::of).buildRs(getEngine()); } @Override public Integer createElement(int element) { return element; } } /** * Verifies the inner subscriber passed to publishers produced by the mapper function. */ public class InnerSubscriberVerification extends StageSubscriberWhiteboxVerification<Integer> { @Override public Subscriber<Integer> createSubscriber(WhiteboxSubscriberProbe<Integer> probe) { CompletableFuture<Subscriber<? super Integer>> subscriber = new CompletableFuture<>(); rs.of(rs.<Integer>fromPublisher(subscriber::complete)) .flatMap(Function.identity()) .to(new Subscriber<Integer>() { @Override public void onSubscribe(Subscription subscription) { // We need to initially request an element to ensure that we get the publisher. subscription.request(1); probe.registerOnSubscribe(new SubscriberPuppet() { @Override public void triggerRequest(long elements) { subscription.request(elements); } @Override public void signalCancel() { subscription.cancel(); } }); } @Override public void onNext(Integer item) { probe.registerOnNext(item); } @Override public void onError(Throwable throwable) { probe.registerOnError(throwable); } @Override public void onComplete() { probe.registerOnComplete(); } }) .run(getEngine()); return (Subscriber) await(subscriber); } @Override public Integer createElement(int element) { return element; } } }