Eclipse and Asynchronous Unit Testing

I’ve been retro-fitting hundreds of JUnit tests to Archi‘s code base. It’s a Zen meditation, as the process of devising the unit tests ensures that I re-engage with the code, clean things up, refactor here and there, and finally understand what that nifty algorithm does that I wrote 3 years ago. Like children not eating their greens, no-one wants to write unit tests. But it’s a valuable thing to do and rewarding, especially when you see the green bar in Eclipse:

Testing

Perhaps the process of testing and being rewarded by the green bar is an example of “gamification”? Maybe, but I’m not interested in vacuous neologisms, I’d rather just get stuff done.

Some tests that I wrote were failing. The code that these tests was calling depended on events occurring in the UI of the application, such as an editor window opening. The tests were finishing before the editor window opened. Not sure what to do about this I went for a jog to have a think. And then I remembered that I already had the answer in one of my own posts, Xcode and Asynchronous Unit Testing. This has turned out to be quite a popular post in the Xcode and Objective-C community, solving a common problem. So I wondered if Eclipse would be happy with a similar pattern. I’m never sure what’s going to happen when I start playing with threads and dispatch queues.

Here’s my solution, an abstract Java class called “AsyncTestRunner”.

public abstract class AsyncTestRunner implements Runnable {

    boolean waiting = true;

    @Override
    public void run() {
        stop();
    }

    public void start() {
        Display.getCurrent().asyncExec(this);

        while(waiting) {
            if(!Display.getCurrent().readAndDispatch()) {
                Display.getCurrent().sleep();
            }
        }
    }

    public void stop() {
        waiting = false;
    }

}

This class implements Runnable which has one method, run(). This run() method will be called by the current Display instance asynchronously in this line:

Display.getCurrent().asyncExec(this);

Meanwhile the read and dispatch process will go off and do its thing in the event loop:

while(waiting) {
    if(!Display.getCurrent().readAndDispatch()) {
        Display.getCurrent().sleep();
    }
}

This ensures that UI tasks can be completed.

Here’s an example of how we use it:

AsyncTestRunner runner = new AsyncTestRunner() {
    @Override
    public void run() {
        super();

        // Run some code that needs testing in the UI
        Object someThing = ...;
        assertNotNull(someThing);
    }
};

runner.start();

Here, we create a new anonymous instance of AsyncTestRunner and implement run() with the testing code and then call its start() method. Be sure to call super() in the run method to invoke stop() which sets a boolean flag to false which breaks the event loop in the start() method.

The class may seem verbose with its stop() method, you could simply directly set the waiting flag to false, but I wrote it like this so it can be sub-classed and to make it obvious what’s going on. I also like the idea of a run() method consisting of one stop() method.

public void run() {
    stop();
}

Very Zen.

So far it works in both Eclipse 3.8.2 and Eclipse 4.4. I may find a gotcha further down the line, and I may refine the class in the future, but so far it’s looking good. If it can be improved I’d be glad to hear about it.

Bundling a Java Runtime Environment (JRE) with an Eclipse RCP application

I’ve figured out how to bundle the Oracle Java Runtime Environment (JRE) 7 with the Mac version of Archi, an Eclipse-based Rich Client Platform (RCP) application that I developed. There’s no real need to do this at the moment, because the first time an application with a dependency on desktop Java, such as Archi, is run on a Mac, and Java is not currently installed on the system, the user will be informed that the application needs it and Java will be automagically downloaded and installed. This of course ensures that you can easily run your favourite Java-based applications such as Archi, XMind, SmartGit, Eclipse, IDEA and a lot of other useful tools. (Note that Apple only supports JRE 6.)

For Archi I ensure that the user has reasonable choices – on Windows they can either use the forget-about-it installer which includes its own local copy of the JRE (and is only used by Archi and for no other purposes, not even the Browser) or download the manual zip file, which means that the user needs to manually install their own copy of Java. On Linux, the user will probably want to compile the source anyway and knows what type of Java framework they want on their system (probably OpenJDK). But on the Mac, the user has to let the system install Apple’s version of Java (version 6) or, if they prefer to use the latest version 7, they have to manually download and install the JRE from Oracle’s website. This is a bit of a pain. I tried this myself and could only get the JDK to work, not the JRE. Whilst Lion, Mountain Lion and Mavericks OS X will install JRE 6, this might not always be the case in future version of OS X.

The advantages of bundling a local copy of the JRE with Archi are:

  • The user doesn’t have to worry about installing Java (or even care that the application requires it)
  • The JRE is local and is only used by the application and is therefore “sandboxed”
  • It isn’t installed as an extension in the Browser (this is the real vector for trojans and virii)
  • When the user deletes the application off their system, they also delete the local JRE – an instant complete uninstall

Disadvantages:

  • The download size and application footprint is bigger (adds about another 140mb or so when unpacked)
  • Each Java based application will have its own copy of the JRE when only one system-wide copy is necessary, so you could end up with some disk bloat
  • …can’t think of any more 🙂

So, how do we bundle a copy of the the Mac JRE 7 with Archi, or any Eclipse-based RCP application for that matter?

I already do this for the Windows version of Archi by simply copying the “jre” folder of the official JRE (with its “lib” and “bin” sub-directories) and putting it at the root level of the Archi installation. This procedure works for any other Eclipse-based application, including Eclipse itself.

On OS X this has only been possible since Oracle’s later versions of JRE 7, and later versions of Eclipse itself. The same principle applies on a Mac as for Windows – include the JRE in a “jre” folder at the root level of the application. Of course, as Archi is delivered as a self-contained application bundle on Mac (Archi.app) the “jre” folder sits inside the Archi.app bundle at the same level as the other folders:

Archi.app
   |______ configuration
   |______ Contents
   |______ plugins
   |______ jre

So, how do we make a re-distributable copy of the JRE to add to the application bundle? The only way I could figure out how to do this was to firstly install the JDK onto a Mac and then make a copy of some sub-folders and files:

  1. Install Oracle’s JDK 7 on a Mac (not the JRE)
  2. Copy the “/Library/Java/JavaVirtualMachines/jdk1.7.0_xx.jdk” folder and rename the copy  to “jre” (xx = the two-digit version number of the JDK)
  3. Delete everything in the copy’s “Contents/Home” sub-folder except for the “jre” sub-folder

You end up with a slimmed-down JRE with this folder structure:

jre
   |______ Contents
            |______ Home
                     |______ jre
            |______ Info.plist
            |______ MacOS

This “Jre” folder then needs to be added to the Archi.app bundle.

Note – as I use a Windows build machine and an Ant script to create the installation archives for Archi I found that some files in the JRE folder lost their executable bit and so some files didn’t work. To get around this I simply zipped up the copied JRE folder on a Mac and used this zip file as the source of the Mac JRE, so that the Ant script simply copies the zip file’s content to the overall target installation archive, preserving file attributes (including an “alias” type file).

I haven’t rolled this out yet, but might do so for a future version of Archi. It can only be the 64-bit version, though, as Oracle’s JRE 7 only supports 64-bit.

Update – some people have emailed to ask how I get the Archi Eclipse application into an Archi.app application bundle in the first place. By default, an Eclipse product export doesn’t do this, so I’ve written an Ant script that moves the “plugins” and “configuration” folders down one level into the .app bundle, moves the “Archi” executable launcher file down a level into the Archi.app bundke, and modifies the Info.plist file to adjust the path to the launcher file. The folder structure inside the app bundle looks like this:

archi foldersThe Info.plist file is modified to set the launcher path:

CFBundleExecutable 
launcher/Archi

Begin typing your search term above and press enter to search. Press ESC to cancel.