Commit b093eeb6 authored by Rajiv Prabhakar's avatar Rajiv Prabhakar
Browse files

v2.0.0 release:

- ThreadUtilc.get now throws ExecException, showing the main-thread stack-trace, in addition to the cause-exception-info
- Validatec overhauled
- FileUtilc.getClasspathFile added
- CheckedExceptionWrapper now prints out the class-name of the wrapper, when calling toString
- Significant unit test updates
parent 312caf35
Pipeline #15546360 passed with stage
in 1 minute and 31 seconds
......@@ -6,7 +6,7 @@
<groupId>com.rajivprab</groupId>
<artifactId>cava</artifactId>
<version>1.14.3</version>
<version>2.0.0</version>
<name>Cava: Clean Java</name>
<description>A library that enables users to write minimal, clean and simple Java</description>
......
......@@ -61,7 +61,7 @@ public class CheckedExceptionWrapper extends RuntimeException {
@Override
public String toString() {
return "CheckedExceptionWrapper around {\n" + e.toString() + "\n}";
return getClass() + " around {\n" + e.toString() + "\n}";
}
@Override
......
......@@ -6,6 +6,7 @@ import org.rajivprab.cava.IOUtilc.IoException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URISyntaxException;
/**
* Utilities related to Files
......@@ -28,4 +29,12 @@ public class FileUtilc {
throw new IoException(new FileNotFoundException("Classpath file does not exist: " + path));
}
}
public static File getClasspathFile(String path) {
try {
return new File(ClassLoader.getSystemResource(path).toURI());
} catch (URISyntaxException e) {
throw new CheckedExceptionWrapper(e);
}
}
}
......@@ -86,14 +86,14 @@ public class JsonUtilc {
@Override
public boolean hasNext() {
Validatec.equals(length, array.length(), "Array length has changed", ConcurrentModificationException.class);
Validatec.equals(length, array.length(), ConcurrentModificationException.class, "JSONArray length has changed");
return length > nextIndex;
}
@Override
@SuppressWarnings("unchecked")
public T next() {
Validatec.isTrue(hasNext(), "Next element does not exist", NoSuchElementException.class);
Validatec.isTrue(hasNext(), NoSuchElementException.class, "Next element does not exist");
nextIndex++;
return (T) array.get(nextIndex - 1);
}
......
......@@ -14,6 +14,8 @@ import java.util.concurrent.*;
public class ThreadUtilc {
private static final Log log = LogFactory.getLog(ThreadUtilc.class);
// ---------- Sleep ------------
public static void sleep(Duration duration) {
sleep(duration.toMillis());
}
......@@ -26,6 +28,12 @@ public class ThreadUtilc {
}
}
// ---------- Fork ------------
public static FutureTask<Boolean> fork(Runnable runnable) {
return fork(() -> { runnable.run(); return true; });
}
public static <T> FutureTask<T> fork(Callable<T> callable) {
FutureTask<T> futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask);
......@@ -33,9 +41,7 @@ public class ThreadUtilc {
return futureTask;
}
public static FutureTask<Boolean> fork(Runnable runnable) {
return fork(() -> { runnable.run(); return true; });
}
// ---------- Get ------------
public static <T> T get(Future<T> future) {
return get(future, Duration.ofDays(100000));
......@@ -57,11 +63,13 @@ public class ThreadUtilc {
throw new TimeException(e);
} catch (ExecutionException e) {
log.warn("Hit ExecutionException while calling future.get", e);
Throwable cause = e.getCause();
throw (cause instanceof RuntimeException) ? (RuntimeException) cause : new CheckedExceptionWrapper(cause);
// Do not simply throw the cause, because that lacks the stack-trace for this get-method
throw new ExecException(e);
}
}
// ---------- Exception-Wrappers ------------
public static class ExecException extends CheckedExceptionWrapper {
public ExecException(ExecutionException e) {
super(e);
......
......@@ -3,6 +3,7 @@ package org.rajivprab.cava;
import com.google.common.truth.Truth;
import org.junit.Assert;
import org.junit.Test;
import org.rajivprab.cava.ThreadUtilc.ExecException;
import org.rajivprab.cava.ThreadUtilc.InterruptException;
import org.rajivprab.cava.ThreadUtilc.TimeException;
......@@ -22,7 +23,7 @@ import java.util.stream.IntStream;
* Created by rprabhakar on 12/22/15.
*/
public class ThreadUtilcTest extends TestBase {
private static final Duration TEST_DURATION = Duration.ofMillis(1000);
private static final Duration TEST_DURATION = Duration.ofSeconds(1);
private static final int RETURN_VALUE = 42;
private static final Random RNG = new Random();
......@@ -36,37 +37,46 @@ public class ThreadUtilcTest extends TestBase {
}, TEST_DURATION);
}
// Task-A on Thread-1 is sleeping
// Task-B on Thread-2 is waiting for Task-A to finish (ie, calling taskA.get)
// Thread-1 gets interrupted => Task-A will throw InterruptedException => Task-B will throw ExecutionException
// Thread-2 gets interrupted => Task-B will throw InterruptedException
// The only way for the main-thread to emit an InterruptedException, is for some other thread to interrupt the main-thread
@Test
public void sleep_interrupted_getCallsExecutionException() {
FutureTask<Integer> futureTask = new FutureTask<>(() -> ThreadUtilc.sleep(10000), RETURN_VALUE);
Thread thread = new Thread(futureTask);
thread.start();
thread.interrupt();
public void sleep_interrupted_get_throwExecutionException() {
FutureTask<Integer> taskA = new FutureTask<>(() -> ThreadUtilc.sleep(10000), RETURN_VALUE);
Thread thread1 = new Thread(taskA);
thread1.start();
thread1.interrupt();
try {
futureTask.get();
taskA.get();
Assert.fail();
} catch (InterruptedException e) {
Assert.fail("InterruptedException should have been converted to InterruptException, " +
"which is a ExecutionException");
Assert.fail("This will never happen. " +
"InterruptedException is converted into ExecutionException for current-thread");
} catch (ExecutionException e) {
Truth.assertThat(e.getCause()).isInstanceOf(CheckedExceptionWrapper.class);
Truth.assertThat(((CheckedExceptionWrapper) e.getCause()).getUnderlying())
.isInstanceOf(InterruptedException.class);
Truth.assertThat(e.getCause()).isInstanceOf(InterruptException.class);
Truth.assertThat(((InterruptException) e.getCause()).getUnderlying()).isInstanceOf(InterruptedException.class);
}
}
@Test
public void sleep_interrupted_threadUtilcGet_throwsCauseWithWrapper() {
FutureTask<Integer> futureTask = new FutureTask<>(() -> ThreadUtilc.sleep(10000), RETURN_VALUE);
Thread thread = new Thread(futureTask);
public void sleep_interrupted_threadUtilcGet_throwExecException() {
FutureTask<Integer> taskA = new FutureTask<>(() -> ThreadUtilc.sleep(1000000), RETURN_VALUE);
Thread thread = new Thread(taskA);
thread.start();
thread.interrupt();
try {
ThreadUtilc.get(futureTask);
ThreadUtilc.get(taskA);
Assert.fail();
} catch (CheckedExceptionWrapper e) {
Truth.assertThat(e).hasMessageThat().isEqualTo("sleep interrupted");
Truth.assertThat(e.getUnderlying()).isInstanceOf(InterruptedException.class);
} catch (InterruptException e) {
Assert.fail("This will never happen. " +
"InterruptException is converted into ExecException for current-thread");
} catch (ExecException e) {
Truth.assertThat(e.getUnderlying()).isInstanceOf(ExecutionException.class);
Truth.assertThat(e.getCause()).isInstanceOf(InterruptException.class);
Truth.assertThat(((InterruptException) e.getCause()).getUnderlying()).isInstanceOf(InterruptedException.class);
}
}
......@@ -103,66 +113,35 @@ public class ThreadUtilcTest extends TestBase {
}
@Test
public void get_executionException_causedByRunTimeException_shouldThrowCause() {
FutureTask<Integer> future = new FutureTask<>(() -> {
throw new ArithmeticException("test");
});
public void get_executionException_causedByCheckedException_shouldThrowExecException() {
FutureTask<Integer> future = new FutureTask<>(() -> { throw new IOException("test"); });
new Thread(future).start();
try {
ThreadUtilc.get(future);
Assert.fail("Exception should have been thrown");
} catch (ArithmeticException e) {
Truth.assertThat(e).hasMessageThat().isEqualTo("test");
} catch (ExecException e) {
Truth.assertThat(e.getUnderlying()).isInstanceOf(ExecutionException.class);
Truth.assertThat(e.getUnderlying().getCause()).isInstanceOf(IOException.class);
Truth.assertThat(e.getCause()).isInstanceOf(IOException.class);
Truth.assertThat(e).hasMessageThat().isEqualTo("java.io.IOException: test");
Truth.assertThat(e.toString())
.matches(".*ExecException around \\{\n.*ExecutionException.*IOException.*test\n\\}");
}
}
@Test
public void get_executionException_causedByCheckedException_shouldThrowCauseWithWrapper() {
FutureTask<Integer> future = new FutureTask<>(() -> {
throw new IOException("test");
});
public void get_executionException_causedByRunTimeException_shouldThrowExecException() {
FutureTask<Integer> future = new FutureTask<>(() -> { throw new ArithmeticException("test"); });
new Thread(future).start();
try {
ThreadUtilc.get(future);
Assert.fail("Exception should have been thrown");
} catch (CheckedExceptionWrapper e) {
Truth.assertThat(e).hasMessageThat().isEqualTo("test");
Truth.assertThat(e.getUnderlying()).isInstanceOf(IOException.class);
}
}
// TODO Enhancment: Following two tests expose confusing behavior? Review
@Test
public void threadGet_interrupted_throwsExecutionException() {
FutureTask<Integer> sleepTask = runSleepTask(Duration.ofSeconds(10));
FutureTask<Integer> getTask = new FutureTask<>(() -> ThreadUtilc.get(sleepTask));
Thread thread = new Thread(getTask);
thread.start();
thread.interrupt();
try {
getTask.get();
Assert.fail("Expecting exception to be thrown");
} catch (InterruptedException e) {
Assert.fail("Expecting ExecutionException, not InterruptedException");
} catch (ExecutionException e) {
Truth.assertThat(e.getCause()).isInstanceOf(CheckedExceptionWrapper.class);
Truth.assertThat(((CheckedExceptionWrapper) e.getCause()).getUnderlying())
.isInstanceOf(InterruptedException.class);
}
}
@Test
public void threadUtilcGet_interrupted_throwsInterruptException() {
FutureTask<Integer> sleepTask = runSleepTask(Duration.ofSeconds(10));
FutureTask<Integer> getTask = new FutureTask<>(() -> ThreadUtilc.get(sleepTask));
Thread thread = new Thread(getTask);
thread.start();
thread.interrupt();
try {
ThreadUtilc.get(getTask);
Assert.fail("Expecting exception to be thrown");
} catch (InterruptException ignored) {
} catch (ExecException e) {
Truth.assertThat(e.getUnderlying()).isInstanceOf(ExecutionException.class);
Truth.assertThat(e.getCause()).isInstanceOf(ArithmeticException.class);
Truth.assertThat(e).hasMessageThat().isEqualTo("java.lang.ArithmeticException: test");
Truth.assertThat(e.toString())
.matches(".*ExecException around \\{\n.*ExecutionException.*ArithmeticException.*test\n\\}");
}
}
......@@ -174,7 +153,7 @@ public class ThreadUtilcTest extends TestBase {
FutureTask<Boolean> task = ThreadUtilc.fork(() -> ThreadUtilc.sleep(TEST_DURATION));
Truth.assertThat(task.isDone()).isFalse();
Truth.assertThat(ThreadUtilc.get(task)).isTrue();
}, TEST_DURATION);
}, TEST_DURATION, TEST_DURATION.multipliedBy(3).dividedBy(2));
}
@Test
......@@ -191,19 +170,25 @@ public class ThreadUtilcTest extends TestBase {
.map(ThreadUtilc::get)
.collect(Collectors.toList());
Truth.assertThat(result).containsExactly(0, 1, 2, 3, 4).inOrder();
}, TEST_DURATION);
}, TEST_DURATION, TEST_DURATION.multipliedBy(2));
}
// -------------------- Helpers ---------------
private static void checkRunTime(Runnable runnable, Duration runTime) {
private static void checkRunTime(Runnable runnable, Duration approximate) {
checkRunTime(runnable,
approximate.multipliedBy(98).dividedBy(100), approximate.multipliedBy(120).dividedBy(100));
}
private static void checkRunTime(Runnable runnable, Duration min, Duration max) {
long controlStart = System.nanoTime();
long start = System.nanoTime();
runnable.run();
long end = System.nanoTime();
Duration duration = Duration.ofNanos(end - start);
Duration duration = Duration.ofNanos((end - start) - (start - controlStart));
Truth.assertThat(duration).isAtLeast(runTime.multipliedBy(98).dividedBy(100));
Truth.assertThat(duration).isLessThan(runTime.multipliedBy(110).dividedBy(100));
Truth.assertThat(duration).isAtLeast(min);
Truth.assertThat(duration).isLessThan(max);
}
private static FutureTask<Integer> runSleepTask(Duration sleep) {
......
......@@ -38,7 +38,7 @@ public class ValidatecTest extends TestBase {
@Test
public void isNotTrue_constructException() {
try {
Validatec.isTrue(false, "1 is not equal to 2", ArithmeticException.class);
Validatec.isTrue(false, ArithmeticException.class, "1 is not equal to 2");
Assert.fail();
} catch (ArithmeticException e) {
Truth.assertThat(e).hasMessageThat().contains("1 is not equal to 2");
......@@ -65,7 +65,7 @@ public class ValidatecTest extends TestBase {
@Test
public void isFalse_failed_constructException() {
try {
Validatec.isFalse(true, "2 is equal to 2", ArithmeticException.class);
Validatec.isFalse(true, ArithmeticException.class, "2 is equal to 2");
Assert.fail();
} catch (ArithmeticException e) {
Truth.assertThat(e).hasMessageThat().contains("2 is equal to 2");
......@@ -92,7 +92,7 @@ public class ValidatecTest extends TestBase {
@Test
public void isNull_fail_constructException() {
try {
Validatec.isNull(true, "Expecting to find null", ArithmeticException.class);
Validatec.isNull(true, ArithmeticException.class, "Expecting to find null");
Assert.fail();
} catch (ArithmeticException e) {
Truth.assertThat(e).hasMessageThat().contains("Expecting to find null");
......@@ -138,41 +138,26 @@ public class ValidatecTest extends TestBase {
Validatec.noNulls(123, "abc");
}
@Test (expected = IllegalArgumentException.class)
public void noNulls_nullArray_constructDefaultException() {
Validatec.noNulls("abc",null);
}
@Test
public void noNulls_fail_throwGivenException() {
try {
Validatec.noNulls(new ArithmeticException(), null, Optional.empty());
Validatec.noNulls(new ArithmeticException("abc"), null, Optional.empty());
Assert.fail();
} catch (ArithmeticException e) {
Truth.assertThat(e).hasMessageThat().isNull();
Truth.assertThat(e).hasMessageThat().isEqualTo("abc");
}
}
@Test
public void noNulls_fail_constructExceptionWithDefaultMessage() {
public void noNulls_nullArray_constructDefaultException() {
try {
Validatec.noNulls(ArithmeticException.class, null, Optional.empty());
Validatec.noNulls((Object) null, Optional.empty());
Assert.fail();
} catch (ArithmeticException e) {
} catch (IllegalArgumentException e) {
Truth.assertThat(e).hasMessageThat().isEqualTo("[null, Optional.empty] should not contain nulls");
}
}
@Test
public void noNulls_fail_constructExceptionWithCustomMessage() {
try {
Validatec.noNulls("My custom message", ArithmeticException.class, null, 123);
Assert.fail();
} catch (ArithmeticException expected) {
Truth.assertThat(expected).hasMessageThat().contains("My custom message");
}
}
// ----- Collection.Size
@Test
......@@ -311,7 +296,7 @@ public class ValidatecTest extends TestBase {
@Test
public void equalsIsFalse_nullMessageGiven_shouldThrowGivenExceptionType() {
try {
Validatec.equals("test1", "test2", null, ArithmeticException.class);
Validatec.equals("test1", "test2", ArithmeticException.class, null);
Assert.fail();
} catch (ArithmeticException e) {
Truth.assertThat(e).hasMessageThat().isEmpty();
......@@ -483,8 +468,8 @@ public class ValidatecTest extends TestBase {
// -------- String notEmpty
@Test
public void stringNotEmpty_allGood() {
Truth.assertThat(Validatec.notEmpty("a", null, ArithmeticException.class)).isEqualTo("a");
public void stringNotEmpty_nullMessage_allGood() {
Truth.assertThat(Validatec.notEmpty("a", ArithmeticException.class, null)).isEqualTo("a");
}
@Test
......@@ -501,24 +486,24 @@ public class ValidatecTest extends TestBase {
@Test
public void stringNotEmpty_failNull_constructGivenException() {
try {
Validatec.notEmpty((String) null, "my custom message", ArithmeticException.class);
Validatec.notEmpty((String) null, ArithmeticException.class, "my custom message");
Assert.fail();
} catch (ArithmeticException expected) {
Truth.assertThat(expected).hasMessageThat().contains("my custom message");
}
}
// -------- Strings array notEmpty
// -------- Strings[] notEmpty
@Test
public void stringsNotEmpty_allGood() {
Validatec.notEmpty(null, ArithmeticException.class, "a");
public void stringsNoneEmpty_allGood() {
Validatec.noneEmpty("a", "b", "c");
}
@Test
public void stringsNotEmpty_fail_constructDefaultException() {
public void stringsNoneEmpty_containsEmpty_constructDefaultException() {
try {
Validatec.notEmpty("123", "");
Validatec.noneEmpty("123", "");
Assert.fail();
} catch (IllegalArgumentException expected) {
Truth.assertThat(expected).hasMessageThat()
......@@ -527,9 +512,9 @@ public class ValidatecTest extends TestBase {
}
@Test
public void stringsNotEmpty_fail_constructGivenException() {
public void stringsNoneEmpty_containsNull_constructGivenException() {
try {
Validatec.notEmpty("my custom message", ArithmeticException.class, null, "123");
Validatec.noneEmpty(() -> {throw new ArithmeticException("my custom message");}, null, "123");
Assert.fail();
} catch (ArithmeticException expected) {
Truth.assertThat(expected).hasMessageThat().contains("my custom message");
......@@ -537,9 +522,9 @@ public class ValidatecTest extends TestBase {
}
@Test
public void stringsNotEmpty_arrayIsNull_constructGivenException() {
public void stringsNoneEmpty_arrayIsNull_constructGivenException() {
try {
Validatec.notEmpty("my custom message", ArithmeticException.class, null);
Validatec.noneEmpty(new ArithmeticException("my custom message"), (String[]) null);
Assert.fail();
} catch (ArithmeticException expected) {
Truth.assertThat(expected).hasMessageThat().contains("my custom message");
......@@ -602,7 +587,7 @@ public class ValidatecTest extends TestBase {
@Test
public void constructsRightException() {
try {
Validatec.isTrue(false, "testing", IllegalThreadStateException.class);
Validatec.isTrue(false, IllegalThreadStateException.class, "testing");
} catch (IllegalThreadStateException e) {
Truth.assertThat(e.getMessage()).contains("testing");
}
......@@ -611,7 +596,7 @@ public class ValidatecTest extends TestBase {
@Test
public void parentCatchWorks() {
try {
Validatec.isTrue(false, "testing", IllegalThreadStateException.class);
Validatec.isTrue(false, IllegalThreadStateException.class, "testing");
} catch (RuntimeException e) {
Truth.assertThat(e.getMessage()).contains("testing");
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment