From 84d3e00e99f369f2bb681f9fe00b5ae50c30bb7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Karnok?= Date: Thu, 15 Mar 2018 11:16:04 +0100 Subject: [PATCH] 2.x: Improve the scalar source perf of Obs.(concat|switch)MapX --- .../operators/maybe/MaybeToObservable.java | 19 ++- .../mixed/ObservableConcatMapCompletable.java | 31 +--- .../mixed/ObservableConcatMapMaybe.java | 4 +- .../mixed/ObservableConcatMapSingle.java | 4 +- .../mixed/ObservableSwitchMapCompletable.java | 4 +- .../mixed/ObservableSwitchMapMaybe.java | 4 +- .../mixed/ObservableSwitchMapSingle.java | 4 +- .../operators/mixed/ScalarXMapZHelper.java | 156 ++++++++++++++++++ .../operators/single/SingleToObservable.java | 15 +- .../mixed/ObservableConcatMapMaybeTest.java | 29 +++- .../mixed/ObservableConcatMapSingleTest.java | 29 +++- .../ObservableSwitchMapCompletableTest.java | 44 +++++ .../mixed/ObservableSwitchMapMaybeTest.java | 46 +++++- .../mixed/ObservableSwitchMapSingleTest.java | 48 +++++- .../mixed/ScalarXMapZHelperTest.java | 26 +++ 15 files changed, 420 insertions(+), 43 deletions(-) create mode 100644 src/main/java/io/reactivex/internal/operators/mixed/ScalarXMapZHelper.java create mode 100644 src/test/java/io/reactivex/internal/operators/mixed/ScalarXMapZHelperTest.java diff --git a/src/main/java/io/reactivex/internal/operators/maybe/MaybeToObservable.java b/src/main/java/io/reactivex/internal/operators/maybe/MaybeToObservable.java index dabd8a0d1b..53f87e743c 100644 --- a/src/main/java/io/reactivex/internal/operators/maybe/MaybeToObservable.java +++ b/src/main/java/io/reactivex/internal/operators/maybe/MaybeToObservable.java @@ -14,6 +14,7 @@ package io.reactivex.internal.operators.maybe; import io.reactivex.*; +import io.reactivex.annotations.Experimental; import io.reactivex.disposables.Disposable; import io.reactivex.internal.disposables.DisposableHelper; import io.reactivex.internal.fuseable.HasUpstreamMaybeSource; @@ -40,17 +41,29 @@ public MaybeSource source() { @Override protected void subscribeActual(Observer s) { - source.subscribe(new MaybeToFlowableSubscriber(s)); + source.subscribe(create(s)); } - static final class MaybeToFlowableSubscriber extends DeferredScalarDisposable + /** + * Creates a {@link MaybeObserver} wrapper around a {@link Observer}. + * @param the value type + * @param downstream the downstream {@code Observer} to talk to + * @return the new MaybeObserver instance + * @since 2.1.11 - experimental + */ + @Experimental + public static MaybeObserver create(Observer downstream) { + return new MaybeToObservableObserver(downstream); + } + + static final class MaybeToObservableObserver extends DeferredScalarDisposable implements MaybeObserver { private static final long serialVersionUID = 7603343402964826922L; Disposable d; - MaybeToFlowableSubscriber(Observer actual) { + MaybeToObservableObserver(Observer actual) { super(actual); } diff --git a/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapCompletable.java b/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapCompletable.java index 5416125366..da2f635437 100644 --- a/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapCompletable.java +++ b/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapCompletable.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.mixed; -import java.util.concurrent.Callable; import java.util.concurrent.atomic.*; import io.reactivex.*; @@ -21,7 +20,7 @@ import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.Exceptions; import io.reactivex.functions.Function; -import io.reactivex.internal.disposables.*; +import io.reactivex.internal.disposables.DisposableHelper; import io.reactivex.internal.functions.ObjectHelper; import io.reactivex.internal.fuseable.*; import io.reactivex.internal.queue.SpscLinkedArrayQueue; @@ -57,7 +56,7 @@ public ObservableConcatMapCompletable(Observable source, @Override protected void subscribeActual(CompletableObserver s) { - if (!tryScalarSource(source, mapper, s)) { + if (!ScalarXMapZHelper.tryAsCompletable(source, mapper, s)) { source.subscribe(new ConcatMapCompletableObserver(s, mapper, errorMode, prefetch)); } } @@ -301,30 +300,4 @@ void dispose() { } } } - - static boolean tryScalarSource(Observable source, Function mapper, CompletableObserver observer) { - if (source instanceof Callable) { - @SuppressWarnings("unchecked") - Callable call = (Callable) source; - CompletableSource cs = null; - try { - T item = call.call(); - if (item != null) { - cs = ObjectHelper.requireNonNull(mapper.apply(item), "The mapper returned a null CompletableSource"); - } - } catch (Throwable ex) { - Exceptions.throwIfFatal(ex); - EmptyDisposable.error(ex, observer); - return true; - } - - if (cs == null) { - EmptyDisposable.complete(observer); - } else { - cs.subscribe(observer); - } - return true; - } - return false; - } } diff --git a/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapMaybe.java b/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapMaybe.java index 9d83c77e59..24e0b45027 100644 --- a/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapMaybe.java +++ b/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapMaybe.java @@ -59,7 +59,9 @@ public ObservableConcatMapMaybe(Observable source, @Override protected void subscribeActual(Observer s) { - source.subscribe(new ConcatMapMaybeMainObserver(s, mapper, prefetch, errorMode)); + if (!ScalarXMapZHelper.tryAsMaybe(source, mapper, s)) { + source.subscribe(new ConcatMapMaybeMainObserver(s, mapper, prefetch, errorMode)); + } } static final class ConcatMapMaybeMainObserver diff --git a/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapSingle.java b/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapSingle.java index d29204ccc7..96a3443ad9 100644 --- a/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapSingle.java +++ b/src/main/java/io/reactivex/internal/operators/mixed/ObservableConcatMapSingle.java @@ -59,7 +59,9 @@ public ObservableConcatMapSingle(Observable source, @Override protected void subscribeActual(Observer s) { - source.subscribe(new ConcatMapSingleMainObserver(s, mapper, prefetch, errorMode)); + if (!ScalarXMapZHelper.tryAsSingle(source, mapper, s)) { + source.subscribe(new ConcatMapSingleMainObserver(s, mapper, prefetch, errorMode)); + } } static final class ConcatMapSingleMainObserver diff --git a/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapCompletable.java b/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapCompletable.java index 650c9dfbd8..4b98e63bf2 100644 --- a/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapCompletable.java +++ b/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapCompletable.java @@ -51,7 +51,9 @@ public ObservableSwitchMapCompletable(Observable source, @Override protected void subscribeActual(CompletableObserver s) { - source.subscribe(new SwitchMapCompletableObserver(s, mapper, delayErrors)); + if (!ScalarXMapZHelper.tryAsCompletable(source, mapper, s)) { + source.subscribe(new SwitchMapCompletableObserver(s, mapper, delayErrors)); + } } static final class SwitchMapCompletableObserver implements Observer, Disposable { diff --git a/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapMaybe.java b/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapMaybe.java index ef7f863603..57baef78ea 100644 --- a/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapMaybe.java +++ b/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapMaybe.java @@ -53,7 +53,9 @@ public ObservableSwitchMapMaybe(Observable source, @Override protected void subscribeActual(Observer s) { - source.subscribe(new SwitchMapMaybeMainObserver(s, mapper, delayErrors)); + if (!ScalarXMapZHelper.tryAsMaybe(source, mapper, s)) { + source.subscribe(new SwitchMapMaybeMainObserver(s, mapper, delayErrors)); + } } static final class SwitchMapMaybeMainObserver extends AtomicInteger diff --git a/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapSingle.java b/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapSingle.java index c70bad0bcc..1000e1bd81 100644 --- a/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapSingle.java +++ b/src/main/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapSingle.java @@ -53,7 +53,9 @@ public ObservableSwitchMapSingle(Observable source, @Override protected void subscribeActual(Observer s) { - source.subscribe(new SwitchMapSingleMainObserver(s, mapper, delayErrors)); + if (!ScalarXMapZHelper.tryAsSingle(source, mapper, s)) { + source.subscribe(new SwitchMapSingleMainObserver(s, mapper, delayErrors)); + } } static final class SwitchMapSingleMainObserver extends AtomicInteger diff --git a/src/main/java/io/reactivex/internal/operators/mixed/ScalarXMapZHelper.java b/src/main/java/io/reactivex/internal/operators/mixed/ScalarXMapZHelper.java new file mode 100644 index 0000000000..901cbd5cbf --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/mixed/ScalarXMapZHelper.java @@ -0,0 +1,156 @@ +/** + * 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.mixed; + +import java.util.concurrent.Callable; + +import io.reactivex.*; +import io.reactivex.annotations.Experimental; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Function; +import io.reactivex.internal.disposables.EmptyDisposable; +import io.reactivex.internal.functions.ObjectHelper; +import io.reactivex.internal.operators.maybe.MaybeToObservable; +import io.reactivex.internal.operators.single.SingleToObservable; + +/** + * Utility class to extract a value from a scalar source reactive type, + * map it to a 0-1 type then subscribe the output type's consumer to it, + * saving on the overhead of the regular subscription channel. + * @since 2.1.11 - experimental + */ +@Experimental +final class ScalarXMapZHelper { + + private ScalarXMapZHelper() { + throw new IllegalStateException("No instances!"); + } + + /** + * Try subscribing to a {@link CompletableSource} mapped from + * a scalar source (which implements {@link Callable}). + * @param the upstream value type + * @param source the source reactive type ({@code Flowable} or {@code Observable}) + * possibly implementing {@link Callable}. + * @param mapper the function that turns the scalar upstream value into a + * {@link CompletableSource} + * @param observer the consumer to subscribe to the mapped {@link CompletableSource} + * @return true if a subscription did happen and the regular path should be skipped + */ + static boolean tryAsCompletable(Object source, + Function mapper, + CompletableObserver observer) { + if (source instanceof Callable) { + @SuppressWarnings("unchecked") + Callable call = (Callable) source; + CompletableSource cs = null; + try { + T item = call.call(); + if (item != null) { + cs = ObjectHelper.requireNonNull(mapper.apply(item), "The mapper returned a null CompletableSource"); + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return true; + } + + if (cs == null) { + EmptyDisposable.complete(observer); + } else { + cs.subscribe(observer); + } + return true; + } + return false; + } + + /** + * Try subscribing to a {@link MaybeSource} mapped from + * a scalar source (which implements {@link Callable}). + * @param the upstream value type + * @param source the source reactive type ({@code Flowable} or {@code Observable}) + * possibly implementing {@link Callable}. + * @param mapper the function that turns the scalar upstream value into a + * {@link MaybeSource} + * @param observer the consumer to subscribe to the mapped {@link MaybeSource} + * @return true if a subscription did happen and the regular path should be skipped + */ + static boolean tryAsMaybe(Object source, + Function> mapper, + Observer observer) { + if (source instanceof Callable) { + @SuppressWarnings("unchecked") + Callable call = (Callable) source; + MaybeSource cs = null; + try { + T item = call.call(); + if (item != null) { + cs = ObjectHelper.requireNonNull(mapper.apply(item), "The mapper returned a null MaybeSource"); + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return true; + } + + if (cs == null) { + EmptyDisposable.complete(observer); + } else { + cs.subscribe(MaybeToObservable.create(observer)); + } + return true; + } + return false; + } + + /** + * Try subscribing to a {@link SingleSource} mapped from + * a scalar source (which implements {@link Callable}). + * @param the upstream value type + * @param source the source reactive type ({@code Flowable} or {@code Observable}) + * possibly implementing {@link Callable}. + * @param mapper the function that turns the scalar upstream value into a + * {@link SingleSource} + * @param observer the consumer to subscribe to the mapped {@link SingleSource} + * @return true if a subscription did happen and the regular path should be skipped + */ + static boolean tryAsSingle(Object source, + Function> mapper, + Observer observer) { + if (source instanceof Callable) { + @SuppressWarnings("unchecked") + Callable call = (Callable) source; + SingleSource cs = null; + try { + T item = call.call(); + if (item != null) { + cs = ObjectHelper.requireNonNull(mapper.apply(item), "The mapper returned a null SingleSource"); + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return true; + } + + if (cs == null) { + EmptyDisposable.complete(observer); + } else { + cs.subscribe(SingleToObservable.create(observer)); + } + return true; + } + return false; + } +} diff --git a/src/main/java/io/reactivex/internal/operators/single/SingleToObservable.java b/src/main/java/io/reactivex/internal/operators/single/SingleToObservable.java index b9a5ff9937..02844ece2c 100644 --- a/src/main/java/io/reactivex/internal/operators/single/SingleToObservable.java +++ b/src/main/java/io/reactivex/internal/operators/single/SingleToObservable.java @@ -13,6 +13,7 @@ package io.reactivex.internal.operators.single; import io.reactivex.*; +import io.reactivex.annotations.Experimental; import io.reactivex.disposables.Disposable; import io.reactivex.internal.disposables.DisposableHelper; @@ -31,7 +32,19 @@ public SingleToObservable(SingleSource source) { @Override public void subscribeActual(final Observer s) { - source.subscribe(new SingleToObservableObserver(s)); + source.subscribe(create(s)); + } + + /** + * Creates a {@link SingleObserver} wrapper around a {@link Observer}. + * @param the value type + * @param downstream the downstream {@code Observer} to talk to + * @return the new SingleObserver instance + * @since 2.1.11 - experimental + */ + @Experimental + public static SingleObserver create(Observer downstream) { + return new SingleToObservableObserver(downstream); } static final class SingleToObservableObserver diff --git a/src/test/java/io/reactivex/internal/operators/mixed/ObservableConcatMapMaybeTest.java b/src/test/java/io/reactivex/internal/operators/mixed/ObservableConcatMapMaybeTest.java index bdedbd2055..c22c38db64 100644 --- a/src/test/java/io/reactivex/internal/operators/mixed/ObservableConcatMapMaybeTest.java +++ b/src/test/java/io/reactivex/internal/operators/mixed/ObservableConcatMapMaybeTest.java @@ -340,10 +340,37 @@ public MaybeSource apply(Integer v) assertFalse(ps.hasObservers()); } + @Test + public void scalarMapperCrash() { + TestObserver to = Observable.just(1) + .concatMapMaybe(new Function>() { + @Override + public MaybeSource apply(Integer v) + throws Exception { + throw new TestException(); + } + }) + .test(); + + to.assertFailure(TestException.class); + } + @Test public void disposed() { - TestHelper.checkDisposed(Observable.just(1) + TestHelper.checkDisposed(Observable.just(1).hide() .concatMapMaybe(Functions.justFunction(Maybe.never())) ); } + + @Test + public void scalarEmptySource() { + MaybeSubject ms = MaybeSubject.create(); + + Observable.empty() + .concatMapMaybe(Functions.justFunction(ms)) + .test() + .assertResult(); + + assertFalse(ms.hasObservers()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/mixed/ObservableConcatMapSingleTest.java b/src/test/java/io/reactivex/internal/operators/mixed/ObservableConcatMapSingleTest.java index 0a1ff4e1be..420cef2171 100644 --- a/src/test/java/io/reactivex/internal/operators/mixed/ObservableConcatMapSingleTest.java +++ b/src/test/java/io/reactivex/internal/operators/mixed/ObservableConcatMapSingleTest.java @@ -255,9 +255,24 @@ public SingleSource apply(Integer v) assertFalse(ps.hasObservers()); } + @Test + public void mapperCrashScalar() { + TestObserver to = Observable.just(1) + .concatMapSingle(new Function>() { + @Override + public SingleSource apply(Integer v) + throws Exception { + throw new TestException(); + } + }) + .test(); + + to.assertFailure(TestException.class); + } + @Test public void disposed() { - TestHelper.checkDisposed(Observable.just(1) + TestHelper.checkDisposed(Observable.just(1).hide() .concatMapSingle(Functions.justFunction(Single.never())) ); } @@ -283,4 +298,16 @@ public void mainCompletesWhileInnerActive() { to.assertResult(1, 1); } + + @Test + public void scalarEmptySource() { + SingleSubject ss = SingleSubject.create(); + + Observable.empty() + .concatMapSingle(Functions.justFunction(ss)) + .test() + .assertResult(); + + assertFalse(ss.hasObservers()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapCompletableTest.java b/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapCompletableTest.java index 17edbf46e5..72d4af0d52 100644 --- a/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapCompletableTest.java +++ b/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapCompletableTest.java @@ -384,4 +384,48 @@ public void mainErrorDelayed() { to.assertFailure(TestException.class); } + + @Test + public void scalarMapperCrash() { + TestObserver to = Observable.just(1) + .switchMapCompletable(new Function() { + @Override + public CompletableSource apply(Integer v) + throws Exception { + throw new TestException(); + } + }) + .test(); + + to.assertFailure(TestException.class); + } + + @Test + public void scalarEmptySource() { + CompletableSubject cs = CompletableSubject.create(); + + Observable.empty() + .switchMapCompletable(Functions.justFunction(cs)) + .test() + .assertResult(); + + assertFalse(cs.hasObservers()); + } + + @Test + public void scalarSource() { + CompletableSubject cs = CompletableSubject.create(); + + TestObserver to = Observable.just(1) + .switchMapCompletable(Functions.justFunction(cs)) + .test(); + + assertTrue(cs.hasObservers()); + + to.assertEmpty(); + + cs.onComplete(); + + to.assertResult(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapMaybeTest.java b/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapMaybeTest.java index 4b0046bb61..363f8c0614 100644 --- a/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapMaybeTest.java +++ b/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapMaybeTest.java @@ -268,7 +268,7 @@ public MaybeSource apply(Integer v) @Test public void mapperCrash() { - Observable.just(1) + Observable.just(1).hide() .switchMapMaybe(new Function>() { @Override public MaybeSource apply(Integer v) @@ -642,4 +642,48 @@ public MaybeSource apply(Integer v) to.assertResult(1, 2); } + + @Test + public void scalarMapperCrash() { + TestObserver to = Observable.just(1) + .switchMapMaybe(new Function>() { + @Override + public MaybeSource apply(Integer v) + throws Exception { + throw new TestException(); + } + }) + .test(); + + to.assertFailure(TestException.class); + } + + @Test + public void scalarEmptySource() { + MaybeSubject ms = MaybeSubject.create(); + + Observable.empty() + .switchMapMaybe(Functions.justFunction(ms)) + .test() + .assertResult(); + + assertFalse(ms.hasObservers()); + } + + @Test + public void scalarSource() { + MaybeSubject ms = MaybeSubject.create(); + + TestObserver to = Observable.just(1) + .switchMapMaybe(Functions.justFunction(ms)) + .test(); + + assertTrue(ms.hasObservers()); + + to.assertEmpty(); + + ms.onSuccess(2); + + to.assertResult(2); + } } diff --git a/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapSingleTest.java b/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapSingleTest.java index 10b3dba8d8..92ec3c5523 100644 --- a/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapSingleTest.java +++ b/src/test/java/io/reactivex/internal/operators/mixed/ObservableSwitchMapSingleTest.java @@ -237,7 +237,7 @@ public SingleSource apply(Integer v) @Test public void mapperCrash() { - Observable.just(1) + Observable.just(1).hide() .switchMapSingle(new Function>() { @Override public SingleSource apply(Integer v) @@ -253,7 +253,7 @@ public SingleSource apply(Integer v) public void disposeBeforeSwitchInOnNext() { final TestObserver to = new TestObserver(); - Observable.just(1) + Observable.just(1).hide() .switchMapSingle(new Function>() { @Override public SingleSource apply(Integer v) @@ -610,4 +610,48 @@ public SingleSource apply(Integer v) to.assertResult(1, 2); } + + @Test + public void scalarMapperCrash() { + TestObserver to = Observable.just(1) + .switchMapSingle(new Function>() { + @Override + public SingleSource apply(Integer v) + throws Exception { + throw new TestException(); + } + }) + .test(); + + to.assertFailure(TestException.class); + } + + @Test + public void scalarEmptySource() { + SingleSubject ss = SingleSubject.create(); + + Observable.empty() + .switchMapSingle(Functions.justFunction(ss)) + .test() + .assertResult(); + + assertFalse(ss.hasObservers()); + } + + @Test + public void scalarSource() { + SingleSubject ss = SingleSubject.create(); + + TestObserver to = Observable.just(1) + .switchMapSingle(Functions.justFunction(ss)) + .test(); + + assertTrue(ss.hasObservers()); + + to.assertEmpty(); + + ss.onSuccess(2); + + to.assertResult(2); + } } diff --git a/src/test/java/io/reactivex/internal/operators/mixed/ScalarXMapZHelperTest.java b/src/test/java/io/reactivex/internal/operators/mixed/ScalarXMapZHelperTest.java new file mode 100644 index 0000000000..c30d0c4624 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/mixed/ScalarXMapZHelperTest.java @@ -0,0 +1,26 @@ +/** + * 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.mixed; + +import org.junit.Test; + +import io.reactivex.TestHelper; + +public class ScalarXMapZHelperTest { + + @Test + public void utilityClass() { + TestHelper.checkUtilityClass(ScalarXMapZHelper.class); + } +}