简体   繁体   中英

TestNG tests execution against JDK 9 module causes InaccessibleObjectException

I'm trying to transform the following library into Java 9 module: https://github.com/sskorol/test-data-supplier

Followed this guide: https://guides.gradle.org/building-java-9-modules

After some manipulations and refactoring (couldn't manage lombok issues, so just temporary removed it), I have the following module-info.java :

module io.github.sskorol {
    exports io.github.sskorol.core;
    exports io.github.sskorol.model;

    requires testng;
    requires vavr;
    requires streamex;
    requires joor;
    requires aspectjrt;
}

And it even compiles / builds in case of tests' skipping. However, when I try to run a test task, I'm getting the following exception:

org.gradle.api.internal.tasks.testing.TestSuiteExecutionException: Could not complete execution for Gradle Test Executor 2.
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:63)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
    at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
    at com.sun.proxy.$Proxy1.stop(Unknown Source)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.stop(TestWorker.java:120)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:146)
    at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:128)
    at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:404)
    at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
    at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
    at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55)
    at java.base/java.lang.Thread.run(Thread.java:844)
Caused by: org.testng.TestNGException: 
Cannot instantiate class io.github.sskorol.testcases.DataSupplierTests
    at testng@6.11/org.testng.internal.ObjectFactoryImpl.newInstance(ObjectFactoryImpl.java:31)
    at testng@6.11/org.testng.internal.ClassHelper.createInstance1(ClassHelper.java:410)
    at testng@6.11/org.testng.internal.ClassHelper.createInstance(ClassHelper.java:323)
    at testng@6.11/org.testng.internal.ClassImpl.getDefaultInstance(ClassImpl.java:126)
    at testng@6.11/org.testng.internal.ClassImpl.getInstances(ClassImpl.java:191)
    at testng@6.11/org.testng.TestClass.getInstances(TestClass.java:99)
    at testng@6.11/org.testng.TestClass.initTestClassesAndInstances(TestClass.java:85)
    at testng@6.11/org.testng.TestClass.init(TestClass.java:77)
    at testng@6.11/org.testng.TestClass.<init>(TestClass.java:42)
    at testng@6.11/org.testng.TestRunner.initMethods(TestRunner.java:423)
    at testng@6.11/org.testng.TestRunner.init(TestRunner.java:250)
    at testng@6.11/org.testng.TestRunner.init(TestRunner.java:220)
    at testng@6.11/org.testng.TestRunner.<init>(TestRunner.java:161)
    at testng@6.11/org.testng.SuiteRunner$DefaultTestRunnerFactory.newTestRunner(SuiteRunner.java:578)
    at testng@6.11/org.testng.SuiteRunner.init(SuiteRunner.java:185)
    at testng@6.11/org.testng.SuiteRunner.<init>(SuiteRunner.java:131)
    at testng@6.11/org.testng.TestNG.createSuiteRunner(TestNG.java:1383)
    at testng@6.11/org.testng.TestNG.createSuiteRunners(TestNG.java:1363)
    at testng@6.11/org.testng.TestNG.runSuitesLocally(TestNG.java:1217)
    at testng@6.11/org.testng.TestNG.runSuites(TestNG.java:1144)
    at testng@6.11/org.testng.TestNG.run(TestNG.java:1115)
    at org.gradle.api.internal.tasks.testing.testng.TestNGTestClassProcessor.runTests(TestNGTestClassProcessor.java:129)
    at org.gradle.api.internal.tasks.testing.testng.TestNGTestClassProcessor.stop(TestNGTestClassProcessor.java:88)
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
    ... 25 more
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make public io.github.sskorol.testcases.DataSupplierTests() accessible: module io.github.sskorol does not "exports io.github.sskorol.testcases" to module testng
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:337)
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:281)
    at java.base/java.lang.reflect.Constructor.checkCanSetAccessible(Constructor.java:192)
    at java.base/java.lang.reflect.Constructor.setAccessible(Constructor.java:185)
    at testng@6.11/org.testng.internal.ObjectFactoryImpl.newInstance(ObjectFactoryImpl.java:22)
    ... 48 more

It seems a bit confusing to me, as io.github.sskorol.testcases is a part of src/test/java and there's no module-info for tests. So I can't export this package to TestNG. Have an assumption that the root cause in a TestNG reflection usage within ObjectFactoryImpl against test classes.

Does anyone have any idea how to workaround it?

Environment: JDK 9 (build 9+181), Gradle 4.1, TestNG 6.11

Have an assumption that the root cause in a TestNG reflection usage within ObjectFactoryImpl against test classes.

It's one of two causes, yes. The other is that, apparently, Gradle runs your tests as a module. As you point out, there's no module descriptor for your tests. Gradle may use --patch-module to add the tests to the module containing the production code.

This question and answer provides a lot of background information and possible fixes. As a short term fix, I recommend to add opens io.github.sskorol.testcases to your production code's module descriptor. Judging by its name, I'd guess there is no such package yet, so you'd either have to rename or add a dummy class (I would prefer the former).

I would also take this issue to a Gradle mailing list or bug tracker. Unless we've overlooked something (entirely possible), Gradle's behavior is very unfortunate because it would require adapting the production code's module descriptor to the test code's needs.

If the tests are in the same package as the module under test then they need to be compiled (with --patch-module ) so that they are compiled "as if" they are part of the module. The references to TestNG types in the tests means that that --add-reads io.github.sskorol=testng will be needed too.

Running is similar. The tests need to be run "as if" they are in module io.github.sskorol . This means running with:

--patch-module io.github.sskorol=<testclasses> \
--add-reads io.github.sskorol=testng

Additionally, you may need to export or open the packages with the tests to TestNG. This will come down to whether the test classes and methods are public in an exported package. To avoid scanning, the simplest is to open the all packages containing tests to TestNG, eg

--add-opens io.github.sskorol/io.github.sskorol.core=testng \
--add-opens io.github.sskorol/io.github.sskorol.core.internal=testng

( .internal is just a filler for a non-exported package in the module)

All this might look complicated but it's something that the Maven Surefire plugin and also Gradle should do.

Did some tricks suggested by @AlanBateman, who gave me a valid direction.

Finally, I came up with the following configuration:

module-info.java

module io.github.sskorol {
    exports io.github.sskorol.core;
    exports io.github.sskorol.model;

    opens io.github.sskorol.utils to joor;

    requires testng;
    requires vavr;
    requires streamex;
    requires joor;
}

build.gradle

test {
    inputs.property("moduleName", moduleName)
    doFirst {
        jvmArgs = [
                '--module-path', classpath.asPath,
                '--add-modules', 'ALL-MODULE-PATH',
                '--add-opens', 'io.github.sskorol/io.github.sskorol.testcases=testng',
                '--add-opens', 'io.github.sskorol/io.github.sskorol.testcases=joor',
                '--add-opens', 'io.github.sskorol/io.github.sskorol.datasuppliers=joor',
                '--add-opens', 'io.github.sskorol/io.github.sskorol.datasuppliers=testng',
                '--add-opens', 'java.base/java.util=streamex',
                '--add-opens', 'java.base/java.util.stream=streamex',
                '--patch-module', "$moduleName=" + files(sourceSets.test.java.outputDir).asPath
        ]
        classpath = files()
    }
}

Both testng and joor required access to my test packages. So --add-opens flag did the trick. streamex module also had concerns accessing java.base packages.

Note that in comparison with original Java 8 code I had to remove lombok and aspectj dependencies, as I couldn't manage to fully resolve all the issues occurred after migration.

Another problem I've faced with was related to SPI testing. According to docs I've read, in Java 9 SPI implementations should be listed within module-info.java instead of META-INF/services . But it doesn't seem to be a solution, when implementation class is located in one of test packages, as again test is not a module. Just wondering if there's some jvmflag for replacing provides ... with ... syntax, and doing the same trick as with --add-opens . Any thoughts would be appreciated.

Full implementation, modified to support Java 9, could be found here .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM