/**
* 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.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.*;
import org.mockito.*;
import rx.*;
import rx.Observable;
import rx.Observer;
import rx.observers.TestSubscriber;
import rx.schedulers.Schedulers;
public class OperatorMergeMaxConcurrentTest {
@Mock
Observer<String> stringObserver;
@Before
public void before() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testWhenMaxConcurrentIsOne() {
for (int i = 0; i < 100; i++) {
List<Observable<String>> os = new ArrayList<Observable<String>>();
os.add(Observable.just("one", "two", "three", "four", "five").subscribeOn(Schedulers.newThread()));
os.add(Observable.just("one", "two", "three", "four", "five").subscribeOn(Schedulers.newThread()));
os.add(Observable.just("one", "two", "three", "four", "five").subscribeOn(Schedulers.newThread()));
List<String> expected = Arrays.asList("one", "two", "three", "four", "five", "one", "two", "three", "four", "five", "one", "two", "three", "four", "five");
Iterator<String> iter = Observable.merge(os, 1).toBlocking().toIterable().iterator();
List<String> actual = new ArrayList<String>();
while (iter.hasNext()) {
actual.add(iter.next());
}
assertEquals(expected, actual);
}
}
@Test
public void testMaxConcurrent() {
for (int times = 0; times < 100; times++) {
int observableCount = 100;
// Test maxConcurrent from 2 to 12
int maxConcurrent = 2 + (times % 10);
AtomicInteger subscriptionCount = new AtomicInteger(0);
List<Observable<String>> os = new ArrayList<Observable<String>>();
List<SubscriptionCheckObservable> scos = new ArrayList<SubscriptionCheckObservable>();
for (int i = 0; i < observableCount; i++) {
SubscriptionCheckObservable sco = new SubscriptionCheckObservable(subscriptionCount, maxConcurrent);
scos.add(sco);
os.add(Observable.create(sco));
}
Iterator<String> iter = Observable.merge(os, maxConcurrent).toBlocking().toIterable().iterator();
List<String> actual = new ArrayList<String>();
while (iter.hasNext()) {
actual.add(iter.next());
}
// System.out.println("actual: " + actual);
assertEquals(5 * observableCount, actual.size());
for (SubscriptionCheckObservable sco : scos) {
assertFalse(sco.failed);
}
}
}
private static class SubscriptionCheckObservable implements Observable.OnSubscribe<String> {
private final AtomicInteger subscriptionCount;
private final int maxConcurrent;
volatile boolean failed = false;
SubscriptionCheckObservable(AtomicInteger subscriptionCount, int maxConcurrent) {
this.subscriptionCount = subscriptionCount;
this.maxConcurrent = maxConcurrent;
}
@Override
public void call(final Subscriber<? super String> t1) {
new Thread(new Runnable() {
@Override
public void run() {
if (subscriptionCount.incrementAndGet() > maxConcurrent) {
failed = true;
}
t1.onNext("one");
t1.onNext("two");
t1.onNext("three");
t1.onNext("four");
t1.onNext("five");
// We could not decrement subscriptionCount in the unsubscribe method
// as "unsubscribe" is not guaranteed to be called before the next "subscribe".
subscriptionCount.decrementAndGet();
t1.onCompleted();
}
}).start();
}
}
@Test
public void testMergeALotOfSourcesOneByOneSynchronously() {
int n = 10000;
List<Observable<Integer>> sourceList = new ArrayList<Observable<Integer>>(n);
for (int i = 0; i < n; i++) {
sourceList.add(Observable.just(i));
}
Iterator<Integer> it = Observable.merge(Observable.from(sourceList), 1).toBlocking().getIterator();
int j = 0;
while (it.hasNext()) {
assertEquals((Integer)j, it.next());
j++;
}
assertEquals(j, n);
}
@Test
public void testMergeALotOfSourcesOneByOneSynchronouslyTakeHalf() {
int n = 10000;
List<Observable<Integer>> sourceList = new ArrayList<Observable<Integer>>(n);
for (int i = 0; i < n; i++) {
sourceList.add(Observable.just(i));
}
Iterator<Integer> it = Observable.merge(Observable.from(sourceList), 1).take(n / 2).toBlocking().getIterator();
int j = 0;
while (it.hasNext()) {
assertEquals((Integer)j, it.next());
j++;
}
assertEquals(j, n / 2);
}
@Test
public void testSimple() {
for (int i = 1; i < 100; i++) {
TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
List<Observable<Integer>> sourceList = new ArrayList<Observable<Integer>>(i);
List<Integer> result = new ArrayList<Integer>(i);
for (int j = 1; j <= i; j++) {
sourceList.add(Observable.just(j));
result.add(j);
}
Observable.merge(sourceList, i).subscribe(ts);
ts.assertNoErrors();
ts.assertTerminalEvent();
ts.assertReceivedOnNext(result);
}
}
@Test
public void testSimpleOneLess() {
for (int i = 2; i < 100; i++) {
TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
List<Observable<Integer>> sourceList = new ArrayList<Observable<Integer>>(i);
List<Integer> result = new ArrayList<Integer>(i);
for (int j = 1; j <= i; j++) {
sourceList.add(Observable.just(j));
result.add(j);
}
Observable.merge(sourceList, i - 1).subscribe(ts);
ts.assertNoErrors();
ts.assertTerminalEvent();
ts.assertReceivedOnNext(result);
}
}
@Test(timeout = 10000)
public void testSimpleAsyncLoop() {
for (int i = 0; i < 200; i++) {
testSimpleAsync();
}
}
@Test(timeout = 10000)
public void testSimpleAsync() {
for (int i = 1; i < 50; i++) {
TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
List<Observable<Integer>> sourceList = new ArrayList<Observable<Integer>>(i);
Set<Integer> expected = new HashSet<Integer>(i);
for (int j = 1; j <= i; j++) {
sourceList.add(Observable.just(j).subscribeOn(Schedulers.io()));
expected.add(j);
}
Observable.merge(sourceList, i).subscribe(ts);
ts.awaitTerminalEvent(1, TimeUnit.SECONDS);
ts.assertNoErrors();
Set<Integer> actual = new HashSet<Integer>(ts.getOnNextEvents());
assertEquals(expected, actual);
}
}
@Test(timeout = 10000)
public void testSimpleOneLessAsyncLoop() {
for (int i = 0; i < 200; i++) {
testSimpleOneLessAsync();
}
}
@Test(timeout = 10000)
public void testSimpleOneLessAsync() {
for (int i = 2; i < 50; i++) {
TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
List<Observable<Integer>> sourceList = new ArrayList<Observable<Integer>>(i);
Set<Integer> expected = new HashSet<Integer>(i);
for (int j = 1; j <= i; j++) {
sourceList.add(Observable.just(j).subscribeOn(Schedulers.io()));
expected.add(j);
}
Observable.merge(sourceList, i - 1).subscribe(ts);
ts.awaitTerminalEvent(1, TimeUnit.SECONDS);
ts.assertNoErrors();
Set<Integer> actual = new HashSet<Integer>(ts.getOnNextEvents());
assertEquals(expected, actual);
}
}
@Test(timeout = 5000)
public void testBackpressureHonored() throws Exception {
List<Observable<Integer>> sourceList = new ArrayList<Observable<Integer>>(3);
sourceList.add(Observable.range(0, 100000).subscribeOn(Schedulers.io()));
sourceList.add(Observable.range(0, 100000).subscribeOn(Schedulers.io()));
sourceList.add(Observable.range(0, 100000).subscribeOn(Schedulers.io()));
final CountDownLatch cdl = new CountDownLatch(5);
TestSubscriber<Integer> ts = new TestSubscriber<Integer>() {
@Override
public void onStart() {
request(0);
}
@Override
public void onNext(Integer t) {
super.onNext(t);
cdl.countDown();
}
};
Observable.merge(sourceList, 2).subscribe(ts);
ts.requestMore(5);
cdl.await();
ts.assertNoErrors();
assertEquals(5, ts.getOnNextEvents().size());
assertEquals(0, ts.getOnCompletedEvents().size());
ts.unsubscribe();
}
@Test(timeout = 5000)
public void testTake() throws Exception {
List<Observable<Integer>> sourceList = new ArrayList<Observable<Integer>>(3);
sourceList.add(Observable.range(0, 100000).subscribeOn(Schedulers.io()));
sourceList.add(Observable.range(0, 100000).subscribeOn(Schedulers.io()));
sourceList.add(Observable.range(0, 100000).subscribeOn(Schedulers.io()));
TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
Observable.merge(sourceList, 2).take(5).subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
assertEquals(5, ts.getOnNextEvents().size());
}
}