4

In my Cucumber-jvm scenarios I need to run an external jar program before each scenario, interact with it using the FEST library in the steps, and finally shut the program down to clean the slate for the next scenario. The particular external program I need uses System.exit() to quit when closed. In turn I cannot just quit the program in my tests as that would terminate the entire VM. Instead I use the custom SecurityManager built into FEST to override System.exit() call. However, I cannot get it to work correctly.

The code in example 1 below tries to start the external program in a Cucumber @Before hook and shut it down in an @After hook. It works perfectly fine with only one scenario when I run mvn verify. However, with two or more scenarios maven just hangs on the lines:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running test.acceptance.CucumberRunner 

Nothing happens afterwards. I can see that the external program is launched and closed once, but the second time its launched it doesn't close. When I close it manually maven outputs the following:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-failsafe-
plugin:2.16:integration-test (default) on project acceptance-tests: Execution default of
goal org.apache.maven.plugins:maven-failsafe-plugin:2.16:integration-test failed: The
forked VM terminated without saying properly goodbye. VM crash or System.exit called ?

Does anyone have any idea of what's going on here? It seems like the problem is that the external program is not terminated at all - maybe the fault of the NoExitSecurityManagerInstaller I am using. However, I don't know how else to prevent the call to System.exit terminating the entire VM. Somehow I just want to exit the program I started without affecting the VM in which it is running. Is that not possible?

Update - Solution found!

After having played around with the code for several hours, I accidentally discovered that the Robot class used by the WindowFinder has a cleanUp method that: "Cleans up any used resources (keyboard, mouse, open windows and {@link ScreenLock}) used by this robot.". I tried using this instead of frame.close() and it turns out it works! It doesn't even need the custom SecurityManager.

The problem seems to be that the BasicRobot.robotWithCurrentAwtHierarchy() call aquires a lock on the screen which is NOT release by frame.close(). So when the next call to BasicRobot.robotWithCurrentAwtHierarchy() is made in the second scenario/test the call will block waiting for the lock to be released, and effectively creating a deadlock. The solution is to manually release the lock using robot.cleanUp (which also closes and disposes any open windows). However, why frame.close doesn't do this when it closes the last frame is beyond me.

Example 1

public class CucumberHooks {
    private FrameFixture frame;

    @Before
    public void setup() throws InterruptedException, IOException {
        Thread t = new Thread(new Runnable() {
            public void run() {
                File file = new File(System.getProperty("external-jar"));
                URLClassLoader cl = null;
                try {
                    cl = new URLClassLoader( new URL[]{file.toURI().toURL()} );
                }
                catch (MalformedURLException e) {}

                Class<?> clazz = null;
                try {
                    clazz = cl.loadClass("MainClass");
                }
                catch (ClassNotFoundException e) {}

                Method main = null;
                try {
                    main = clazz.getMethod("main", String[].class);
                }
                catch (NoSuchMethodException e) {}

                try {
                    main.invoke(null, new Object[]{new String[]{}});
                }
                catch (Exception e) {}
            }
        });
        t.start();

        GenericTypeMatcher<JFrame> matcher = new GenericTypeMatcher<JFrame>(JFrame.class) {
            protected boolean isMatching(JFrame frame) {
                return "External Jar Title".equals(frame.getTitle()) && frame.isShowing();
            }
        };

        frame = WindowFinder.findFrame(matcher).using(BasicRobot.robotWithCurrentAwtHierarchy());
    }

    @After
    public void shutDown() throws InterruptedException {
        NoExitSecurityManagerInstaller i = NoExitSecurityManagerInstaller.installNoExitSecurityManager();
        frame.close();
        i.uninstall();
    }
}

Example 2

public class CucumberHooks {
    private FrameFixture frame;
    private Robot robot;

    @Before
    public void setup() throws InterruptedException, IOException {
        Thread t = new Thread(new Runnable() {
            public void run() {
                File file = new File(System.getProperty("external-jar"));
                URLClassLoader cl = null;
                try {
                    cl = new URLClassLoader( new URL[]{file.toURI().toURL()} );
                }
                catch (MalformedURLException e) {}

                Class<?> clazz = null;
                try {
                    clazz = cl.loadClass("MainClass");
                }
                catch (ClassNotFoundException e) {}

                Method main = null;
                try {
                    main = clazz.getMethod("main", String[].class);
                }
                catch (NoSuchMethodException e) {}

                try {
                    main.invoke(null, new Object[]{new String[]{}});
                }
                catch (Exception e) {}
            }
        });
        t.start();

        GenericTypeMatcher<JFrame> matcher = new GenericTypeMatcher<JFrame>(JFrame.class) {
            protected boolean isMatching(JFrame frame) {
                return "External Jar Title".equals(frame.getTitle()) && frame.isShowing();
            }
        };

        robot = BasicRobot.robotWithCurrentAwtHierarchy();
        frame = WindowFinder.findFrame(matcher).using(robot);
    }

    @After
    public void shutDown() {
        robot.cleanUp();
    }
}
johnrl
  • 583
  • 6
  • 17
  • I'm not familiar with FEST. It seems from looking at the code that the call to `frame.close()` *should* wait for all of the resulting EDT events to be processed before returning. But can you run your tests with a 5 second sleep between `frame.close()` and `i.uninstall()` to rule out a race condition? – Mark Peters Jan 21 '14 at 15:36
  • Mark, unfortunately it doesn't work with the sleep either. However, I have updated my post with a working solution I discovered by accident. If you can explain why the new solution works I would gladly hear it! – johnrl Jan 21 '14 at 19:53
  • Can you Mock the class that calls System.exit(), perhaps using Mockito? – DwB Jan 21 '14 at 19:59
  • Solution (and reason for the problem) found! Please see my post update. Thanks for the help though! Much appreciated. – johnrl Jan 21 '14 at 20:00
  • Is that code running in the thread your app's specific? I got same trouble but I'm using definitely another way of getting frame – bgplaya Oct 28 '14 at 14:16
  • 1
    The update should be a separate (accepted) answer. Could you fix that, please? – palacsint Jan 17 '16 at 19:38

1 Answers1

1

It's just a guess: you have to install the NoExitSecurityManagerInstaller before you start your thread. See http://docs.codehaus.org/display/FEST/Handling+System.exit

Stefan Birkner
  • 24,059
  • 12
  • 57
  • 72
  • Unfortunately it doesn't work even when I install the secuity manager before starting the thread. However, I have updated my post with a working solution I discovered by accident. If you can explain why the new solution works I would gladly hear it! – johnrl Jan 21 '14 at 19:54