diff --git a/src/main/java/org/junit/contrib/java/lang/system/ExpectedSystemExit.java b/src/main/java/org/junit/contrib/java/lang/system/ExpectedSystemExit.java index f798940c..12e0ceb3 100644 --- a/src/main/java/org/junit/contrib/java/lang/system/ExpectedSystemExit.java +++ b/src/main/java/org/junit/contrib/java/lang/system/ExpectedSystemExit.java @@ -28,7 +28,9 @@ *
* Some care must be taken if your system under test creates a new thread and * this thread calls {@code System.exit()}. In this case you have to ensure that - * the test does not finish before {@code System.exit()} is called. + * the test does not finish before {@code System.exit()} is called. Use + * {@code ExpectedSystemExit.timeout(...)} method call to specify test method + * waiting time in milliseconds. * *
* public class AppWithExit { @@ -78,6 +80,13 @@ * } * * @Test + * public void systemExitInSeparateThread() { + * exit.expectSystemExitWithStatus(1); + * exit.timeout(1000); + * AppWithExit.doSomethingInSeparateThreadAndExit(); + * } + * + * @Test * public void noSystemExit() { * AppWithExit.doNothing(); * //passes @@ -93,6 +102,7 @@ public static ExpectedSystemExit none() { private final Collectionassertions = new ArrayList (); private boolean expectExit = false; private Integer expectedStatus = null; + private long timeout = 0; private ExpectedSystemExit() { } @@ -106,6 +116,10 @@ public void expectSystemExit() { expectExit = true; } + public void timeout(long timeout) { + this.timeout = timeout; + } + public void checkAssertionAfterwards(Assertion assertion) { assertions.add(assertion); } @@ -117,8 +131,7 @@ public Statement apply(final Statement base, Description description) { } private ProvideSecurityManager createNoExitSecurityManagerRule() { - NoExitSecurityManager noExitSecurityManager = new NoExitSecurityManager( - getSecurityManager()); + SecurityManager noExitSecurityManager = new NoExitSecurityManager(getSecurityManager()); return new ProvideSecurityManager(noExitSecurityManager); } @@ -136,10 +149,11 @@ public void evaluate() throws Throwable { }; } - private void checkSystemExit() { + private void checkSystemExit() throws InterruptedException { NoExitSecurityManager securityManager = (NoExitSecurityManager) getSecurityManager(); - if (securityManager.isCheckExitCalled()) - handleSystemExitWithStatus(securityManager.getStatusOfFirstCheckExitCall()); + Integer exitStatus = securityManager.getStatusOfFirstCheckExitCall(timeout); + if (exitStatus != null) + handleSystemExitWithStatus(exitStatus); else handleMissingSystemExit(); } diff --git a/src/main/java/org/junit/contrib/java/lang/system/internal/NoExitSecurityManager.java b/src/main/java/org/junit/contrib/java/lang/system/internal/NoExitSecurityManager.java index df7fee45..b35f6761 100644 --- a/src/main/java/org/junit/contrib/java/lang/system/internal/NoExitSecurityManager.java +++ b/src/main/java/org/junit/contrib/java/lang/system/internal/NoExitSecurityManager.java @@ -3,6 +3,9 @@ import java.io.FileDescriptor; import java.net.InetAddress; import java.security.Permission; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; /** * A {@code NoExitSecurityManager} throws a {@link CheckExitCalled} exception @@ -10,8 +13,9 @@ * delegated to the original security manager. */ public class NoExitSecurityManager extends SecurityManager { + private final SecurityManager originalSecurityManager; - private Integer statusOfFirstExitCall = null; + private final BlockingQueue exitStatusHolder = new LinkedBlockingQueue (1); public NoExitSecurityManager(SecurityManager originalSecurityManager) { this.originalSecurityManager = originalSecurityManager; @@ -19,21 +23,12 @@ public NoExitSecurityManager(SecurityManager originalSecurityManager) { @Override public void checkExit(int status) { - if (statusOfFirstExitCall == null) - statusOfFirstExitCall = status; + exitStatusHolder.offer(status); throw new CheckExitCalled(status); } - public boolean isCheckExitCalled() { - return statusOfFirstExitCall != null; - } - - public int getStatusOfFirstCheckExitCall() { - if (isCheckExitCalled()) - return statusOfFirstExitCall; - else - throw new IllegalStateException( - "checkExit(int) has not been called."); + public Integer getStatusOfFirstCheckExitCall(long timeout) throws InterruptedException { + return exitStatusHolder.poll(timeout, TimeUnit.MILLISECONDS); } @Override @@ -44,7 +39,8 @@ public boolean getInCheck() { @Override public Object getSecurityContext() { - return (originalSecurityManager == null) ? super.getSecurityContext() + return originalSecurityManager == null + ? super.getSecurityContext() : originalSecurityManager.getSecurityContext(); } @@ -176,7 +172,8 @@ public void checkPropertyAccess(String key) { @Override public boolean checkTopLevelWindow(Object window) { - return (originalSecurityManager == null) ? super.checkTopLevelWindow(window) + return originalSecurityManager == null + ? super.checkTopLevelWindow(window) : originalSecurityManager.checkTopLevelWindow(window); } @@ -230,7 +227,8 @@ public void checkSecurityAccess(String target) { @Override public ThreadGroup getThreadGroup() { - return (originalSecurityManager == null) ? super.getThreadGroup() + return originalSecurityManager == null + ? super.getThreadGroup() : originalSecurityManager.getThreadGroup(); } } diff --git a/src/test/java/org/junit/contrib/java/lang/system/ExpectedSystemExitTest.java b/src/test/java/org/junit/contrib/java/lang/system/ExpectedSystemExitTest.java index ae1a40f5..fc86fafc 100644 --- a/src/test/java/org/junit/contrib/java/lang/system/ExpectedSystemExitTest.java +++ b/src/test/java/org/junit/contrib/java/lang/system/ExpectedSystemExitTest.java @@ -1,21 +1,19 @@ package org.junit.contrib.java.lang.system; +import org.junit.Test; +import org.junit.runners.model.Statement; + +import java.security.Permission; + import static java.lang.System.getSecurityManager; import static java.lang.System.setSecurityManager; import static java.lang.Thread.sleep; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; import static org.junit.contrib.java.lang.system.Executor.exceptionThrownWhenTestIsExecutedWithRule; import static org.junit.contrib.java.lang.system.Executor.executeTestWithRule; import static org.junit.contrib.java.lang.system.Statements.TEST_THAT_DOES_NOTHING; -import java.security.Permission; - -import org.junit.Test; -import org.junit.runners.model.Statement; - public class ExpectedSystemExitTest { private static final Object ARBITRARY_CONTEXT = new Object(); @@ -128,7 +126,8 @@ public void current_security_manager_is_used_for_anything_else_than_system_exit( @Test public void test_is_successful_if_expected_exit_is_called_in_a_thread() { rule.expectSystemExitWithStatus(ARBITRARY_EXIT_STATUS); - executeTestWithRule(new SystemExitInThread(), rule); + rule.timeout(1000); + executeTestWithRule(new SystemExitInSeparateThread(), rule); } private static class SystemExit0 extends Statement { @@ -145,17 +144,21 @@ public void evaluate() throws Throwable { } } - private static class SystemExitInThread extends Statement { + private static class SystemExitInSeparateThread extends Statement { @Override public void evaluate() throws Throwable { - Runnable callSystemExit = new Runnable() { - public void run() { + new Thread(new LongExecutionBeforeExitCall()).start(); + } + + private static class LongExecutionBeforeExitCall implements Runnable { + public void run() { + try { + sleep(500); System.exit(ARBITRARY_EXIT_STATUS); + } catch (InterruptedException e) { + e.printStackTrace(); } - }; - Thread thread = new Thread(callSystemExit); - thread.start(); - sleep(1000); // wait until the thread exits + } } } diff --git a/src/test/java/org/junit/contrib/java/lang/system/internal/NoExitSecurityManagerTest.java b/src/test/java/org/junit/contrib/java/lang/system/internal/NoExitSecurityManagerTest.java index 86f2e74c..40d73850 100644 --- a/src/test/java/org/junit/contrib/java/lang/system/internal/NoExitSecurityManagerTest.java +++ b/src/test/java/org/junit/contrib/java/lang/system/internal/NoExitSecurityManagerTest.java @@ -22,8 +22,7 @@ public class NoExitSecurityManagerTest { public final TemporaryFolder temporaryFolder = new TemporaryFolder(); private final SecurityManager originalSecurityManager = mock(SecurityManager.class); - private final NoExitSecurityManager managerWithOriginal = new NoExitSecurityManager( - originalSecurityManager); + private final NoExitSecurityManager managerWithOriginal = new NoExitSecurityManager(originalSecurityManager); private final NoExitSecurityManager managerWithoutOriginal = new NoExitSecurityManager(null); @Test @@ -440,21 +439,10 @@ public void getThreadGroup_may_be_called_without_original_security_manager() { } @Test - public void information_about_a_missing_checkExit_call_is_available() { - assertThat(managerWithOriginal.isCheckExitCalled()).isFalse(); - } - - @Test - public void information_about_a_checkExit_call_is_available() { - safeCallCheckExitWithStatus(DUMMY_STATUS); - assertThat(managerWithOriginal.isCheckExitCalled()).isTrue(); - } - - @Test - public void status_of_first_call_of_checkExit_is_available() { + public void status_of_first_call_of_checkExit_is_available() throws InterruptedException { safeCallCheckExitWithStatus(DUMMY_STATUS); safeCallCheckExitWithStatus(DUMMY_STATUS + 1); - assertThat(managerWithOriginal.getStatusOfFirstCheckExitCall()) + assertThat(managerWithOriginal.getStatusOfFirstCheckExitCall(0)) .isEqualTo(DUMMY_STATUS); } @@ -466,14 +454,7 @@ private void safeCallCheckExitWithStatus(int status) { } @Test - public void fails_to_provide_status_of_first_checkExit_call_if_this_call_did_not_happen() { - Throwable exception = exceptionThrownBy(new Statement() { - public void evaluate() throws Throwable { - managerWithOriginal.getStatusOfFirstCheckExitCall(); - } - }); - assertThat(exception) - .isInstanceOf(IllegalStateException.class) - .hasMessage("checkExit(int) has not been called."); + public void null_status_of_first_checkExit_call_if_this_call_did_not_happen() throws InterruptedException { + assertThat(managerWithOriginal.getStatusOfFirstCheckExitCall(0)).isNull(); } }