/**
* 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.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Before;
import org.junit.Test;
import org.mockito.MockitoAnnotations;
import rx.Observable;
import rx.Observer;
import rx.Producer;
import rx.Subscriber;
import rx.functions.Action2;
import rx.functions.Func0;
import rx.functions.Func1;
import rx.functions.Func2;
import rx.observers.TestSubscriber;
public class OperatorScanTest {
@Before
public void before() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testScanIntegersWithInitialValue() {
@SuppressWarnings("unchecked")
Observer<String> observer = mock(Observer.class);
Observable<Integer> observable = Observable.just(1, 2, 3);
Observable<String> m = observable.scan("", new Func2<String, Integer, String>() {
@Override
public String call(String s, Integer n) {
return s + n.toString();
}
});
m.subscribe(observer);
verify(observer, never()).onError(any(Throwable.class));
verify(observer, times(1)).onNext("");
verify(observer, times(1)).onNext("1");
verify(observer, times(1)).onNext("12");
verify(observer, times(1)).onNext("123");
verify(observer, times(4)).onNext(anyString());
verify(observer, times(1)).onCompleted();
verify(observer, never()).onError(any(Throwable.class));
}
@Test
public void testScanIntegersWithoutInitialValue() {
@SuppressWarnings("unchecked")
Observer<Integer> observer = mock(Observer.class);
Observable<Integer> observable = Observable.just(1, 2, 3);
Observable<Integer> m = observable.scan(new Func2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer t1, Integer t2) {
return t1 + t2;
}
});
m.subscribe(observer);
verify(observer, never()).onError(any(Throwable.class));
verify(observer, never()).onNext(0);
verify(observer, times(1)).onNext(1);
verify(observer, times(1)).onNext(3);
verify(observer, times(1)).onNext(6);
verify(observer, times(3)).onNext(anyInt());
verify(observer, times(1)).onCompleted();
verify(observer, never()).onError(any(Throwable.class));
}
@Test
public void testScanIntegersWithoutInitialValueAndOnlyOneValue() {
@SuppressWarnings("unchecked")
Observer<Integer> observer = mock(Observer.class);
Observable<Integer> observable = Observable.just(1);
Observable<Integer> m = observable.scan(new Func2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer t1, Integer t2) {
return t1 + t2;
}
});
m.subscribe(observer);
verify(observer, never()).onError(any(Throwable.class));
verify(observer, never()).onNext(0);
verify(observer, times(1)).onNext(1);
verify(observer, times(1)).onNext(anyInt());
verify(observer, times(1)).onCompleted();
verify(observer, never()).onError(any(Throwable.class));
}
@Test
public void shouldNotEmitUntilAfterSubscription() {
TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
Observable.range(1, 100).scan(0, new Func2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer t1, Integer t2) {
return t1 + t2;
}
}).filter(new Func1<Integer, Boolean>() {
@Override
public Boolean call(Integer t1) {
// this will cause request(1) when 0 is emitted
return t1 > 0;
}
}).subscribe(ts);
assertEquals(100, ts.getOnNextEvents().size());
}
@Test
public void testBackpressureWithInitialValue() {
final AtomicInteger count = new AtomicInteger();
Observable.range(1, 100)
.scan(0, new Func2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer t1, Integer t2) {
return t1 + t2;
}
})
.subscribe(new Subscriber<Integer>() {
@Override
public void onStart() {
request(10);
}
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
fail(e.getMessage());
e.printStackTrace();
}
@Override
public void onNext(Integer t) {
count.incrementAndGet();
}
});
// we only expect to receive 10 since we request(10)
assertEquals(10, count.get());
}
@Test
public void testBackpressureWithoutInitialValue() {
final AtomicInteger count = new AtomicInteger();
Observable.range(1, 100)
.scan(new Func2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer t1, Integer t2) {
return t1 + t2;
}
})
.subscribe(new Subscriber<Integer>() {
@Override
public void onStart() {
request(10);
}
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
fail(e.getMessage());
e.printStackTrace();
}
@Override
public void onNext(Integer t) {
count.incrementAndGet();
}
});
// we only expect to receive 10 since we request(10)
assertEquals(10, count.get());
}
@Test
public void testNoBackpressureWithInitialValue() {
final AtomicInteger count = new AtomicInteger();
Observable.range(1, 100)
.scan(0, new Func2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer t1, Integer t2) {
return t1 + t2;
}
})
.subscribe(new Subscriber<Integer>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
fail(e.getMessage());
e.printStackTrace();
}
@Override
public void onNext(Integer t) {
count.incrementAndGet();
}
});
// we only expect to receive 101 as we'll receive all 100 + the initial value
assertEquals(101, count.get());
}
/**
* This uses the public API collect which uses scan under the covers.
*/
@Test
public void testSeedFactory() {
Observable<List<Integer>> o = Observable.range(1, 10)
.collect(new Func0<List<Integer>>() {
@Override
public List<Integer> call() {
return new ArrayList<Integer>();
}
}, new Action2<List<Integer>, Integer>() {
@Override
public void call(List<Integer> list, Integer t2) {
list.add(t2);
}
}).takeLast(1);
assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), o.toBlocking().single());
assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), o.toBlocking().single());
}
@Test
public void testScanWithRequestOne() {
Observable<Integer> o = Observable.just(1, 2).scan(0, new Func2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer t1, Integer t2) {
return t1 + t2;
}
}).take(1);
TestSubscriber<Integer> subscriber = new TestSubscriber<Integer>();
o.subscribe(subscriber);
subscriber.assertReceivedOnNext(Arrays.asList(0));
subscriber.assertTerminalEvent();
subscriber.assertNoErrors();
}
@Test
public void testScanShouldNotRequestZero() {
final AtomicReference<Producer> producer = new AtomicReference<Producer>();
Observable<Integer> o = Observable.create(new Observable.OnSubscribe<Integer>() {
@Override
public void call(final Subscriber subscriber) {
Producer p = spy(new Producer() {
private AtomicBoolean requested = new AtomicBoolean(false);
@Override
public void request(long n) {
if (requested.compareAndSet(false, true)) {
subscriber.onNext(1);
} else {
subscriber.onCompleted();
}
}
});
producer.set(p);
subscriber.setProducer(p);
}
}).scan(100, new Func2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer t1, Integer t2) {
return t1 + t2;
}
});
o.subscribe(new TestSubscriber<Integer>() {
@Override
public void onStart() {
request(1);
}
@Override
public void onNext(Integer integer) {
request(1);
}
});
verify(producer.get(), never()).request(0);
verify(producer.get(), times(2)).request(1);
}
}