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();
}
}