/**
* Copyright 2009-2016 the original author or authors.
*
* 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 net.javacrumbs.futureconverter.common.test.rxjava2;
import io.reactivex.Single;
import io.reactivex.subjects.PublishSubject;
import net.javacrumbs.futureconverter.common.test.ConvertedFutureTestHelper;
import org.junit.After;
import org.junit.Test;
import java.io.IOException;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import static net.javacrumbs.futureconverter.common.test.AbstractConverterTest.VALUE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public abstract class AbstractSingleToFutureConverterTest<T extends Future<String>> {
private final CountDownLatch waitLatch = new CountDownLatch(1);
private final CountDownLatch taskStartedLatch = new CountDownLatch(1);
private AtomicInteger subscribed = new AtomicInteger(0);
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
private AtomicReference<Future> futureTaskRef = new AtomicReference<>();
private final ConvertedFutureTestHelper<T> convertedFutureTestHelper;
protected AbstractSingleToFutureConverterTest(ConvertedFutureTestHelper<T> convertedFutureTestHelper) {
this.convertedFutureTestHelper = convertedFutureTestHelper;
}
protected abstract T toFuture(Single<String> single);
protected abstract Single<String> toSingle(T future);
@After
public void cleanup() {
waitLatch.countDown();
executorService.shutdown();
}
@Test
public void testConvertToFutureCompleted() throws ExecutionException, InterruptedException {
Single<String> single = Single.just(VALUE);
T future = toFuture(single);
convertedFutureTestHelper.addCallbackTo(future);
assertEquals(VALUE, future.get());
assertEquals(true, future.isDone());
assertEquals(false, future.isCancelled());
convertedFutureTestHelper.verifyCallbackCalledWithCorrectValue();
assertSame(single, toSingle(future));
}
@Test
public void testRun() throws ExecutionException, InterruptedException {
Single<String> single = createAsyncSingle();
T future = toFuture(single);
assertEquals(false, future.isDone());
assertEquals(false, future.isCancelled());
convertedFutureTestHelper.addCallbackTo(future);
waitLatch.countDown();
//wait for the result
assertEquals(VALUE, future.get());
assertEquals(true, future.isDone());
assertEquals(false, future.isCancelled());
convertedFutureTestHelper.verifyCallbackCalledWithCorrectValue();
assertEquals(1, subscribed.get());
}
@Test
public void testCancelOriginal() throws ExecutionException, InterruptedException {
Single<String> single = createAsyncSingle();
T future = toFuture(single);
taskStartedLatch.await(); //wait for the task to start
getWorkerFuture().cancel(true);
assertTrue(getWorkerFuture().isCancelled());
try {
future.get();
fail("Exception expected");
} catch (ExecutionException e) {
//ok
}
assertEquals(true, future.isDone());
assertEquals(false, future.isCancelled());
assertEquals(1, subscribed.get());
}
@Test
public void shouldEndExceptionallyIfObservableFailsBeforeConversion() throws InterruptedException {
RuntimeException exception = new RuntimeException("test");
Single<String> single = Single.error(exception);
T future = toFuture(single);
assertTrue(future.isDone());
assertFalse(future.isCancelled());
try {
future.get();
fail("Exception expected");
} catch (ExecutionException e) {
assertSame(exception, e.getCause());
}
}
@Test
public void testCancelNew() throws ExecutionException, InterruptedException {
Single<String> single = createAsyncSingle();
T future = toFuture(single);
assertTrue(future.cancel(true));
try {
future.get();
fail("Exception expected");
} catch (CancellationException e) {
//ok
}
assertEquals(true, future.isDone());
assertEquals(true, future.isCancelled());
assertEquals(1, subscribed.get());
}
@Test
public void cancelShouldUnsubscribe() {
PublishSubject<String> single = PublishSubject.create();
assertFalse(single.hasObservers());
T future = toFuture(single.singleOrError());
assertTrue(single.hasObservers());
future.cancel(true);
assertFalse(single.hasObservers());
}
@Test
public void testCancelCompleted() throws ExecutionException, InterruptedException {
Single<String> single = Single.just(VALUE);
T future = toFuture(single);
assertFalse(future.cancel(true));
assertEquals(VALUE, future.get());
assertTrue(future.isDone());
assertFalse(future.isCancelled());
}
@Test
public void testRuntimeException() throws ExecutionException, InterruptedException {
doTestException(new RuntimeException("test"));
}
@Test
public void testIOException() throws ExecutionException, InterruptedException {
doTestException(new IOException("test"));
}
private void doTestException(final Exception exception) throws ExecutionException, InterruptedException {
Single<String> single = Single.error(exception);
T future = toFuture(single);
try {
future.get();
} catch (ExecutionException e) {
assertSame(exception, e.getCause());
}
}
private Single<String> createAsyncSingle() {
return Single.create(emitter -> {
subscribed.incrementAndGet();
Future<?> future = executorService.submit(() -> {
try {
taskStartedLatch.countDown();
waitLatch.await();
emitter.onSuccess(VALUE);
} catch (InterruptedException e) {
emitter.onError(e);
throw new RuntimeException(e);
}
});
//subscriber.add(Subscriptions.from(future));
assertTrue(this.futureTaskRef.compareAndSet(null, future));
});
}
/**
* Future that is running underneath the Observable.
*
* @return
*/
protected Future getWorkerFuture() {
return futureTaskRef.get();
}
}