diff --git a/README.md b/README.md index ecaef38..9aec0f5 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,9 @@ class TestSystemExit } ``` -Note that in order to be able to trap system exit, the `ExitGuard` temporarily replaces the existing security manager (if any). +The `ExitGuard` temporarily replaces the existing security manager. + +From version 1.2.0 on if a security guard existed before, it serves as a delegate for all security checks with the exception of the `checkExit`. ## Asserting Data Sent to `System.out` @@ -91,6 +93,10 @@ Capturing data sent to `System.err` works in the exact same way as in the [`Syst Please check our [contribution guide](.github/CONTRIBUTING.md) to learn how you can help with the project, report errors or request features. +## Changelog + +[Changelog](doc/changes/changelog.md) + ## Development ### Build Time Dependencies @@ -177,4 +183,4 @@ mvn versions:display-plugin-updates 1. Merge `develop` to `master` branch 1. Create a [release](https://github.com/itsallcode/junit5-system-extensions/releases) of the `master` branch on GitHub. -1. After some time the release will be available at [Maven Central](https://repo1.maven.org/maven2/org/itsallcode/junit5-system-extensions/). +1. After some time the release will be available at [Maven Central](https://repo1.maven.org/maven2/org/itsallcode/junit5-system-extensions/). \ No newline at end of file diff --git a/doc/changes/changelog.md b/doc/changes/changelog.md new file mode 100644 index 0000000..bee3298 --- /dev/null +++ b/doc/changes/changelog.md @@ -0,0 +1,4 @@ +# Changes + +* [1.2.0](changes_1.2.0.md) +* [1.1.0](changes_1.1.0.md) \ No newline at end of file diff --git a/doc/changes/changes_1.1.0.md b/doc/changes/changes_1.1.0.md new file mode 100644 index 0000000..440b61d --- /dev/null +++ b/doc/changes/changes_1.1.0.md @@ -0,0 +1,11 @@ +# JUnit5 System Extensions 1.1.0, released 2019-12-28 + +Code name: Mute output + +## Summary + +To mute the output (i.e. don't forward output to `System.out` / `System.err`) call `stream.captureMuted()` instead of `stream.capture()`. This can be useful to speed up unit tests. + +## Features + +* [#16](https://github.com/itsallcode/junit5-system-extensions/issues/16): Mute output of captured streams diff --git a/doc/changes/changes_1.2.0.md b/doc/changes/changes_1.2.0.md new file mode 100644 index 0000000..bce39ef --- /dev/null +++ b/doc/changes/changes_1.2.0.md @@ -0,0 +1,17 @@ +# JUnit5 System Extensions 1.2.0, released 2019-04-22 + +Code name: Mute output + +## Summary + +When trapping `System.exit` calls, the `ExitGuardSecurityManager` now doesn't simply replace an existing security manager anymore. Instead it uses the existing one as a delegate for all checks except the exit check. + +This way applications that require a security manager don't change their behavior. + +## Features + +* [#23](https://github.com/itsallcode/junit5-system-extensions/issues/23): Delegate security manager calls to the original security manager + +## Refactoring + +* [#21](https://github.com/itsallcode/junit5-system-extensions/issues/21): Migrate to Maven Central diff --git a/launch/j5se - run all tests.launch b/launch/j5se - run all tests.launch index a81b840..01aec37 100644 --- a/launch/j5se - run all tests.launch +++ b/launch/j5se - run all tests.launch @@ -1,27 +1,27 @@ - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/org/itsallcode/junit/sysextensions/ExitGuard.java b/src/main/java/org/itsallcode/junit/sysextensions/ExitGuard.java index c5b5670..9b4e61d 100644 --- a/src/main/java/org/itsallcode/junit/sysextensions/ExitGuard.java +++ b/src/main/java/org/itsallcode/junit/sysextensions/ExitGuard.java @@ -9,13 +9,13 @@ * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0. - * + * * This Source Code may also be made available under the following Secondary * Licenses when the conditions for such availability set forth in the Eclipse * Public License, v. 2.0 are satisfied: GNU General Public License, version 2 * with the GNU Classpath Exception which is * available at https://www.gnu.org/software/classpath/license.html. - * + * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 * #L% */ @@ -48,7 +48,8 @@ private void saveCurrentSecurityManager(final ExtensionContext context) private void installExitGuardSecurityManager(final ExtensionContext context) { - final SecurityManager exitGuardSecurityManager = new ExitGuardSecurityManager(); + final SecurityManager previousSecurityManager = getPreviousSecurityManager(context); + final SecurityManager exitGuardSecurityManager = new ExitGuardSecurityManager(previousSecurityManager); System.setSecurityManager(exitGuardSecurityManager); context.getStore(getNamespace()).put(EXIT_GUARD_SECURITY_MANAGER_KEY, exitGuardSecurityManager); } @@ -78,8 +79,13 @@ public void afterTestExecution(final ExtensionContext context) throws Exception @Override public void afterAll(final ExtensionContext context) throws Exception { - final SecurityManager previousManager = (SecurityManager) context.getStore(getNamespace()) - .get(PREVIOUS_SECURITY_MANAGER_KEY); + final SecurityManager previousManager = getPreviousSecurityManager(context); System.setSecurityManager(previousManager); } -} + + private SecurityManager getPreviousSecurityManager(final ExtensionContext context) + { + return (SecurityManager) context.getStore(getNamespace()) + .get(PREVIOUS_SECURITY_MANAGER_KEY); + } +} \ No newline at end of file diff --git a/src/main/java/org/itsallcode/junit/sysextensions/security/ExitGuardSecurityManager.java b/src/main/java/org/itsallcode/junit/sysextensions/security/ExitGuardSecurityManager.java index 38163b5..fb1d524 100644 --- a/src/main/java/org/itsallcode/junit/sysextensions/security/ExitGuardSecurityManager.java +++ b/src/main/java/org/itsallcode/junit/sysextensions/security/ExitGuardSecurityManager.java @@ -1,5 +1,8 @@ package org.itsallcode.junit.sysextensions.security; +import java.io.FileDescriptor; +import java.net.InetAddress; + /*- * #%L * JUnit5 System Extensions @@ -9,13 +12,13 @@ * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0. - * + * * This Source Code may also be made available under the following Secondary * Licenses when the conditions for such availability set forth in the Eclipse * Public License, v. 2.0 are satisfied: GNU General Public License, version 2 * with the GNU Classpath Exception which is * available at https://www.gnu.org/software/classpath/license.html. - * + * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 * #L% */ @@ -25,6 +28,12 @@ public class ExitGuardSecurityManager extends SecurityManager { private boolean trapExit = false; + private final SecurityManager delegate; + + public ExitGuardSecurityManager(final SecurityManager delegate) + { + this.delegate = delegate; + } @Override public synchronized void checkExit(final int status) @@ -46,11 +55,233 @@ public void checkPermission(final Permission perm) /** * Switch trapping {@link System#exit(int)} calls on or off. * - * @param trapExit set to true if the - * {@link ExitGuardSecurityManager} should trap exit calls + * @param trapExit + * set to true if the + * {@link ExitGuardSecurityManager} should trap exit calls */ public void trapExit(final boolean trapExit) { this.trapExit = trapExit; } -} + + private boolean hasDelegate() + { + return this.delegate != null; + } + + @Override + public void checkAccept(final String host, final int port) + { + if (this.hasDelegate()) + { + this.delegate.checkAccept(host, port); + } + } + + @Override + public void checkAccess(final Thread thread) + { + if (this.hasDelegate()) + { + this.delegate.checkAccess(thread); + } + } + + @Override + public void checkAccess(final ThreadGroup group) + { + if (this.hasDelegate()) + { + this.delegate.checkAccess(group); + } + } + + @Override + public void checkConnect(final String host, final int port, final Object context) + { + if (this.hasDelegate()) + { + this.delegate.checkConnect(host, port, context); + } + } + + @Override + public void checkConnect(final String host, final int port) + { + if (this.hasDelegate()) + { + this.delegate.checkConnect(host, port); + } + } + + @Override + public void checkCreateClassLoader() + { + if (this.hasDelegate()) + { + this.delegate.checkCreateClassLoader(); + } + } + + @Override + public void checkDelete(final String file) + { + if (this.hasDelegate()) + { + this.delegate.checkDelete(file); + } + } + + @Override + public void checkExec(final String cmd) + { + if (this.hasDelegate()) + { + this.delegate.checkExec(cmd); + } + } + + @Override + public void checkLink(final String lib) + { + if (this.hasDelegate()) + { + this.delegate.checkLink(lib); + } + } + + @Override + public void checkListen(final int port) + { + if (this.hasDelegate()) + { + this.delegate.checkListen(port); + } + } + + @Override + public void checkMulticast(final InetAddress maddr) + { + if (this.hasDelegate()) + { + this.delegate.checkMulticast(maddr); + } + } + + @Override + public void checkPackageAccess(final String pkg) + { + if (this.hasDelegate()) + { + this.delegate.checkPackageAccess(pkg); + } + } + + @Override + public void checkPackageDefinition(final String pkg) + { + if (this.hasDelegate()) + { + this.delegate.checkPackageDefinition(pkg); + } + } + + @Override + public void checkPermission(final Permission perm, final Object context) + { + if (this.hasDelegate()) + { + this.delegate.checkPermission(perm, context); + } + } + + @Override + public void checkPrintJobAccess() + { + if (this.hasDelegate()) + { + this.delegate.checkPrintJobAccess(); + } + } + + @Override + public void checkPropertiesAccess() + { + if (this.hasDelegate()) + { + this.delegate.checkPropertiesAccess(); + } + } + + @Override + public void checkPropertyAccess(final String key) + { + if (this.hasDelegate()) + { + this.delegate.checkPropertyAccess(key); + } + } + + @Override + public void checkRead(final FileDescriptor fd) + { + if (this.hasDelegate()) + { + this.delegate.checkRead(fd); + } + } + + @Override + public void checkRead(final String file, final Object context) + { + if (this.hasDelegate()) + { + this.delegate.checkRead(file, context); + } + } + + @Override + public void checkRead(final String file) + { + if (this.hasDelegate()) + { + this.delegate.checkRead(file); + } + } + + @Override + public void checkSecurityAccess(final String target) + { + if (this.hasDelegate()) + { + this.delegate.checkSecurityAccess(target); + } + } + + @Override + public void checkSetFactory() + { + if (this.hasDelegate()) + { + this.delegate.checkSetFactory(); + } + } + + @Override + public void checkWrite(final FileDescriptor fileDescriptor) + { + if (this.hasDelegate()) + { + this.delegate.checkWrite(fileDescriptor); + } + } + + @Override + public void checkWrite(final String file) + { + if (this.hasDelegate()) + { + this.delegate.checkWrite(file); + } + } +} \ No newline at end of file diff --git a/src/test/java/org/itsallcode/junit/sysextensions/TestSystemExit.java b/src/test/java/org/itsallcode/junit/sysextensions/TestSystemExit.java index e6ebc97..5d6a2b8 100644 --- a/src/test/java/org/itsallcode/junit/sysextensions/TestSystemExit.java +++ b/src/test/java/org/itsallcode/junit/sysextensions/TestSystemExit.java @@ -1,5 +1,3 @@ -package org.itsallcode.junit.sysextensions; - /*- * #%L * JUnit5 System Extensions @@ -9,16 +7,17 @@ * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0. - * + * * This Source Code may also be made available under the following Secondary * Licenses when the conditions for such availability set forth in the Eclipse * Public License, v. 2.0 are satisfied: GNU General Public License, version 2 * with the GNU Classpath Exception which is * available at https://www.gnu.org/software/classpath/license.html. - * + * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 * #L% */ +package org.itsallcode.junit.sysextensions; import static org.itsallcode.junit.sysextensions.AssertExit.assertExit; import static org.itsallcode.junit.sysextensions.AssertExit.assertExitWithStatus; @@ -45,7 +44,8 @@ void testSystemExitMissingThrowsAssertError() assertExit(() -> { // intentionally empty }); - } catch (final AssertionError assertionError) + } + catch (final AssertionError assertionError) { assertMissingExitMessage(assertionError); return; @@ -70,7 +70,8 @@ void testSystemExitWithUnexpectedStatusThrowsAssertionError() try { assertExitWithStatus(0, () -> System.exit(1)); - } catch (final AssertionError assertionError) + } + catch (final AssertionError assertionError) { return; } @@ -85,11 +86,12 @@ void testSystemWithStatusMissingExitThrowsAssertError() assertExitWithStatus(0, () -> { // intentionally empty }); - } catch (final AssertionError assertionError) + } + catch (final AssertionError assertionError) { assertMissingExitMessage(assertionError); return; } fail("Code sequence where exit was expected did not exit, no assertion error was raised."); } -} +} \ No newline at end of file diff --git a/src/test/java/org/itsallcode/junit/sysextensions/TestSystemExitReplacingExistingSecurityManager.java b/src/test/java/org/itsallcode/junit/sysextensions/TestSystemExitReplacingExistingSecurityManager.java new file mode 100644 index 0000000..aa51171 --- /dev/null +++ b/src/test/java/org/itsallcode/junit/sysextensions/TestSystemExitReplacingExistingSecurityManager.java @@ -0,0 +1,98 @@ +/*- + * #%L + * JUnit5 System Extensions + * %% + * Copyright (C) 2018 itsallcode.org + * %% + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License, v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is + * available at https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + * #L% + */ +package org.itsallcode.junit.sysextensions; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.itsallcode.junit.sysextensions.security.ExitGuardSecurityManager; +import org.itsallcode.junit.sysextensions.security.ExitTrapException; +import org.junit.jupiter.api.Test; + +class TestSystemExitReplacingExistingSecurityManager +{ + @Test + void testFirstSystemExitIntercepted() + { + final SecurityManager previousSecuritymanager = System.getSecurityManager(); + try + { + final SecurityManagerStub securityManagerStub = new SecurityManagerStub(); + final ExitGuardSecurityManager exitGuardsecurityManager = new ExitGuardSecurityManager(securityManagerStub); + System.setSecurityManager(exitGuardsecurityManager); + exitGuardsecurityManager.trapExit(true); + assertAll(() -> assertThrows(ExitTrapException.class, () -> System.exit(1)), + () -> assertFalse(securityManagerStub.wasCheckExitCalled(), "Delegate exit called")); + } + finally + { + System.setSecurityManager(previousSecuritymanager); + } + } + + @Test + void testSecurityCheckGetsDelegated() + { + final SecurityManager previousSecuritymanager = System.getSecurityManager(); + try + { + final SecurityManagerStub securityManagerStub = new SecurityManagerStub(); + final ExitGuardSecurityManager exitGuardsecurityManager = new ExitGuardSecurityManager(securityManagerStub); + System.setSecurityManager(exitGuardsecurityManager); + System.getProperty("any-property"); + assertTrue(securityManagerStub.wasCheckPropertyAccessCalled()); + } + finally + { + System.setSecurityManager(previousSecuritymanager); + } + } + + private static class SecurityManagerStub extends SecurityManager + { + private boolean checkExitCalled = false; + private boolean checkPropertyAccessCalled = false; + + @Override + public void checkExit(final int status) + { + this.checkExitCalled = true; + super.checkExit(status); + } + + @Override + public void checkPropertyAccess(final String key) + { + this.checkPropertyAccessCalled = true; + } + + public boolean wasCheckExitCalled() + { + return this.checkExitCalled; + } + + public boolean wasCheckPropertyAccessCalled() + { + return this.checkPropertyAccessCalled; + } + } +} \ No newline at end of file