robolectric / robolectric

Android Unit Testing Framework
http://robolectric.org
Other
5.89k stars 1.37k forks source link

StAX and Aalto in same project #3269

Open rosstc opened 7 years ago

rosstc commented 7 years ago

Description

We have come across an issue which causes exceptions during unit tests that utilise the Aalto extension of StAX since the 3.3.2 release of roboletric. When running tests with the RoboletricTestRunner, we get the following exception:

javax.xml.stream.FactoryConfigurationError: Provider for class javax.xml.stream.XMLInputFactory cannot be created
    at javax.xml.stream.FactoryFinder.findServiceProvider(FactoryFinder.java:370)
    at javax.xml.stream.FactoryFinder.find(FactoryFinder.java:313)
    at javax.xml.stream.FactoryFinder.find(FactoryFinder.java:227)
    at javax.xml.stream.XMLInputFactory.newFactory(XMLInputFactory.java:205)
    at org.robolectric.res.StaxDocumentLoader.<init>(StaxDocumentLoader.java:20)
    at org.robolectric.res.ResourceTableFactory.parseResourceFiles(ResourceTableFactory.java:113)
    at org.robolectric.res.ResourceTableFactory.newFrameworkResourceTable(ResourceTableFactory.java:24)
    at org.robolectric.internal.SdkEnvironment.getSystemResourceTable(SdkEnvironment.java:23)
    at org.robolectric.RobolectricTestRunner.beforeTest(RobolectricTestRunner.java:287)
    at org.robolectric.internal.SandboxTestRunner$2.evaluate(SandboxTestRunner.java:203)
    at org.robolectric.internal.SandboxTestRunner.runChild(SandboxTestRunner.java:109)
    at org.robolectric.internal.SandboxTestRunner.runChild(SandboxTestRunner.java:36)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.robolectric.internal.SandboxTestRunner$1.evaluate(SandboxTestRunner.java:63)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:117)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:262)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:84)
    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:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Caused by: java.lang.RuntimeException: Provider for class javax.xml.stream.XMLInputFactory cannot be created
    at javax.xml.stream.FactoryFinder.findServiceProvider(FactoryFinder.java:367)
    ... 28 more
Caused by: java.util.ServiceConfigurationError: javax.xml.stream.XMLInputFactory: Provider com.fasterxml.aalto.stax.InputFactoryImpl not a subtype
    at java.util.ServiceLoader.fail(ServiceLoader.java:239)
    at java.util.ServiceLoader.access$300(ServiceLoader.java:185)
    at java.util.ServiceLoader$LazyIterator.nextService(ServiceLoader.java:376)
    at java.util.ServiceLoader$LazyIterator.next(ServiceLoader.java:404)
    at java.util.ServiceLoader$1.next(ServiceLoader.java:480)
    at javax.xml.stream.FactoryFinder$1.run(FactoryFinder.java:353)
    at java.security.AccessController.doPrivileged(Native Method)
    at javax.xml.stream.FactoryFinder.findServiceProvider(FactoryFinder.java:341)
    ... 28 more

After some investigation on this issue I concluded that the Aalto concrete implementation of XMLInputFactory simply couldn't be found, so I created a custom Test Runner subclassing RoboletricTestRunner and set the javax.xml.stream.XMLInputFactory system property to com.fasterxml.aalto.stax.InputFactoryImpl like such:

public MyTestRunner(Class<?> testClass) throws InitializationError {
        super(testClass);
        System.setProperty("javax.xml.stream.XMLInputFactory", "com.fasterxml.aalto.stax.InputFactoryImpl");
    }

While this resolved the first exception, it created another:

javax.xml.stream.FactoryConfigurationError: Provider com.fasterxml.aalto.stax.InputFactoryImpl could not be instantiated: java.lang.ClassCastException: com.fasterxml.aalto.stax.InputFactoryImpl cannot be cast to javax.xml.stream.XMLInputFactory

    at javax.xml.stream.FactoryFinder.newInstance(FactoryFinder.java:205)
    at javax.xml.stream.FactoryFinder.newInstance(FactoryFinder.java:152)
    at javax.xml.stream.FactoryFinder.find(FactoryFinder.java:265)
    at javax.xml.stream.FactoryFinder.find(FactoryFinder.java:227)
    at javax.xml.stream.XMLInputFactory.newFactory(XMLInputFactory.java:205)
    at org.robolectric.res.StaxDocumentLoader.<init>(StaxDocumentLoader.java:20)
    at org.robolectric.res.ResourceTableFactory.parseResourceFiles(ResourceTableFactory.java:113)
    at org.robolectric.res.ResourceTableFactory.newFrameworkResourceTable(ResourceTableFactory.java:24)
    at org.robolectric.internal.SdkEnvironment.getSystemResourceTable(SdkEnvironment.java:23)
    at org.robolectric.RobolectricTestRunner.beforeTest(RobolectricTestRunner.java:287)
    at org.robolectric.internal.SandboxTestRunner$2.evaluate(SandboxTestRunner.java:203)
    at org.robolectric.internal.SandboxTestRunner.runChild(SandboxTestRunner.java:109)
    at org.robolectric.internal.SandboxTestRunner.runChild(SandboxTestRunner.java:36)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.robolectric.internal.SandboxTestRunner$1.evaluate(SandboxTestRunner.java:63)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:117)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:262)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:84)
    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:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Caused by: java.lang.ClassCastException: com.fasterxml.aalto.stax.InputFactoryImpl cannot be cast to javax.xml.stream.XMLInputFactory
    at javax.xml.stream.FactoryFinder.newInstance(FactoryFinder.java:191)
    ... 29 more

InputFactoryImpl does extend by way of a few a classes the XMLInputFactory class, so the exception isn't because of an actual casting issue. Upon further investigation it's lead me to believe that this might be a Class Loader issue. It's my understanding Roboletric uses its own class loader, and that if this is not loading the Aalto extension/implementation of StAX then the classes are not visible to it at runtime, hence the exception when trying to use the InputFactoryImpl class.

Is it possible to somehow force Roboletric's class loader to load all of the dependencies in the depedencies closure in build.gradle so that all classes are visible during testing?

Steps to Reproduce

Our project is a little more complex so haven't split out into a unique project replicating the issue but the basic gist is to do the following:

Create an Android project with the following compile dependencies: aalto-xml-android:0.9.8 jackson-dataformat-xml-android:2.2.3 stax-api-android:1.0 stax2-api-android:3.1.1

and testCompile ('org.robolectric:robolectric:3.3.2')

Create a test class using RoboletricTestRunner and run a test.

Robolectric & Android Version

Roboletric 3.3.2

peterhav commented 7 years ago

I'm running into this same issue. In case more information is required that could help solve this issue please let me know! The output of my (first Roboelectric test) is as follows:

WARNING: No manifest file found at .\AndroidManifest.xml.
Falling back to the Android OS resources only.
To remove this warning, annotate your test class with @Config(manifest=Config.NONE).
javax.xml.stream.FactoryConfigurationError: Provider for class javax.xml.stream.XMLInputFactory cannot be created

    at javax.xml.stream.FactoryFinder.findServiceProvider(FactoryFinder.java:370)
    at javax.xml.stream.FactoryFinder.find(FactoryFinder.java:313)
    at javax.xml.stream.FactoryFinder.find(FactoryFinder.java:227)
    at javax.xml.stream.XMLInputFactory.newFactory(XMLInputFactory.java:205)
    at org.robolectric.res.StaxDocumentLoader.<init>(StaxDocumentLoader.java:20)
    at org.robolectric.res.ResourceTableFactory.parseResourceFiles(ResourceTableFactory.java:113)
    at org.robolectric.res.ResourceTableFactory.newFrameworkResourceTable(ResourceTableFactory.java:24)
    at org.robolectric.internal.SdkEnvironment.getSystemResourceTable(SdkEnvironment.java:23)
    at org.robolectric.RobolectricTestRunner.beforeTest(RobolectricTestRunner.java:287)
    at org.robolectric.internal.SandboxTestRunner$2.evaluate(SandboxTestRunner.java:203)
    at org.robolectric.internal.SandboxTestRunner.runChild(SandboxTestRunner.java:109)
    at org.robolectric.internal.SandboxTestRunner.runChild(SandboxTestRunner.java:36)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.robolectric.internal.SandboxTestRunner$1.evaluate(SandboxTestRunner.java:63)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
    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:498)
    at com.intellij.rt.execution.application.AppMainV2.main(AppMainV2.java:131)
Caused by: java.lang.RuntimeException: Provider for class javax.xml.stream.XMLInputFactory cannot be created
    at javax.xml.stream.FactoryFinder.findServiceProvider(FactoryFinder.java:367)
    ... 28 more
Caused by: java.util.ServiceConfigurationError: javax.xml.stream.XMLInputFactory: Provider com.fasterxml.aalto.stax.InputFactoryImpl not a subtype
    at java.util.ServiceLoader.fail(ServiceLoader.java:239)
    at java.util.ServiceLoader.access$300(ServiceLoader.java:185)
    at java.util.ServiceLoader$LazyIterator.nextService(ServiceLoader.java:376)
    at java.util.ServiceLoader$LazyIterator.next(ServiceLoader.java:404)
    at java.util.ServiceLoader$1.next(ServiceLoader.java:480)
    at javax.xml.stream.FactoryFinder$1.run(FactoryFinder.java:353)
    at java.security.AccessController.doPrivileged(Native Method)
    at javax.xml.stream.FactoryFinder.findServiceProvider(FactoryFinder.java:341)
    ... 28 more

Process finished with exit code -1
hoisie commented 7 years ago

What happens if you use System.setProperty("javax.xml.stream.XMLInputFactory", "com.sun.xml.internal.stream.XMLInputFactoryImpl")? Will it break your application code?

peterhav commented 7 years ago

I had these calls my code before:

System.setProperty("org.apache.poi.javax.xml.stream.XMLInputFactory", "com.fasterxml.aalto.stax.InputFactoryImpl");
System.setProperty("org.apache.poi.javax.xml.stream.XMLOutputFactory", "com.fasterxml.aalto.stax.OutputFactoryImpl");
System.setProperty("org.apache.poi.javax.xml.stream.XMLEventFactory", "com.fasterxml.aalto.stax.EventFactoryImpl");

When I change this to:

System.setProperty("org.apache.poi.javax.xml.stream.XMLInputFactory", "com.sun.xml.internal.stream.XMLInputFactoryImpl");
System.setProperty("org.apache.poi.javax.xml.stream.XMLOutputFactory", "com.sun.xml.internal.stream.XMLOutputFactoryImpl");
System.setProperty("org.apache.poi.javax.xml.stream.XMLEventFactory", "com.fasterxml.aalto.stax.EventFactoryImpl");

My application still runs fine, but I do get the following exception when running the Roboelectric test:

javax.xml.stream.FactoryConfigurationError: Provider for class javax.xml.stream.XMLInputFactory cannot be created

When I change my code to:

System.setProperty("org.apache.poi.javax.xml.stream.XMLInputFactory", "com.sun.xml.internal.stream.XMLInputFactoryImpl");
System.setProperty("org.apache.poi.javax.xml.stream.XMLOutputFactory", "com.sun.xml.internal.stream.XMLOutputFactoryImpl");
System.setProperty("org.apache.poi.javax.xml.stream.XMLEventFactory", "com.sun.xml.internal.stream.events.XMLEventFactoryImpl");

My application does no longer work and crashes with a:

org.apache.poi.javax.xml.stream.FactoryConfigurationError: Provider com.sun.xml.internal.stream.events.XMLEventFactoryImpl not found

ghost commented 6 years ago

the point is wrong , when start it on android ,i found at javax.xml.stream.FactoryFinder.findServiceProvider(FactoryFinder.java:370) at javax.xml.stream.FactoryFinder.find(FactoryFinder.java:313) at javax.xml.stream.FactoryFinder.find(FactoryFinder.java:227) this code call the wrong classloader , so the class cant be found ,the right loader is PathClassloader , wrong classloader is LoadedApk.WarningContextClassloader . but i dont konw how to slove

you can try change the thread.classloader by Threan.CurrentThread().setClassLoader(this new)