/**
* Copyright (c) 2016-present, RxJava Contributors.
*
* 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 io.reactivex.internal.operators.flowable;
import static org.junit.Assert.*;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.*;
import org.junit.*;
import org.reactivestreams.*;
import io.reactivex.*;
import io.reactivex.exceptions.*;
import io.reactivex.functions.*;
import io.reactivex.internal.functions.Functions;
import io.reactivex.internal.subscriptions.BooleanSubscription;
import io.reactivex.processors.*;
import io.reactivex.schedulers.*;
import io.reactivex.subscribers.*;
public class FlowableWindowWithTimeTest {
private TestScheduler scheduler;
private Scheduler.Worker innerScheduler;
@Before
public void before() {
scheduler = new TestScheduler();
innerScheduler = scheduler.createWorker();
}
@Test
public void testTimedAndCount() {
final List<String> list = new ArrayList<String>();
final List<List<String>> lists = new ArrayList<List<String>>();
Flowable<String> source = Flowable.unsafeCreate(new Publisher<String>() {
@Override
public void subscribe(Subscriber<? super String> observer) {
observer.onSubscribe(new BooleanSubscription());
push(observer, "one", 10);
push(observer, "two", 90);
push(observer, "three", 110);
push(observer, "four", 190);
push(observer, "five", 210);
complete(observer, 250);
}
});
Flowable<Flowable<String>> windowed = source.window(100, TimeUnit.MILLISECONDS, scheduler, 2);
windowed.subscribe(observeWindow(list, lists));
scheduler.advanceTimeTo(100, TimeUnit.MILLISECONDS);
assertEquals(1, lists.size());
assertEquals(lists.get(0), list("one", "two"));
scheduler.advanceTimeTo(200, TimeUnit.MILLISECONDS);
assertEquals(2, lists.size());
assertEquals(lists.get(1), list("three", "four"));
scheduler.advanceTimeTo(300, TimeUnit.MILLISECONDS);
assertEquals(3, lists.size());
assertEquals(lists.get(2), list("five"));
}
@Test
public void testTimed() {
final List<String> list = new ArrayList<String>();
final List<List<String>> lists = new ArrayList<List<String>>();
Flowable<String> source = Flowable.unsafeCreate(new Publisher<String>() {
@Override
public void subscribe(Subscriber<? super String> observer) {
observer.onSubscribe(new BooleanSubscription());
push(observer, "one", 98);
push(observer, "two", 99);
push(observer, "three", 99); // FIXME happens after the window is open
push(observer, "four", 101);
push(observer, "five", 102);
complete(observer, 150);
}
});
Flowable<Flowable<String>> windowed = source.window(100, TimeUnit.MILLISECONDS, scheduler);
windowed.subscribe(observeWindow(list, lists));
scheduler.advanceTimeTo(101, TimeUnit.MILLISECONDS);
assertEquals(1, lists.size());
assertEquals(lists.get(0), list("one", "two", "three"));
scheduler.advanceTimeTo(201, TimeUnit.MILLISECONDS);
assertEquals(2, lists.size());
assertEquals(lists.get(1), list("four", "five"));
}
private List<String> list(String... args) {
List<String> list = new ArrayList<String>();
for (String arg : args) {
list.add(arg);
}
return list;
}
private <T> void push(final Subscriber<T> observer, final T value, int delay) {
innerScheduler.schedule(new Runnable() {
@Override
public void run() {
observer.onNext(value);
}
}, delay, TimeUnit.MILLISECONDS);
}
private void complete(final Subscriber<?> observer, int delay) {
innerScheduler.schedule(new Runnable() {
@Override
public void run() {
observer.onComplete();
}
}, delay, TimeUnit.MILLISECONDS);
}
private <T> Consumer<Flowable<T>> observeWindow(final List<T> list, final List<List<T>> lists) {
return new Consumer<Flowable<T>>() {
@Override
public void accept(Flowable<T> stringFlowable) {
stringFlowable.subscribe(new DefaultSubscriber<T>() {
@Override
public void onComplete() {
lists.add(new ArrayList<T>(list));
list.clear();
}
@Override
public void onError(Throwable e) {
Assert.fail(e.getMessage());
}
@Override
public void onNext(T args) {
list.add(args);
}
});
}
};
}
@Test
public void testExactWindowSize() {
Flowable<Flowable<Integer>> source = Flowable.range(1, 10)
.window(1, TimeUnit.MINUTES, scheduler, 3);
final List<Integer> list = new ArrayList<Integer>();
final List<List<Integer>> lists = new ArrayList<List<Integer>>();
source.subscribe(observeWindow(list, lists));
assertEquals(4, lists.size());
assertEquals(3, lists.get(0).size());
assertEquals(Arrays.asList(1, 2, 3), lists.get(0));
assertEquals(3, lists.get(1).size());
assertEquals(Arrays.asList(4, 5, 6), lists.get(1));
assertEquals(3, lists.get(2).size());
assertEquals(Arrays.asList(7, 8, 9), lists.get(2));
assertEquals(1, lists.get(3).size());
assertEquals(Arrays.asList(10), lists.get(3));
}
@Test
public void testTakeFlatMapCompletes() {
TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
final AtomicInteger wip = new AtomicInteger();
final int indicator = 999999999;
FlowableWindowWithSizeTest.hotStream()
.window(300, TimeUnit.MILLISECONDS)
.take(10)
.doOnComplete(new Action() {
@Override
public void run() {
System.out.println("Main done!");
}
})
.flatMap(new Function<Flowable<Integer>, Flowable<Integer>>() {
@Override
public Flowable<Integer> apply(Flowable<Integer> w) {
return w.startWith(indicator)
.doOnComplete(new Action() {
@Override
public void run() {
System.out.println("inner done: " + wip.incrementAndGet());
}
})
;
}
})
.doOnNext(new Consumer<Integer>() {
@Override
public void accept(Integer pv) {
System.out.println(pv);
}
})
.subscribe(ts);
ts.awaitTerminalEvent(5, TimeUnit.SECONDS);
ts.assertComplete();
Assert.assertTrue(ts.valueCount() != 0);
}
@Test
public void timespanTimeskipCustomSchedulerBufferSize() {
Flowable.range(1, 10)
.window(1, 1, TimeUnit.MINUTES, Schedulers.io(), 2)
.flatMap(Functions.<Flowable<Integer>>identity())
.test()
.awaitDone(5, TimeUnit.SECONDS)
.assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
}
@Test
public void timespanDefaultSchedulerSize() {
Flowable.range(1, 10)
.window(1, TimeUnit.MINUTES, 20)
.flatMap(Functions.<Flowable<Integer>>identity())
.test()
.awaitDone(5, TimeUnit.SECONDS)
.assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
}
@Test
public void timespanDefaultSchedulerSizeRestart() {
Flowable.range(1, 10)
.window(1, TimeUnit.MINUTES, 20, true)
.flatMap(Functions.<Flowable<Integer>>identity(), true)
.test()
.awaitDone(5, TimeUnit.SECONDS)
.assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
}
@Test
public void invalidSpan() {
try {
Flowable.just(1).window(-99, 1, TimeUnit.SECONDS);
fail("Should have thrown!");
} catch (IllegalArgumentException ex) {
assertEquals("timespan > 0 required but it was -99", ex.getMessage());
}
}
@Test
public void timespanTimeskipDefaultScheduler() {
Flowable.just(1)
.window(1, 1, TimeUnit.MINUTES)
.flatMap(Functions.<Flowable<Integer>>identity())
.test()
.awaitDone(5, TimeUnit.SECONDS)
.assertResult(1);
}
@Test
public void timespanTimeskipCustomScheduler() {
Flowable.just(1)
.window(1, 1, TimeUnit.MINUTES, Schedulers.io())
.flatMap(Functions.<Flowable<Integer>>identity())
.test()
.awaitDone(5, TimeUnit.SECONDS)
.assertResult(1);
}
@Test
public void timeskipJustOverlap() {
Flowable.just(1)
.window(2, 1, TimeUnit.MINUTES, Schedulers.single())
.flatMap(Functions.<Flowable<Integer>>identity())
.test()
.awaitDone(5, TimeUnit.SECONDS)
.assertResult(1);
}
@Test
public void timeskipJustSkip() {
Flowable.just(1)
.window(1, 2, TimeUnit.MINUTES, Schedulers.single())
.flatMap(Functions.<Flowable<Integer>>identity())
.test()
.awaitDone(5, TimeUnit.SECONDS)
.assertResult(1);
}
@Test
public void timeskipSkipping() {
TestScheduler scheduler = new TestScheduler();
PublishProcessor<Integer> pp = PublishProcessor.create();
TestSubscriber<Integer> ts = pp.window(1, 2, TimeUnit.SECONDS, scheduler)
.flatMap(Functions.<Flowable<Integer>>identity())
.test();
pp.onNext(1);
pp.onNext(2);
scheduler.advanceTimeBy(1, TimeUnit.SECONDS);
pp.onNext(3);
pp.onNext(4);
scheduler.advanceTimeBy(1, TimeUnit.SECONDS);
pp.onNext(5);
pp.onNext(6);
scheduler.advanceTimeBy(1, TimeUnit.SECONDS);
pp.onNext(7);
pp.onComplete();
ts.assertResult(1, 2, 5, 6);
}
@Test
public void timeskipOverlapping() {
TestScheduler scheduler = new TestScheduler();
PublishProcessor<Integer> pp = PublishProcessor.create();
TestSubscriber<Integer> ts = pp.window(2, 1, TimeUnit.SECONDS, scheduler)
.flatMap(Functions.<Flowable<Integer>>identity())
.test();
pp.onNext(1);
pp.onNext(2);
scheduler.advanceTimeBy(1, TimeUnit.SECONDS);
pp.onNext(3);
pp.onNext(4);
scheduler.advanceTimeBy(1, TimeUnit.SECONDS);
pp.onNext(5);
pp.onNext(6);
scheduler.advanceTimeBy(1, TimeUnit.SECONDS);
pp.onNext(7);
pp.onComplete();
ts.assertResult(1, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7);
}
@Test
public void exactOnError() {
TestScheduler scheduler = new TestScheduler();
PublishProcessor<Integer> pp = PublishProcessor.create();
TestSubscriber<Integer> ts = pp.window(1, 1, TimeUnit.SECONDS, scheduler)
.flatMap(Functions.<Flowable<Integer>>identity())
.test();
pp.onError(new TestException());
ts.assertFailure(TestException.class);
}
@Test
public void overlappingOnError() {
TestScheduler scheduler = new TestScheduler();
PublishProcessor<Integer> pp = PublishProcessor.create();
TestSubscriber<Integer> ts = pp.window(2, 1, TimeUnit.SECONDS, scheduler)
.flatMap(Functions.<Flowable<Integer>>identity())
.test();
pp.onError(new TestException());
ts.assertFailure(TestException.class);
}
@Test
public void skipOnError() {
TestScheduler scheduler = new TestScheduler();
PublishProcessor<Integer> pp = PublishProcessor.create();
TestSubscriber<Integer> ts = pp.window(1, 2, TimeUnit.SECONDS, scheduler)
.flatMap(Functions.<Flowable<Integer>>identity())
.test();
pp.onError(new TestException());
ts.assertFailure(TestException.class);
}
@Test
@SuppressWarnings("unchecked")
public void exactBackpressure() {
TestScheduler scheduler = new TestScheduler();
PublishProcessor<Integer> pp = PublishProcessor.create();
TestSubscriber<Flowable<Integer>> ts = pp.window(1, 1, TimeUnit.SECONDS, scheduler)
.test(0L);
scheduler.advanceTimeBy(1, TimeUnit.SECONDS);
ts.assertFailure(MissingBackpressureException.class);
}
@Test
@SuppressWarnings("unchecked")
public void skipBackpressure() {
TestScheduler scheduler = new TestScheduler();
PublishProcessor<Integer> pp = PublishProcessor.create();
TestSubscriber<Flowable<Integer>> ts = pp.window(1, 2, TimeUnit.SECONDS, scheduler)
.test(0L);
scheduler.advanceTimeBy(1, TimeUnit.SECONDS);
ts.assertFailure(MissingBackpressureException.class);
}
@Test
@SuppressWarnings("unchecked")
public void overlapBackpressure() {
TestScheduler scheduler = new TestScheduler();
PublishProcessor<Integer> pp = PublishProcessor.create();
TestSubscriber<Flowable<Integer>> ts = pp.window(2, 1, TimeUnit.SECONDS, scheduler)
.test(0L);
scheduler.advanceTimeBy(1, TimeUnit.SECONDS);
ts.assertFailure(MissingBackpressureException.class);
}
@Test
public void exactBackpressure2() {
TestScheduler scheduler = new TestScheduler();
PublishProcessor<Integer> pp = PublishProcessor.create();
TestSubscriber<Flowable<Integer>> ts = pp.window(1, 1, TimeUnit.SECONDS, scheduler)
.test(1L);
scheduler.advanceTimeBy(2, TimeUnit.SECONDS);
ts.assertError(MissingBackpressureException.class);
}
@Test
public void skipBackpressure2() {
TestScheduler scheduler = new TestScheduler();
PublishProcessor<Integer> pp = PublishProcessor.create();
TestSubscriber<Flowable<Integer>> ts = pp.window(1, 2, TimeUnit.SECONDS, scheduler)
.test(1L);
scheduler.advanceTimeBy(2, TimeUnit.SECONDS);
ts.assertError(MissingBackpressureException.class);
}
@Test
public void overlapBackpressure2() {
TestScheduler scheduler = new TestScheduler();
PublishProcessor<Integer> pp = PublishProcessor.create();
TestSubscriber<Flowable<Integer>> ts = pp.window(2, 1, TimeUnit.SECONDS, scheduler)
.test(1L);
scheduler.advanceTimeBy(2, TimeUnit.SECONDS);
ts.assertError(MissingBackpressureException.class);
}
@Test
public void dispose() {
TestHelper.checkDisposed(Flowable.range(1, 5).window(1, TimeUnit.DAYS, Schedulers.single()).onBackpressureDrop());
TestHelper.checkDisposed(Flowable.range(1, 5).window(2, 1, TimeUnit.DAYS, Schedulers.single()).onBackpressureDrop());
TestHelper.checkDisposed(Flowable.range(1, 5).window(1, 2, TimeUnit.DAYS, Schedulers.single()).onBackpressureDrop());
TestHelper.checkDisposed(Flowable.never()
.window(1, TimeUnit.DAYS, Schedulers.single(), 2, true).onBackpressureDrop());
}
@Test
public void restartTimer() {
Flowable.range(1, 5)
.window(1, TimeUnit.DAYS, Schedulers.single(), 2, true)
.flatMap(Functions.<Flowable<Integer>>identity())
.test()
.assertResult(1, 2, 3, 4, 5);
}
@Test
public void exactBoundaryError() {
Flowable.error(new TestException())
.window(1, TimeUnit.DAYS, Schedulers.single(), 2, true)
.test()
.assertSubscribed()
.assertError(TestException.class)
.assertNotComplete();
}
@Test
public void restartTimerMany() throws Exception {
final AtomicBoolean cancel1 = new AtomicBoolean();
Flowable.intervalRange(1, 1000, 1, 1, TimeUnit.MILLISECONDS)
.doOnCancel(new Action() {
@Override
public void run() throws Exception {
cancel1.set(true);
}
})
.window(1, TimeUnit.MILLISECONDS, Schedulers.single(), 2, true)
.flatMap(Functions.<Flowable<Long>>identity())
.take(500)
.test()
.awaitDone(5, TimeUnit.SECONDS)
.assertSubscribed()
.assertValueCount(500)
.assertNoErrors()
.assertComplete();
int timeout = 20;
while (timeout-- > 0 && !cancel1.get()) {
Thread.sleep(100);
}
assertTrue("intervalRange was not cancelled!", cancel1.get());
}
@Test
public void exactUnboundedReentrant() {
TestScheduler scheduler = new TestScheduler();
final FlowableProcessor<Integer> ps = PublishProcessor.<Integer>create();
TestSubscriber<Integer> to = new TestSubscriber<Integer>() {
@Override
public void onNext(Integer t) {
super.onNext(t);
if (t == 1) {
ps.onNext(2);
ps.onComplete();
}
}
};
ps.window(1, TimeUnit.MILLISECONDS, scheduler)
.flatMap(new Function<Flowable<Integer>, Flowable<Integer>>() {
@Override
public Flowable<Integer> apply(Flowable<Integer> v) throws Exception {
return v;
}
})
.subscribe(to);
ps.onNext(1);
to
.awaitDone(1, TimeUnit.SECONDS)
.assertResult(1, 2);
}
@Test
public void exactBoundedReentrant() {
TestScheduler scheduler = new TestScheduler();
final FlowableProcessor<Integer> ps = PublishProcessor.<Integer>create();
TestSubscriber<Integer> to = new TestSubscriber<Integer>() {
@Override
public void onNext(Integer t) {
super.onNext(t);
if (t == 1) {
ps.onNext(2);
ps.onComplete();
}
}
};
ps.window(1, TimeUnit.MILLISECONDS, scheduler, 10, true)
.flatMap(new Function<Flowable<Integer>, Flowable<Integer>>() {
@Override
public Flowable<Integer> apply(Flowable<Integer> v) throws Exception {
return v;
}
})
.subscribe(to);
ps.onNext(1);
to
.awaitDone(1, TimeUnit.SECONDS)
.assertResult(1, 2);
}
@Test
public void exactBoundedReentrant2() {
TestScheduler scheduler = new TestScheduler();
final FlowableProcessor<Integer> ps = PublishProcessor.<Integer>create();
TestSubscriber<Integer> to = new TestSubscriber<Integer>() {
@Override
public void onNext(Integer t) {
super.onNext(t);
if (t == 1) {
ps.onNext(2);
ps.onComplete();
}
}
};
ps.window(1, TimeUnit.MILLISECONDS, scheduler, 2, true)
.flatMap(new Function<Flowable<Integer>, Flowable<Integer>>() {
@Override
public Flowable<Integer> apply(Flowable<Integer> v) throws Exception {
return v;
}
})
.subscribe(to);
ps.onNext(1);
to
.awaitDone(1, TimeUnit.SECONDS)
.assertResult(1, 2);
}
@Test
public void skipReentrant() {
TestScheduler scheduler = new TestScheduler();
final FlowableProcessor<Integer> ps = PublishProcessor.<Integer>create();
TestSubscriber<Integer> to = new TestSubscriber<Integer>() {
@Override
public void onNext(Integer t) {
super.onNext(t);
if (t == 1) {
ps.onNext(2);
ps.onComplete();
}
}
};
ps.window(1, 2, TimeUnit.MILLISECONDS, scheduler)
.flatMap(new Function<Flowable<Integer>, Flowable<Integer>>() {
@Override
public Flowable<Integer> apply(Flowable<Integer> v) throws Exception {
return v;
}
})
.subscribe(to);
ps.onNext(1);
to
.awaitDone(1, TimeUnit.SECONDS)
.assertResult(1, 2);
}
@Test
public void sizeTimeTimeout() {
TestScheduler scheduler = new TestScheduler();
PublishProcessor<Integer> ps = PublishProcessor.<Integer>create();
TestSubscriber<Flowable<Integer>> ts = ps.window(5, TimeUnit.MILLISECONDS, scheduler, 100)
.test()
.assertValueCount(1);
scheduler.advanceTimeBy(5, TimeUnit.MILLISECONDS);
ts.assertValueCount(2)
.assertNoErrors()
.assertNotComplete();
ts.values().get(0).test().assertResult();
}
@Test
public void periodicWindowCompletion() {
TestScheduler scheduler = new TestScheduler();
FlowableProcessor<Integer> ps = PublishProcessor.<Integer>create();
TestSubscriber<Flowable<Integer>> ts = ps.window(5, TimeUnit.MILLISECONDS, scheduler, Long.MAX_VALUE, false)
.test();
scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS);
ts.assertValueCount(21)
.assertNoErrors()
.assertNotComplete();
}
@Test
public void periodicWindowCompletionRestartTimer() {
TestScheduler scheduler = new TestScheduler();
FlowableProcessor<Integer> ps = PublishProcessor.<Integer>create();
TestSubscriber<Flowable<Integer>> ts = ps.window(5, TimeUnit.MILLISECONDS, scheduler, Long.MAX_VALUE, true)
.test();
scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS);
ts.assertValueCount(21)
.assertNoErrors()
.assertNotComplete();
}
@Test
public void periodicWindowCompletionBounded() {
TestScheduler scheduler = new TestScheduler();
FlowableProcessor<Integer> ps = PublishProcessor.<Integer>create();
TestSubscriber<Flowable<Integer>> ts = ps.window(5, TimeUnit.MILLISECONDS, scheduler, 5, false)
.test();
scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS);
ts.assertValueCount(21)
.assertNoErrors()
.assertNotComplete();
}
@Test
public void periodicWindowCompletionRestartTimerBounded() {
TestScheduler scheduler = new TestScheduler();
FlowableProcessor<Integer> ps = PublishProcessor.<Integer>create();
TestSubscriber<Flowable<Integer>> ts = ps.window(5, TimeUnit.MILLISECONDS, scheduler, 5, true)
.test();
scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS);
ts.assertValueCount(21)
.assertNoErrors()
.assertNotComplete();
}
@Test
public void periodicWindowCompletionRestartTimerBoundedSomeData() {
TestScheduler scheduler = new TestScheduler();
FlowableProcessor<Integer> ps = PublishProcessor.<Integer>create();
TestSubscriber<Flowable<Integer>> ts = ps.window(5, TimeUnit.MILLISECONDS, scheduler, 2, true)
.test();
ps.onNext(1);
ps.onNext(2);
scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS);
ts.assertValueCount(22)
.assertNoErrors()
.assertNotComplete();
}
@Test
public void countRestartsOnTimeTick() {
TestScheduler scheduler = new TestScheduler();
FlowableProcessor<Integer> ps = PublishProcessor.<Integer>create();
TestSubscriber<Flowable<Integer>> ts = ps.window(5, TimeUnit.MILLISECONDS, scheduler, 5, true)
.test();
// window #1
ps.onNext(1);
ps.onNext(2);
scheduler.advanceTimeBy(5, TimeUnit.MILLISECONDS);
// window #2
ps.onNext(3);
ps.onNext(4);
ps.onNext(5);
ps.onNext(6);
ts.assertValueCount(2)
.assertNoErrors()
.assertNotComplete();
}
}