/** * Copyright (c) 2016-present, RxJava Contributors. * * 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 io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; import org.junit.*; import org.reactivestreams.*; import io.reactivex.*; import io.reactivex.Flowable; import io.reactivex.exceptions.*; import io.reactivex.flowable.*; import io.reactivex.flowable.FlowableEventStream.Event; import io.reactivex.functions.*; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; import io.reactivex.subscribers.*; public class FlowableScanTest { @Test public void testScanIntegersWithInitialValue() { Subscriber<String> observer = TestHelper.mockSubscriber(); Flowable<Integer> observable = Flowable.just(1, 2, 3); Flowable<String> m = observable.scan("", new BiFunction<String, Integer, String>() { @Override public String apply(String s, Integer n) { return s + n.toString(); } }); m.subscribe(observer); verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onNext(""); verify(observer, times(1)).onNext("1"); verify(observer, times(1)).onNext("12"); verify(observer, times(1)).onNext("123"); verify(observer, times(4)).onNext(anyString()); verify(observer, times(1)).onComplete(); verify(observer, never()).onError(any(Throwable.class)); } @Test public void testScanIntegersWithoutInitialValue() { Subscriber<Integer> observer = TestHelper.mockSubscriber(); Flowable<Integer> observable = Flowable.just(1, 2, 3); Flowable<Integer> m = observable.scan(new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { return t1 + t2; } }); m.subscribe(observer); verify(observer, never()).onError(any(Throwable.class)); verify(observer, never()).onNext(0); verify(observer, times(1)).onNext(1); verify(observer, times(1)).onNext(3); verify(observer, times(1)).onNext(6); verify(observer, times(3)).onNext(anyInt()); verify(observer, times(1)).onComplete(); verify(observer, never()).onError(any(Throwable.class)); } @Test public void testScanIntegersWithoutInitialValueAndOnlyOneValue() { Subscriber<Integer> observer = TestHelper.mockSubscriber(); Flowable<Integer> observable = Flowable.just(1); Flowable<Integer> m = observable.scan(new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { return t1 + t2; } }); m.subscribe(observer); verify(observer, never()).onError(any(Throwable.class)); verify(observer, never()).onNext(0); verify(observer, times(1)).onNext(1); verify(observer, times(1)).onNext(anyInt()); verify(observer, times(1)).onComplete(); verify(observer, never()).onError(any(Throwable.class)); } @Test public void shouldNotEmitUntilAfterSubscription() { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); Flowable.range(1, 100).scan(0, new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { return t1 + t2; } }).filter(new Predicate<Integer>() { @Override public boolean test(Integer t1) { // this will cause request(1) when 0 is emitted return t1 > 0; } }).subscribe(ts); assertEquals(100, ts.values().size()); } @Test public void testBackpressureWithInitialValue() { final AtomicInteger count = new AtomicInteger(); Flowable.range(1, 100) .scan(0, new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { return t1 + t2; } }) .subscribe(new DefaultSubscriber<Integer>() { @Override public void onStart() { request(10); } @Override public void onComplete() { } @Override public void onError(Throwable e) { Assert.fail(e.getMessage()); e.printStackTrace(); } @Override public void onNext(Integer t) { count.incrementAndGet(); } }); // we only expect to receive 10 since we request(10) assertEquals(10, count.get()); } @Test public void testBackpressureWithoutInitialValue() { final AtomicInteger count = new AtomicInteger(); Flowable.range(1, 100) .scan(new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { return t1 + t2; } }) .subscribe(new DefaultSubscriber<Integer>() { @Override public void onStart() { request(10); } @Override public void onComplete() { } @Override public void onError(Throwable e) { Assert.fail(e.getMessage()); e.printStackTrace(); } @Override public void onNext(Integer t) { count.incrementAndGet(); } }); // we only expect to receive 10 since we request(10) assertEquals(10, count.get()); } @Test public void testNoBackpressureWithInitialValue() { final AtomicInteger count = new AtomicInteger(); Flowable.range(1, 100) .scan(0, new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { return t1 + t2; } }) .subscribe(new DefaultSubscriber<Integer>() { @Override public void onComplete() { } @Override public void onError(Throwable e) { Assert.fail(e.getMessage()); e.printStackTrace(); } @Override public void onNext(Integer t) { count.incrementAndGet(); } }); // we only expect to receive 101 as we'll receive all 100 + the initial value assertEquals(101, count.get()); } /** * This uses the public API collect which uses scan under the covers. */ @Test public void testSeedFactory() { Single<List<Integer>> o = Flowable.range(1, 10) .collect(new Callable<List<Integer>>() { @Override public List<Integer> call() { return new ArrayList<Integer>(); } }, new BiConsumer<List<Integer>, Integer>() { @Override public void accept(List<Integer> list, Integer t2) { list.add(t2); } }); assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), o.blockingGet()); assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), o.blockingGet()); } /** * This uses the public API collect which uses scan under the covers. */ @Test public void testSeedFactoryFlowable() { Flowable<List<Integer>> o = Flowable.range(1, 10) .collect(new Callable<List<Integer>>() { @Override public List<Integer> call() { return new ArrayList<Integer>(); } }, new BiConsumer<List<Integer>, Integer>() { @Override public void accept(List<Integer> list, Integer t2) { list.add(t2); } }).toFlowable().takeLast(1); assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), o.blockingSingle()); assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), o.blockingSingle()); } @Test public void testScanWithRequestOne() { Flowable<Integer> o = Flowable.just(1, 2).scan(0, new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { return t1 + t2; } }).take(1); TestSubscriber<Integer> subscriber = new TestSubscriber<Integer>(); o.subscribe(subscriber); subscriber.assertValue(0); subscriber.assertTerminated(); subscriber.assertNoErrors(); } @Test public void testScanShouldNotRequestZero() { final AtomicReference<Subscription> producer = new AtomicReference<Subscription>(); Flowable<Integer> o = Flowable.unsafeCreate(new Publisher<Integer>() { @Override public void subscribe(final Subscriber<? super Integer> subscriber) { Subscription p = spy(new Subscription() { private AtomicBoolean requested = new AtomicBoolean(false); @Override public void request(long n) { if (requested.compareAndSet(false, true)) { subscriber.onNext(1); subscriber.onComplete(); } } @Override public void cancel() { } }); producer.set(p); subscriber.onSubscribe(p); } }).scan(100, new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { return t1 + t2; } }); o.subscribe(new TestSubscriber<Integer>(1L) { @Override public void onNext(Integer integer) { request(1); } }); verify(producer.get(), never()).request(0); verify(producer.get(), times(1)).request(Flowable.bufferSize() - 1); } @Test @Ignore("scanSeed no longer emits without upstream signal") public void testInitialValueEmittedNoProducer() { PublishProcessor<Integer> source = PublishProcessor.create(); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); source.scan(0, new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { return t1 + t2; } }).subscribe(ts); ts.assertNoErrors(); ts.assertNotComplete(); ts.assertValue(0); } @Test @Ignore("scanSeed no longer emits without upstream signal") public void testInitialValueEmittedWithProducer() { Flowable<Integer> source = Flowable.never(); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); source.scan(0, new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) { return t1 + t2; } }).subscribe(ts); ts.assertNoErrors(); ts.assertNotComplete(); ts.assertValue(0); } @Test public void dispose() { TestHelper.checkDisposed(PublishProcessor.create().scan(new BiFunction<Object, Object, Object>() { @Override public Object apply(Object a, Object b) throws Exception { return a; } })); TestHelper.checkDisposed(PublishProcessor.<Integer>create().scan(0, new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer a, Integer b) throws Exception { return a + b; } })); } @Test public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override public Flowable<Object> apply(Flowable<Object> o) throws Exception { return o.scan(new BiFunction<Object, Object, Object>() { @Override public Object apply(Object a, Object b) throws Exception { return a; } }); } }); TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override public Flowable<Object> apply(Flowable<Object> o) throws Exception { return o.scan(0, new BiFunction<Object, Object, Object>() { @Override public Object apply(Object a, Object b) throws Exception { return a; } }); } }); } @Test public void error() { Flowable.error(new TestException()) .scan(new BiFunction<Object, Object, Object>() { @Override public Object apply(Object a, Object b) throws Exception { return a; } }) .test() .assertFailure(TestException.class); } @Test public void neverSource() { Flowable.<Integer>never() .scan(0, new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer a, Integer b) throws Exception { return a + b; } }) .test() .assertValue(0) .assertNoErrors() .assertNotComplete(); } @Test public void testUnsubscribeScan() { FlowableEventStream.getEventStream("HTTP-ClusterB", 20) .scan(new HashMap<String, String>(), new BiFunction<HashMap<String, String>, Event, HashMap<String, String>>() { @Override public HashMap<String, String> apply(HashMap<String, String> accum, Event perInstanceEvent) { accum.put("instance", perInstanceEvent.instanceId); return accum; } }) .take(10) .blockingForEach(new Consumer<HashMap<String, String>>() { @Override public void accept(HashMap<String, String> v) { System.out.println(v); } }); } @Test public void testScanWithSeedDoesNotEmitErrorTwiceIfScanFunctionThrows() { final List<Throwable> list = new CopyOnWriteArrayList<Throwable>(); Consumer<Throwable> errorConsumer = new Consumer<Throwable>() { @Override public void accept(Throwable t) throws Exception { list.add(t); }}; try { RxJavaPlugins.setErrorHandler(errorConsumer); final RuntimeException e = new RuntimeException(); final RuntimeException e2 = new RuntimeException(); Burst.items(1).error(e2) .scan(0, throwingBiFunction(e)) .test() .assertValues(0) .assertError(e); assertEquals("" + list, 1, list.size()); assertTrue("" + list, list.get(0) instanceof UndeliverableException); assertEquals(e2, list.get(0).getCause()); } finally { RxJavaPlugins.reset(); } } @Test public void testScanWithSeedDoesNotEmitTerminalEventTwiceIfScanFunctionThrows() { final RuntimeException e = new RuntimeException(); Burst.item(1).create() .scan(0, throwingBiFunction(e)) .test() .assertValue(0) .assertError(e); } @Test public void testScanWithSeedDoesNotProcessOnNextAfterTerminalEventIfScanFunctionThrows() { final RuntimeException e = new RuntimeException(); final AtomicInteger count = new AtomicInteger(); Burst.items(1, 2).create().scan(0, new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer n1, Integer n2) throws Exception { count.incrementAndGet(); throw e; }}) .test() .assertValues(0) .assertError(e); assertEquals(1, count.get()); } @Test public void testScanWithSeedCompletesNormally() { Flowable.just(1,2,3).scan(0, SUM) .test() .assertValues(0, 1, 3, 6) .assertComplete(); } @Test public void testScanWithSeedWhenScanSeedProviderThrows() { final RuntimeException e = new RuntimeException(); Flowable.just(1,2,3).scanWith(throwingCallable(e), SUM) .test() .assertError(e) .assertNoValues(); } @Test public void testScanNoSeed() { Flowable.just(1, 2, 3) .scan(SUM) .test() .assertValues(1, 3, 6) .assertComplete(); } @Test public void testScanNoSeedDoesNotEmitErrorTwiceIfScanFunctionThrows() { final List<Throwable> list = new CopyOnWriteArrayList<Throwable>(); Consumer<Throwable> errorConsumer = new Consumer<Throwable>() { @Override public void accept(Throwable t) throws Exception { list.add(t); }}; try { RxJavaPlugins.setErrorHandler(errorConsumer); final RuntimeException e = new RuntimeException(); final RuntimeException e2 = new RuntimeException(); Burst.items(1, 2).error(e2) .scan(throwingBiFunction(e)) .test() .assertValue(1) .assertError(e); assertEquals("" + list, 1, list.size()); assertTrue("" + list, list.get(0) instanceof UndeliverableException); assertEquals(e2, list.get(0).getCause()); } finally { RxJavaPlugins.reset(); } } @Test public void testScanNoSeedDoesNotEmitTerminalEventTwiceIfScanFunctionThrows() { final RuntimeException e = new RuntimeException(); Burst.items(1, 2).create() .scan(throwingBiFunction(e)) .test() .assertValue(1) .assertError(e); } @Test public void testScanNoSeedDoesNotProcessOnNextAfterTerminalEventIfScanFunctionThrows() { final RuntimeException e = new RuntimeException(); final AtomicInteger count = new AtomicInteger(); Burst.items(1, 2, 3).create().scan(new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer n1, Integer n2) throws Exception { count.incrementAndGet(); throw e; }}) .test() .assertValue(1) .assertError(e); assertEquals(1, count.get()); } private static BiFunction<Integer,Integer, Integer> throwingBiFunction(final RuntimeException e) { return new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer n1, Integer n2) throws Exception { throw e; } }; } private static final BiFunction<Integer, Integer, Integer> SUM = new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer t1, Integer t2) throws Exception { return t1 + t2; } }; private static Callable<Integer> throwingCallable(final RuntimeException e) { return new Callable<Integer>() { @Override public Integer call() throws Exception { throw e; } }; } @Test public void scanEmptyBackpressured() { Flowable.<Integer>empty() .scan(0, SUM) .test(1) .assertResult(0); } @Test public void scanErrorBackpressured() { Flowable.<Integer>error(new TestException()) .scan(0, SUM) .test(0) .assertFailure(TestException.class); } @Test public void scanTake() { TestSubscriber<Integer> ts = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); onComplete(); cancel(); } }; Flowable.range(1, 10) .scan(0, SUM) .subscribe(ts) ; ts.assertResult(0); } @Test public void scanLong() { int n = 2 * Flowable.bufferSize(); for (int b = 1; b <= n; b *= 2) { List<Integer> list = Flowable.range(1, n) .scan(0, new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(Integer a, Integer b) throws Exception { return b; } }) .rebatchRequests(b) .toList() .blockingGet(); for (int i = 0; i <= n; i++) { assertEquals(i, list.get(i).intValue()); } } } }