/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.usergrid.persistence.core.rx;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import rx.Subscriber;
import rx.schedulers.Schedulers;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class OrderedMergeTest {
private static final Logger logger = LoggerFactory.getLogger( OrderedMergeTest.class );
@Test
public void singleOperator() throws InterruptedException {
List<Integer> expected = Arrays.asList( 0, 1, 2, 3, 4, 5 );
Observable<Integer> ints = Observable.from( expected );
Observable<Integer> ordered = OrderedMerge.orderedMerge( new IntegerComparator(), 10, ints );
final CountDownLatch latch = new CountDownLatch( 1 );
final List<Integer> results = new ArrayList();
ordered.subscribe( new Subscriber<Integer>() {
@Override
public void onCompleted() {
latch.countDown();
}
@Override
public void onError( final Throwable e ) {
e.printStackTrace();
fail( "An error was thrown " );
}
@Override
public void onNext( final Integer integer ) {
logger.info( "onNext invoked with {}", integer );
results.add( integer );
}
} );
latch.await();
assertEquals( expected.size(), results.size() );
for ( int i = 0; i < expected.size(); i++ ) {
assertEquals( "Same element expected", expected.get( i ), results.get( i ) );
}
}
@Test
public void multipleOperatorSameThread() throws InterruptedException {
List<Integer> expected1List = Arrays.asList( 5, 3, 2, 0 );
Observable<Integer> expected1 = Observable.from( expected1List );
List<Integer> expected2List = Arrays.asList( 10, 7, 6, 4 );
Observable<Integer> expected2 = Observable.from( expected2List );
List<Integer> expected3List = Arrays.asList( 9, 8, 1 );
Observable<Integer> expected3 = Observable.from( expected3List );
Observable<Integer> ordered =
OrderedMerge.orderedMerge( new ReverseIntegerComparator(), 10, expected1, expected2, expected3 );
final CountDownLatch latch = new CountDownLatch( 1 );
final List<Integer> results = new ArrayList();
ordered.subscribe( new Subscriber<Integer>() {
@Override
public void onCompleted() {
latch.countDown();
}
@Override
public void onError( final Throwable e ) {
logger.error("Test failed due to exception", e);
fail( "An error was thrown " );
}
@Override
public void onNext( final Integer integer ) {
logger.info( "onNext invoked with {}", integer );
results.add( integer );
}
} );
latch.await();
List<Integer> expected = Arrays.asList( 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 );
assertEquals( expected.size(), results.size() );
for ( int i = 0; i < expected.size(); i++ ) {
assertEquals( "Same element expected", expected.get( i ), results.get( i ) );
}
}
@Test
@Ignore( "Pending implementation of backpressure" )
public void multipleOperatorSingleThreadSizeException() throws InterruptedException {
List<Integer> expected1List = Arrays.asList( 5, 3, 2, 0 );
Observable<Integer> expected1 = Observable.from(expected1List);
List<Integer> expected2List = Arrays.asList(10, 7, 6, 4);
Observable<Integer> expected2 = Observable.from(expected2List);
List<Integer> expected3List = Arrays.asList(9, 8, 1);
Observable<Integer> expected3 = Observable.from(expected3List);
//set our buffer size to 2. We should easily exceed this since every observable has more than 2 elements
Observable<Integer> ordered =
OrderedMerge.orderedMerge(new ReverseIntegerComparator(), 2, expected1, expected2, expected3);
final CountDownLatch latch = new CountDownLatch( 1 );
final List<Integer> results = new ArrayList();
final boolean[] errorThrown = new boolean[1];
ordered.subscribe( new Subscriber<Integer>() {
@Override
public void onCompleted() {
latch.countDown();
}
@Override
public void onError( final Throwable e ) {
logger.error( "Expected error thrown", e );
if ( e.getMessage().contains( "The maximum queue size of 2 has been reached" ) ) {
errorThrown[0] = true;
}
latch.countDown();
}
@Override
public void onNext( final Integer integer ) {
logger.info( "onNext invoked with {}", integer );
results.add( integer );
}
} );
latch.await();
/**
* Since we're on the same thread, we should blow up before we begin producing elements our size
*/
assertEquals(0, results.size());
assertTrue("An exception was thrown", errorThrown[0]);
}
@Test
public void multipleOperatorThreads() throws InterruptedException {
List<Integer> expected1List = Arrays.asList(5, 3, 2, 0);
Observable<Integer> expected1 = Observable.from( expected1List ).subscribeOn(Schedulers.io());
List<Integer> expected2List = Arrays.asList(10, 7, 6, 4);
Observable<Integer> expected2 = Observable.from( expected2List ).subscribeOn(Schedulers.io());
List<Integer> expected3List = Arrays.asList(9, 8, 1);
Observable<Integer> expected3 = Observable.from( expected3List ).subscribeOn(Schedulers.io());
Observable<Integer> ordered =
OrderedMerge.orderedMerge( new ReverseIntegerComparator(), 10, expected1, expected2, expected3 );
final CountDownLatch latch = new CountDownLatch( 1 );
final List<Integer> results = new ArrayList();
ordered.subscribe(new Subscriber<Integer>() {
@Override
public void onCompleted() {
latch.countDown();
}
@Override
public void onError(final Throwable e) {
e.printStackTrace();
fail("An error was thrown ");
}
@Override
public void onNext(final Integer integer) {
logger.info("onNext invoked with {}", integer);
results.add(integer);
}
});
latch.await();
List<Integer> expected = Arrays.asList( 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 );
assertEquals(expected.size(), results.size());
for ( int i = 0; i < expected.size(); i++ ) {
assertEquals( "Same element expected", expected.get( i ), results.get( i ) );
}
}
@Test
@Ignore("Pending implementation of backpressure")
public void multipleOperatorMultipleThreadSizeException() throws InterruptedException {
List<Integer> expected1List = Arrays.asList( 10, 4, 3, 2, 1 );
Observable<Integer> expected1 = Observable.from( expected1List ).subscribeOn( Schedulers.io() );
List<Integer> expected2List = Arrays.asList( 9, 8, 7 );
Observable<Integer> expected2 = Observable.from( expected2List ).subscribeOn( Schedulers.io() );
List<Integer> expected3List = Arrays.asList( 6, 5, 0 );
Observable<Integer> expected3 = Observable.from( expected3List ).subscribeOn( Schedulers.io() );
/**
* Fails because our first observable will have to buffer the last 4 elements while waiting for the others to
* proceed
*/
Observable<Integer> ordered =
OrderedMerge.orderedMerge( new IntegerComparator(), 2, expected1, expected2, expected3 );
final CountDownLatch latch = new CountDownLatch( 1 );
final boolean[] errorThrown = new boolean[1];
ordered.subscribe( new Subscriber<Integer>() {
@Override
public void onCompleted() {
latch.countDown();
}
@Override
public void onError( final Throwable e ) {
logger.error("Expected error thrown", e);
if ( e.getMessage().contains( "The maximum queue size of 2 has been reached" ) ) {
errorThrown[0] = true;
}
latch.countDown();
}
@Override
public void onNext( final Integer integer ) {
logger.info("onNext invoked with {}", integer);
}
} );
latch.await();
assertTrue("An exception was thrown", errorThrown[0]);
}
/**
* Tests that with a buffer size much smaller than our inputs, we successfully block observables from
* producing values when our pressure gets too high. Eventually, one of these events should begin production, eventually
* draining all values
*
* @throws InterruptedException
*/
@Test
public void multipleOperatorMultipleThreadSizePressure() throws InterruptedException {
List<Integer> expected1List = Arrays.asList( 10, 4, 3, 2, 1 );
Observable<Integer> expected1 = Observable.from( expected1List ).subscribeOn( Schedulers.io() );
List<Integer> expected2List = Arrays.asList( 9, 8, 7 );
Observable<Integer> expected2 = Observable.from( expected2List ).subscribeOn( Schedulers.io() );
List<Integer> expected3List = Arrays.asList( 6, 5, 0 );
Observable<Integer> expected3 = Observable.from( expected3List ).subscribeOn( Schedulers.io() );
/**
* Fails because our first observable will have to buffer the last 4 elements while waiting for the others to
* proceed
*/
Observable<Integer> ordered =
OrderedMerge.orderedMerge( new ReverseIntegerComparator(), 2, expected1, expected2, expected3 );
final CountDownLatch latch = new CountDownLatch( 1 );
final List<Integer> results = new ArrayList();
ordered.subscribe( new Subscriber<Integer>() {
@Override
public void onCompleted() {
latch.countDown();
}
@Override
public void onError( final Throwable e ) {
logger.error("Test failed due to exception", e);
fail("An error was thrown ");
}
@Override
public void onNext( final Integer integer ) {
logger.info( "onNext invoked with {}", integer );
results.add(integer);
}
} );
latch.await();
List<Integer> expected = Arrays.asList( 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 );
assertEquals(expected.size(), results.size());
for ( int i = 0; i < expected.size(); i++ ) {
assertEquals( "Same element expected", expected.get( i ), results.get( i ) );
}
}
/**
* Tests that with a buffer size much smaller than our inputs, we successfully block observables from
* producing values when our pressure gets too high. Eventually, one of these events should begin production, eventually
* draining all values
*
* @throws InterruptedException
*/
@Test
public void testDuplicateOrderingCorrect() throws InterruptedException {
List<Integer> expected1List = Arrays.asList( 10, 5, 4, 3, 2, 1 );
Observable<Integer> expected1 = Observable.from( expected1List ).subscribeOn( Schedulers.io() );
List<Integer> expected2List = Arrays.asList( 9, 8, 7, 6, 5 );
Observable<Integer> expected2 = Observable.from( expected2List ).subscribeOn( Schedulers.io() );
List<Integer> expected3List = Arrays.asList( 9, 6, 5, 3, 2, 1, 0 );
Observable<Integer> expected3 = Observable.from( expected3List ).subscribeOn( Schedulers.io() );
/**
* Fails because our first observable will have to buffer the last 4 elements while waiting for the others to
* proceed
*/
Observable<Integer> ordered =
OrderedMerge.orderedMerge( new ReverseIntegerComparator(), 2, expected1, expected2, expected3 );
final CountDownLatch latch = new CountDownLatch( 1 );
final List<Integer> results = new ArrayList();
ordered.subscribe( new Subscriber<Integer>() {
@Override
public void onCompleted() {
latch.countDown();
}
@Override
public void onError( final Throwable e ) {
e.printStackTrace();
fail( "An error was thrown " );
}
@Override
public void onNext( final Integer integer ) {
logger.info( "onNext invoked with {}", integer );
results.add( integer );
}
} );
latch.await();
List<Integer> expected = Arrays.asList( 10, 9, 9, 8, 7, 6, 6, 5, 5, 5, 4, 3, 3, 2, 2, 1, 1, 0);
assertEquals( expected.size(), results.size() );
for ( int i = 0; i < expected.size(); i++ ) {
assertEquals( "Same element expected", expected.get( i ), results.get( i ) );
}
}
@Test
public void testSubscribe(){
List<Integer> expected = Arrays.asList( 10, 9, 9, 8, 7, 6, 6, 5, 5, 5, 4, 3, 3, 2, 2, 1, 1, 0);
final AtomicInteger i = new AtomicInteger();
Observable.from(expected).doOnNext(x -> {
logger.info("print " + x);
i.set(x);
}).doOnError(e -> logger.error(e.getMessage())).subscribe();
logger.info("last");
assertTrue(i.get()==0);
}
@Test
public void testSubscribeException() {
try {
List<Integer> expected = Arrays.asList(10, 9, 9, 8, 7, 6, 6, 5, 5, 5, 4, 3, 3, 2, 2, 1, 1, 0);
Observable.from(expected).doOnNext(x -> {
logger.info("print " + x);
throw new RuntimeException();
}).doOnError(e -> logger.error(e.getMessage())).subscribe();
logger.info("last");
fail();
} catch (Exception e) {
}
}
/**
* Tests that with a buffer size much smaller than our inputs, we successfully block observables from
* producing values when our pressure gets too high. Eventually, one of these events should begin production, eventually
* draining all values
*
* @throws InterruptedException
*/
@Test
public void testDuplicateOrderingCorrectComparator() throws InterruptedException {
List<Integer> expected1List = Arrays.asList( 1, 2, 3, 4, 5, 10 );
Observable<Integer> expected1 = Observable.from( expected1List ).subscribeOn( Schedulers.io() );
List<Integer> expected2List = Arrays.asList( 5, 6, 7, 8, 9 );
Observable<Integer> expected2 = Observable.from( expected2List ).subscribeOn( Schedulers.io() );
List<Integer> expected3List = Arrays.asList( 0, 1, 2, 3, 5, 6, 9 );
Observable<Integer> expected3 = Observable.from( expected3List ).subscribeOn( Schedulers.io() );
/**
* Fails because our first observable will have to buffer the last 4 elements while waiting for the others to
* proceed
*/
Observable<Integer> ordered =
OrderedMerge.orderedMerge( new IntegerComparator(), 2, expected1, expected2, expected3 );
final CountDownLatch latch = new CountDownLatch( 1 );
final List<Integer> results = new ArrayList();
ordered.subscribe( new Subscriber<Integer>() {
@Override
public void onCompleted() {
latch.countDown();
}
@Override
public void onError( final Throwable e ) {
e.printStackTrace();
fail( "An error was thrown " );
}
@Override
public void onNext( final Integer integer ) {
logger.info( "onNext invoked with {}", integer );
results.add( integer );
}
} );
latch.await();
List<Integer> expected = Arrays.asList( 0, 1, 1,2, 2, 3, 3,4, 5, 5, 5, 6, 6, 7,8, 9, 9,10 );
assertEquals( expected.size(), results.size() );
for ( int i = 0; i < expected.size(); i++ ) {
assertEquals( "Same element expected", expected.get( i ), results.get( i ) );
}
}
private static class IntegerComparator implements Comparator<Integer> {
@Override
public int compare( final Integer o1, final Integer o2 ) {
return Integer.compare( o1, o2 );
}
}
private static class ReverseIntegerComparator implements Comparator<Integer> {
@Override
public int compare( final Integer o1, final Integer o2 ) {
return Integer.compare( o1, o2 ) * -1;
}
}
}