/** * 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; import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import java.lang.reflect.*; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.reactivestreams.*; import io.reactivex.disposables.*; import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.internal.fuseable.*; import io.reactivex.internal.operators.completable.CompletableToFlowable; import io.reactivex.internal.operators.maybe.MaybeToFlowable; import io.reactivex.internal.operators.single.SingleToFlowable; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.internal.util.ExceptionHelper; import io.reactivex.observers.TestObserver; import io.reactivex.parallel.ParallelFlowable; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.Subject; import io.reactivex.subscribers.TestSubscriber; /** * Common methods for helping with tests from 1.x mostly. */ public enum TestHelper { ; /** * Mocks a subscriber and prepares it to request Long.MAX_VALUE. * @param <T> the value type * @return the mocked subscriber */ @SuppressWarnings("unchecked") public static <T> FlowableSubscriber<T> mockSubscriber() { FlowableSubscriber<T> w = mock(FlowableSubscriber.class); Mockito.doAnswer(new Answer<Object>() { @Override public Object answer(InvocationOnMock a) throws Throwable { Subscription s = a.getArgument(0); s.request(Long.MAX_VALUE); return null; } }).when(w).onSubscribe((Subscription)any()); return w; } /** * Mocks an Observer with the proper receiver type. * @param <T> the value type * @return the mocked observer */ @SuppressWarnings("unchecked") public static <T> Observer<T> mockObserver() { return mock(Observer.class); } /** * Mocks an MaybeObserver with the proper receiver type. * @param <T> the value type * @return the mocked observer */ @SuppressWarnings("unchecked") public static <T> MaybeObserver<T> mockMaybeObserver() { return mock(MaybeObserver.class); } /** * Mocks an SingleObserver with the proper receiver type. * @param <T> the value type * @return the mocked observer */ @SuppressWarnings("unchecked") public static <T> SingleObserver<T> mockSingleObserver() { return mock(SingleObserver.class); } /** * Mocks an CompletableObserver. * @return the mocked observer */ public static CompletableObserver mockCompletableObserver() { return mock(CompletableObserver.class); } /** * Validates that the given class, when forcefully instantiated throws * an IllegalArgumentException("No instances!") exception. * @param clazz the class to test, not null */ public static void checkUtilityClass(Class<?> clazz) { try { Constructor<?> c = clazz.getDeclaredConstructor(); c.setAccessible(true); try { c.newInstance(); fail("Should have thrown InvocationTargetException(IllegalStateException)"); } catch (InvocationTargetException ex) { assertEquals("No instances!", ex.getCause().getMessage()); } } catch (Exception ex) { AssertionError ae = new AssertionError(ex.toString()); ae.initCause(ex); throw ae; } } public static List<Throwable> trackPluginErrors() { final List<Throwable> list = Collections.synchronizedList(new ArrayList<Throwable>()); RxJavaPlugins.setErrorHandler(new Consumer<Throwable>() { @Override public void accept(Throwable t) { list.add(t); } }); return list; } public static void assertError(List<Throwable> list, int index, Class<? extends Throwable> clazz) { Throwable ex = list.get(index); if (!clazz.isInstance(ex)) { AssertionError err = new AssertionError(clazz + " expected but got " + list.get(index)); err.initCause(list.get(index)); throw err; } } public static void assertUndeliverable(List<Throwable> list, int index, Class<? extends Throwable> clazz) { Throwable ex = list.get(index); if (!(ex instanceof UndeliverableException)) { AssertionError err = new AssertionError("Outer exception UndeliverableException expected but got " + list.get(index)); err.initCause(list.get(index)); throw err; } ex = ex.getCause(); if (!clazz.isInstance(ex)) { AssertionError err = new AssertionError("Inner exception " + clazz + " expected but got " + list.get(index)); err.initCause(list.get(index)); throw err; } } public static void assertError(List<Throwable> list, int index, Class<? extends Throwable> clazz, String message) { Throwable ex = list.get(index); if (!clazz.isInstance(ex)) { AssertionError err = new AssertionError("Type " + clazz + " expected but got " + ex); err.initCause(ex); throw err; } if (!ObjectHelper.equals(message, ex.getMessage())) { AssertionError err = new AssertionError("Message " + message + " expected but got " + ex.getMessage()); err.initCause(ex); throw err; } } public static void assertUndeliverable(List<Throwable> list, int index, Class<? extends Throwable> clazz, String message) { Throwable ex = list.get(index); if (!(ex instanceof UndeliverableException)) { AssertionError err = new AssertionError("Outer exception UndeliverableException expected but got " + list.get(index)); err.initCause(list.get(index)); throw err; } ex = ex.getCause(); if (!clazz.isInstance(ex)) { AssertionError err = new AssertionError("Inner exception " + clazz + " expected but got " + list.get(index)); err.initCause(list.get(index)); throw err; } if (!ObjectHelper.equals(message, ex.getMessage())) { AssertionError err = new AssertionError("Message " + message + " expected but got " + ex.getMessage()); err.initCause(ex); throw err; } } public static void assertError(TestObserver<?> ts, int index, Class<? extends Throwable> clazz) { Throwable ex = ts.errors().get(0); try { if (ex instanceof CompositeException) { CompositeException ce = (CompositeException) ex; List<Throwable> cel = ce.getExceptions(); assertTrue(cel.get(index).toString(), clazz.isInstance(cel.get(index))); } else { fail(ex.toString() + ": not a CompositeException"); } } catch (AssertionError e) { ex.printStackTrace(); throw e; } } public static void assertError(TestSubscriber<?> ts, int index, Class<? extends Throwable> clazz) { Throwable ex = ts.errors().get(0); if (ex instanceof CompositeException) { CompositeException ce = (CompositeException) ex; List<Throwable> cel = ce.getExceptions(); assertTrue(cel.get(index).toString(), clazz.isInstance(cel.get(index))); } else { fail(ex.toString() + ": not a CompositeException"); } } public static void assertError(TestObserver<?> ts, int index, Class<? extends Throwable> clazz, String message) { Throwable ex = ts.errors().get(0); if (ex instanceof CompositeException) { CompositeException ce = (CompositeException) ex; List<Throwable> cel = ce.getExceptions(); assertTrue(cel.get(index).toString(), clazz.isInstance(cel.get(index))); assertEquals(message, cel.get(index).getMessage()); } else { fail(ex.toString() + ": not a CompositeException"); } } public static void assertError(TestSubscriber<?> ts, int index, Class<? extends Throwable> clazz, String message) { Throwable ex = ts.errors().get(0); if (ex instanceof CompositeException) { CompositeException ce = (CompositeException) ex; List<Throwable> cel = ce.getExceptions(); assertTrue(cel.get(index).toString(), clazz.isInstance(cel.get(index))); assertEquals(message, cel.get(index).getMessage()); } else { fail(ex.toString() + ": not a CompositeException"); } } /** * Verify that a specific enum type has no enum constants. * @param <E> the enum type * @param e the enum class instance */ public static <E extends Enum<E>> void assertEmptyEnum(Class<E> e) { assertEquals(0, e.getEnumConstants().length); try { try { Method m0 = e.getDeclaredMethod("values"); Object[] a = (Object[])m0.invoke(null); assertEquals(0, a.length); Method m = e.getDeclaredMethod("valueOf", String.class); m.invoke("INSTANCE"); fail("Should have thrown!"); } catch (InvocationTargetException ex) { fail(ex.toString()); } catch (IllegalAccessException ex) { fail(ex.toString()); } catch (IllegalArgumentException ex) { // we expected this } } catch (NoSuchMethodException ex) { fail(ex.toString()); } } /** * Assert that by consuming the Publisher with a bad request amount, it is * reported to the plugin error handler promptly. * @param source the source to consume */ public static void assertBadRequestReported(Publisher<?> source) { List<Throwable> list = trackPluginErrors(); try { final CountDownLatch cdl = new CountDownLatch(1); source.subscribe(new FlowableSubscriber<Object>() { @Override public void onSubscribe(Subscription s) { try { s.request(-99); s.cancel(); s.cancel(); } finally { cdl.countDown(); } } @Override public void onNext(Object t) { } @Override public void onError(Throwable t) { } @Override public void onComplete() { } }); try { assertTrue(cdl.await(5, TimeUnit.SECONDS)); } catch (InterruptedException ex) { throw new AssertionError(ex.getMessage()); } assertTrue(list.toString(), list.get(0) instanceof IllegalArgumentException); assertEquals("n > 0 required but it was -99", list.get(0).getMessage()); } finally { RxJavaPlugins.setErrorHandler(null); } } /** * Synchronizes the execution of two runnables (as much as possible) * to test race conditions. * <p>The method blocks until both have run to completion. * @param r1 the first runnable * @param r2 the second runnable */ public static void race(final Runnable r1, final Runnable r2) { race(r1, r2, Schedulers.single()); } /** * Synchronizes the execution of two runnables (as much as possible) * to test race conditions. * <p>The method blocks until both have run to completion. * @param r1 the first runnable * @param r2 the second runnable * @param s the scheduler to use */ public static void race(final Runnable r1, final Runnable r2, Scheduler s) { final AtomicInteger count = new AtomicInteger(2); final CountDownLatch cdl = new CountDownLatch(2); final Throwable[] errors = { null, null }; s.scheduleDirect(new Runnable() { @Override public void run() { if (count.decrementAndGet() != 0) { while (count.get() != 0) { } } try { try { r1.run(); } catch (Throwable ex) { errors[0] = ex; } } finally { cdl.countDown(); } } }); if (count.decrementAndGet() != 0) { while (count.get() != 0) { } } try { try { r2.run(); } catch (Throwable ex) { errors[1] = ex; } } finally { cdl.countDown(); } try { if (!cdl.await(5, TimeUnit.SECONDS)) { throw new AssertionError("The wait timed out!"); } } catch (InterruptedException ex) { throw new RuntimeException(ex); } if (errors[0] != null && errors[1] == null) { throw ExceptionHelper.wrapOrThrow(errors[0]); } if (errors[0] == null && errors[1] != null) { throw ExceptionHelper.wrapOrThrow(errors[1]); } if (errors[0] != null && errors[1] != null) { throw new CompositeException(errors); } } /** * Cast the given Throwable to CompositeException and returns its inner * Throwable list. * @param ex the target Throwable * @return the list of Throwables */ public static List<Throwable> compositeList(Throwable ex) { if (ex instanceof UndeliverableException) { ex = ex.getCause(); } return ((CompositeException)ex).getExceptions(); } /** * Assert that the offer methods throw UnsupportedOperationExcetpion. * @param q the queue implementation */ public static void assertNoOffer(SimpleQueue<?> q) { try { q.offer(null); fail("Should have thrown!"); } catch (UnsupportedOperationException ex) { // expected } try { q.offer(null, null); fail("Should have thrown!"); } catch (UnsupportedOperationException ex) { // expected } } @SuppressWarnings("unchecked") public static <E extends Enum<E>> void checkEnum(Class<E> enumClass) { try { Method m = enumClass.getMethod("values"); m.setAccessible(true); Method e = enumClass.getMethod("valueOf", String.class); m.setAccessible(true); for (Enum<E> o : (Enum<E>[])m.invoke(null)) { assertSame(o, e.invoke(null, o.name())); } } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } } /** * Returns an Consumer that asserts the TestSubscriber has exaclty one value + completed * normally and that single value is not the value specified. * @param <T> the value type * @param value the value not expected * @return the consumer */ public static <T> Consumer<TestSubscriber<T>> subscriberSingleNot(final T value) { return new Consumer<TestSubscriber<T>>() { @Override public void accept(TestSubscriber<T> ts) throws Exception { ts .assertSubscribed() .assertValueCount(1) .assertNoErrors() .assertComplete(); T v = ts.values().get(0); assertNotEquals(value, v); } }; } /** * Returns an Consumer that asserts the TestObserver has exaclty one value + completed * normally and that single value is not the value specified. * @param <T> the value type * @param value the value not expected * @return the consumer */ public static <T> Consumer<TestObserver<T>> observerSingleNot(final T value) { return new Consumer<TestObserver<T>>() { @Override public void accept(TestObserver<T> ts) throws Exception { ts .assertSubscribed() .assertValueCount(1) .assertNoErrors() .assertComplete(); T v = ts.values().get(0); assertNotEquals(value, v); } }; } /** * Calls onSubscribe twice and checks if it doesn't affect the first Subscription while * reporting it to plugin error handler. * @param subscriber the target */ public static void doubleOnSubscribe(Subscriber<?> subscriber) { List<Throwable> errors = trackPluginErrors(); try { BooleanSubscription s1 = new BooleanSubscription(); subscriber.onSubscribe(s1); BooleanSubscription s2 = new BooleanSubscription(); subscriber.onSubscribe(s2); assertFalse(s1.isCancelled()); assertTrue(s2.isCancelled()); assertError(errors, 0, IllegalStateException.class, "Subscription already set!"); } finally { RxJavaPlugins.reset(); } } /** * Calls onSubscribe twice and checks if it doesn't affect the first Disposable while * reporting it to plugin error handler. * @param subscriber the target */ public static void doubleOnSubscribe(Observer<?> subscriber) { List<Throwable> errors = trackPluginErrors(); try { Disposable d1 = Disposables.empty(); subscriber.onSubscribe(d1); Disposable d2 = Disposables.empty(); subscriber.onSubscribe(d2); assertFalse(d1.isDisposed()); assertTrue(d2.isDisposed()); assertError(errors, 0, IllegalStateException.class, "Disposable already set!"); } finally { RxJavaPlugins.reset(); } } /** * Calls onSubscribe twice and checks if it doesn't affect the first Disposable while * reporting it to plugin error handler. * @param subscriber the target */ public static void doubleOnSubscribe(SingleObserver<?> subscriber) { List<Throwable> errors = trackPluginErrors(); try { Disposable d1 = Disposables.empty(); subscriber.onSubscribe(d1); Disposable d2 = Disposables.empty(); subscriber.onSubscribe(d2); assertFalse(d1.isDisposed()); assertTrue(d2.isDisposed()); assertError(errors, 0, IllegalStateException.class, "Disposable already set!"); } finally { RxJavaPlugins.reset(); } } /** * Calls onSubscribe twice and checks if it doesn't affect the first Disposable while * reporting it to plugin error handler. * @param subscriber the target */ public static void doubleOnSubscribe(CompletableObserver subscriber) { List<Throwable> errors = trackPluginErrors(); try { Disposable d1 = Disposables.empty(); subscriber.onSubscribe(d1); Disposable d2 = Disposables.empty(); subscriber.onSubscribe(d2); assertFalse(d1.isDisposed()); assertTrue(d2.isDisposed()); assertError(errors, 0, IllegalStateException.class, "Disposable already set!"); } finally { RxJavaPlugins.reset(); } } /** * Calls onSubscribe twice and checks if it doesn't affect the first Disposable while * reporting it to plugin error handler. * @param subscriber the target */ public static void doubleOnSubscribe(MaybeObserver<?> subscriber) { List<Throwable> errors = trackPluginErrors(); try { Disposable d1 = Disposables.empty(); subscriber.onSubscribe(d1); Disposable d2 = Disposables.empty(); subscriber.onSubscribe(d2); assertFalse(d1.isDisposed()); assertTrue(d2.isDisposed()); assertError(errors, 0, IllegalStateException.class, "Disposable already set!"); } finally { RxJavaPlugins.reset(); } } /** * Checks if the upstream's Subscription sent through the onSubscribe reports * isCancelled properly before and after calling dispose. * @param <T> the input value type * @param source the source to test */ public static <T> void checkDisposed(Flowable<T> source) { final TestSubscriber<Object> ts = new TestSubscriber<Object>(0L); source.subscribe(new FlowableSubscriber<Object>() { @Override public void onSubscribe(Subscription s) { ts.onSubscribe(new BooleanSubscription()); s.cancel(); s.cancel(); } @Override public void onNext(Object t) { ts.onNext(t); } @Override public void onError(Throwable t) { ts.onError(t); } @Override public void onComplete() { ts.onComplete(); } }); ts.assertEmpty(); } /** * Checks if the upstream's Disposable sent through the onSubscribe reports * isDisposed properly before and after calling dispose. * @param source the source to test */ public static void checkDisposed(Maybe<?> source) { final Boolean[] b = { null, null }; final CountDownLatch cdl = new CountDownLatch(1); source.subscribe(new MaybeObserver<Object>() { @Override public void onSubscribe(Disposable d) { try { b[0] = d.isDisposed(); d.dispose(); b[1] = d.isDisposed(); d.dispose(); } finally { cdl.countDown(); } } @Override public void onSuccess(Object value) { // ignored } @Override public void onError(Throwable e) { // ignored } @Override public void onComplete() { // ignored } }); try { assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); } catch (InterruptedException ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertEquals("Reports disposed upfront?", false, b[0]); assertEquals("Didn't report disposed after?", true, b[1]); } /** * Checks if the upstream's Disposable sent through the onSubscribe reports * isDisposed properly before and after calling dispose. * @param source the source to test */ public static void checkDisposed(Observable<?> source) { final Boolean[] b = { null, null }; final CountDownLatch cdl = new CountDownLatch(1); source.subscribe(new Observer<Object>() { @Override public void onSubscribe(Disposable d) { try { b[0] = d.isDisposed(); d.dispose(); b[1] = d.isDisposed(); d.dispose(); } finally { cdl.countDown(); } } @Override public void onNext(Object value) { // ignored } @Override public void onError(Throwable e) { // ignored } @Override public void onComplete() { // ignored } }); try { assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); } catch (InterruptedException ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertEquals("Reports disposed upfront?", false, b[0]); assertEquals("Didn't report disposed after?", true, b[1]); } /** * Checks if the upstream's Disposable sent through the onSubscribe reports * isDisposed properly before and after calling dispose. * @param source the source to test */ public static void checkDisposed(Single<?> source) { final Boolean[] b = { null, null }; final CountDownLatch cdl = new CountDownLatch(1); source.subscribe(new SingleObserver<Object>() { @Override public void onSubscribe(Disposable d) { try { b[0] = d.isDisposed(); d.dispose(); b[1] = d.isDisposed(); d.dispose(); } finally { cdl.countDown(); } } @Override public void onSuccess(Object value) { // ignored } @Override public void onError(Throwable e) { // ignored } }); try { assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); } catch (InterruptedException ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertEquals("Reports disposed upfront?", false, b[0]); assertEquals("Didn't report disposed after?", true, b[1]); } /** * Checks if the upstream's Disposable sent through the onSubscribe reports * isDisposed properly before and after calling dispose. * @param source the source to test */ public static void checkDisposed(Completable source) { final Boolean[] b = { null, null }; final CountDownLatch cdl = new CountDownLatch(1); source.subscribe(new CompletableObserver() { @Override public void onSubscribe(Disposable d) { try { b[0] = d.isDisposed(); d.dispose(); b[1] = d.isDisposed(); d.dispose(); } finally { cdl.countDown(); } } @Override public void onError(Throwable e) { // ignored } @Override public void onComplete() { // ignored } }); try { assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); } catch (InterruptedException ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertEquals("Reports disposed upfront?", false, b[0]); assertEquals("Didn't report disposed after?", true, b[1]); } /** * Consumer for all base reactive types. */ enum NoOpConsumer implements FlowableSubscriber<Object>, Observer<Object>, MaybeObserver<Object>, SingleObserver<Object>, CompletableObserver { INSTANCE; @Override public void onSubscribe(Disposable d) { // deliberately no-op } @Override public void onSuccess(Object value) { // deliberately no-op } @Override public void onError(Throwable e) { // deliberately no-op } @Override public void onComplete() { // deliberately no-op } @Override public void onSubscribe(Subscription s) { // deliberately no-op } @Override public void onNext(Object t) { // deliberately no-op } } /** * Check if the given transformed reactive type reports multiple onSubscribe calls to * RxJavaPlugins. * @param <T> the input value type * @param <R> the output value type * @param transform the transform to drive an operator */ public static <T, R> void checkDoubleOnSubscribeMaybe(Function<Maybe<T>, ? extends MaybeSource<R>> transform) { List<Throwable> errors = trackPluginErrors(); try { final Boolean[] b = { null, null }; final CountDownLatch cdl = new CountDownLatch(1); Maybe<T> source = new Maybe<T>() { @Override protected void subscribeActual(MaybeObserver<? super T> observer) { try { Disposable d1 = Disposables.empty(); observer.onSubscribe(d1); Disposable d2 = Disposables.empty(); observer.onSubscribe(d2); b[0] = d1.isDisposed(); b[1] = d2.isDisposed(); } finally { cdl.countDown(); } } }; MaybeSource<R> out = transform.apply(source); out.subscribe(NoOpConsumer.INSTANCE); try { assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); } catch (InterruptedException ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertEquals("First disposed?", false, b[0]); assertEquals("Second not disposed?", true, b[1]); assertError(errors, 0, IllegalStateException.class, "Disposable already set!"); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } finally { RxJavaPlugins.reset(); } } /** * Check if the given transformed reactive type reports multiple onSubscribe calls to * RxJavaPlugins. * @param <T> the input value type * @param <R> the output value type * @param transform the transform to drive an operator */ public static <T, R> void checkDoubleOnSubscribeMaybeToSingle(Function<Maybe<T>, ? extends SingleSource<R>> transform) { List<Throwable> errors = trackPluginErrors(); try { final Boolean[] b = { null, null }; final CountDownLatch cdl = new CountDownLatch(1); Maybe<T> source = new Maybe<T>() { @Override protected void subscribeActual(MaybeObserver<? super T> observer) { try { Disposable d1 = Disposables.empty(); observer.onSubscribe(d1); Disposable d2 = Disposables.empty(); observer.onSubscribe(d2); b[0] = d1.isDisposed(); b[1] = d2.isDisposed(); } finally { cdl.countDown(); } } }; SingleSource<R> out = transform.apply(source); out.subscribe(NoOpConsumer.INSTANCE); try { assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); } catch (InterruptedException ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertEquals("First disposed?", false, b[0]); assertEquals("Second not disposed?", true, b[1]); assertError(errors, 0, IllegalStateException.class, "Disposable already set!"); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } finally { RxJavaPlugins.reset(); } } /** * Check if the given transformed reactive type reports multiple onSubscribe calls to * RxJavaPlugins. * @param <T> the input value type * @param <R> the output value type * @param transform the transform to drive an operator */ public static <T, R> void checkDoubleOnSubscribeMaybeToObservable(Function<Maybe<T>, ? extends ObservableSource<R>> transform) { List<Throwable> errors = trackPluginErrors(); try { final Boolean[] b = { null, null }; final CountDownLatch cdl = new CountDownLatch(1); Maybe<T> source = new Maybe<T>() { @Override protected void subscribeActual(MaybeObserver<? super T> observer) { try { Disposable d1 = Disposables.empty(); observer.onSubscribe(d1); Disposable d2 = Disposables.empty(); observer.onSubscribe(d2); b[0] = d1.isDisposed(); b[1] = d2.isDisposed(); } finally { cdl.countDown(); } } }; ObservableSource<R> out = transform.apply(source); out.subscribe(NoOpConsumer.INSTANCE); try { assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); } catch (InterruptedException ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertEquals("First disposed?", false, b[0]); assertEquals("Second not disposed?", true, b[1]); assertError(errors, 0, IllegalStateException.class, "Disposable already set!"); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } finally { RxJavaPlugins.reset(); } } /** * Check if the given transformed reactive type reports multiple onSubscribe calls to * RxJavaPlugins. * @param <T> the input value type * @param <R> the output value type * @param transform the transform to drive an operator */ public static <T, R> void checkDoubleOnSubscribeMaybeToFlowable(Function<Maybe<T>, ? extends Publisher<R>> transform) { List<Throwable> errors = trackPluginErrors(); try { final Boolean[] b = { null, null }; final CountDownLatch cdl = new CountDownLatch(1); Maybe<T> source = new Maybe<T>() { @Override protected void subscribeActual(MaybeObserver<? super T> observer) { try { Disposable d1 = Disposables.empty(); observer.onSubscribe(d1); Disposable d2 = Disposables.empty(); observer.onSubscribe(d2); b[0] = d1.isDisposed(); b[1] = d2.isDisposed(); } finally { cdl.countDown(); } } }; Publisher<R> out = transform.apply(source); out.subscribe(NoOpConsumer.INSTANCE); try { assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); } catch (InterruptedException ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertEquals("First disposed?", false, b[0]); assertEquals("Second not disposed?", true, b[1]); assertError(errors, 0, IllegalStateException.class, "Disposable already set!"); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } finally { RxJavaPlugins.reset(); } } /** * Check if the given transformed reactive type reports multiple onSubscribe calls to * RxJavaPlugins. * @param <T> the input value type * @param <R> the output value type * @param transform the transform to drive an operator */ public static <T, R> void checkDoubleOnSubscribeSingleToMaybe(Function<Single<T>, ? extends MaybeSource<R>> transform) { List<Throwable> errors = trackPluginErrors(); try { final Boolean[] b = { null, null }; final CountDownLatch cdl = new CountDownLatch(1); Single<T> source = new Single<T>() { @Override protected void subscribeActual(SingleObserver<? super T> observer) { try { Disposable d1 = Disposables.empty(); observer.onSubscribe(d1); Disposable d2 = Disposables.empty(); observer.onSubscribe(d2); b[0] = d1.isDisposed(); b[1] = d2.isDisposed(); } finally { cdl.countDown(); } } }; MaybeSource<R> out = transform.apply(source); out.subscribe(NoOpConsumer.INSTANCE); try { assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); } catch (InterruptedException ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertEquals("First disposed?", false, b[0]); assertEquals("Second not disposed?", true, b[1]); assertError(errors, 0, IllegalStateException.class, "Disposable already set!"); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } finally { RxJavaPlugins.reset(); } } /** * Check if the given transformed reactive type reports multiple onSubscribe calls to * RxJavaPlugins. * @param <T> the input value type * @param <R> the output value type * @param transform the transform to drive an operator */ public static <T, R> void checkDoubleOnSubscribeSingleToObservable(Function<Single<T>, ? extends ObservableSource<R>> transform) { List<Throwable> errors = trackPluginErrors(); try { final Boolean[] b = { null, null }; final CountDownLatch cdl = new CountDownLatch(1); Single<T> source = new Single<T>() { @Override protected void subscribeActual(SingleObserver<? super T> observer) { try { Disposable d1 = Disposables.empty(); observer.onSubscribe(d1); Disposable d2 = Disposables.empty(); observer.onSubscribe(d2); b[0] = d1.isDisposed(); b[1] = d2.isDisposed(); } finally { cdl.countDown(); } } }; ObservableSource<R> out = transform.apply(source); out.subscribe(NoOpConsumer.INSTANCE); try { assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); } catch (InterruptedException ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertEquals("First disposed?", false, b[0]); assertEquals("Second not disposed?", true, b[1]); assertError(errors, 0, IllegalStateException.class, "Disposable already set!"); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } finally { RxJavaPlugins.reset(); } } /** * Check if the given transformed reactive type reports multiple onSubscribe calls to * RxJavaPlugins. * @param <T> the input value type * @param transform the transform to drive an operator */ public static <T> void checkDoubleOnSubscribeMaybeToCompletable(Function<Maybe<T>, ? extends CompletableSource> transform) { List<Throwable> errors = trackPluginErrors(); try { final Boolean[] b = { null, null }; final CountDownLatch cdl = new CountDownLatch(1); Maybe<T> source = new Maybe<T>() { @Override protected void subscribeActual(MaybeObserver<? super T> observer) { try { Disposable d1 = Disposables.empty(); observer.onSubscribe(d1); Disposable d2 = Disposables.empty(); observer.onSubscribe(d2); b[0] = d1.isDisposed(); b[1] = d2.isDisposed(); } finally { cdl.countDown(); } } }; CompletableSource out = transform.apply(source); out.subscribe(NoOpConsumer.INSTANCE); try { assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); } catch (InterruptedException ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertEquals("First disposed?", false, b[0]); assertEquals("Second not disposed?", true, b[1]); assertError(errors, 0, IllegalStateException.class, "Disposable already set!"); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } finally { RxJavaPlugins.reset(); } } /** * Check if the given transformed reactive type reports multiple onSubscribe calls to * RxJavaPlugins. * @param <T> the input value type * @param <R> the output value type * @param transform the transform to drive an operator */ public static <T, R> void checkDoubleOnSubscribeSingle(Function<Single<T>, ? extends SingleSource<R>> transform) { List<Throwable> errors = trackPluginErrors(); try { final Boolean[] b = { null, null }; final CountDownLatch cdl = new CountDownLatch(1); Single<T> source = new Single<T>() { @Override protected void subscribeActual(SingleObserver<? super T> observer) { try { Disposable d1 = Disposables.empty(); observer.onSubscribe(d1); Disposable d2 = Disposables.empty(); observer.onSubscribe(d2); b[0] = d1.isDisposed(); b[1] = d2.isDisposed(); } finally { cdl.countDown(); } } }; SingleSource<R> out = transform.apply(source); out.subscribe(NoOpConsumer.INSTANCE); try { assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); } catch (InterruptedException ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertEquals("First disposed?", false, b[0]); assertEquals("Second not disposed?", true, b[1]); assertError(errors, 0, IllegalStateException.class, "Disposable already set!"); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } finally { RxJavaPlugins.reset(); } } /** * Check if the given transformed reactive type reports multiple onSubscribe calls to * RxJavaPlugins. * @param <T> the input value type * @param <R> the output value type * @param transform the transform to drive an operator */ public static <T, R> void checkDoubleOnSubscribeFlowable(Function<Flowable<T>, ? extends Publisher<R>> transform) { List<Throwable> errors = trackPluginErrors(); try { final Boolean[] b = { null, null }; final CountDownLatch cdl = new CountDownLatch(1); Flowable<T> source = new Flowable<T>() { @Override protected void subscribeActual(Subscriber<? super T> subscriber) { try { BooleanSubscription d1 = new BooleanSubscription(); subscriber.onSubscribe(d1); BooleanSubscription d2 = new BooleanSubscription(); subscriber.onSubscribe(d2); b[0] = d1.isCancelled(); b[1] = d2.isCancelled(); } finally { cdl.countDown(); } } }; Publisher<R> out = transform.apply(source); out.subscribe(NoOpConsumer.INSTANCE); try { assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); } catch (InterruptedException ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertEquals("First disposed?", false, b[0]); assertEquals("Second not disposed?", true, b[1]); assertError(errors, 0, IllegalStateException.class, "Subscription already set!"); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } finally { RxJavaPlugins.reset(); } } /** * Check if the given transformed reactive type reports multiple onSubscribe calls to * RxJavaPlugins. * @param <T> the input value type * @param <R> the output value type * @param transform the transform to drive an operator */ public static <T, R> void checkDoubleOnSubscribeObservable(Function<Observable<T>, ? extends ObservableSource<R>> transform) { List<Throwable> errors = trackPluginErrors(); try { final Boolean[] b = { null, null }; final CountDownLatch cdl = new CountDownLatch(1); Observable<T> source = new Observable<T>() { @Override protected void subscribeActual(Observer<? super T> observer) { try { Disposable d1 = Disposables.empty(); observer.onSubscribe(d1); Disposable d2 = Disposables.empty(); observer.onSubscribe(d2); b[0] = d1.isDisposed(); b[1] = d2.isDisposed(); } finally { cdl.countDown(); } } }; ObservableSource<R> out = transform.apply(source); out.subscribe(NoOpConsumer.INSTANCE); try { assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); } catch (InterruptedException ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertEquals("First disposed?", false, b[0]); assertEquals("Second not disposed?", true, b[1]); assertError(errors, 0, IllegalStateException.class, "Disposable already set!"); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } finally { RxJavaPlugins.reset(); } } /** * Check if the given transformed reactive type reports multiple onSubscribe calls to * RxJavaPlugins. * @param <T> the input value type * @param <R> the output value type * @param transform the transform to drive an operator */ public static <T, R> void checkDoubleOnSubscribeObservableToSingle(Function<Observable<T>, ? extends SingleSource<R>> transform) { List<Throwable> errors = trackPluginErrors(); try { final Boolean[] b = { null, null }; final CountDownLatch cdl = new CountDownLatch(1); Observable<T> source = new Observable<T>() { @Override protected void subscribeActual(Observer<? super T> observer) { try { Disposable d1 = Disposables.empty(); observer.onSubscribe(d1); Disposable d2 = Disposables.empty(); observer.onSubscribe(d2); b[0] = d1.isDisposed(); b[1] = d2.isDisposed(); } finally { cdl.countDown(); } } }; SingleSource<R> out = transform.apply(source); out.subscribe(NoOpConsumer.INSTANCE); try { assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); } catch (InterruptedException ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertEquals("First disposed?", false, b[0]); assertEquals("Second not disposed?", true, b[1]); assertError(errors, 0, IllegalStateException.class, "Disposable already set!"); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } finally { RxJavaPlugins.reset(); } } /** * Check if the given transformed reactive type reports multiple onSubscribe calls to * RxJavaPlugins. * @param <T> the input value type * @param <R> the output value type * @param transform the transform to drive an operator */ public static <T, R> void checkDoubleOnSubscribeObservableToMaybe(Function<Observable<T>, ? extends MaybeSource<R>> transform) { List<Throwable> errors = trackPluginErrors(); try { final Boolean[] b = { null, null }; final CountDownLatch cdl = new CountDownLatch(1); Observable<T> source = new Observable<T>() { @Override protected void subscribeActual(Observer<? super T> observer) { try { Disposable d1 = Disposables.empty(); observer.onSubscribe(d1); Disposable d2 = Disposables.empty(); observer.onSubscribe(d2); b[0] = d1.isDisposed(); b[1] = d2.isDisposed(); } finally { cdl.countDown(); } } }; MaybeSource<R> out = transform.apply(source); out.subscribe(NoOpConsumer.INSTANCE); try { assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); } catch (InterruptedException ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertEquals("First disposed?", false, b[0]); assertEquals("Second not disposed?", true, b[1]); assertError(errors, 0, IllegalStateException.class, "Disposable already set!"); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } finally { RxJavaPlugins.reset(); } } /** * Check if the given transformed reactive type reports multiple onSubscribe calls to * RxJavaPlugins. * @param <T> the input value type * @param transform the transform to drive an operator */ public static <T> void checkDoubleOnSubscribeObservableToCompletable(Function<Observable<T>, ? extends CompletableSource> transform) { List<Throwable> errors = trackPluginErrors(); try { final Boolean[] b = { null, null }; final CountDownLatch cdl = new CountDownLatch(1); Observable<T> source = new Observable<T>() { @Override protected void subscribeActual(Observer<? super T> observer) { try { Disposable d1 = Disposables.empty(); observer.onSubscribe(d1); Disposable d2 = Disposables.empty(); observer.onSubscribe(d2); b[0] = d1.isDisposed(); b[1] = d2.isDisposed(); } finally { cdl.countDown(); } } }; CompletableSource out = transform.apply(source); out.subscribe(NoOpConsumer.INSTANCE); try { assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); } catch (InterruptedException ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertEquals("First disposed?", false, b[0]); assertEquals("Second not disposed?", true, b[1]); assertError(errors, 0, IllegalStateException.class, "Disposable already set!"); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } finally { RxJavaPlugins.reset(); } } /** * Check if the given transformed reactive type reports multiple onSubscribe calls to * RxJavaPlugins. * @param <T> the input value type * @param <R> the output value type * @param transform the transform to drive an operator */ public static <T, R> void checkDoubleOnSubscribeFlowableToObservable(Function<Flowable<T>, ? extends ObservableSource<R>> transform) { List<Throwable> errors = trackPluginErrors(); try { final Boolean[] b = { null, null }; final CountDownLatch cdl = new CountDownLatch(1); Flowable<T> source = new Flowable<T>() { @Override protected void subscribeActual(Subscriber<? super T> observer) { try { BooleanSubscription d1 = new BooleanSubscription(); observer.onSubscribe(d1); BooleanSubscription d2 = new BooleanSubscription(); observer.onSubscribe(d2); b[0] = d1.isCancelled(); b[1] = d2.isCancelled(); } finally { cdl.countDown(); } } }; ObservableSource<R> out = transform.apply(source); out.subscribe(NoOpConsumer.INSTANCE); try { assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); } catch (InterruptedException ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertEquals("First cancelled?", false, b[0]); assertEquals("Second not cancelled?", true, b[1]); assertError(errors, 0, IllegalStateException.class, "Subscription already set!"); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } finally { RxJavaPlugins.reset(); } } /** * Check if the given transformed reactive type reports multiple onSubscribe calls to * RxJavaPlugins. * @param <T> the input value type * @param <R> the output value type * @param transform the transform to drive an operator */ public static <T, R> void checkDoubleOnSubscribeFlowableToSingle(Function<Flowable<T>, ? extends SingleSource<R>> transform) { List<Throwable> errors = trackPluginErrors(); try { final Boolean[] b = { null, null }; final CountDownLatch cdl = new CountDownLatch(1); Flowable<T> source = new Flowable<T>() { @Override protected void subscribeActual(Subscriber<? super T> observer) { try { BooleanSubscription d1 = new BooleanSubscription(); observer.onSubscribe(d1); BooleanSubscription d2 = new BooleanSubscription(); observer.onSubscribe(d2); b[0] = d1.isCancelled(); b[1] = d2.isCancelled(); } finally { cdl.countDown(); } } }; SingleSource<R> out = transform.apply(source); out.subscribe(NoOpConsumer.INSTANCE); try { assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); } catch (InterruptedException ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertEquals("First cancelled?", false, b[0]); assertEquals("Second not cancelled?", true, b[1]); assertError(errors, 0, IllegalStateException.class, "Subscription already set!"); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } finally { RxJavaPlugins.reset(); } } /** * Check if the given transformed reactive type reports multiple onSubscribe calls to * RxJavaPlugins. * @param <T> the input value type * @param <R> the output value type * @param transform the transform to drive an operator */ public static <T, R> void checkDoubleOnSubscribeFlowableToMaybe(Function<Flowable<T>, ? extends MaybeSource<R>> transform) { List<Throwable> errors = trackPluginErrors(); try { final Boolean[] b = { null, null }; final CountDownLatch cdl = new CountDownLatch(1); Flowable<T> source = new Flowable<T>() { @Override protected void subscribeActual(Subscriber<? super T> observer) { try { BooleanSubscription d1 = new BooleanSubscription(); observer.onSubscribe(d1); BooleanSubscription d2 = new BooleanSubscription(); observer.onSubscribe(d2); b[0] = d1.isCancelled(); b[1] = d2.isCancelled(); } finally { cdl.countDown(); } } }; MaybeSource<R> out = transform.apply(source); out.subscribe(NoOpConsumer.INSTANCE); try { assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); } catch (InterruptedException ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertEquals("First cancelled?", false, b[0]); assertEquals("Second not cancelled?", true, b[1]); assertError(errors, 0, IllegalStateException.class, "Subscription already set!"); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } finally { RxJavaPlugins.reset(); } } /** * Check if the given transformed reactive type reports multiple onSubscribe calls to * RxJavaPlugins. * @param <T> the input value type * @param transform the transform to drive an operator */ public static <T> void checkDoubleOnSubscribeFlowableToCompletable(Function<Flowable<T>, ? extends Completable> transform) { List<Throwable> errors = trackPluginErrors(); try { final Boolean[] b = { null, null }; final CountDownLatch cdl = new CountDownLatch(1); Flowable<T> source = new Flowable<T>() { @Override protected void subscribeActual(Subscriber<? super T> observer) { try { BooleanSubscription d1 = new BooleanSubscription(); observer.onSubscribe(d1); BooleanSubscription d2 = new BooleanSubscription(); observer.onSubscribe(d2); b[0] = d1.isCancelled(); b[1] = d2.isCancelled(); } finally { cdl.countDown(); } } }; Completable out = transform.apply(source); out.subscribe(NoOpConsumer.INSTANCE); try { assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); } catch (InterruptedException ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertEquals("First cancelled?", false, b[0]); assertEquals("Second not cancelled?", true, b[1]); assertError(errors, 0, IllegalStateException.class, "Subscription already set!"); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } finally { RxJavaPlugins.reset(); } } /** * Check if the given transformed reactive type reports multiple onSubscribe calls to * RxJavaPlugins. * @param transform the transform to drive an operator */ public static void checkDoubleOnSubscribeCompletable(Function<Completable, ? extends CompletableSource> transform) { List<Throwable> errors = trackPluginErrors(); try { final Boolean[] b = { null, null }; final CountDownLatch cdl = new CountDownLatch(1); Completable source = new Completable() { @Override protected void subscribeActual(CompletableObserver observer) { try { Disposable d1 = Disposables.empty(); observer.onSubscribe(d1); Disposable d2 = Disposables.empty(); observer.onSubscribe(d2); b[0] = d1.isDisposed(); b[1] = d2.isDisposed(); } finally { cdl.countDown(); } } }; CompletableSource out = transform.apply(source); out.subscribe(NoOpConsumer.INSTANCE); try { assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); } catch (InterruptedException ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertEquals("First disposed?", false, b[0]); assertEquals("Second not disposed?", true, b[1]); assertError(errors, 0, IllegalStateException.class, "Disposable already set!"); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } finally { RxJavaPlugins.reset(); } } /** * Check if the given transformed reactive type reports multiple onSubscribe calls to * RxJavaPlugins. * @param <T> the output value tye * @param transform the transform to drive an operator */ public static <T> void checkDoubleOnSubscribeCompletableToMaybe(Function<Completable, ? extends MaybeSource<T>> transform) { List<Throwable> errors = trackPluginErrors(); try { final Boolean[] b = { null, null }; final CountDownLatch cdl = new CountDownLatch(1); Completable source = new Completable() { @Override protected void subscribeActual(CompletableObserver observer) { try { Disposable d1 = Disposables.empty(); observer.onSubscribe(d1); Disposable d2 = Disposables.empty(); observer.onSubscribe(d2); b[0] = d1.isDisposed(); b[1] = d2.isDisposed(); } finally { cdl.countDown(); } } }; MaybeSource<T> out = transform.apply(source); out.subscribe(NoOpConsumer.INSTANCE); try { assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); } catch (InterruptedException ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertEquals("First disposed?", false, b[0]); assertEquals("Second not disposed?", true, b[1]); assertError(errors, 0, IllegalStateException.class, "Disposable already set!"); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } finally { RxJavaPlugins.reset(); } } /** * Check if the given transformed reactive type reports multiple onSubscribe calls to * RxJavaPlugins. * @param <T> the output value tye * @param transform the transform to drive an operator */ public static <T> void checkDoubleOnSubscribeCompletableToSingle(Function<Completable, ? extends SingleSource<T>> transform) { List<Throwable> errors = trackPluginErrors(); try { final Boolean[] b = { null, null }; final CountDownLatch cdl = new CountDownLatch(1); Completable source = new Completable() { @Override protected void subscribeActual(CompletableObserver observer) { try { Disposable d1 = Disposables.empty(); observer.onSubscribe(d1); Disposable d2 = Disposables.empty(); observer.onSubscribe(d2); b[0] = d1.isDisposed(); b[1] = d2.isDisposed(); } finally { cdl.countDown(); } } }; SingleSource<T> out = transform.apply(source); out.subscribe(NoOpConsumer.INSTANCE); try { assertTrue("Timed out", cdl.await(5, TimeUnit.SECONDS)); } catch (InterruptedException ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertEquals("First disposed?", false, b[0]); assertEquals("Second not disposed?", true, b[1]); assertError(errors, 0, IllegalStateException.class, "Disposable already set!"); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } finally { RxJavaPlugins.reset(); } } /** * Check if the operator applied to a Maybe source propagates dispose properly. * @param <T> the source value type * @param <U> the output value type * @param composer the function to apply an operator to the provided Maybe source */ public static <T, U> void checkDisposedMaybe(Function<Maybe<T>, ? extends MaybeSource<U>> composer) { PublishProcessor<T> pp = PublishProcessor.create(); TestSubscriber<U> ts = new TestSubscriber<U>(); try { new MaybeToFlowable<U>(composer.apply(pp.singleElement())).subscribe(ts); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertTrue("Not subscribed to source!", pp.hasSubscribers()); ts.cancel(); assertFalse("Dispose not propagated!", pp.hasSubscribers()); } /** * Check if the operator applied to a Completable source propagates dispose properly. * @param composer the function to apply an operator to the provided Completable source */ public static void checkDisposedCompletable(Function<Completable, ? extends CompletableSource> composer) { PublishProcessor<Integer> pp = PublishProcessor.create(); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); try { new CompletableToFlowable<Integer>(composer.apply(pp.ignoreElements())).subscribe(ts); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertTrue("Not subscribed to source!", pp.hasSubscribers()); ts.cancel(); assertFalse("Dispose not propagated!", pp.hasSubscribers()); } /** * Check if the operator applied to a Maybe source propagates dispose properly. * @param <T> the source value type * @param <U> the output value type * @param composer the function to apply an operator to the provided Maybe source */ public static <T, U> void checkDisposedMaybeToSingle(Function<Maybe<T>, ? extends SingleSource<U>> composer) { PublishProcessor<T> pp = PublishProcessor.create(); TestSubscriber<U> ts = new TestSubscriber<U>(); try { new SingleToFlowable<U>(composer.apply(pp.singleElement())).subscribe(ts); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } assertTrue(pp.hasSubscribers()); ts.cancel(); assertFalse(pp.hasSubscribers()); } /** * Check if the TestSubscriber has a CompositeException with the specified class * of Throwables in the given order. * @param ts the TestSubscriber instance * @param classes the array of expected Throwables inside the Composite */ public static void assertCompositeExceptions(TestSubscriber<?> ts, Class<? extends Throwable>... classes) { ts .assertSubscribed() .assertError(CompositeException.class) .assertNotComplete(); List<Throwable> list = compositeList(ts.errors().get(0)); assertEquals(classes.length, list.size()); for (int i = 0; i < classes.length; i++) { assertError(list, i, classes[i]); } } /** * Check if the TestSubscriber has a CompositeException with the specified class * of Throwables in the given order. * @param ts the TestSubscriber instance * @param classes the array of subsequent Class and String instances representing the * expected Throwable class and the expected error message */ @SuppressWarnings("unchecked") public static void assertCompositeExceptions(TestSubscriber<?> ts, Object... classes) { ts .assertSubscribed() .assertError(CompositeException.class) .assertNotComplete(); List<Throwable> list = compositeList(ts.errors().get(0)); assertEquals(classes.length, list.size()); for (int i = 0; i < classes.length; i += 2) { assertError(list, i, (Class<Throwable>)classes[i], (String)classes[i + 1]); } } /** * Check if the TestSubscriber has a CompositeException with the specified class * of Throwables in the given order. * @param ts the TestSubscriber instance * @param classes the array of expected Throwables inside the Composite */ public static void assertCompositeExceptions(TestObserver<?> ts, Class<? extends Throwable>... classes) { ts .assertSubscribed() .assertError(CompositeException.class) .assertNotComplete(); List<Throwable> list = compositeList(ts.errors().get(0)); assertEquals(classes.length, list.size()); for (int i = 0; i < classes.length; i++) { assertError(list, i, classes[i]); } } /** * Check if the TestSubscriber has a CompositeException with the specified class * of Throwables in the given order. * @param ts the TestSubscriber instance * @param classes the array of subsequent Class and String instances representing the * expected Throwable class and the expected error message */ @SuppressWarnings("unchecked") public static void assertCompositeExceptions(TestObserver<?> ts, Object... classes) { ts .assertSubscribed() .assertError(CompositeException.class) .assertNotComplete(); List<Throwable> list = compositeList(ts.errors().get(0)); assertEquals(classes.length, list.size()); for (int i = 0; i < classes.length; i += 2) { assertError(list, i, (Class<Throwable>)classes[i], (String)classes[i + 1]); } } /** * Emit the given values and complete the Processor. * @param <T> the value type * @param p the target processor * @param values the values to emit */ public static <T> void emit(Processor<T, ?> p, T... values) { for (T v : values) { p.onNext(v); } p.onComplete(); } /** * Emit the given values and complete the Subject. * @param <T> the value type * @param p the target subject * @param values the values to emit */ public static <T> void emit(Subject<T> p, T... values) { for (T v : values) { p.onNext(v); } p.onComplete(); } /** * Checks if the source is fuseable and its isEmpty/clear works properly. * @param <T> the value type * @param source the source sequence */ public static <T> void checkFusedIsEmptyClear(Observable<T> source) { final CountDownLatch cdl = new CountDownLatch(1); final Boolean[] state = { null, null, null, null }; source.subscribe(new Observer<T>() { @Override public void onSubscribe(Disposable d) { try { if (d instanceof QueueDisposable) { @SuppressWarnings("unchecked") QueueDisposable<Object> qd = (QueueDisposable<Object>) d; state[0] = true; int m = qd.requestFusion(QueueDisposable.ANY); if (m != QueueDisposable.NONE) { state[1] = true; state[2] = qd.isEmpty(); qd.clear(); state[3] = qd.isEmpty(); } } cdl.countDown(); } finally { d.dispose(); } } @Override public void onNext(T value) { } @Override public void onError(Throwable e) { } @Override public void onComplete() { } }); try { assertTrue(cdl.await(5, TimeUnit.SECONDS)); assertTrue("Not fuseable", state[0]); assertTrue("Fusion rejected", state[1]); assertNotNull(state[2]); assertTrue("Did not empty", state[3]); } catch (InterruptedException ex) { throw new RuntimeException(ex); } } /** * Checks if the source is fuseable and its isEmpty/clear works properly. * @param <T> the value type * @param source the source sequence */ public static <T> void checkFusedIsEmptyClear(Flowable<T> source) { final CountDownLatch cdl = new CountDownLatch(1); final Boolean[] state = { null, null, null, null }; source.subscribe(new FlowableSubscriber<T>() { @Override public void onSubscribe(Subscription d) { try { if (d instanceof QueueSubscription) { @SuppressWarnings("unchecked") QueueSubscription<Object> qd = (QueueSubscription<Object>) d; state[0] = true; int m = qd.requestFusion(QueueSubscription.ANY); if (m != QueueSubscription.NONE) { state[1] = true; state[2] = qd.isEmpty(); qd.clear(); state[3] = qd.isEmpty(); } } cdl.countDown(); } finally { d.cancel(); } } @Override public void onNext(T value) { } @Override public void onError(Throwable e) { } @Override public void onComplete() { } }); try { assertTrue(cdl.await(5, TimeUnit.SECONDS)); assertTrue("Not fuseable", state[0]); assertTrue("Fusion rejected", state[1]); assertNotNull(state[2]); assertTrue("Did not empty", state[3]); } catch (InterruptedException ex) { throw new RuntimeException(ex); } } /** * Returns an expanded error list of the given test consumer. * @param to the test consumer instance * @return the list */ public static List<Throwable> errorList(TestObserver<?> to) { return compositeList(to.errors().get(0)); } /** * Returns an expanded error list of the given test consumer. * @param to the test consumer instance * @return the list */ public static List<Throwable> errorList(TestSubscriber<?> to) { return compositeList(to.errors().get(0)); } /** * Tests the given mapping of a bad Observable by emitting the good values, then an error/completion and then * a bad value followed by a TestException and and a completion. * @param <T> the value type * @param mapper the mapper that receives a bad Observable and returns a reactive base type (detected via reflection). * @param error if true, the good value emission is followed by a TestException("error"), if false then onComplete is called * @param badValue the bad value to emit if not null * @param goodValue the good value to emit before turning bad, if not null * @param expected the expected resulting values, null to ignore values received */ public static <T> void checkBadSourceObservable(Function<Observable<T>, Object> mapper, final boolean error, final T goodValue, final T badValue, final Object... expected) { List<Throwable> errors = trackPluginErrors(); try { Observable<T> bad = new Observable<T>() { boolean once; @Override protected void subscribeActual(Observer<? super T> observer) { observer.onSubscribe(Disposables.empty()); if (once) { return; } once = true; if (goodValue != null) { observer.onNext(goodValue); } if (error) { observer.onError(new TestException("error")); } else { observer.onComplete(); } if (badValue != null) { observer.onNext(badValue); } observer.onError(new TestException("second")); observer.onComplete(); } }; Object o = mapper.apply(bad); if (o instanceof ObservableSource) { ObservableSource<?> os = (ObservableSource<?>) o; TestObserver<Object> to = new TestObserver<Object>(); os.subscribe(to); to.awaitDone(5, TimeUnit.SECONDS); to.assertSubscribed(); if (expected != null) { to.assertValues(expected); } if (error) { to.assertError(TestException.class) .assertErrorMessage("error") .assertNotComplete(); } else { to.assertNoErrors().assertComplete(); } } if (o instanceof Publisher) { Publisher<?> os = (Publisher<?>) o; TestSubscriber<Object> to = new TestSubscriber<Object>(); os.subscribe(to); to.awaitDone(5, TimeUnit.SECONDS); to.assertSubscribed(); if (expected != null) { to.assertValues(expected); } if (error) { to.assertError(TestException.class) .assertErrorMessage("error") .assertNotComplete(); } else { to.assertNoErrors().assertComplete(); } } if (o instanceof SingleSource) { SingleSource<?> os = (SingleSource<?>) o; TestObserver<Object> to = new TestObserver<Object>(); os.subscribe(to); to.awaitDone(5, TimeUnit.SECONDS); to.assertSubscribed(); if (expected != null) { to.assertValues(expected); } if (error) { to.assertError(TestException.class) .assertErrorMessage("error") .assertNotComplete(); } else { to.assertNoErrors().assertComplete(); } } if (o instanceof MaybeSource) { MaybeSource<?> os = (MaybeSource<?>) o; TestObserver<Object> to = new TestObserver<Object>(); os.subscribe(to); to.awaitDone(5, TimeUnit.SECONDS); to.assertSubscribed(); if (expected != null) { to.assertValues(expected); } if (error) { to.assertError(TestException.class) .assertErrorMessage("error") .assertNotComplete(); } else { to.assertNoErrors().assertComplete(); } } if (o instanceof CompletableSource) { CompletableSource os = (CompletableSource) o; TestObserver<Object> to = new TestObserver<Object>(); os.subscribe(to); to.awaitDone(5, TimeUnit.SECONDS); to.assertSubscribed(); if (expected != null) { to.assertValues(expected); } if (error) { to.assertError(TestException.class) .assertErrorMessage("error") .assertNotComplete(); } else { to.assertNoErrors().assertComplete(); } } assertUndeliverable(errors, 0, TestException.class, "second"); } catch (AssertionError ex) { throw ex; } catch (Throwable ex) { throw new RuntimeException(ex); } finally { RxJavaPlugins.reset(); } } /** * Tests the given mapping of a bad Observable by emitting the good values, then an error/completion and then * a bad value followed by a TestException and and a completion. * @param <T> the value type * @param mapper the mapper that receives a bad Observable and returns a reactive base type (detected via reflection). * @param error if true, the good value emission is followed by a TestException("error"), if false then onComplete is called * @param badValue the bad value to emit if not null * @param goodValue the good value to emit before turning bad, if not null * @param expected the expected resulting values, null to ignore values received */ public static <T> void checkBadSourceFlowable(Function<Flowable<T>, Object> mapper, final boolean error, final T goodValue, final T badValue, final Object... expected) { List<Throwable> errors = trackPluginErrors(); try { Flowable<T> bad = new Flowable<T>() { @Override protected void subscribeActual(Subscriber<? super T> observer) { observer.onSubscribe(new BooleanSubscription()); if (goodValue != null) { observer.onNext(goodValue); } if (error) { observer.onError(new TestException("error")); } else { observer.onComplete(); } if (badValue != null) { observer.onNext(badValue); } observer.onError(new TestException("second")); observer.onComplete(); } }; Object o = mapper.apply(bad); if (o instanceof ObservableSource) { ObservableSource<?> os = (ObservableSource<?>) o; TestObserver<Object> to = new TestObserver<Object>(); os.subscribe(to); to.awaitDone(5, TimeUnit.SECONDS); to.assertSubscribed(); if (expected != null) { to.assertValues(expected); } if (error) { to.assertError(TestException.class) .assertErrorMessage("error") .assertNotComplete(); } else { to.assertNoErrors().assertComplete(); } } if (o instanceof Publisher) { Publisher<?> os = (Publisher<?>) o; TestSubscriber<Object> to = new TestSubscriber<Object>(); os.subscribe(to); to.awaitDone(5, TimeUnit.SECONDS); to.assertSubscribed(); if (expected != null) { to.assertValues(expected); } if (error) { to.assertError(TestException.class) .assertErrorMessage("error") .assertNotComplete(); } else { to.assertNoErrors().assertComplete(); } } if (o instanceof SingleSource) { SingleSource<?> os = (SingleSource<?>) o; TestObserver<Object> to = new TestObserver<Object>(); os.subscribe(to); to.awaitDone(5, TimeUnit.SECONDS); to.assertSubscribed(); if (expected != null) { to.assertValues(expected); } if (error) { to.assertError(TestException.class) .assertErrorMessage("error") .assertNotComplete(); } else { to.assertNoErrors().assertComplete(); } } if (o instanceof MaybeSource) { MaybeSource<?> os = (MaybeSource<?>) o; TestObserver<Object> to = new TestObserver<Object>(); os.subscribe(to); to.awaitDone(5, TimeUnit.SECONDS); to.assertSubscribed(); if (expected != null) { to.assertValues(expected); } if (error) { to.assertError(TestException.class) .assertErrorMessage("error") .assertNotComplete(); } else { to.assertNoErrors().assertComplete(); } } if (o instanceof CompletableSource) { CompletableSource os = (CompletableSource) o; TestObserver<Object> to = new TestObserver<Object>(); os.subscribe(to); to.awaitDone(5, TimeUnit.SECONDS); to.assertSubscribed(); if (expected != null) { to.assertValues(expected); } if (error) { to.assertError(TestException.class) .assertErrorMessage("error") .assertNotComplete(); } else { to.assertNoErrors().assertComplete(); } } assertUndeliverable(errors, 0, TestException.class, "second"); } catch (AssertionError ex) { throw ex; } catch (Throwable ex) { throw new RuntimeException(ex); } finally { RxJavaPlugins.reset(); } } public static <T> void checkInvalidParallelSubscribers(ParallelFlowable<T> source) { int n = source.parallelism(); @SuppressWarnings("unchecked") TestSubscriber<Object>[] tss = new TestSubscriber[n + 1]; for (int i = 0; i <= n; i++) { tss[i] = new TestSubscriber<Object>().withTag("" + i); } source.subscribe(tss); for (int i = 0; i <= n; i++) { tss[i].assertFailure(IllegalArgumentException.class); } } }