/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.camel.component.reactive.streams; import java.util.LinkedList; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import io.reactivex.Flowable; import org.apache.camel.component.reactive.streams.engine.DelayedMonoPublisher; import org.apache.camel.component.reactive.streams.support.TestSubscriber; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class DelayedMonoPublisherTest { private ExecutorService service; @Before public void init() { service = new ScheduledThreadPoolExecutor(3); } @After public void tearDown() throws Exception { service.shutdown(); service.awaitTermination(1, TimeUnit.SECONDS); } @Test public void testAlreadyAvailable() throws Exception { DelayedMonoPublisher<Integer> pub = new DelayedMonoPublisher<>(service); pub.setData(5); LinkedList<Integer> data = new LinkedList<>(); CountDownLatch latch = new CountDownLatch(1); Flowable.fromPublisher(pub) .doOnNext(data::add) .doOnComplete(latch::countDown) .subscribe(); assertTrue(latch.await(1, TimeUnit.SECONDS)); assertEquals(1, data.size()); assertEquals(5, data.get(0).intValue()); } @Test public void testExceptionAlreadyAvailable() throws Exception { Exception ex = new RuntimeException("An exception"); DelayedMonoPublisher<Integer> pub = new DelayedMonoPublisher<>(service); pub.setException(ex); LinkedList<Throwable> exceptions = new LinkedList<>(); CountDownLatch latch = new CountDownLatch(1); Flowable.fromPublisher(pub) .subscribe(item -> { }, e -> { exceptions.add(e); latch.countDown(); }); assertTrue(latch.await(1, TimeUnit.SECONDS)); assertEquals(1, exceptions.size()); assertEquals(ex, exceptions.get(0)); } @Test public void testAvailableSoon() throws Exception { DelayedMonoPublisher<Integer> pub = new DelayedMonoPublisher<>(service); LinkedList<Integer> data = new LinkedList<>(); CountDownLatch latch = new CountDownLatch(1); Flowable.fromPublisher(pub) .doOnNext(data::add) .doOnComplete(latch::countDown) .subscribe(); Thread.yield(); pub.setData(5); assertTrue(latch.await(1, TimeUnit.SECONDS)); assertEquals(1, data.size()); assertEquals(5, data.get(0).intValue()); } @Test public void testAvailableLater() throws Exception { DelayedMonoPublisher<Integer> pub = new DelayedMonoPublisher<>(service); LinkedList<Integer> data = new LinkedList<>(); CountDownLatch latch = new CountDownLatch(1); Flowable.fromPublisher(pub) .doOnNext(data::add) .doOnComplete(latch::countDown) .subscribe(); Thread.sleep(200); pub.setData(5); assertTrue(latch.await(1, TimeUnit.SECONDS)); assertEquals(1, data.size()); assertEquals(5, data.get(0).intValue()); } @Test public void testMultipleSubscribers() throws Exception { DelayedMonoPublisher<Integer> pub = new DelayedMonoPublisher<>(service); ConcurrentLinkedDeque<Integer> data = new ConcurrentLinkedDeque<>(); CountDownLatch latch = new CountDownLatch(2); Flowable.fromPublisher(pub) .doOnNext(data::add) .doOnComplete(latch::countDown) .subscribe(); Flowable.fromPublisher(pub) .doOnNext(data::add) .doOnComplete(latch::countDown) .subscribe(); Thread.sleep(200); pub.setData(5); assertTrue(latch.await(1, TimeUnit.SECONDS)); assertEquals(2, data.size()); for (Integer n : data) { assertEquals(5, n.intValue()); } } @Test public void testMultipleSubscribersMixedArrival() throws Exception { DelayedMonoPublisher<Integer> pub = new DelayedMonoPublisher<>(service); ConcurrentLinkedDeque<Integer> data = new ConcurrentLinkedDeque<>(); CountDownLatch latch = new CountDownLatch(2); Flowable.fromPublisher(pub) .doOnNext(data::add) .doOnComplete(latch::countDown) .subscribe(); Thread.sleep(200); pub.setData(5); Flowable.fromPublisher(pub) .doOnNext(data::add) .doOnComplete(latch::countDown) .subscribe(); assertTrue(latch.await(1, TimeUnit.SECONDS)); assertEquals(2, data.size()); for (Integer n : data) { assertEquals(5, n.intValue()); } } @Test public void testMultipleSubscribersMixedArrivalException() throws Exception { DelayedMonoPublisher<Integer> pub = new DelayedMonoPublisher<>(service); Exception ex = new RuntimeException("An exception"); ConcurrentLinkedDeque<Throwable> exceptions = new ConcurrentLinkedDeque<>(); CountDownLatch latch = new CountDownLatch(2); Flowable.fromPublisher(pub) .subscribe(item -> { }, e -> { exceptions.add(e); latch.countDown(); }); Thread.sleep(200); pub.setException(ex); Flowable.fromPublisher(pub) .subscribe(item -> { }, e -> { exceptions.add(e); latch.countDown(); }); assertTrue(latch.await(1, TimeUnit.SECONDS)); assertEquals(2, exceptions.size()); for (Throwable t : exceptions) { assertEquals(ex, t); } } @Test public void testDelayedRequest() throws Exception { DelayedMonoPublisher<Integer> pub = new DelayedMonoPublisher<>(service); pub.setData(2); BlockingQueue<Integer> queue = new LinkedBlockingDeque<>(); TestSubscriber<Integer> sub = new TestSubscriber<Integer>() { @Override public void onNext(Integer o) { queue.add(o); } }; sub.setInitiallyRequested(0); pub.subscribe(sub); Thread.sleep(100); sub.request(1); Integer res = queue.poll(1, TimeUnit.SECONDS); assertEquals(new Integer(2), res); } @Test(expected = IllegalStateException.class) public void testDataOrExceptionAllowed() throws Exception { DelayedMonoPublisher<Integer> pub = new DelayedMonoPublisher<>(service); Exception ex = new RuntimeException("An exception"); pub.setException(ex); pub.setData(1); } @Test(expected = IllegalStateException.class) public void testDataOrExceptionAllowed2() throws Exception { DelayedMonoPublisher<Integer> pub = new DelayedMonoPublisher<>(service); pub.setData(1); Exception ex = new RuntimeException("An exception"); pub.setException(ex); } @Test(expected = IllegalStateException.class) public void testOnlyOneDataAllowed() throws Exception { DelayedMonoPublisher<Integer> pub = new DelayedMonoPublisher<>(service); pub.setData(1); pub.setData(2); } @Test(expected = IllegalStateException.class) public void testOnlyOneExceptionAllowed() throws Exception { DelayedMonoPublisher<Integer> pub = new DelayedMonoPublisher<>(service); pub.setException(new RuntimeException("An exception")); pub.setException(new RuntimeException("An exception")); } }