/**
* 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.observable;
import static org.junit.Assert.*;
import java.util.*;
import java.util.concurrent.*;
import org.junit.*;
import io.reactivex.*;
import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.disposables.Disposables;
import io.reactivex.exceptions.TestException;
import io.reactivex.functions.*;
import io.reactivex.internal.functions.Functions;
import io.reactivex.observers.*;
import io.reactivex.schedulers.TestScheduler;
import io.reactivex.subjects.*;
public class ObservableWindowWithStartEndObservableTest {
private TestScheduler scheduler;
private Scheduler.Worker innerScheduler;
@Before
public void before() {
scheduler = new TestScheduler();
innerScheduler = scheduler.createWorker();
}
@Test
public void testObservableBasedOpenerAndCloser() {
final List<String> list = new ArrayList<String>();
final List<List<String>> lists = new ArrayList<List<String>>();
Observable<String> source = Observable.unsafeCreate(new ObservableSource<String>() {
@Override
public void subscribe(Observer<? super String> innerObserver) {
innerObserver.onSubscribe(Disposables.empty());
push(innerObserver, "one", 10);
push(innerObserver, "two", 60);
push(innerObserver, "three", 110);
push(innerObserver, "four", 160);
push(innerObserver, "five", 210);
complete(innerObserver, 500);
}
});
Observable<Object> openings = Observable.unsafeCreate(new ObservableSource<Object>() {
@Override
public void subscribe(Observer<? super Object> innerObserver) {
innerObserver.onSubscribe(Disposables.empty());
push(innerObserver, new Object(), 50);
push(innerObserver, new Object(), 200);
complete(innerObserver, 250);
}
});
Function<Object, Observable<Object>> closer = new Function<Object, Observable<Object>>() {
@Override
public Observable<Object> apply(Object opening) {
return Observable.unsafeCreate(new ObservableSource<Object>() {
@Override
public void subscribe(Observer<? super Object> innerObserver) {
innerObserver.onSubscribe(Disposables.empty());
push(innerObserver, new Object(), 100);
complete(innerObserver, 101);
}
});
}
};
Observable<Observable<String>> windowed = source.window(openings, closer);
windowed.subscribe(observeWindow(list, lists));
scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS);
assertEquals(2, lists.size());
assertEquals(lists.get(0), list("two", "three"));
assertEquals(lists.get(1), list("five"));
}
@Test
public void testObservableBasedCloser() {
final List<String> list = new ArrayList<String>();
final List<List<String>> lists = new ArrayList<List<String>>();
Observable<String> source = Observable.unsafeCreate(new ObservableSource<String>() {
@Override
public void subscribe(Observer<? super String> innerObserver) {
innerObserver.onSubscribe(Disposables.empty());
push(innerObserver, "one", 10);
push(innerObserver, "two", 60);
push(innerObserver, "three", 110);
push(innerObserver, "four", 160);
push(innerObserver, "five", 210);
complete(innerObserver, 250);
}
});
Callable<Observable<Object>> closer = new Callable<Observable<Object>>() {
int calls;
@Override
public Observable<Object> call() {
return Observable.unsafeCreate(new ObservableSource<Object>() {
@Override
public void subscribe(Observer<? super Object> innerObserver) {
innerObserver.onSubscribe(Disposables.empty());
int c = calls++;
if (c == 0) {
push(innerObserver, new Object(), 100);
} else
if (c == 1) {
push(innerObserver, new Object(), 100);
} else {
complete(innerObserver, 101);
}
}
});
}
};
Observable<Observable<String>> windowed = source.window(closer);
windowed.subscribe(observeWindow(list, lists));
scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS);
assertEquals(3, lists.size());
assertEquals(lists.get(0), list("one", "two"));
assertEquals(lists.get(1), list("three", "four"));
assertEquals(lists.get(2), list("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 Observer<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 Observer<?> observer, int delay) {
innerScheduler.schedule(new Runnable() {
@Override
public void run() {
observer.onComplete();
}
}, delay, TimeUnit.MILLISECONDS);
}
private Consumer<Observable<String>> observeWindow(final List<String> list, final List<List<String>> lists) {
return new Consumer<Observable<String>>() {
@Override
public void accept(Observable<String> stringObservable) {
stringObservable.subscribe(new DefaultObserver<String>() {
@Override
public void onComplete() {
lists.add(new ArrayList<String>(list));
list.clear();
}
@Override
public void onError(Throwable e) {
fail(e.getMessage());
}
@Override
public void onNext(String args) {
list.add(args);
}
});
}
};
}
@Test
public void testNoUnsubscribeAndNoLeak() {
PublishSubject<Integer> source = PublishSubject.create();
PublishSubject<Integer> open = PublishSubject.create();
final PublishSubject<Integer> close = PublishSubject.create();
TestObserver<Observable<Integer>> ts = new TestObserver<Observable<Integer>>();
source.window(open, new Function<Integer, Observable<Integer>>() {
@Override
public Observable<Integer> apply(Integer t) {
return close;
}
}).subscribe(ts);
open.onNext(1);
source.onNext(1);
assertTrue(open.hasObservers());
assertTrue(close.hasObservers());
close.onNext(1);
assertFalse(close.hasObservers());
source.onComplete();
ts.assertComplete();
ts.assertNoErrors();
ts.assertValueCount(1);
// 2.0.2 - not anymore
// assertTrue("Not cancelled!", ts.isCancelled());
assertFalse(open.hasObservers());
assertFalse(close.hasObservers());
}
@Test
public void testUnsubscribeAll() {
PublishSubject<Integer> source = PublishSubject.create();
PublishSubject<Integer> open = PublishSubject.create();
final PublishSubject<Integer> close = PublishSubject.create();
TestObserver<Observable<Integer>> ts = new TestObserver<Observable<Integer>>();
source.window(open, new Function<Integer, Observable<Integer>>() {
@Override
public Observable<Integer> apply(Integer t) {
return close;
}
}).subscribe(ts);
open.onNext(1);
assertTrue(open.hasObservers());
assertTrue(close.hasObservers());
ts.dispose();
// FIXME subject has subscribers because of the open window
assertTrue(open.hasObservers());
// FIXME subject has subscribers because of the open window
assertTrue(close.hasObservers());
}
@Test
public void boundarySelectorNormal() {
PublishSubject<Integer> source = PublishSubject.create();
PublishSubject<Integer> start = PublishSubject.create();
final PublishSubject<Integer> end = PublishSubject.create();
TestObserver<Integer> to = source.window(start, new Function<Integer, ObservableSource<Integer>>() {
@Override
public ObservableSource<Integer> apply(Integer v) throws Exception {
return end;
}
})
.flatMap(Functions.<Observable<Integer>>identity())
.test();
start.onNext(0);
source.onNext(1);
source.onNext(2);
source.onNext(3);
source.onNext(4);
start.onNext(1);
source.onNext(5);
source.onNext(6);
end.onNext(1);
start.onNext(2);
TestHelper.emit(source, 7, 8);
to.assertResult(1, 2, 3, 4, 5, 5, 6, 6, 7, 8);
}
@Test
public void startError() {
PublishSubject<Integer> source = PublishSubject.create();
PublishSubject<Integer> start = PublishSubject.create();
final PublishSubject<Integer> end = PublishSubject.create();
TestObserver<Integer> to = source.window(start, new Function<Integer, ObservableSource<Integer>>() {
@Override
public ObservableSource<Integer> apply(Integer v) throws Exception {
return end;
}
})
.flatMap(Functions.<Observable<Integer>>identity())
.test();
start.onError(new TestException());
to.assertFailure(TestException.class);
assertFalse("Source has observers!", source.hasObservers());
assertFalse("Start has observers!", start.hasObservers());
assertFalse("End has observers!", end.hasObservers());
}
@Test
public void endError() {
PublishSubject<Integer> source = PublishSubject.create();
PublishSubject<Integer> start = PublishSubject.create();
final PublishSubject<Integer> end = PublishSubject.create();
TestObserver<Integer> to = source.window(start, new Function<Integer, ObservableSource<Integer>>() {
@Override
public ObservableSource<Integer> apply(Integer v) throws Exception {
return end;
}
})
.flatMap(Functions.<Observable<Integer>>identity())
.test();
start.onNext(1);
end.onError(new TestException());
to.assertFailure(TestException.class);
assertFalse("Source has observers!", source.hasObservers());
assertFalse("Start has observers!", start.hasObservers());
assertFalse("End has observers!", end.hasObservers());
}
@Test
public void dispose() {
TestHelper.checkDisposed(Observable.just(1).window(Observable.just(2), Functions.justFunction(Observable.never())));
}
@Test
public void reentrant() {
final Subject<Integer> ps = PublishSubject.<Integer>create();
TestObserver<Integer> to = new TestObserver<Integer>() {
@Override
public void onNext(Integer t) {
super.onNext(t);
if (t == 1) {
ps.onNext(2);
ps.onComplete();
}
}
};
ps.window(BehaviorSubject.createDefault(1), Functions.justFunction(Observable.never()))
.flatMap(new Function<Observable<Integer>, ObservableSource<Integer>>() {
@Override
public ObservableSource<Integer> apply(Observable<Integer> v) throws Exception {
return v;
}
})
.subscribe(to);
ps.onNext(1);
to
.awaitDone(1, TimeUnit.SECONDS)
.assertResult(1, 2);
}
@Test
public void badSourceCallable() {
TestHelper.checkBadSourceObservable(new Function<Observable<Object>, Object>() {
@Override
public Object apply(Observable<Object> o) throws Exception {
return o.window(Observable.just(1), Functions.justFunction(Observable.never()));
}
}, false, 1, 1, (Object[])null);
}
}