Java - how to call Java main() from a new process thread

时间:2019-04-08 13:49:44

标签: java integration-testing junit4 jvm-arguments

I am trying to build out Integration Tests (IT) for an application. The application has at its centre a Server, written in Java, that set-ups a message queue from which it polls for messages sent to it on a particular port-number. I would like to write an Integration Test which fires some messages at this server/port-number and tests the response.

Below is the full list of VM arguments that I run when I start the server from within Intellij manually. I can start the server this way and then fire my test messages at it but I would like to convert this into IT tests so that I can start/stop the server programmatically at the start and end of my tests.

The problem I am having is that I dont know how to start the server application from within my test class. So to ask it more plainly, how to start the Java main() of a class in its own process thread. I am working within Intellij (2019.1) and Java 8. Should I be using the ProcessBuilder or ExecutorService maybe ?

I think I can use System.setProperty for some of the VM arguments but not sure how to specify the -XX ones...so that would be a second part to this question.

-Djava.endorsed.dirs=/Users/xxx/dev/src/repo/xxx/myapp/target/classes/lib/endorsed
       -Dmyapp.home=/private/var/tmp/myapp
                -Dmyapp.log.dir=/private/var/tmp
                          -Dmyapp.env=/Users/xxx/dev/src/repo/xxx/myapp/target/classes/etc/examples/environment-apa.sh
                         -Dsimplelogger.properties=/private/var/tmp/myapp/etc/simplelogger.properties
                           -server                   
        -XX:CompileThreshold=2500
                          -XX:+UseFastAccessorMethods
                          -Xss256k
                           -Xmx1g
                           -Xms512m

I've tried implementing this using the ExecutorService

public class ServerTest {
    @Test
    public void shouldStartServerOk() {
        try{
            startServer();
        }catch(Exception e){
            e.printStackTrace();
            fail();
        }
    }

    private void startServer(){
        ExecutorService executor = Executors.newFixedThreadPool(1);

        Runnable runnableTask = new Runnable() {
            @Override
            public void run() {
                String [] args = new String[0];
                try {
                    System.setProperty("java.endorsed.dirs", "/Users/xxx/dev/src/gitlab/xxx/myapp/target/classes/lib/endorsed");
                    System.setProperty("myapp.home", "/private/var/tmp/myapp");
                    System.setProperty("myapp.log.dir", "/private/var/tmp");
                    System.setProperty("myapp.env", "/Users/xxx/dev/src/gitlab/xxx/myapp/target/classes/etc/examples/environment-apa.sh");
                    System.setProperty("simplelogger.properties", "/private/var/tmp/myapp/etc/simplelogger.properties");
                    System.setProperty("-server", "TRUE");
                    MyApp.main(args);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };

        executor.execute(runnableTask);

        // shut down the executor manually
        //executor.shutdown();

    }

But this doesn't seem to work although the test does complete green. When I debug the process, the flow doesn't Step-Into MyApp.main(args). Strangely when I just try running MyApp.main(args) on its own outside of the ExecutorService then it starts and runs fine until I hit Stop in my IDE. This is behaviour I would like just the additional ability to Start/Stop the process.

UPDATE-1: following the comments from @dmitrievanthony and @RealSkeptic I have tried to implement something along those lines based on SO question, Executing a Java application in a separate process/636367,

public final class JavaProcess {

    private JavaProcess() {}

    public static int exec(Class klass) throws IOException,
            InterruptedException {
        String javaHome = System.getProperty("java.home");
        String javaBin = javaHome +
                File.separator + "bin" +
                File.separator + "java";
        String classpath = System.getProperty("java.class.path");
        String className = klass.getName();

        ProcessBuilder processBuilder = new ProcessBuilder(
                javaBin,
                "-cp",
                classpath,
                className
        );

        Map<String, String> env = processBuilder.environment();
        env.put("java.endorsed.dirs", "/Users/xxx/dev/src/gitlab/xxx/myapp/target/classes/lib/endorsed");
        env.put("myapp.home", "/private/var/tmp/myapp");
        env.put("myapp.log.dir", "/private/var/tmp");
        env.put("myapp.env", "/Users/xxx/dev/src/gitlab/xxx/myapp/target/classes/etc/examples/environment-apa.sh");
        env.put("simplelogger.properties", "/private/var/tmp/myapp/etc/simplelogger.properties");
        env.put("-XX:CompileThreshold", "2500");
        env.put("-XX:+UseFastAccessorMethods", "");
        env.put("-Xss256k", "");
        env.put("-Xmx1g", "");
        env.put("-Xms512m", "");

        Process process = processBuilder.inheritIO().start();
        process.waitFor();
        return process.exitValue();
    }

and calling it in my myAppIT test class as int status = JavaProcess.exec(MyAapp.class);

I can now see my class "MyApp" starting - and can confirm that the process flow is running into my MyApp.main() class. The problem now is that the System.env variables that I am setting in my ProcessBuilder do not appear to be available in the called programme ie. when I print to log System.getProperty("myapp.home") its returning null even though I can confirm that it is being set as shown in the code - does anyone have any ideas on this one please ?

UPDATE-2: I am trying to implement suggestion by @RealSkeptic and passing in the arguments in a similar way as passing commandline arguments as shown in the code snippet below. Now I am getting an exception Error: Could not find or load main class xxx.xxx.xxx.xxx.MyApp -Djava.endorsed.dirs=.Users.xxx.dev.src.gitlab.myapp.myapp.target.classes.lib.endorsed one problem I see is that the forward slashes of the path have been translated to ".". The path should read, Djava.endorsed.dirs=/Users/xxx/dev/src/gitlab/myapp/myapp/target/classes/lib/endorsed

ProcessBuilder processBuilder = new ProcessBuilder(
                javaBin,
                "-cp",
                classpath,
                className + " " +
                "-Djava.endorsed.dirs=" + "/Users/xxx/dev/src/gitlab/xxx/myapp/target/classes/lib/endorsed " +
                "-Dmyapp.home=/private/var/tmp/myapp " +
                "-Dmyapp.log.dir=/private/var/tmp" +
                "-Dmyapp.env=/Users/xxx/dev/src/gitlab/xxx/myapp/target/classes/etc/examples/environment-apa.sh " +
                "-Dsimplelogger.properties=/private/var/tmp/myapp/etc/simplelogger.properties " +
                "-server " +
                "-XX:CompileThreshold=2500 " +
                "-XX:+UseFastAccessorMethods " +
                "-Xss256k " +
                "-Xmx1g " +
                "-Xms512m"
        );

Update-3 following the last comment from @RealSkeptic I've modified my code (see below) and this now works.

ProcessBuilder processBuilder = new ProcessBuilder(
                javaBin,
                "-cp",
                classpath,
                "-Djava.endorsed.dirs=" + "/Users/xxx/dev/src/gitlab/xxx/myapp/target/classes/lib/endorsed",
                "-Dmyapp.home=/private/var/tmp/myapp",
                "-Dmyapp.log.dir=/private/var/tmp",
                "-Dmyapp.env=/Users/xxx/dev/src/gitlab/xxx/myapp/target/classes/etc/examples/environment-apa.sh",
                "-Dsimplelogger.properties=/private/var/tmp/myapp/etc/simplelogger.properties ",
                "-server",
                "-XX:CompileThreshold=2500",
                "-XX:+UseFastAccessorMethods",
                "-Xss256k",
                "-Xmx1g",
                "-Xms512m",
                className
        );

1 个答案:

答案 0 :(得分:0)

下面是从UPDATE-3复制的,我将其发布为答案。谢谢那些回答的人,尤其是@RealSkeptic。

ProcessBuilder processBuilder = new ProcessBuilder(
                javaBin,
                "-cp",
                classpath,
                "-Djava.endorsed.dirs=" + "/Users/xxx/dev/src/gitlab/xxx/myapp/target/classes/lib/endorsed",
                "-Drrc.home=/private/var/tmp/myapp",
                "-Drrc.log.dir=/private/var/tmp",
                "-Drrc.env=/Users/xxx/dev/src/gitlab/xxx/myapp/target/classes/etc/examples/environment-apa.sh",
                "-Dsimplelogger.properties=/private/var/tmp/myapp/etc/simplelogger.properties ",
                "-server",
                "-XX:CompileThreshold=2500",
                "-XX:+UseFastAccessorMethods",
                "-Xss256k",
                "-Xmx1g",
                "-Xms512m",
                className
        );

我已经重构了上述内容,将每个参数放入一个列表中,因此对ProcessBuilder的调用减少为

ProcessBuilder processBuilder = new ProcessBuilder(arguments); Process process = processBuilder.inheritIO().start();

要停止该过程,您只需致电 process.destroy();