1

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
        );
robbie70
  • 1,515
  • 4
  • 21
  • 30
  • 1
    Aren't you going to start it in a separate process? You pass all the parameters - including all `-D` and `-X` as command line parameters - see `ProcessBuilder`. – RealSkeptic Apr 08 '19 at 13:54
  • @RealSkeptic thanks - yes thats correct I would like to start it in a separate process. I will check-out ProcessBuilder – robbie70 Apr 08 '19 at 13:55
  • @RealSkeptic I am not sure that I can use this approach - it looks like this approach is intended to run Operating System commands, scripts or executable applications. I am trying to start another Java class which will start a Server type functionality. – robbie70 Apr 09 '19 at 11:56
  • See also https://stackoverflow.com/questions/3931246/howto-unit-test-client-server-code – Raedwald Apr 09 '19 at 12:04
  • @Raedwald thanks but thats not quite what I am looking for. I have a Java class and I would like to run its "main()" in a separate process. – robbie70 Apr 09 '19 at 12:48
  • 1
    You can't just run `main()` in separate process. To create a separate process you need to start jvm, load classes and only after that start `main()`. The most parameters you mentioned is also parameters of jvm, so you can't set them for already running jvm, only for a new one. – dmitrievanthony Apr 09 '19 at 14:56
  • @dmitrievanthony thank you for your comment. I have tried to implement something along those lines and this looks more promising. I can see the application trying to start-up. What appears to be wrong at the moment is that the VM arguments that i am setting aren't available in the called program. Please see my UPDATE-1. – robbie70 Apr 09 '19 at 17:56
  • 1
    These things should be in the command list pass to the process, not in the environment variables. Basically you pass the same stuff as if you would run this manually from command line - but with no special quoting. – RealSkeptic Apr 09 '19 at 23:21
  • @RealSkeptic I am trying to implement this but getting issue with the forward-slash character in the command parameter getting replaced by a "." - please see update-2 – robbie70 Apr 10 '19 at 09:28
  • Why did you decide to stick the class name in the middle of the characters? Please read the documentation of the `java` command. The class name should come after all the JVM-related arguments. The only things coming after it are the arguments that are passed to the `main` of the program. – RealSkeptic Apr 10 '19 at 09:35
  • @RealSkeptic sorry I am not sure I understand your point...the classname is the last parameter before the arguments. – robbie70 Apr 10 '19 at 09:42
  • 1
    It should be **after** the arguments if those arguments are JVM arguments like `-D...` and `-XX...` etc. All JVM arguments should precede the class name. – RealSkeptic Apr 10 '19 at 09:45
  • @RealSkeptic I moved the arguments as you suggested and that worked :) see Update-3. Thank you for your help. – robbie70 Apr 10 '19 at 11:03

1 Answers1

0

The below is copied from UPDATE-3 which I am posting as the answer. Thank you to those who responded and especially @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
        );

I have refactored the above to put each of the arguments into a List so the call to ProcessBuilder reduces to,

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

To Stop the process you just need to call process.destroy();

robbie70
  • 1,515
  • 4
  • 21
  • 30