Closed averbraeck closed 1 hour ago
See https://openjdk.org/jeps/411 for the rationale. See https://openjdk.org/jeps/486 for the final removal in JDK-24.
In https://openjdk.org/jeps/486#Appendix an alternative is mentioned that uses a (dirty) trick to filter out the System.exit(...)
call from the bytecode in the .class file when it is loaded. An implementation of this principle is already available in https://github.com/tginsberg/junit5-system-exit and this code works with Java-17. Another example is the JMockit
library as mentioned in https://www.baeldung.com/junit-system-exit#1-dependency. Both install an instrumentation agent that changes the bytecode on the fly when it is being loaded.
A first try with JMockit to see if we can make this work in Java 17.
In djutils-cli, we add the following dependency:
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>1.49</version>
<scope>test</scope>
</dependency>
And the following plugin with instrumentation setting:
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven.surefire.version}</version>
<configuration>
<argLine>
-javaagent:"${settings.localRepository}"/org/jmockit/jmockit/1.49/jmockit-1.49.jar
</argLine>
</configuration>
</plugin>
</plugins>
</build>
This should ensure that jmockit is loaded before junit starts, and that the coverage tests of jacoco are not influenced.
In TestCliCommandLine
, the test method testCli
causes and exit because a port number is provided that is not between 1 and 65535.
The method not starts by mocking the System.exit(...)
method:
new MockUp<System>()
{
@Mock
public void exit(final int value)
{
throw new RuntimeException(String.valueOf(value));
}
};
For the exit, it causes a RunTimeException
instead.
In our code, we need to test if the correct error message is displayed via System.err
, so we capture System.err
to see if the correct error message is displayed and also check the exit of the program. Afterward, the System.err
is reset to its original value (the console):
PrintStream oldPrintStream = System.err;
ByteArrayOutputStream errContent = new ByteArrayOutputStream();
System.setErr(new PrintStream(errContent));
try
{
CliUtil.execute(cmdErr, argsErr);
fail("calling CliUtil.execute did not exit when it should");
}
catch (RuntimeException e)
{
assertTrue(errContent.toString().startsWith("Port should be between 1 and 65535"));
}
finally
{
System.setErr(oldPrintStream);
}
The code works correctly and code coverage also still works.
Tests complete correctly in Maven. Note that a test with Run as... JUnit Test
might not work anymore because the instrumentation will not be properly initialized.
If you want to run in Eclipse, the -javaagent
path should be explicitly provided. Unfortunately, this means that this is a manual addition to the run of the JUnit test:
So, in my case, the arguments are:
-ea -javaagent:C:/Users/alexandv/.m2/repository/org/jmockit/jmockit/1.49/jmockit-1.49.jar
All unit tests run again, and are compliant with Java17+.
In Java-17, the
System.setSecurityManager
function is deprecated and can disappear any moment. The error is:The method is used to test those CLI functions that calls
System.exit(...)
and of course should not exit the unit tests, since it is expected behavior.