/**
* 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.internal.operators;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.junit.Test;
import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.Observable.Operator;
import rx.Observer;
import rx.Producer;
import rx.Scheduler;
import rx.Subscriber;
import rx.Subscription;
import rx.functions.Action0;
import rx.observers.TestObserver;
import rx.observers.TestSubscriber;
import rx.schedulers.Schedulers;
public class OperatorSubscribeOnTest {
@Test(timeout = 2000)
public void testIssue813() throws InterruptedException {
// https://github.com/ReactiveX/RxJava/issues/813
final CountDownLatch scheduled = new CountDownLatch(1);
final CountDownLatch latch = new CountDownLatch(1);
final CountDownLatch doneLatch = new CountDownLatch(1);
TestObserver<Integer> observer = new TestObserver<Integer>();
final Subscription subscription = Observable
.create(new Observable.OnSubscribe<Integer>() {
@Override
public void call(
final Subscriber<? super Integer> subscriber) {
scheduled.countDown();
try {
try {
latch.await();
} catch (InterruptedException e) {
// this means we were unsubscribed (Scheduler shut down and interrupts)
// ... but we'll pretend we are like many Observables that ignore interrupts
}
subscriber.onCompleted();
} catch (Throwable e) {
subscriber.onError(e);
} finally {
doneLatch.countDown();
}
}
}).subscribeOn(Schedulers.computation()).subscribe(observer);
// wait for scheduling
scheduled.await();
// trigger unsubscribe
subscription.unsubscribe();
latch.countDown();
doneLatch.await();
assertEquals(0, observer.getOnErrorEvents().size());
assertEquals(1, observer.getOnCompletedEvents().size());
}
@Test
public void testThrownErrorHandling() {
TestSubscriber<String> ts = new TestSubscriber<String>();
Observable.create(new OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> s) {
throw new RuntimeException("fail");
}
}).subscribeOn(Schedulers.computation()).subscribe(ts);
ts.awaitTerminalEvent(1000, TimeUnit.MILLISECONDS);
ts.assertTerminalEvent();
}
@Test
public void testOnError() {
TestSubscriber<String> ts = new TestSubscriber<String>();
Observable.create(new OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> s) {
s.onError(new RuntimeException("fail"));
}
}).subscribeOn(Schedulers.computation()).subscribe(ts);
ts.awaitTerminalEvent(1000, TimeUnit.MILLISECONDS);
ts.assertTerminalEvent();
}
public static class SlowScheduler extends Scheduler {
final Scheduler actual;
final long delay;
final TimeUnit unit;
public SlowScheduler() {
this(Schedulers.computation(), 2, TimeUnit.SECONDS);
}
public SlowScheduler(Scheduler actual, long delay, TimeUnit unit) {
this.actual = actual;
this.delay = delay;
this.unit = unit;
}
@Override
public Worker createWorker() {
return new SlowInner(actual.createWorker());
}
private final class SlowInner extends Worker {
private final Scheduler.Worker actualInner;
private SlowInner(Worker actual) {
this.actualInner = actual;
}
@Override
public void unsubscribe() {
actualInner.unsubscribe();
}
@Override
public boolean isUnsubscribed() {
return actualInner.isUnsubscribed();
}
@Override
public Subscription schedule(final Action0 action) {
return actualInner.schedule(action, delay, unit);
}
@Override
public Subscription schedule(final Action0 action, final long delayTime, final TimeUnit delayUnit) {
TimeUnit common = delayUnit.compareTo(unit) < 0 ? delayUnit : unit;
long t = common.convert(delayTime, delayUnit) + common.convert(delay, unit);
return actualInner.schedule(action, t, common);
}
}
}
@Test(timeout = 5000)
public void testUnsubscribeInfiniteStream() throws InterruptedException {
TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
final AtomicInteger count = new AtomicInteger();
Observable.create(new OnSubscribe<Integer>() {
@Override
public void call(Subscriber<? super Integer> sub) {
for (int i = 1; !sub.isUnsubscribed(); i++) {
count.incrementAndGet();
sub.onNext(i);
}
}
}).subscribeOn(Schedulers.newThread()).take(10).subscribe(ts);
ts.awaitTerminalEventAndUnsubscribeOnTimeout(1000, TimeUnit.MILLISECONDS);
Thread.sleep(200); // give time for the loop to continue
ts.assertReceivedOnNext(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
assertEquals(10, count.get());
}
@Test
public void testBackpressureReschedulesCorrectly() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(10);
TestSubscriber<Integer> ts = new TestSubscriber<Integer>(new Observer<Integer>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Integer t) {
latch.countDown();
}
});
ts.requestMore(10);
Observable.range(1, 10000000).subscribeOn(Schedulers.newThread()).take(20).subscribe(ts);
latch.await();
Thread t = ts.getLastSeenThread();
System.out.println("First schedule: " + t);
assertTrue(t.getName().startsWith("Rx"));
ts.requestMore(10);
ts.awaitTerminalEvent();
System.out.println("After reschedule: " + ts.getLastSeenThread());
assertEquals(t, ts.getLastSeenThread());
}
@Test
public void testSetProducerSynchronousRequest() {
TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
Observable.just(1, 2, 3).lift(new Operator<Integer, Integer>() {
@Override
public Subscriber<? super Integer> call(final Subscriber<? super Integer> child) {
final AtomicLong requested = new AtomicLong();
child.setProducer(new Producer() {
@Override
public void request(long n) {
if (!requested.compareAndSet(0, n)) {
child.onError(new RuntimeException("Expected to receive request before onNext but didn't"));
}
}
});
Subscriber<Integer> parent = new Subscriber<Integer>() {
@Override
public void onCompleted() {
child.onCompleted();
}
@Override
public void onError(Throwable e) {
child.onError(e);
}
@Override
public void onNext(Integer t) {
if (requested.compareAndSet(0, -99)) {
child.onError(new RuntimeException("Got values before requested"));
}
}
};
child.add(parent);
return parent;
}
}).subscribeOn(Schedulers.newThread()).subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
}
}