/** * Copyright 2014 Netflix, Inc. * * 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 rx; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import org.junit.Test; import rx.Observable.OnSubscribe; import rx.Observable.Operator; import rx.functions.Func1; import rx.observers.TestSubscriber; public class SubscriberTest { /** * Should request n for whatever the final Subscriber asks for */ @Test public void testRequestFromFinalSubscribeWithRequestValue() { Subscriber<String> s = new TestSubscriber<String>(); s.request(10); final AtomicLong r = new AtomicLong(); s.setProducer(new Producer() { @Override public void request(long n) { r.set(n); } }); assertEquals(10, r.get()); } /** * Should request -1 for infinite */ @Test public void testRequestFromFinalSubscribeWithoutRequestValue() { Subscriber<String> s = new TestSubscriber<String>(); final AtomicLong r = new AtomicLong(); s.setProducer(new Producer() { @Override public void request(long n) { r.set(n); } }); assertEquals(Long.MAX_VALUE, r.get()); } @Test public void testRequestFromChainedOperator() { Subscriber<String> s = new TestSubscriber<String>(); Operator<String, String> o = new Operator<String, String>() { @Override public Subscriber<? super String> call(Subscriber<? super String> s) { return new Subscriber<String>(s) { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(String t) { } }; } }; s.request(10); Subscriber<? super String> ns = o.call(s); final AtomicLong r = new AtomicLong(); // set set the producer at the top of the chain (ns) and it should flow through the operator to the (s) subscriber // and then it should request up with the value set on the final Subscriber (s) ns.setProducer(new Producer() { @Override public void request(long n) { r.set(n); } }); assertEquals(10, r.get()); } @Test public void testRequestFromDecoupledOperator() { Subscriber<String> s = new TestSubscriber<String>(); Operator<String, String> o = new Operator<String, String>() { @Override public Subscriber<? super String> call(Subscriber<? super String> s) { return new Subscriber<String>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(String t) { } }; } }; s.request(10); Subscriber<? super String> ns = o.call(s); final AtomicLong r = new AtomicLong(); // set set the producer at the top of the chain (ns) and it should flow through the operator to the (s) subscriber // and then it should request up with the value set on the final Subscriber (s) ns.setProducer(new Producer() { @Override public void request(long n) { r.set(n); } }); // this will be Long.MAX_VALUE because it is decoupled and nothing requsted on the Operator subscriber assertEquals(Long.MAX_VALUE, r.get()); } @Test public void testRequestFromDecoupledOperatorThatRequestsN() { Subscriber<String> s = new TestSubscriber<String>(); final AtomicLong innerR = new AtomicLong(); Operator<String, String> o = new Operator<String, String>() { @Override public Subscriber<? super String> call(Subscriber<? super String> child) { // we want to decouple the chain so set our own Producer on the child instead of it coming from the parent child.setProducer(new Producer() { @Override public void request(long n) { innerR.set(n); } }); Subscriber<String> as = new Subscriber<String>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(String t) { } }; // we request 99 up to the parent as.request(99); return as; } }; s.request(10); Subscriber<? super String> ns = o.call(s); final AtomicLong r = new AtomicLong(); // set set the producer at the top of the chain (ns) and it should flow through the operator to the (s) subscriber // and then it should request up with the value set on the final Subscriber (s) ns.setProducer(new Producer() { @Override public void request(long n) { r.set(n); } }); assertEquals(99, r.get()); assertEquals(10, innerR.get()); } @Test public void testRequestToObservable() { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); ts.request(3); final AtomicLong requested = new AtomicLong(); Observable.create(new OnSubscribe<Integer>() { @Override public void call(Subscriber<? super Integer> s) { s.setProducer(new Producer() { @Override public void request(long n) { requested.set(n); } }); } }).subscribe(ts); assertEquals(3, requested.get()); } @Test public void testRequestThroughMap() { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); ts.request(3); final AtomicLong requested = new AtomicLong(); Observable.create(new OnSubscribe<Integer>() { @Override public void call(Subscriber<? super Integer> s) { s.setProducer(new Producer() { @Override public void request(long n) { requested.set(n); } }); } }).map(new Func1<Integer, Integer>() { @Override public Integer call(Integer t1) { return t1; } }).subscribe(ts); assertEquals(3, requested.get()); } @Test public void testRequestThroughTakeThatReducesRequest() { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); ts.request(3); final AtomicLong requested = new AtomicLong(); Observable.create(new OnSubscribe<Integer>() { @Override public void call(Subscriber<? super Integer> s) { s.setProducer(new Producer() { @Override public void request(long n) { requested.set(n); } }); } }).take(2).subscribe(ts); assertEquals(2, requested.get()); } @Test public void testRequestThroughTakeWhereRequestIsSmallerThanTake() { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); ts.request(3); final AtomicLong requested = new AtomicLong(); Observable.create(new OnSubscribe<Integer>() { @Override public void call(Subscriber<? super Integer> s) { s.setProducer(new Producer() { @Override public void request(long n) { requested.set(n); } }); } }).take(10).subscribe(ts); assertEquals(3, requested.get()); } @Test public void testOnStartCalledOnceViaSubscribe() { final AtomicInteger c = new AtomicInteger(); Observable.just(1, 2, 3, 4).take(2).subscribe(new Subscriber<Integer>() { @Override public void onStart() { c.incrementAndGet(); request(1); } @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(Integer t) { request(1); } }); assertEquals(1, c.get()); } @Test public void testOnStartCalledOnceViaUnsafeSubscribe() { final AtomicInteger c = new AtomicInteger(); Observable.just(1, 2, 3, 4).take(2).unsafeSubscribe(new Subscriber<Integer>() { @Override public void onStart() { c.incrementAndGet(); request(1); } @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(Integer t) { request(1); } }); assertEquals(1, c.get()); } @Test public void testOnStartCalledOnceViaLift() { final AtomicInteger c = new AtomicInteger(); Observable.just(1, 2, 3, 4).lift(new Operator<Integer, Integer>() { @Override public Subscriber<? super Integer> call(final Subscriber<? super Integer> child) { return new Subscriber<Integer>() { @Override public void onStart() { c.incrementAndGet(); request(1); } @Override public void onCompleted() { child.onCompleted(); } @Override public void onError(Throwable e) { child.onError(e); } @Override public void onNext(Integer t) { child.onNext(t); request(1); } }; } }).subscribe(); assertEquals(1, c.get()); } @Test public void testNegativeRequestThrowsIllegalArgumentException() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final AtomicReference<Throwable> exception = new AtomicReference<Throwable>(); Observable.just(1,2,3,4).subscribe(new Subscriber<Integer>() { @Override public void onStart() { request(1); } @Override public void onCompleted() { } @Override public void onError(Throwable e) { exception.set(e); latch.countDown(); } @Override public void onNext(Integer t) { request(-1); request(1); }}); assertTrue(latch.await(10, TimeUnit.SECONDS)); assertTrue(exception.get() instanceof IllegalArgumentException); } @Test public void testOnStartRequestsAreAdditive() { final List<Integer> list = new ArrayList<Integer>(); Observable.just(1,2,3,4,5).subscribe(new Subscriber<Integer>() { @Override public void onStart() { request(3); request(2); } @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(Integer t) { list.add(t); }}); assertEquals(Arrays.asList(1,2,3,4,5), list); } @Test public void testOnStartRequestsAreAdditiveAndOverflowBecomesMaxValue() { final List<Integer> list = new ArrayList<Integer>(); Observable.just(1,2,3,4,5).subscribe(new Subscriber<Integer>() { @Override public void onStart() { request(2); request(Long.MAX_VALUE-1); } @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(Integer t) { list.add(t); }}); assertEquals(Arrays.asList(1,2,3,4,5), list); } }