/** * 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.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.io.IOException; import java.util.*; import org.junit.*; import org.reactivestreams.*; import io.reactivex.*; import io.reactivex.Flowable; import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; import io.reactivex.internal.fuseable.*; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.*; import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.*; public class FlowableMapTest { Subscriber<String> stringSubscriber; Subscriber<String> stringSubscriber2; static final BiFunction<String, Integer, String> APPEND_INDEX = new BiFunction<String, Integer, String>() { @Override public String apply(String value, Integer index) { return value + index; } }; @Before public void before() { stringSubscriber = TestHelper.mockSubscriber(); stringSubscriber2 = TestHelper.mockSubscriber(); } @Test public void testMap() { Map<String, String> m1 = getMap("One"); Map<String, String> m2 = getMap("Two"); Flowable<Map<String, String>> observable = Flowable.just(m1, m2); Flowable<String> m = observable.map(new Function<Map<String, String>, String>() { @Override public String apply(Map<String, String> map) { return map.get("firstName"); } }); m.subscribe(stringSubscriber); verify(stringSubscriber, never()).onError(any(Throwable.class)); verify(stringSubscriber, times(1)).onNext("OneFirst"); verify(stringSubscriber, times(1)).onNext("TwoFirst"); verify(stringSubscriber, times(1)).onComplete(); } @Test public void testMapMany() { /* simulate a top-level async call which returns IDs */ Flowable<Integer> ids = Flowable.just(1, 2); /* now simulate the behavior to take those IDs and perform nested async calls based on them */ Flowable<String> m = ids.flatMap(new Function<Integer, Flowable<String>>() { @Override public Flowable<String> apply(Integer id) { /* simulate making a nested async call which creates another Flowable */ Flowable<Map<String, String>> subFlowable = null; if (id == 1) { Map<String, String> m1 = getMap("One"); Map<String, String> m2 = getMap("Two"); subFlowable = Flowable.just(m1, m2); } else { Map<String, String> m3 = getMap("Three"); Map<String, String> m4 = getMap("Four"); subFlowable = Flowable.just(m3, m4); } /* simulate kicking off the async call and performing a select on it to transform the data */ return subFlowable.map(new Function<Map<String, String>, String>() { @Override public String apply(Map<String, String> map) { return map.get("firstName"); } }); } }); m.subscribe(stringSubscriber); verify(stringSubscriber, never()).onError(any(Throwable.class)); verify(stringSubscriber, times(1)).onNext("OneFirst"); verify(stringSubscriber, times(1)).onNext("TwoFirst"); verify(stringSubscriber, times(1)).onNext("ThreeFirst"); verify(stringSubscriber, times(1)).onNext("FourFirst"); verify(stringSubscriber, times(1)).onComplete(); } @Test public void testMapMany2() { Map<String, String> m1 = getMap("One"); Map<String, String> m2 = getMap("Two"); Flowable<Map<String, String>> observable1 = Flowable.just(m1, m2); Map<String, String> m3 = getMap("Three"); Map<String, String> m4 = getMap("Four"); Flowable<Map<String, String>> observable2 = Flowable.just(m3, m4); Flowable<Flowable<Map<String, String>>> observable = Flowable.just(observable1, observable2); Flowable<String> m = observable.flatMap(new Function<Flowable<Map<String, String>>, Flowable<String>>() { @Override public Flowable<String> apply(Flowable<Map<String, String>> o) { return o.map(new Function<Map<String, String>, String>() { @Override public String apply(Map<String, String> map) { return map.get("firstName"); } }); } }); m.subscribe(stringSubscriber); verify(stringSubscriber, never()).onError(any(Throwable.class)); verify(stringSubscriber, times(1)).onNext("OneFirst"); verify(stringSubscriber, times(1)).onNext("TwoFirst"); verify(stringSubscriber, times(1)).onNext("ThreeFirst"); verify(stringSubscriber, times(1)).onNext("FourFirst"); verify(stringSubscriber, times(1)).onComplete(); } @Test public void testMapWithError() { Flowable<String> w = Flowable.just("one", "fail", "two", "three", "fail"); Flowable<String> m = w.map(new Function<String, String>() { @Override public String apply(String s) { if ("fail".equals(s)) { throw new RuntimeException("Forced Failure"); } return s; } }).doOnError(new Consumer<Throwable>() { @Override public void accept(Throwable t1) { t1.printStackTrace(); } }); m.subscribe(stringSubscriber); verify(stringSubscriber, times(1)).onNext("one"); verify(stringSubscriber, never()).onNext("two"); verify(stringSubscriber, never()).onNext("three"); verify(stringSubscriber, never()).onComplete(); verify(stringSubscriber, times(1)).onError(any(Throwable.class)); } @Test(expected = IllegalArgumentException.class) public void testMapWithIssue417() { Flowable.just(1).observeOn(Schedulers.computation()) .map(new Function<Integer, Integer>() { @Override public Integer apply(Integer arg0) { throw new IllegalArgumentException("any error"); } }).blockingSingle(); } @Test(expected = IllegalArgumentException.class) public void testMapWithErrorInFuncAndThreadPoolScheduler() throws InterruptedException { // The error will throw in one of threads in the thread pool. // If map does not handle it, the error will disappear. // so map needs to handle the error by itself. Flowable<String> m = Flowable.just("one") .observeOn(Schedulers.computation()) .map(new Function<String, String>() { @Override public String apply(String arg0) { throw new IllegalArgumentException("any error"); } }); // block for response, expecting exception thrown m.blockingLast(); } /** * While mapping over range(1,0).last() we expect NoSuchElementException since the sequence is empty. */ @Test public void testErrorPassesThruMap() { assertNull(Flowable.range(1, 0).lastElement().map(new Function<Integer, Integer>() { @Override public Integer apply(Integer i) { return i; } }).blockingGet()); } /** * We expect IllegalStateException to pass thru map. */ @Test(expected = IllegalStateException.class) public void testErrorPassesThruMap2() { Flowable.error(new IllegalStateException()).map(new Function<Object, Object>() { @Override public Object apply(Object i) { return i; } }).blockingSingle(); } /** * We expect an ArithmeticException exception here because last() emits a single value * but then we divide by 0. */ @Test(expected = ArithmeticException.class) public void testMapWithErrorInFunc() { Flowable.range(1, 1).lastElement().map(new Function<Integer, Integer>() { @Override public Integer apply(Integer i) { return i / 0; } }).blockingGet(); } // FIXME RS subscribers can't throw // @Test(expected = OnErrorNotImplementedException.class) // public void verifyExceptionIsThrownIfThereIsNoExceptionHandler() { // // Publisher<Object> creator = new Publisher<Object>() { // // @Override // public void subscribe(Subscriber<? super Object> observer) { // observer.onSubscribe(EmptySubscription.INSTANCE); // observer.onNext("a"); // observer.onNext("b"); // observer.onNext("c"); // observer.onComplete(); // } // }; // // Function<Object, Flowable<Object>> manyMapper = new Function<Object, Flowable<Object>>() { // // @Override // public Flowable<Object> apply(Object object) { // return Flowable.just(object); // } // }; // // Function<Object, Object> mapper = new Function<Object, Object>() { // private int count = 0; // // @Override // public Object apply(Object object) { // ++count; // if (count > 2) { // throw new RuntimeException(); // } // return object; // } // }; // // Consumer<Object> onNext = new Consumer<Object>() { // // @Override // public void accept(Object object) { // System.out.println(object.toString()); // } // }; // // try { // Flowable.create(creator).flatMap(manyMapper).map(mapper).subscribe(onNext); // } catch (RuntimeException e) { // e.printStackTrace(); // throw e; // } // } private static Map<String, String> getMap(String prefix) { Map<String, String> m = new HashMap<String, String>(); m.put("firstName", prefix + "First"); m.put("lastName", prefix + "Last"); return m; } @Test//(expected = OnErrorNotImplementedException.class) @Ignore("RS subscribers can't throw") public void testShouldNotSwallowOnErrorNotImplementedException() { // Flowable.just("a", "b").flatMap(new Function<String, Flowable<String>>() { // @Override // public Flowable<String> apply(String s) { // return Flowable.just(s + "1", s + "2"); // } // }).flatMap(new Function<String, Flowable<String>>() { // @Override // public Flowable<String> apply(String s) { // return Flowable.error(new Exception("test")); // } // }).forEach(new Consumer<String>() { // @Override // public void accept(String s) { // System.out.println(s); // } // }); } @Test//(expected = OnErrorNotImplementedException.class) @Ignore("RS subscribers can't throw") public void verifyExceptionIsThrownIfThereIsNoExceptionHandler() { // // Flowable.OnSubscribe<Object> creator = new Flowable.OnSubscribe<Object>() { // // @Override // public void call(Subscriber<? super Object> observer) { // observer.onNext("a"); // observer.onNext("b"); // observer.onNext("c"); // observer.onComplete(); // } // }; // // Func1<Object, Flowable<Object>> manyMapper = new Func1<Object, Flowable<Object>>() { // // @Override // public Flowable<Object> call(Object object) { // return Flowable.just(object); // } // }; // // Func1<Object, Object> mapper = new Func1<Object, Object>() { // private int count = 0; // // @Override // public Object call(Object object) { // ++count; // if (count > 2) { // throw new RuntimeException(); // } // return object; // } // }; // // Action1<Object> onNext = new Action1<Object>() { // // @Override // public void call(Object object) { // System.out.println(object.toString()); // } // }; // // try { // Flowable.create(creator).flatMap(manyMapper).map(mapper).subscribe(onNext); // } catch (RuntimeException e) { // e.printStackTrace(); // throw e; // } } @Test public void functionCrashUnsubscribes() { PublishProcessor<Integer> ps = PublishProcessor.create(); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); ps.map(new Function<Integer, Integer>() { @Override public Integer apply(Integer v) { throw new TestException(); } }).subscribe(ts); Assert.assertTrue("Not subscribed?", ps.hasSubscribers()); ps.onNext(1); Assert.assertFalse("Subscribed?", ps.hasSubscribers()); ts.assertError(TestException.class); } @Test public void mapFilter() { Flowable.range(1, 2) .map(new Function<Integer, Integer>() { @Override public Integer apply(Integer v) throws Exception { return v + 1; } }) .filter(new Predicate<Integer>() { @Override public boolean test(Integer v) throws Exception { return true; } }) .test() .assertResult(2, 3); } @Test public void mapFilterMapperCrash() { Flowable.range(1, 2) .map(new Function<Integer, Integer>() { @Override public Integer apply(Integer v) throws Exception { throw new TestException(); } }) .filter(new Predicate<Integer>() { @Override public boolean test(Integer v) throws Exception { return true; } }) .test() .assertFailure(TestException.class); } @Test public void mapFilterHidden() { Flowable.range(1, 2).hide() .map(new Function<Integer, Integer>() { @Override public Integer apply(Integer v) throws Exception { return v + 1; } }) .filter(new Predicate<Integer>() { @Override public boolean test(Integer v) throws Exception { return true; } }) .test() .assertResult(2, 3); } @Test public void mapFilterFused() { TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); Flowable.range(1, 2) .map(new Function<Integer, Integer>() { @Override public Integer apply(Integer v) throws Exception { return v + 1; } }) .filter(new Predicate<Integer>() { @Override public boolean test(Integer v) throws Exception { return true; } }) .subscribe(ts); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.SYNC)) .assertResult(2, 3); } @Test public void mapFilterFusedHidden() { TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); Flowable.range(1, 2).hide() .map(new Function<Integer, Integer>() { @Override public Integer apply(Integer v) throws Exception { return v + 1; } }) .filter(new Predicate<Integer>() { @Override public boolean test(Integer v) throws Exception { return true; } }) .subscribe(ts); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.NONE)) .assertResult(2, 3); } @Test public void sourceIgnoresCancel() { List<Throwable> errors = TestHelper.trackPluginErrors(); try { Flowable.fromPublisher(new Publisher<Integer>() { @Override public void subscribe(Subscriber<? super Integer> s) { s.onSubscribe(new BooleanSubscription()); s.onNext(1); s.onNext(2); s.onError(new IOException()); s.onComplete(); } }) .map(new Function<Integer, Object>() { @Override public Object apply(Integer v) throws Exception { throw new TestException(); } }) .test() .assertFailure(TestException.class); TestHelper.assertUndeliverable(errors, 0, IOException.class); } finally { RxJavaPlugins.reset(); } } @Test public void mapFilterMapperCrashFused() { TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); Flowable.range(1, 2).hide() .map(new Function<Integer, Integer>() { @Override public Integer apply(Integer v) throws Exception { throw new TestException(); } }) .filter(new Predicate<Integer>() { @Override public boolean test(Integer v) throws Exception { return true; } }) .subscribe(ts); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.NONE)) .assertFailure(TestException.class); } @Test public void sourceIgnoresCancelFilter() { List<Throwable> errors = TestHelper.trackPluginErrors(); try { Flowable.fromPublisher(new Publisher<Integer>() { @Override public void subscribe(Subscriber<? super Integer> s) { s.onSubscribe(new BooleanSubscription()); s.onNext(1); s.onNext(2); s.onError(new IOException()); s.onComplete(); } }) .map(new Function<Integer, Integer>() { @Override public Integer apply(Integer v) throws Exception { throw new TestException(); } }) .filter(new Predicate<Integer>() { @Override public boolean test(Integer v) throws Exception { return true; } }) .test() .assertFailure(TestException.class); TestHelper.assertUndeliverable(errors, 0, IOException.class); } finally { RxJavaPlugins.reset(); } } @Test public void mapFilterFused2() { TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY); UnicastProcessor<Integer> up = UnicastProcessor.create(); up .map(new Function<Integer, Integer>() { @Override public Integer apply(Integer v) throws Exception { return v + 1; } }) .filter(new Predicate<Integer>() { @Override public boolean test(Integer v) throws Exception { return true; } }) .subscribe(ts); up.onNext(1); up.onNext(2); up.onComplete(); ts.assertOf(SubscriberFusion.<Integer>assertFuseable()) .assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC)) .assertResult(2, 3); } @Test public void sourceIgnoresCancelConditional() { List<Throwable> errors = TestHelper.trackPluginErrors(); try { Flowable.fromPublisher(new Publisher<Integer>() { @Override public void subscribe(Subscriber<? super Integer> s) { ConditionalSubscriber<? super Integer> cs = (ConditionalSubscriber<? super Integer>)s; cs.onSubscribe(new BooleanSubscription()); cs.tryOnNext(1); cs.tryOnNext(2); cs.onError(new IOException()); cs.onComplete(); } }) .map(new Function<Integer, Integer>() { @Override public Integer apply(Integer v) throws Exception { throw new TestException(); } }) .filter(new Predicate<Integer>() { @Override public boolean test(Integer v) throws Exception { return true; } }) .test() .assertFailure(TestException.class); TestHelper.assertUndeliverable(errors, 0, IOException.class); } finally { RxJavaPlugins.reset(); } } @Test public void dispose() { TestHelper.checkDisposed(Flowable.range(1, 5).map(Functions.identity())); } @Test public void doubleOnSubscribe() { TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() { @Override public Flowable<Object> apply(Flowable<Object> o) throws Exception { return o.map(Functions.identity()); } }); } @Test public void fusedSync() { TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueDisposable.ANY); Flowable.range(1, 5) .map(Functions.<Integer>identity()) .subscribe(to); SubscriberFusion.assertFusion(to, QueueDisposable.SYNC) .assertResult(1, 2, 3, 4, 5); } @Test public void fusedAsync() { TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueDisposable.ANY); UnicastProcessor<Integer> us = UnicastProcessor.create(); us .map(Functions.<Integer>identity()) .subscribe(to); TestHelper.emit(us, 1, 2, 3, 4, 5); SubscriberFusion.assertFusion(to, QueueDisposable.ASYNC) .assertResult(1, 2, 3, 4, 5); } @Test public void fusedReject() { TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueDisposable.ANY | QueueDisposable.BOUNDARY); Flowable.range(1, 5) .map(Functions.<Integer>identity()) .subscribe(to); SubscriberFusion.assertFusion(to, QueueDisposable.NONE) .assertResult(1, 2, 3, 4, 5); } @Test public void badSource() { TestHelper.checkBadSourceFlowable(new Function<Flowable<Object>, Object>() { @Override public Object apply(Flowable<Object> o) throws Exception { return o.map(Functions.identity()); } }, false, 1, 1, 1); } }