commit 29d6e92a10e6c5a186e9a1c1748c43e3a0824b40
Author: code-clash <>
Date: Thu Apr 30 20:39:39 2020 +0200
Initial commit
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..40f974f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,45 @@
+# Gradle
+.gradle
+build/
+
+# Android built artifacts
+*.apk
+*.ap_
+*.dex
+
+# Java build artifacts class files
+*.class
+
+# other generated files
+gen/
+target/
+
+# local configuration file (for Android sdk path, etc)
+local.properties
+
+# OSX files
+.DS_Store
+
+# Eclipse
+/.classpath
+/.settings/
+/.project
+/.metadata
+bin/
+
+# IntelliJ
+*.iml
+*.ipr
+*.iws
+*.uml
+.idea/compiler.xml
+.idea/workspace.xml
+out/
+
+# NDK
+obj/
+
+# Misc
+*.log
+*.graphml
+coverage.db*
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..ded026c
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
The function + * {@link #withSecurityManager(SecurityManager, Statement) withSecurityManager} + * lets you specify which {@code SecurityManager} is returned by + * {@code System.getSecurityManger()} while your code under test is executed. + *
+ * @Test + * void execute_code_with_specific_SecurityManager() { + * SecurityManager securityManager = new ASecurityManager(); + * withSecurityManager( + * securityManager, + * () -> { + * //code under test + * //e.g. the following assertion is met + * assertSame( + * securityManager, + * System.getSecurityManager() + * ); + * } + * ); + * } + *+ *
After the statement {@code withSecurityManager(...)} is executed + * {@code System.getSecurityManager()} will return the original security manager + * again. + * + *
The method + * {@link #withEnvironmentVariable(String, String) withEnvironmentVariable} + * allows you to set environment variables within your test code that are + * removed after you code under test is executed. + *
+ * @Test + * void set_environment_variables() throws Exception { + * withEnvironmentVariable("first", "first value") + * .and("second", "second value") + * .execute(() -> { + * assertEquals("first value", System.getenv("first")); + * assertEquals("second value", System.getenv("second")); + * }); + * }+ * + *
The function + * {@link #restoreSystemProperties(Statement) restoreSystemProperties} + * guarantees that after executing the test code each System property has the + * same value like before. Therefore you can modify System properties inside of + * the test code without having an impact on other tests. + *
+ * @Test + * void execute_code_that_manipulates_system_properties() { + * restoreSystemProperties( + * () -> { + * System.setProperty("some.property", "some value"); + * //code under test that reads properties (e.g. "some.property") or + * //modifies them. + * } + * ); + * } + *+ * + *
Command-line applications terminate by calling {@code System.exit} with + * some status code. If you test such an application then the JVM that runs the + * test would exit when the application under test calls {@code System.exit}. + * You can avoid this with the method + * {@link #catchSystemExit(Statement) catchSystemExit} which also provides the + * status code of the {@code System.exit} call. + * + *
+ * @Test + * public void catch_status_code_of_System_exit() { + * int statusCode = catchSystemExit(() -> { + * System.exit(42); + * }); + * assertEquals(42, statusCode); + * } + *+ * + * The method `{@code catchSystemExit} throws an {@code AssertionError} if the + * code under test does not call {@code System.exit}. Therefore your test fails + * with a failure message "System.exit has not been called." + * + *
Command-line applications usually write to the console. If you write such + * applications you need to test the output of these applications. The methods + * {@link #tapSystemErr(Statement) tapSystemErr}, + * {@link #tapSystemErrNormalized(Statement) tapSystemErrNormalized}, + * {@link #tapSystemOut(Statement) tapSystemOut} and + * {@link #tapSystemOutNormalized(Statement) tapSystemOutNormalized} allow you + * to tap the text that is written to {@code System.err}/{@code System.out}. The + * methods with the suffix {@code Normalized} normalize line breaks to + * {@code \n} so that you can run tests with the same assertions on Linux, + * macOS and Windows. + * + *
+ * @Test + * void check_text_written_to_System_err() throws Exception { + * String text = tapSystemErr( + * () -> System.err.println("some text") + * ); + * assertEquals(text, "some text"); + * } + * + * @Test + * void check_multiple_lines_written_to_System_err() throws Exception { + * String text = tapSystemErrNormalized( + * () -> { + * System.err.println("first line"); + * System.err.println("second line"); + * } + * ); + * assertEquals(text, "first line\nsecond line"); + * } + * + * @Test + * void check_text_written_to_System_out() throws Exception { + * String text = tapSystemOut( + * () -> System.out.println("some text") + * ); + * assertEquals(text, "some text"); + * } + * + * @Test + * void check_multiple_lines_written_to_System_out() throws Exception { + * String text = tapSystemOutNormalized( + * () -> { + * System.out.println("first line"); + * System.out.println("second line"); + * } + * ); + * assertEquals(text, "first line\nsecond line"); + * }+ * + *
You can assert that nothing is written to + * {@code System.err}/{@code System.out} by wrapping code with the function + * {@link #assertNothingWrittenToSystemErr(Statement) + * assertNothingWrittenToSystemErr}/{@link #assertNothingWrittenToSystemOut(Statement) + * assertNothingWrittenToSystemOut}. E.g. the following tests fail: + *
+ * @Test + * void fails_because_something_is_written_to_System_err() { + * assertNothingWrittenToSystemErr( + * () -> { + * System.err.println("some text"); + * } + * ); + * } + * + * @Test + * void fails_because_something_is_written_to_System_out() { + * assertNothingWrittenToSystemOut( + * () -> { + * System.out.println("some text"); + * } + * ); + * } + *+ * + *
If the code under test writes text to + * {@code System.err}/{@code System.out} then it is intermixed with the output + * of your build tool. Therefore you may want to avoid that the code under test + * writes to {@code System.err}/{@code System.out}. You can achieve this with + * the function {@link #muteSystemErr(Statement) + * muteSystemErr}/{@link #muteSystemOut(Statement) muteSystemOut}. E.g. the + * following tests don't write anything to + * {@code System.err}/{@code System.out}: + *
+ * @Test + * void nothing_is_written_to_System_err() { + * muteSystemErr( + * () -> { + * System.err.println("some text"); + * } + * ); + * } + * + * @Test + * void nothing_is_written_to_System_out() { + * muteSystemOut( + * () -> { + * System.out.println("some text"); + * } + * ); + * } + *+ * + *
Interactive command-line applications read from {@code System.in}. If you + * write such applications you need to provide input to these applications. You + * can specify the lines that are available from `{@code System.in} with the + * method {@link #withTextFromSystemIn(String...) withTextFromSystemIn} + *
+ * @Test + * void readTextFromSystemIn() { + * withTextFromSystemIn("first line", "second line") + * .execute(() -> { + * Scanner scanner = new Scanner(System.in); + * scanner.nextLine(); + * assertEquals("first line", scanner.nextLine()); + * }); + * } + *+ * + * For a complete test coverage you may also want to simulate `System.in` throwing + * exceptions when the application reads from it. You can specify such an + * exception (either `RuntimeException` or `IOException` after specifying the text. + * The exception will be thrown by the next `read` after the text has been + * consumed. + *
+ * @Test + * void readTextFromSystemInWithIOException() { + * withTextFromSystemIn("first line", "second line") + * .andExceptionThrownOnInputEnd(new IOException()) + * .execute(() -> { + * Scanner scanner = new Scanner(System.in); + * scanner.nextLine(); + * scanner.nextLine(); + * assertThrownBy( + * IOException.class, + * () -> scanner.readLine() + * ); + * }); + * } + * + * @Test + * void readTextFromSystemInFailsWithRuntimeException() { + * withTextFromSystemIn("first line", "second line") + * .andExceptionThrownOnInputEnd(new RuntimeException()) + * .execute(() -> { + * Scanner scanner = new Scanner(System.in); + * scanner.nextLine(); + * scanner.nextLine(); + * assertThrownBy( + * RuntimeException.class, + * () -> scanner.readLine() + * ); + * }); + * } + *+ * + * You can write a test that throws an exception immediately by not providing any + * text. + *
+ * withTextFromSystemIn() + * .andExceptionThrownOnInputEnd(...) + * .execute(() -> { + * Scanner scanner = new Scanner(System.in); + * assertThrownBy( + * ..., + * () -> scanner.readLine() + * ); + * }); + *+ * + *
It provides support for + *
The following test fails + *
+ * @Test + * public void fails_because_something_is_written_to_System_err() { + * assertNothingWrittenToSystemErr( + * () -> { + * System.err.println("some text"); + * } + * ); + * } + *+ * The test fails with the failure "Tried to write 's' to System.err + * although this is not allowed." + * + * @param statement an arbitrary piece of code. + * @throws Exception any exception thrown by the statement or an + * {@code AssertionError} if the statement tries to write + * to {@code System.err}. + * @see #assertNothingWrittenToSystemOut(Statement) + * @since 1.0.0 + */ + public static void assertNothingWrittenToSystemErr( + Statement statement + ) throws Exception { + executeWithSystemErrReplacement( + new DisallowWriteStream(), + statement + ); + } + + /** + * Executes the statement and fails (throws an {@code AssertionError}) if + * the statement tries to write to {@code System.out}. + *
The following test fails + *
+ * @Test + * public void fails_because_something_is_written_to_System_out() { + * assertNothingWrittenToSystemOut( + * () -> { + * System.out.println("some text"); + * } + * ); + * } + *+ * The test fails with the failure "Tried to write 's' to System.out + * although this is not allowed." + * + * @param statement an arbitrary piece of code. + * @throws Exception any exception thrown by the statement or an + * {@code AssertionError} if the statement tries to write + * to {@code System.out}. + * @see #assertNothingWrittenToSystemErr(Statement) + * @since 1.0.0 + */ + public static void assertNothingWrittenToSystemOut( + Statement statement + ) throws Exception { + executeWithSystemOutReplacement( + new DisallowWriteStream(), + statement + ); + } + + /** + * Usually the output of a test to {@code System.err} does not have to be + * visible. It may even slowdown the test. {@code muteSystemErr} can be + * used to suppress this output. + *
+ * @Test + * public void nothing_is_written_to_System_err() { + * muteSystemErr( + * () -> { + * System.err.println("some text"); + * } + * ); + * } + *+ * + * @param statement an arbitrary piece of code. + * @throws Exception any exception thrown by the statement. + * @see #muteSystemOut(Statement) + * @since 1.0.0 + */ + public static void muteSystemErr( + Statement statement + ) throws Exception { + executeWithSystemErrReplacement( + new NoopStream(), + statement + ); + } + + /** + * Usually the output of a test to {@code System.out} does not have to be + * visible. It may even slowdown the test. {@code muteSystemOut} can be + * used to suppress this output. + *
+ * @Test + * public void nothing_is_written_to_System_out() { + * muteSystemOut( + * () -> { + * System.out.println("some text"); + * } + * ); + * } + *+ * + * @param statement an arbitrary piece of code. + * @throws Exception any exception thrown by the statement. + * @see #muteSystemErr(Statement) + * @since 1.0.0 + */ + public static void muteSystemOut( + Statement statement + ) throws Exception { + executeWithSystemOutReplacement( + new NoopStream(), + statement + ); + } + + /** + * {@code tapSystemErr} returns a String with the text that is written to + * {@code System.err} by the provided piece of code. + *
+ * @Test + * public void check_the_text_that_is_written_to_System_err() { + * String textWrittenToSystemErr = tapSystemErr( + * () -> { + * System.err.print("some text"); + * } + * ); + * assertEquals("some text", textWrittenToSystemErr); + * } + *+ * + * @param statement an arbitrary piece of code. + * @return text that is written to {@code System.err} by the statement. + * @throws Exception any exception thrown by the statement. + * @see #tapSystemOut(Statement) + * @since 1.0.0 + */ + public static String tapSystemErr( + Statement statement + ) throws Exception { + TapStream tapStream = new TapStream(); + executeWithSystemErrReplacement( + tapStream, + statement + ); + return tapStream.textThatWasWritten(); + } + + /** + * {@code tapSystemOut} returns a String with the text that is written to + * {@code System.out} by the provided piece of code. + *
+ * @Test + * public void check_the_text_that_is_written_to_System_out() { + * String textWrittenToSystemOut = tapSystemOut( + * () -> { + * System.out.print("some text"); + * } + * ); + * assertEquals("some text", textWrittenToSystemOut); + * } + *+ * + * @param statement an arbitrary piece of code. + * @return text that is written to {@code System.out} by the statement. + * @throws Exception any exception thrown by the statement. + * @see #tapSystemErr(Statement) + * @since 1.0.0 + */ + public static String tapSystemOut( + Statement statement + ) throws Exception { + TapStream tapStream = new TapStream(); + executeWithSystemOutReplacement( + tapStream, + statement + ); + return tapStream.textThatWasWritten(); + } + + /** + * {@code tapSystemErrNormalized} returns a String with the text that is + * written to {@code System.err} by the provided piece of code. New line + * characters are replaced with a single {@code \n}. + *
+ * @Test + * public void check_the_text_that_is_written_to_System_err() { + * String textWrittenToSystemErr = tapSystemErrNormalized( + * () -> { + * System.err.println("some text"); + * } + * ); + * assertEquals("some text\n", textWrittenToSystemErr); + * } + *+ * + * @param statement an arbitrary piece of code. + * @return text that is written to {@code System.err} by the statement. + * @throws Exception any exception thrown by the statement. + * @see #tapSystemOut(Statement) + * @since 1.0.0 + */ + public static String tapSystemErrNormalized( + Statement statement + ) throws Exception { + return tapSystemErr(statement) + .replace(lineSeparator(), "\n"); + } + + /** + * {@code tapSystemOutNormalized} returns a String with the text that is + * written to {@code System.out} by the provided piece of code. New line + * characters are replaced with a single {@code \n}. + *
+ * @Test + * public void check_the_text_that_is_written_to_System_out() { + * String textWrittenToSystemOut = tapSystemOutNormalized( + * () -> { + * System.out.println("some text"); + * } + * ); + * assertEquals("some text\n", textWrittenToSystemOut); + * } + *+ * + * @param statement an arbitrary piece of code. + * @return text that is written to {@code System.out} by the statement. + * @throws Exception any exception thrown by the statement. + * @see #tapSystemErr(Statement) + * @since 1.0.0 + */ + public static String tapSystemOutNormalized( + Statement statement + ) throws Exception { + return tapSystemOut(statement) + .replace(lineSeparator(), "\n"); + } + + /** + * Executes the statement and restores the system properties after the + * statement has been executed. This allows you to set or clear system + * properties within the statement without affecting other tests. + *
+ * @Test + * public void execute_code_that_manipulates_system_properties( + * ) throws Exception { + * System.clearProperty("some property"); + * System.setProperty("another property", "value before test"); + * + * restoreSystemProperties( + * () -> { + * System.setProperty("some property", "some value"); + * assertEquals( + * "some value", + * System.getProperty("some property") + * ); + * + * System.clearProperty("another property"); + * assertNull( + * System.getProperty("another property") + * ); + * } + * ); + * + * //values are restored after test + * assertNull( + * System.getProperty("some property") + * ); + * assertEquals( + * "value before test", + * System.getProperty("another property") + * ); + * } + *+ * @param statement an arbitrary piece of code. + * @throws Exception any exception thrown by the statement. + * @since 1.0.0 + */ + public static void restoreSystemProperties( + Statement statement + ) throws Exception { + Properties originalProperties = getProperties(); + setProperties(copyOf(originalProperties)); + try { + statement.execute(); + } finally { + setProperties(originalProperties); + } + } + + private static Properties copyOf(Properties source) { + Properties copy = new Properties(); + copy.putAll(source); + return copy; + } + + /** + * Executes a statement with the specified environment variables. All + * changes to environment variables are reverted after the statement has + * been executed. + *
+ * @Test + * public void execute_code_with_environment_variables( + * ) throws Exception { + * withEnvironmentVariable("first", "first value") + * .and("second", "second value") + * .and("third", null) + * .execute( + * () -> { + * assertEquals( + * "first value", + * System.getenv("first") + * ); + * assertEquals( + * "second value", + * System.getenv("second") + * ); + * assertNull( + * System.getenv("third") + * ); + * } + * ); + * } + *+ *
You cannot specify the value of an an environment variable twice. An + * {@code IllegalArgumentException} when you try. + *
Warning: This method uses reflection for modifying internals of the + * environment variables map. It fails if your {@code SecurityManager} forbids + * such modifications. + * @param name the name of the environment variable. + * @param value the value of the environment variable. + * @return an {@link WithEnvironmentVariables} instance that can be used to + * set more variables and run a statement with the specified environment + * variables. + * @since 1.0.0 + * @see WithEnvironmentVariables#and(String, String) + * @see WithEnvironmentVariables#execute(Statement) + */ + public static WithEnvironmentVariables withEnvironmentVariable( + String name, + String value + ) { + return new WithEnvironmentVariables( + singletonMap(name, value)); + } + + /** + * Executes the statement with the provided security manager. + *
+ * @Test + * public void execute_code_with_specific_SecurityManager() { + * SecurityManager securityManager = new ASecurityManager(); + * withSecurityManager( + * securityManager, + * () -> { + * assertSame(securityManager, System.getSecurityManager()); + * } + * ); + * } + *+ * The specified security manager is only present during the test. + * @param securityManager the security manager that is used while the + * statement is executed. + * @param statement an arbitrary piece of code. + * @throws Exception any exception thrown by the statement. + * @since 1.0.0 + */ + public static void withSecurityManager( + SecurityManager securityManager, + Statement statement + ) throws Exception { + SecurityManager originalSecurityManager = getSecurityManager(); + setSecurityManager(securityManager); + try { + statement.execute(); + } finally { + setSecurityManager(originalSecurityManager); + } + } + + /** + * Executes the statement and lets {@code System.in} provide the specified + * text during the execution. In addition several Exceptions can be + * specified that are thrown when {@code System.in#read} is called. + * + *
+ * public void MyTest { + * + * @Test + * public void readTextFromStandardInputStream() { + * withTextFromSystemIn("first line", "second line") + * .execute(() -> { + * Scanner scanner = new Scanner(System.in); + * scanner.nextLine(); + * assertEquals("first line", scanner.nextLine()); + * }); + * } + * } + *+ * + *
You can also simulate a {@code System.in} that throws an + * {@code IOException} or {@code RuntimeException}. Use + * + *
+ * public void MyTest { + * + * @Test + * public void readTextFromStandardInputStreamFailsWithIOException() { + * withTextFromSystemIn() + * .andExceptionThrownOnInputEnd(new IOException()) + * .execute(() -> { + * assertThrownBy( + * IOException.class, + * () -> new Scanner(System.in).readLine()) + * ); + * )}; + * } + * + * @Test + * public void readTextFromStandardInputStreamFailsWithRuntimeException() { + * withTextFromSystemIn() + * .andExceptionThrownOnInputEnd(new RuntimeException()) + * .execute(() -> { + * assertThrownBy( + * RuntimeException.class, + * () -> new Scanner(System.in).readLine()) + * ); + * )}; + * } + * } + *+ *
If you provide text as parameters of {@code withTextFromSystemIn(...)}
+ * in addition then the exception is thrown after the text has been read
+ * from {@code System.in}.
+ * @param lines the lines that are available from {@code System.in}.
+ * @return an {@link SystemInStub} instance that is used to execute a
+ * statement with its {@link SystemInStub#execute(Statement) execute}
+ * method. In addition it can be used to specify an exception that is thrown
+ * after the text is read.
+ * @since 1.0.0
+ * @see SystemInStub#execute(Statement)
+ * @see SystemInStub#andExceptionThrownOnInputEnd(IOException)
+ * @see SystemInStub#andExceptionThrownOnInputEnd(RuntimeException)
+ */
+ public static SystemInStub withTextFromSystemIn(
+ String... lines
+ ) {
+ String text = stream(lines)
+ .map(line -> line + getProperty("line.separator"))
+ .collect(joining());
+ return new SystemInStub(text);
+ }
+
+ private static void executeWithSystemErrReplacement(
+ OutputStream replacementForErr,
+ Statement statement
+ ) throws Exception {
+ PrintStream originalStream = err;
+ try {
+ setErr(wrap(replacementForErr));
+ statement.execute();
+ } finally {
+ setErr(originalStream);
+ }
+ }
+
+ private static void executeWithSystemOutReplacement(
+ OutputStream replacementForOut,
+ Statement statement
+ ) throws Exception {
+ PrintStream originalStream = out;
+ try {
+ setOut(wrap(replacementForOut));
+ statement.execute();
+ } finally {
+ setOut(originalStream);
+ }
+ }
+
+ private static PrintStream wrap(
+ OutputStream outputStream
+ ) throws UnsupportedEncodingException {
+ return new PrintStream(
+ outputStream,
+ AUTO_FLUSH,
+ DEFAULT_ENCODING
+ );
+ }
+
+ private static class DisallowWriteStream extends OutputStream {
+ @Override
+ public void write(int b) {
+ throw new AssertionError(
+ "Tried to write '"
+ + (char) b
+ + "' although this is not allowed."
+ );
+ }
+ }
+
+ private static class NoopStream extends OutputStream {
+ @Override
+ public void write(
+ int b
+ ) {
+ }
+ }
+
+ private static class TapStream extends OutputStream {
+ final ByteArrayOutputStream text = new ByteArrayOutputStream();
+
+ @Override
+ public void write(
+ int b
+ ) {
+ text.write(b);
+ }
+
+ String textThatWasWritten() {
+ return text.toString();
+ }
+ }
+
+ /**
+ * A collection of values for environment variables. New values can be
+ * added by {@link #and(String, String)}. The {@code EnvironmentVariables}
+ * object is then used to execute an arbitrary piece of code with these
+ * environment variables being present.
+ */
+ public static final class WithEnvironmentVariables {
+ private final Map You cannot specify the value of an environment variable twice. An
+ * {@code IllegalArgumentException} when you try.
+ * @param name the name of the environment variable.
+ * @param value the value of the environment variable.
+ * @return a new {@code WithEnvironmentVariables} object.
+ * @throws IllegalArgumentException when a value for the environment
+ * variable {@code name} is already specified.
+ * @see #withEnvironmentVariable(String, String)
+ * @see #execute(Statement)
+ */
+ public WithEnvironmentVariables and(
+ String name,
+ String value
+ ) {
+ validateNotSet(name, value);
+ HashMap Warning: This method uses reflection for modifying internals of the
+ * environment variables map. It fails if your {@code SecurityManager} forbids
+ * such modifications.
+ * @throws Exception any exception thrown by the statement.
+ * @since 1.0.0
+ * @see #withEnvironmentVariable(String, String)
+ * @see WithEnvironmentVariables#and(String, String)
+ */
+ public void execute(
+ Statement statement
+ ) throws Exception {
+ Map
+ * @Test
+ * public void execute_code_with_environment_variables(
+ * ) throws Exception {
+ * withEnvironmentVariable("first", "first value")
+ * .and("second", "second value")
+ * .and("third", null)
+ * .execute(
+ * () -> {
+ * assertEquals(
+ * "first value",
+ * System.getenv("first")
+ * );
+ * assertEquals(
+ * "second value",
+ * System.getenv("second")
+ * );
+ * assertNull(
+ * System.getenv("third")
+ * );
+ * }
+ * );
+ * }
+ *
+ *
+ *{@literal @Test}
+ * public void catch_status_code_of_System_exit() {
+ * int statusCode = catchSystemExit((){@literal ->} {
+ * System.exit(42);
+ * });
+ * assertEquals(42, statusCode);
+ * }
+ *
+ * @param statement an arbitrary piece of code.
+ * @return the status code provided to {@code System.exit(int)}.
+ * @throws Exception any exception thrown by the statement.
+ * @throws AssertionError if the statement does not call
+ * {@code System.exit(int)}.
+ * @since 1.0.0
+ */
+ public static int catchSystemExit(
+ Statement statement
+ ) throws Exception {
+ NoExitSecurityManager noExitSecurityManager
+ = new NoExitSecurityManager(getSecurityManager());
+ try {
+ withSecurityManager(noExitSecurityManager, statement);
+ } catch (CheckExitCalled ignored) {
+ }
+ return checkSystemExit(noExitSecurityManager);
+ }
+
+ private static int checkSystemExit(
+ NoExitSecurityManager securityManager
+ ) {
+ if (securityManager.isCheckExitCalled())
+ return securityManager.getStatusOfFirstCheckExitCall();
+ else
+ throw new AssertionError("System.exit has not been called.");
+ }
+
+ private static class CheckExitCalled extends SecurityException {
+ private static final long serialVersionUID = 159678654L;
+ }
+
+ /**
+ * A {@code NoExitSecurityManager} throws a {@link CheckExitCalled} exception
+ * whenever {@link #checkExit(int)} is called. All other method calls are
+ * delegated to the original security manager.
+ */
+ private static class NoExitSecurityManager extends SecurityManager {
+ private final SecurityManager originalSecurityManager;
+ private Integer statusOfFirstExitCall = null;
+
+ NoExitSecurityManager(
+ SecurityManager originalSecurityManager
+ ) {
+ this.originalSecurityManager = originalSecurityManager;
+ }
+
+ @Override
+ public void checkExit(
+ int status
+ ) {
+ if (statusOfFirstExitCall == null)
+ statusOfFirstExitCall = status;
+ throw new CheckExitCalled();
+ }
+
+ boolean isCheckExitCalled() {
+ return statusOfFirstExitCall != null;
+ }
+
+ int getStatusOfFirstCheckExitCall() {
+ if (isCheckExitCalled())
+ return statusOfFirstExitCall;
+ else
+ throw new IllegalStateException(
+ "checkExit(int) has not been called.");
+ }
+
+ @Override
+ public Object getSecurityContext() {
+ return (originalSecurityManager == null) ? super.getSecurityContext()
+ : originalSecurityManager.getSecurityContext();
+ }
+
+ @Override
+ public void checkPermission(Permission perm) {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkPermission(perm);
+ }
+
+ @Override
+ public void checkPermission(Permission perm, Object context) {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkPermission(perm, context);
+ }
+
+ @Override
+ public void checkCreateClassLoader() {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkCreateClassLoader();
+ }
+
+ @Override
+ public void checkAccess(Thread t) {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkAccess(t);
+ }
+
+ @Override
+ public void checkAccess(ThreadGroup g) {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkAccess(g);
+ }
+
+ @Override
+ public void checkExec(String cmd) {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkExec(cmd);
+ }
+
+ @Override
+ public void checkLink(String lib) {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkLink(lib);
+ }
+
+ @Override
+ public void checkRead(FileDescriptor fd) {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkRead(fd);
+ }
+
+ @Override
+ public void checkRead(String file) {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkRead(file);
+ }
+
+ @Override
+ public void checkRead(String file, Object context) {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkRead(file, context);
+ }
+
+ @Override
+ public void checkWrite(FileDescriptor fd) {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkWrite(fd);
+ }
+
+ @Override
+ public void checkWrite(String file) {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkWrite(file);
+ }
+
+ @Override
+ public void checkDelete(String file) {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkDelete(file);
+ }
+
+ @Override
+ public void checkConnect(String host, int port) {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkConnect(host, port);
+ }
+
+ @Override
+ public void checkConnect(String host, int port, Object context) {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkConnect(host, port, context);
+ }
+
+ @Override
+ public void checkListen(int port) {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkListen(port);
+ }
+
+ @Override
+ public void checkAccept(String host, int port) {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkAccept(host, port);
+ }
+
+ @Override
+ public void checkMulticast(InetAddress maddr) {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkMulticast(maddr);
+ }
+
+ @Override
+ public void checkMulticast(InetAddress maddr, byte ttl) {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkMulticast(maddr, ttl);
+ }
+
+ @Override
+ public void checkPropertiesAccess() {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkPropertiesAccess();
+ }
+
+ @Override
+ public void checkPropertyAccess(String key) {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkPropertyAccess(key);
+ }
+
+ @Override
+ public void checkPrintJobAccess() {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkPrintJobAccess();
+ }
+
+ @Override
+ public void checkPackageAccess(String pkg) {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkPackageAccess(pkg);
+ }
+
+ @Override
+ public void checkPackageDefinition(String pkg) {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkPackageDefinition(pkg);
+ }
+
+ @Override
+ public void checkSetFactory() {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkSetFactory();
+ }
+
+ @Override
+ public void checkSecurityAccess(String target) {
+ if (originalSecurityManager != null)
+ originalSecurityManager.checkSecurityAccess(target);
+ }
+
+ @Override
+ public ThreadGroup getThreadGroup() {
+ return (originalSecurityManager == null) ? super.getThreadGroup()
+ : originalSecurityManager.getThreadGroup();
+ }
+ }
+}
diff --git a/src/test/resources/testdata.csv b/src/test/resources/testdata.csv
new file mode 100644
index 0000000..e69de29