Lonzak
Lonzak

Reputation: 9816

Powermock imageio UnsatisfiedLinkError

After adding powermock (1.5.6 in combination with Easymock 3.2) to my current project (jdk 1.6.0) I get some test failures in test methods which worked perfectly fine before:

java.lang.UnsatisfiedLinkError: com.sun.imageio.plugins.jpeg.JPEGImageReader.initReaderIDs(Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/Class;)V

The following code fails:

BufferedImage img = null;
try {
    img = ImageIO.read(this.getClass().getResourceAsStream("/example.jpg"));
}
catch (IOException e) {
    fail(e.getMessage());
}

The powermock page already has a bug from 2009 but no fix and no workaround. (Going back to 32Bit is nonsense since those methods work without powermock) So does anybody know how to fix this?

Update I: A switch to 32 Bit is no option and besides that this is not the problem. If I don't use PowerMock every test is working perfectly in my 64Bit JVM...

Update II: Ok here are the requested infos

Update III: Extended the class

  1. Class to be tested

    import java.awt.image.BufferedImage;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.security.GeneralSecurityException;
    import java.security.cert.X509Certificate;
    import javax.imageio.ImageIO;
    import sun.security.x509.CertificateIssuerName;
    import sun.security.x509.CertificateSubjectName;
    import sun.security.x509.X500Name;
    import sun.security.x509.X509CertImpl;
    import sun.security.x509.X509CertInfo;
    
    public class App {
        private X509Certificate certificate = null;
    
        public ByteArrayOutputStream readImage() {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        BufferedImage img = null;
    try {
        img = ImageIO.read(this.getClass().getResourceAsStream("/example.jpg"));
        ImageIO.write(img, "png", baos);
    }
    catch (IOException e) {
        e.printStackTrace();
    }
    
    return baos;
    }
    
    public String readCertificate() throws Exception{
     this.certificate = generateCertificate();
     return this.certificate.getIssuerX500Principal().getName();
    }
    
    private static X509Certificate generateCertificate() throws   GeneralSecurityException, IOException{
          X509CertInfo info = new X509CertInfo();
          X500Name owner = new X500Name("CN=example.net");
          info.set(X509CertInfo.SUBJECT, new CertificateSubjectName(owner));
          info.set(X509CertInfo.ISSUER, new CertificateIssuerName(owner));
          return new X509CertImpl(info);
     }   
    }
    
  2. Test case:

    import org.junit.Assert;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.powermock.core.classloader.annotations.PowerMockIgnore;
    import org.powermock.core.classloader.annotations.PrepareForTest;
    import org.powermock.modules.junit4.PowerMockRunner;
    @RunWith(PowerMockRunner.class)
    @PrepareForTest(App.class)
    @PowerMockIgnore("javax.imageio.*, javax.security.*") 
    public class AppTest {
    
     @Test
     public void testApp(){
         App test = new App();
         Assert.assertNotNull(test.readImage());
         Assert.assertEquals(284506, test.readImage().size());
     }
     @Test
     public void testCertificate() throws Exception{
       App test = new App();
       test.readCertificate();
     }
    }
    
  3. Maven dependencies:

    <dependencies>
        <!-- TEST -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.easymock</groupId>
            <artifactId>easymock</artifactId>
            <version>3.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-junit4</artifactId>
            <version>1.5.6</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-easymock</artifactId>
            <version>1.5.6</version>
            <scope>test</scope>
        </dependency>
    

So if you comment the line: //@RunWith(PowerMockRunner.class) it is working. If uncommented it the above error is thrown (again!)

Upvotes: 2

Views: 2516

Answers (1)

Rog&#233;rio
Rog&#233;rio

Reputation: 16390

The solution is to tell PowerMock to ignore all JRE classes which conflict with its custom class loader. That is, add the following annotation to the test class:

@PowerMockIgnore({"javax.imageio.*", "javax.security.*"})

(Note the value attribute of the annotation takes an array of regular expressions; it does not support multiple comma-separated expressions in a single string.)

The explanation for why this is needed is that

  1. PowerMock operates by re-loading the prepared class (and also the test class) in its own custom classloader;
  2. when App calls into javax.imageio.ImageIO, it ends up trying to load & initialize the internal class com.sun.imageio.plugins.jpeg.JPEGImageReader, which then attempts to load some other com.sun.imageio classes from the caller class loader; and
  3. the class load fails because PowerMock's custom class loader apparently can't find those JRE classes (it's hard to tell exactly what happens at this point, because the loading is done by a native method in the JPEGImageReader class - maybe it attempts to load some native library as well).

Upvotes: 10

Related Questions