blob: c790a180ede56b260796126fef05bf92ca38cc10 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.hadoop.test;
import org.junit.Assert;
import org.junit.Test;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import static org.apache.hadoop.test.LambdaTestUtils.*;
import static org.apache.hadoop.test.GenericTestUtils.*;
/**
* Test the logic in {@link LambdaTestUtils}.
* This test suite includes Java 8 and Java 7 code; the Java 8 code exists
* to verify that the API is easily used with Lambda expressions.
*/
public class TestLambdaTestUtils extends Assert {
public static final int INTERVAL = 10;
public static final int TIMEOUT = 50;
private FixedRetryInterval retry = new FixedRetryInterval(INTERVAL);
// counter for lambda expressions to use
private int count;
/**
* Always evaluates to true.
*/
public static final Callable<Boolean> ALWAYS_TRUE =
new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return true;
}
};
/**
* Always evaluates to false.
*/
public static final Callable<Boolean> ALWAYS_FALSE =
new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return false;
}
};
/**
* Text in the raised FNFE.
*/
public static final String MISSING = "not found";
/**
* A predicate that always throws a FileNotFoundException.
*/
public static final Callable<Boolean> ALWAYS_FNFE =
new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
throw new FileNotFoundException(MISSING);
}
};
/**
* reusable timeout handler.
*/
public static final GenerateTimeout
TIMEOUT_FAILURE_HANDLER = new GenerateTimeout();
/**
* Always evaluates to 3L.
*/
public static final Callable<Long> EVAL_3L = new Callable<Long>() {
@Override
public Long call() throws Exception {
return 3L;
}
};
/**
* Always raises a {@code FileNotFoundException}.
*/
public static final Callable<Long> EVAL_FNFE = new Callable<Long>() {
@Override
public Long call() throws Exception {
throw new FileNotFoundException(MISSING);
}
};
/**
* Assert the retry count is as expected.
* @param expected expected value
*/
protected void assertRetryCount(int expected) {
assertEquals(retry.toString(), expected, retry.getInvocationCount());
}
/**
* Assert the retry count is as expected.
* @param minCount minimum value
*/
protected void assertMinRetryCount(int minCount) {
assertTrue("retry count of " + retry + " is not >= " + minCount,
minCount <= retry.getInvocationCount());
}
/**
* Raise an exception.
* @param e exception to raise
* @return never
* @throws Exception passed in exception
*/
private boolean r(Exception e) throws Exception {
throw e;
}
/**
* Raise an error.
* @param e error to raise
* @return never
* @throws Exception never
* @throws Error the passed in error
*/
private boolean r(Error e) throws Exception {
throw e;
}
@Test
public void testAwaitAlwaysTrue() throws Throwable {
await(TIMEOUT,
ALWAYS_TRUE,
new FixedRetryInterval(INTERVAL),
TIMEOUT_FAILURE_HANDLER);
}
@Test
public void testAwaitAlwaysFalse() throws Throwable {
try {
await(TIMEOUT,
ALWAYS_FALSE,
retry,
TIMEOUT_FAILURE_HANDLER);
fail("should not have got here");
} catch (TimeoutException e) {
assertMinRetryCount(1);
}
}
@Test
public void testAwaitLinearRetry() throws Throwable {
ProportionalRetryInterval linearRetry =
new ProportionalRetryInterval(INTERVAL * 2, TIMEOUT * 2);
try {
await(TIMEOUT,
ALWAYS_FALSE,
linearRetry,
TIMEOUT_FAILURE_HANDLER);
fail("should not have got here");
} catch (TimeoutException e) {
assertEquals(linearRetry.toString(),
2, linearRetry.getInvocationCount());
}
}
@Test
public void testAwaitFNFE() throws Throwable {
try {
await(TIMEOUT,
ALWAYS_FNFE,
retry,
TIMEOUT_FAILURE_HANDLER);
fail("should not have got here");
} catch (TimeoutException e) {
// inner clause is included
assertTrue(retry.getInvocationCount() > 0);
assertTrue(e.getCause() instanceof FileNotFoundException);
assertExceptionContains(MISSING, e);
}
}
@Test
public void testRetryInterval() throws Throwable {
ProportionalRetryInterval interval =
new ProportionalRetryInterval(200, 1000);
assertEquals(200, (int) interval.call());
assertEquals(400, (int) interval.call());
assertEquals(600, (int) interval.call());
assertEquals(800, (int) interval.call());
assertEquals(1000, (int) interval.call());
assertEquals(1000, (int) interval.call());
assertEquals(1000, (int) interval.call());
}
@Test
public void testInterceptSuccess() throws Throwable {
IOException ioe = intercept(IOException.class, ALWAYS_FNFE);
assertExceptionContains(MISSING, ioe);
}
@Test
public void testInterceptContains() throws Throwable {
intercept(IOException.class, MISSING, ALWAYS_FNFE);
}
@Test
public void testInterceptContainsWrongString() throws Throwable {
try {
FileNotFoundException e =
intercept(FileNotFoundException.class, "404", ALWAYS_FNFE);
assertNotNull(e);
throw e;
} catch (AssertionError expected) {
assertExceptionContains(MISSING, expected);
}
}
@Test
public void testInterceptVoidCallable() throws Throwable {
intercept(AssertionError.class,
NULL_RESULT,
new Callable<IOException>() {
@Override
public IOException call() throws Exception {
return intercept(IOException.class,
new Callable<Void>() {
@Override
public Void call() throws Exception {
return null;
}
});
}
});
}
@Test
public void testEventually() throws Throwable {
long result = eventually(TIMEOUT, EVAL_3L, retry);
assertEquals(3, result);
assertEquals(0, retry.getInvocationCount());
}
@Test
public void testEventuallyFailuresRetry() throws Throwable {
try {
eventually(TIMEOUT, EVAL_FNFE, retry);
fail("should not have got here");
} catch (IOException expected) {
// expected
assertMinRetryCount(1);
}
}
/*
* Java 8 Examples go below this line.
*/
@Test
public void testInterceptFailure() throws Throwable {
try {
IOException ioe = intercept(IOException.class, () -> "hello");
assertNotNull(ioe);
throw ioe;
} catch (AssertionError expected) {
assertExceptionContains("hello", expected);
}
}
@Test
public void testInterceptInterceptLambda() throws Throwable {
// here we use intercept() to test itself.
intercept(AssertionError.class,
MISSING,
() -> intercept(FileNotFoundException.class, "404", ALWAYS_FNFE));
}
@Test
public void testInterceptInterceptVoidResultLambda() throws Throwable {
// see what happens when a null is returned; type inference -> Void
intercept(AssertionError.class,
NULL_RESULT,
() -> intercept(IOException.class, () -> null));
}
@Test
public void testInterceptInterceptStringResultLambda() throws Throwable {
// see what happens when a string is returned; it should be used
// in the message
intercept(AssertionError.class,
"hello, world",
() -> intercept(IOException.class,
() -> "hello, world"));
}
@Test
public void testAwaitNoTimeoutLambda() throws Throwable {
await(0,
() -> true,
retry,
(timeout, ex) -> ex != null ? ex : new Exception("timeout"));
assertRetryCount(0);
}
@Test
public void testAwaitLambdaRepetitions() throws Throwable {
count = 0;
// lambda expression which will succeed after exactly 4 probes
int reps = await(TIMEOUT,
() -> ++count == 4,
() -> 10,
(timeout, ex) -> ex != null ? ex : new Exception("timeout"));
assertEquals(4, reps);
}
@Test
public void testInterceptAwaitLambdaException() throws Throwable {
count = 0;
IOException ioe = intercept(IOException.class,
() -> await(
TIMEOUT,
() -> r(new IOException("inner " + ++count)),
retry,
(timeout, ex) -> ex));
assertRetryCount(count - 1);
// verify that the exception returned was the last one raised
assertExceptionContains(Integer.toString(count), ioe);
}
@Test
public void testInterceptAwaitLambdaDiagnostics() throws Throwable {
intercept(IOException.class, "generated",
() -> await(5,
() -> false,
() -> -1, // force checks -1 timeout probes
(timeout, ex) -> new IOException("generated")));
}
@Test
public void testInterceptAwaitFailFastLambda() throws Throwable {
intercept(FailFastException.class,
() -> await(TIMEOUT,
() -> r(new FailFastException("ffe")),
retry,
(timeout, ex) -> ex));
assertRetryCount(0);
}
@Test
public void testEventuallyOnceLambda() throws Throwable {
String result = eventually(0, () -> "hello", retry);
assertEquals("hello", result);
assertEquals(0, retry.getInvocationCount());
}
@Test
public void testEventuallyLambda() throws Throwable {
long result = eventually(TIMEOUT, () -> 3, retry);
assertEquals(3, result);
assertRetryCount(0);
}
@Test
public void testInterceptEventuallyLambdaFailures() throws Throwable {
intercept(IOException.class,
"oops",
() -> eventually(TIMEOUT,
() -> r(new IOException("oops")),
retry));
assertMinRetryCount(1);
}
@Test
public void testInterceptEventuallyambdaFailuresNegativeRetry()
throws Throwable {
intercept(FileNotFoundException.class,
() -> eventually(TIMEOUT, EVAL_FNFE, () -> -1));
}
@Test
public void testInterceptEventuallyLambdaFailFast() throws Throwable {
intercept(FailFastException.class, "oops",
() -> eventually(
TIMEOUT,
() -> r(new FailFastException("oops")),
retry));
assertRetryCount(0);
}
/**
* Verify that assertions trigger catch and retry.
* @throws Throwable if the code is broken
*/
@Test
public void testEventuallySpinsOnAssertions() throws Throwable {
AtomicInteger counter = new AtomicInteger(0);
eventually(TIMEOUT,
() -> {
while (counter.incrementAndGet() < 5) {
fail("if you see this, we are in trouble");
}
},
retry);
assertMinRetryCount(4);
}
/**
* Verify that VirtualMachineError errors are immediately rethrown.
* @throws Throwable if the code is broken
*/
@Test
public void testInterceptEventuallyThrowsVMErrors() throws Throwable {
intercept(OutOfMemoryError.class, "OOM",
() -> eventually(
TIMEOUT,
() -> r(new OutOfMemoryError("OOM")),
retry));
assertRetryCount(0);
}
/**
* Verify that you can declare that an intercept will intercept Errors.
* @throws Throwable if the code is broken
*/
@Test
public void testInterceptHandlesErrors() throws Throwable {
intercept(OutOfMemoryError.class, "OOM",
() -> r(new OutOfMemoryError("OOM")));
}
/**
* Verify that if an Error raised is not the one being intercepted,
* it gets rethrown.
* @throws Throwable if the code is broken
*/
@Test
public void testInterceptRethrowsVMErrors() throws Throwable {
intercept(StackOverflowError.class, "",
() -> intercept(OutOfMemoryError.class, "",
() -> r(new StackOverflowError())));
}
@Test
public void testAwaitHandlesAssertions() throws Throwable {
// await a state which is never reached, expect a timeout exception
// with the text "failure" in it
TimeoutException ex = intercept(TimeoutException.class,
"failure",
() -> await(TIMEOUT,
() -> r(new AssertionError("failure")),
retry,
TIMEOUT_FAILURE_HANDLER));
// the retry handler must have been invoked
assertMinRetryCount(1);
// and the nested cause is tha raised assertion
if (!(ex.getCause() instanceof AssertionError)) {
throw ex;
}
}
@Test
public void testAwaitRethrowsVMErrors() throws Throwable {
// await a state which is never reached, expect a timeout exception
// with the text "failure" in it
intercept(StackOverflowError.class,
() -> await(TIMEOUT,
() -> r(new StackOverflowError()),
retry,
TIMEOUT_FAILURE_HANDLER));
// the retry handler must not have been invoked
assertMinRetryCount(0);
}
}