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