Ops, ImageJ1 and unit tests

Hi everyone,

I’m trying to call a static method in my Op class, from a @Test method in my JUnit test class which operates with ImagePlus & other ImageJ1 stuff. The Op class also operates with ImagePlus. The situation is as follows:

public class MyOp impelements Op
{
    static {
        LegacyInjector.preinit();
    }

    public static ImagePlus myMethod() {
        ImagePlus image;
        ...
        return image;
    }
}

public class MyTestClass {
    @Test
    public void myTest() throws Exception {
        ImagePlus testImage = MyOp.myMethod();
    }
}

Now the call to myMethod() from myTest() causes the following Exception:

java.lang.ExceptionInInitializerError
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:117)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: java.lang.RuntimeException: Found incompatible ij.IJ class
	at net.imagej.patcher.LegacyEnvironment.initialize(LegacyEnvironment.java:113)
	at net.imagej.patcher.LegacyEnvironment.applyPatches(LegacyEnvironment.java:495)
	at net.imagej.patcher.LegacyInjector.preinit(LegacyInjector.java:397)
	at net.imagej.patcher.LegacyInjector.preinit(LegacyInjector.java:376)
	at org.bonej.common.RoiUtilTest.<clinit>(RoiUtilTest.java:30)
	... 19 more
Caused by: java.lang.RuntimeException: Cannot load class: ij.ImagePlus (loader: sun.misc.Launcher$AppClassLoader@33909752)
It appears that this class was already defined in the class loader!
Please make sure that you initialize the LegacyService before using
any ImageJ 1.x class. You can do that by adding this static initializer:

	static {
		LegacyInjector.preinit();
	}

Removing the static initializer from MyOp class solves the test issue, but then the Op crashes with the same exception as above every time it’s run. Adding similar static initializer to MyTestClass causes every test to fail with that exception instead of just myTest(). I also tried adding a Context to the class like in AbstractOpTest, but that didn’t help either.

Yes, this is expected if you use ImageJ 1.x classes in your API. In this case, you have a static method myMethod that returns an ij.ImagePlus, which means the ij.ImagePlus class must be loaded as a prerequisite to your MyOp class. So your static initializer happens too late!

There are a couple of different workarounds:

  • Make myMethod return Object, then cast it in any calling code.
  • Make MyOp call a helper class which has your static method. This is what we do everywhere in imagej-legacy: you’ll notice the IJ1Helper method is the only class (with very few exceptions, anyway) allowed to use IJ1 classes in its API. And then it is the responsibility of any calling classes to call LegacyInjector.preinit() in their own static initializer to ensure IJ1Helper uses the correct (patched) versions of ImageJ1 classes.

It’s all horribly restrictive, but that’s the price we pay for using Javassist at runtime the way we do. One way we have wanted to address this for quite some time is to update the ImageJA build system to write out an artifact with “patched” classifier, that already has all the Javassist patches applied. That way, we will not have to hot-patch at runtime. This would be much more robust for ImageJ2 in some ways—but it would then prevent users from dropping in replacement ij JAR files as they wish. We would have to replace the Help :arrow_forward: Update ImageJ… command with one that downloads the patched version of each ij JAR file, rather than the original one.

Thanks @ctrueden,

I got around the problem by using the second workaround, i.e. what I now have is:

public class MyOp implements Op
{
    private Dataset myMethod() {
        ImagePlus imagePlus = StaticTestImageHelper.staticMethod();
        final ImgPlus<UnsignedByteType> image = ImagePlusAdapter.wrapImgPlus(imagePlus);
        return datasetService.create(image);
    }
}

public class MyTestClass 
{
    @Test
    public void myTest() throws Exception {
        ImagePlus testImage = StaticTestImageHelper.staticMethod();
    }
}