/**
* 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
*/
package org.corfudb.runtime.view;
import org.apache.maven.wagon.ConnectionException;
import org.corfudb.AbstractCorfuTest;
import org.corfudb.runtime.exceptions.QuorumUnreachableException;
import org.junit.Test;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.*;
import static org.junit.Assert.*;
/**
* Tests the futures used in the quorum replication
* Created by Konstantin Spirov on 2/6/2017.
*/
public class QuorumFuturesFactoryTest extends AbstractCorfuTest {
@Test
public void testSingleFutureIncompleteComplete() throws Exception {
CompletableFuture<String> f1 = new CompletableFuture<>();
Future<String> result = QuorumFuturesFactory.getQuorumFuture(String::compareTo, f1);
try {
result.get(PARAMETERS.TIMEOUT_VERY_SHORT.toMillis(), TimeUnit.MILLISECONDS);
fail();
} catch (TimeoutException e) {
// expected
}
assertFalse(result.isDone());
f1.complete("ok");
Object value = result.get(PARAMETERS.TIMEOUT_SHORT.toMillis(), TimeUnit.MILLISECONDS);
assertEquals("ok", value);
assertTrue(result.isDone());
}
@Test
public void testInfiniteGet() throws Exception {
CompletableFuture<String> f1 = new CompletableFuture<>();
Future<String> result = QuorumFuturesFactory.getQuorumFuture(String::compareTo, f1);
f1.complete("ok");
Object value = result.get();
assertEquals("ok", value);
assertTrue(result.isDone());
}
@Test
public void test2FuturesIncompleteComplete() throws Exception {
CompletableFuture<String> f1 = new CompletableFuture<>();
CompletableFuture<String> f2 = new CompletableFuture<>();
Future<String> result = QuorumFuturesFactory.getQuorumFuture(String::compareTo, f1, f2);
try {
result.get(PARAMETERS.TIMEOUT_VERY_SHORT.toMillis(), TimeUnit.MILLISECONDS);
fail();
} catch (TimeoutException e) {
// expected
}
f2.complete("ok");
try {
result.get(PARAMETERS.TIMEOUT_VERY_SHORT.toMillis(), TimeUnit.MILLISECONDS);
fail();
} catch (TimeoutException e) {
// expected
}
f1.complete("ok");
Object value = result.get(PARAMETERS.TIMEOUT_SHORT.toMillis(), TimeUnit.MILLISECONDS);
assertEquals("ok", value);
}
@Test
public void test3FuturesIncompleteComplete() throws Exception {
CompletableFuture<String> f1 = new CompletableFuture<>();
CompletableFuture<String> f2 = new CompletableFuture<>();
CompletableFuture<String> f3 = new CompletableFuture<>();
QuorumFuturesFactory.CompositeFuture<String> result = QuorumFuturesFactory.getQuorumFuture(String::compareTo, f1, f2, f3);
try {
result.get(PARAMETERS.TIMEOUT_VERY_SHORT.toMillis(), TimeUnit.MILLISECONDS);
fail();
} catch (TimeoutException e) {
// expected
}
f2.complete("ok");
try {
result.get(PARAMETERS.TIMEOUT_VERY_SHORT.toMillis(), TimeUnit.MILLISECONDS);
fail();
} catch (TimeoutException e) {
// expected
}
f3.complete("ok");
Object value = result.get(PARAMETERS.TIMEOUT_SHORT.toMillis(), TimeUnit.MILLISECONDS);
assertEquals("ok", value);
f1.complete("ok");
value = result.get(PARAMETERS.TIMEOUT_VERY_SHORT.toMillis(), TimeUnit.MILLISECONDS);
assertEquals("ok", value);
assertFalse(result.isConflict());
}
@Test
public void test3FuturesWithFirstWinnerIncompleteComplete() throws Exception {
CompletableFuture<String> f1 = new CompletableFuture<>();
CompletableFuture<String> f2 = new CompletableFuture<>();
CompletableFuture<String> f3 = new CompletableFuture<>();
Future<String> result = QuorumFuturesFactory.getFirstWinsFuture(String::compareTo, f1, f2, f3);
try {
result.get(PARAMETERS.TIMEOUT_VERY_SHORT.toMillis(), TimeUnit.MILLISECONDS);
fail();
} catch (TimeoutException e) {
// expected
}
f2.complete("ok");
Object value = result.get(PARAMETERS.TIMEOUT_SHORT.toMillis(), TimeUnit.MILLISECONDS);
assertEquals("ok", value);
}
@Test
public void testException() throws Exception {
CompletableFuture<String> f1 = new CompletableFuture<>();
CompletableFuture<String> f2 = new CompletableFuture<>();
CompletableFuture<String> f3 = new CompletableFuture<>();
QuorumFuturesFactory.CompositeFuture<String> result = QuorumFuturesFactory.getQuorumFuture(String::compareTo, f1, f2, f3);
f1.completeExceptionally(new ConnectionException(""));
f3.complete("");
try {
Object value = result.get(PARAMETERS.TIMEOUT_VERY_SHORT.toMillis(), TimeUnit.MILLISECONDS);
fail();
} catch (TimeoutException e) {
// expected behaviour
}
f2.completeExceptionally(new IllegalArgumentException());
try {
Object value = result.get(PARAMETERS.TIMEOUT_VERY_SHORT.toMillis(), TimeUnit.MILLISECONDS);
fail();
} catch (ExecutionException e) {
// expected behaviour
}
assertTrue(result.isDone());
Set<Class> set = new LinkedHashSet<>();
for (Throwable t: result.getThrowables()) {
set.add(t.getClass());
}
assertTrue(set.contains(ConnectionException.class));
assertTrue(set.contains(IllegalArgumentException.class));
}
@Test
public void testFailFastExceptionSingle() throws Exception {
CompletableFuture<String> f1 = new CompletableFuture<>();
CompletableFuture<String> f2 = new CompletableFuture<>();
CompletableFuture<String> f3 = new CompletableFuture<>();
QuorumFuturesFactory.CompositeFuture<String> result = QuorumFuturesFactory.getQuorumFuture(String::compareTo,
new CompletableFuture[]{f1, f2, f3}, NullPointerException.class, IllegalAccessError.class);
f1.completeExceptionally(new NullPointerException());
try {
Object value = result.get(PARAMETERS.TIMEOUT_VERY_SHORT.toMillis(), TimeUnit.MILLISECONDS);
fail();
} catch (ExecutionException e) {
// expected behaviour
}
assertTrue(result.isDone());
Set<Class> set = new LinkedHashSet<>();
for (Throwable t: result.getThrowables()) {
set.add(t.getClass());
}
assertTrue(set.contains(NullPointerException.class));
}
@Test
public void testCanceledFromInside() throws Exception {
CompletableFuture<String> f1 = new CompletableFuture<>();
QuorumFuturesFactory.CompositeFuture<String> result = QuorumFuturesFactory.getQuorumFuture(String::compareTo, f1);
f1.cancel(true);
try {
Object value = result.get(PARAMETERS.TIMEOUT_SHORT.toMillis(), TimeUnit.MILLISECONDS);
fail();
} catch (ExecutionException e) {
assertTrue(e.getCause() instanceof QuorumUnreachableException);
assertEquals(0, ((QuorumUnreachableException)e.getCause()).getReachable());
assertEquals(1, ((QuorumUnreachableException)e.getCause()).getRequired());
}
assertTrue(result.isCancelled());
assertTrue(result.isDone());
assertFalse(result.isConflict());
assertTrue(result.getThrowables().isEmpty());
}
@Test
public void testCanceledPlusException() throws Exception {
CompletableFuture<String> f1 = new CompletableFuture<>();
CompletableFuture<String> f2 = new CompletableFuture<>();
QuorumFuturesFactory.CompositeFuture<String> result = QuorumFuturesFactory.getQuorumFuture(String::compareTo, f1, f2);
f1.cancel(true);
f2.completeExceptionally(new NullPointerException());
try {
Object value = result.get(PARAMETERS.TIMEOUT_VERY_SHORT.toMillis(), TimeUnit.MILLISECONDS);
fail();
} catch (ExecutionException e) {
// expected behaviour
}
assertTrue(result.isCancelled());
assertTrue(result.isDone());
assertEquals(result.getThrowables().iterator().next().getClass(), NullPointerException.class);
}
@Test
public void test3FuturesCompleteWithResolvedConflict() throws Exception {
CompletableFuture<String> f1 = new CompletableFuture<>();
CompletableFuture<String> f2 = new CompletableFuture<>();
CompletableFuture<String> f3 = new CompletableFuture<>();
QuorumFuturesFactory.CompositeFuture<String> result = QuorumFuturesFactory.getQuorumFuture(String::compareTo, f1, f2, f3);
f2.complete("ok");
try {
result.get(PARAMETERS.TIMEOUT_VERY_SHORT.toMillis(), TimeUnit.MILLISECONDS);
fail();
} catch (TimeoutException e) {
// expected
}
f3.complete("not-ok");
try {
Object value = result.get(PARAMETERS.TIMEOUT_VERY_SHORT.toMillis(), TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
// expected
}
f1.complete("ok");
Object value = result.get(PARAMETERS.TIMEOUT_VERY_SHORT.toMillis(), TimeUnit.MILLISECONDS);
assertEquals("ok", value);
assertTrue(result.isConflict());
}
@Test
public void test3FuturesCompleteWithUnresolvedConflict() throws Exception {
CompletableFuture<String> f1 = new CompletableFuture<>();
CompletableFuture<String> f2 = new CompletableFuture<>();
CompletableFuture<String> f3 = new CompletableFuture<>();
QuorumFuturesFactory.CompositeFuture<String> result = QuorumFuturesFactory.getQuorumFuture(String::compareTo, f1, f2, f3);
f2.complete("ok");
f3.complete("not-ok");
f1.complete("1/3 split brain");
try {
Object value = result.get(PARAMETERS.TIMEOUT_VERY_SHORT.toMillis(), TimeUnit.MILLISECONDS);
fail();
} catch (ExecutionException e) {
assertTrue(e.getCause() instanceof QuorumUnreachableException);
assertEquals(1, ((QuorumUnreachableException) e.getCause()).getReachable());
assertEquals(2, ((QuorumUnreachableException) e.getCause()).getRequired());
}
assertTrue(result.isConflict());
}
}