Junit5: Test against JDK 9 modules

Created on 23 Jul 2016  ·  21Comments  ·  Source: junit-team/junit5

Status Quo

JUnit 5 currently builds and runs against JDK 9 early access builds (including Jigsaw builds), but we do not yet have any tests in place that run against user code built with module info.

Related Pull Requests

  • PR #1061 Part I - Introduce ModuleUtils with ClassFinder SPI
  • PR #1057 Part II - Add module org.junit.platform.commons.jpms

Related Issues

  • #296
  • #600
  • #775

Further Resources

Deliverables

  • [x] Integration test JUnit 5 against sample applications that supply JDK 9 module info.
  • [x] Test module-path scanning within named modules.
  • [x] Test module-path scanning within the unnamed module.
  • [x] Test module-path scanning with _exploded_ modules.
  • [x] Test the use of reflection to instantiate user classes that are loaded from named modules.
  • [x] Test the use of reflection to instantiate user classes that are loaded from the unnamed module.
  • [x] Test support for clazz.getPackage().getImplementationVersion() and related methods for retrieving JAR versioning metadata for classes loaded from named modules.
  • [x] Provide java[c] usage example with --patch-module module=file(;file)*
Java 9+10+11... build task

Most helpful comment

@sbrannen FWIW I managed to get my gradle working with more of a hack than anything haha

Because Gradle launches these separate applications that use java, it wasn't enough just to add the arguments to JAVA_OPTIONS. Instead I moved $JAVA_HOME/bin/java to $JAVA_HOME/bin/java-real and created a new script in its place. This new script adds all of my options to java before it launches:

#!/bin/sh
# Usage: $JAVA_HOME/bin/java <args>
exec $JAVA_HOME/bin/java-real \
  -Dsun.reflect.debugModuleAccessChecks=true \
  --add-opens=java.base/java.lang=ALL-UNNAMED \
  --add-opens=java.base/java.util=ALL-UNNAMED \
  --add-opens=java.base/java.lang.invoke=ALL-UNNAMED \
  --add-opens=java.base/java.lang.reflect=ALL-UNNAMED \
  --add-opens=java.base/java.io=ALL-UNNAMED \
  --add-opens=java.base/java.util.concurrent=ALL-UNNAMED \
  --add-opens=java.base/java.text=ALL-UNNAMED \
  "$@"

You may need more or less items depending on whats being compiled. After that I also added it to org.gradle.jvmargs in my gradle.properties file!

org.gradle.jvmargs=-Dsun.reflect.debugModuleAccessChecks=true \
    --add-opens=java.base/java.lang=ALL-UNNAMED \
    --add-opens=java.base/java.util=ALL-UNNAMED \
    --add-opens=java.base/java.lang.invoke=ALL-UNNAMED \
    --add-opens=java.base/java.lang.reflect=ALL-UNNAMED \
    --add-opens=java.base/java.io=ALL-UNNAMED \
    --add-opens=java.base/java.util.concurrent=ALL-UNNAMED \
    --add-opens=java.base/java.text=ALL-UNNAMED 

Also dont forget to update sourceCompatibility = JavaVersion.VERSION_1_9 .. And here's my version info!

ubuntu@ubuntu-xenial:~/workspace$ java -version
java version "9-ea"
Java(TM) SE Runtime Environment (build 9-ea+157)
Java HotSpot(TM) 64-Bit Server VM (build 9-ea+157, mixed mode)


ubuntu@ubuntu-xenial:~/workspace$ gradle --version

------------------------------------------------------------
Gradle 3.4
------------------------------------------------------------

Build time:   2017-02-20 14:49:26 UTC
Revision:     73f32d68824582945f5ac1810600e8d87794c3d4

Groovy:       2.4.7
Ant:          Apache Ant(TM) version 1.9.6 compiled on June 29 2015
JVM:          9-ea (Oracle Corporation 9-ea+157)
OS:           Linux 4.4.0-64-generic amd64

All 21 comments

Hi JUnit team,

Is there a plan to add module-info to junit5 or even better to junit4?

I have a problem using junit4 as unnamed module when testing jdk module, e.g. jaxb-api. It might need to be fixed in JDK though, thus just a question above.

Thank you.

Is there a plan to add module-info to junit5 or even better to junit4?

Regarding JUnit 4, I am not aware of any intent to provide explicit JDK 9 support (even in the form of module-info), especially since JUnit 4 is based on JDK 5. However, perhaps @kcooney or @marcphilipp can better answer that.

Regarding JUnit 5, we are not currently planning to include module-info in JUnit 5 JARs during the 5.0 GA timeline. However, we will keep this on our radar and consider adding explicit Jigsaw module support in later versions.

@avalez we don't want JUnit 4 to require JDK 9, so JUnit 4 will not have a module-info

Thank you for the information!

UPDATE

We cannot even test now with Gradle. 😢

https://github.com/gradle/gradle/issues/723

Attempting to build JUnit 5 against JDK 9-ea+149 results in the following:

java.lang.ExceptionInInitializerError
        at org.gradle.initialization.DefaultClassLoaderRegistry.restrictTo(DefaultClassLoaderRegistry.java:40)
        at org.gradle.initialization.DefaultClassLoaderRegistry.restrictToGradleApi(DefaultClassLoaderRegistry.java:36)
        at org.gradle.initialization.DefaultClassLoaderRegistry.<init>(DefaultClassLoaderRegistry.java:30)
        at org.gradle.internal.service.scopes.GlobalScopeServices.createClassLoaderRegistry(GlobalScopeServices.java:224)
        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:538)
        at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:73)
        at org.gradle.internal.service.DefaultServiceRegistry.invoke(DefaultServiceRegistry.java:462)
        at org.gradle.internal.service.DefaultServiceRegistry.access$1200(DefaultServiceRegistry.java:84)
        at org.gradle.internal.service.DefaultServiceRegistry$FactoryMethodService.invokeMethod(DefaultServiceRegistry.java:796)
        at org.gradle.internal.service.DefaultServiceRegistry$FactoryService.create(DefaultServiceRegistry.java:752)
        at org.gradle.internal.service.DefaultServiceRegistry$ManagedObjectProvider.getInstance(DefaultServiceRegistry.java:589)
        at org.gradle.internal.service.DefaultServiceRegistry$SingletonService.get(DefaultServiceRegistry.java:634)
        at org.gradle.internal.service.DefaultServiceRegistry.applyConfigureMethod(DefaultServiceRegistry.java:253)
        at org.gradle.internal.service.DefaultServiceRegistry.findProviderMethods(DefaultServiceRegistry.java:214)
        at org.gradle.internal.service.DefaultServiceRegistry.addProvider(DefaultServiceRegistry.java:352)
        at org.gradle.internal.service.ServiceRegistryBuilder.build(ServiceRegistryBuilder.java:52)
        at org.gradle.launcher.cli.BuildActionsFactory.createGlobalClientServices(BuildActionsFactory.java:148)
        at org.gradle.launcher.cli.BuildActionsFactory.runBuildInSingleUseDaemon(BuildActionsFactory.java:141)
        at org.gradle.launcher.cli.BuildActionsFactory.createAction(BuildActionsFactory.java:89)
        at org.gradle.launcher.cli.CommandLineActionFactory$ParseAndBuildAction.createAction(CommandLineActionFactory.java:249)
        at org.gradle.launcher.cli.CommandLineActionFactory$ParseAndBuildAction.execute(CommandLineActionFactory.java:239)
        at org.gradle.launcher.cli.CommandLineActionFactory$ParseAndBuildAction.execute(CommandLineActionFactory.java:217)
        at org.gradle.launcher.cli.JavaRuntimeValidationAction.execute(JavaRuntimeValidationAction.java:33)
        at org.gradle.launcher.cli.JavaRuntimeValidationAction.execute(JavaRuntimeValidationAction.java:24)
        at org.gradle.launcher.cli.ExceptionReportingAction.execute(ExceptionReportingAction.java:33)
        at org.gradle.launcher.cli.ExceptionReportingAction.execute(ExceptionReportingAction.java:22)
        at org.gradle.launcher.cli.CommandLineActionFactory$WithLogging.execute(CommandLineActionFactory.java:210)
        at org.gradle.launcher.cli.CommandLineActionFactory$WithLogging.execute(CommandLineActionFactory.java:174)
        at org.gradle.launcher.Main.doAction(Main.java:33)
        at org.gradle.launcher.bootstrap.EntryPoint.run(EntryPoint.java:45)
        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:538)
        at org.gradle.launcher.bootstrap.ProcessBootstrap.runNoExit(ProcessBootstrap.java:60)
        at org.gradle.launcher.bootstrap.ProcessBootstrap.run(ProcessBootstrap.java:37)
        at org.gradle.launcher.GradleMain.main(GradleMain.java:23)
        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:538)
        at org.gradle.wrapper.BootstrapMainStarter.start(BootstrapMainStarter.java:31)
        at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:108)
        at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make protected java.lang.Package[] java.lang.ClassLoader.getPackages() accessible: module java.base does not "opens java.lang" to unnamed module @176b75f7
        at java.base/jdk.internal.reflect.Reflection.throwInaccessibleObjectException(Reflection.java:427)
        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:201)
        at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:192)
        at java.base/java.lang.reflect.Method.setAccessible(Method.java:186)
        at org.gradle.internal.reflect.JavaMethod.<init>(JavaMethod.java:42)
        at org.gradle.internal.reflect.JavaMethod.<init>(JavaMethod.java:32)
        at org.gradle.internal.reflect.JavaMethod.<init>(JavaMethod.java:36)
        at org.gradle.internal.reflect.JavaReflectionUtil.method(JavaReflectionUtil.java:223)
        at org.gradle.internal.classloader.FilteringClassLoader.<clinit>(FilteringClassLoader.java:49)
        ... 47 more

NOTE: this happens even if you add the following to ~/.gradle/gradle.properties.

org.gradle.jvmargs=--add-opens java.base/java.lang=ALL-UNNAMED -Dsun.reflect.debugModuleAccessChecks=true

UPDATE:

The following gets us one step closer.

export _JAVA_OPTIONS="--add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED"

But even with that, we now run into the following issue with Ruby regarding java.util.

net.rubygrapefruit.platform.NativeException: Unable to get mutable environment variable map.
        at net.rubygrapefruit.platform.internal.WrapperProcess.getEnv(WrapperProcess.java:113)
        at net.rubygrapefruit.platform.internal.WrapperProcess.removeEnvInternal(WrapperProcess.java:91)
        at net.rubygrapefruit.platform.internal.WrapperProcess.setEnvironmentVariable(WrapperProcess.java:82)
        at org.gradle.internal.nativeintegration.processenvironment.NativePlatformBackedProcessEnvironment.removeNativeEnvironmentVariable(NativePlatformBackedProcessEnvironment.java:31)
        at org.gradle.internal.nativeintegration.processenvironment.AbstractProcessEnvironment.removeEnvironmentVariable(AbstractProcessEnvironment.java:47)
        at org.gradle.internal.nativeintegration.processenvironment.AbstractProcessEnvironment.maybeSetEnvironment(AbstractProcessEnvironment.java:37)
        at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:65)
        at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
        at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:50)
        at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:297)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)
        at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1161)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
        at java.base/java.lang.Thread.run(Thread.java:844)
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.util.Map java.util.Collections$UnmodifiableMap.m accessible: module java.base does not "opens java.util" to unnamed module @e15b7e8
        at java.base/jdk.internal.reflect.Reflection.throwInaccessibleObjectException(Reflection.java:427)
        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:201)
        at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:171)
        at java.base/java.lang.reflect.Field.setAccessible(Field.java:165)
        at net.rubygrapefruit.platform.internal.WrapperProcess.getEnv(WrapperProcess.java:110)
        ... 15 more

It looks like we'll have to wait for Gradle to publish something that works with JDK 9, unless somebody else can provide further guidance in the interim.

@sbrannen FWIW I managed to get my gradle working with more of a hack than anything haha

Because Gradle launches these separate applications that use java, it wasn't enough just to add the arguments to JAVA_OPTIONS. Instead I moved $JAVA_HOME/bin/java to $JAVA_HOME/bin/java-real and created a new script in its place. This new script adds all of my options to java before it launches:

#!/bin/sh
# Usage: $JAVA_HOME/bin/java <args>
exec $JAVA_HOME/bin/java-real \
  -Dsun.reflect.debugModuleAccessChecks=true \
  --add-opens=java.base/java.lang=ALL-UNNAMED \
  --add-opens=java.base/java.util=ALL-UNNAMED \
  --add-opens=java.base/java.lang.invoke=ALL-UNNAMED \
  --add-opens=java.base/java.lang.reflect=ALL-UNNAMED \
  --add-opens=java.base/java.io=ALL-UNNAMED \
  --add-opens=java.base/java.util.concurrent=ALL-UNNAMED \
  --add-opens=java.base/java.text=ALL-UNNAMED \
  "$@"

You may need more or less items depending on whats being compiled. After that I also added it to org.gradle.jvmargs in my gradle.properties file!

org.gradle.jvmargs=-Dsun.reflect.debugModuleAccessChecks=true \
    --add-opens=java.base/java.lang=ALL-UNNAMED \
    --add-opens=java.base/java.util=ALL-UNNAMED \
    --add-opens=java.base/java.lang.invoke=ALL-UNNAMED \
    --add-opens=java.base/java.lang.reflect=ALL-UNNAMED \
    --add-opens=java.base/java.io=ALL-UNNAMED \
    --add-opens=java.base/java.util.concurrent=ALL-UNNAMED \
    --add-opens=java.base/java.text=ALL-UNNAMED 

Also dont forget to update sourceCompatibility = JavaVersion.VERSION_1_9 .. And here's my version info!

ubuntu@ubuntu-xenial:~/workspace$ java -version
java version "9-ea"
Java(TM) SE Runtime Environment (build 9-ea+157)
Java HotSpot(TM) 64-Bit Server VM (build 9-ea+157, mixed mode)


ubuntu@ubuntu-xenial:~/workspace$ gradle --version

------------------------------------------------------------
Gradle 3.4
------------------------------------------------------------

Build time:   2017-02-20 14:49:26 UTC
Revision:     73f32d68824582945f5ac1810600e8d87794c3d4

Groovy:       2.4.7
Ant:          Apache Ant(TM) version 1.9.6 compiled on June 29 2015
JVM:          9-ea (Oracle Corporation 9-ea+157)
OS:           Linux 4.4.0-64-generic amd64

This issue originally got me past a sticking point thanks to @cal-pratt, so I thought I would share the new simpler workaround available for the latest Java 9 Jigsaw EA build. I added IgnoreUnrecognizedVMOptions so that it won't cause issues with Java <=8.

JAVA_OPTS is used by the gradle wrapper:

export JAVA_OPTS='-XX:+IgnoreUnrecognizedVMOptions --permit-illegal-access'

gradle.properites:

org.gradle.jvmargs=-XX:+IgnoreUnrecognizedVMOptions --permit-illegal-access

See http://mail.openjdk.java.net/pipermail/jigsaw-dev/2017-March/011763.html

@jamespedwards42 unfortunately seems the junit 5 build not/no longer be working under the latest JDK 9 preview:

Using gradle at '/Users/pr/git/junit5/gradlew' to run buildfile '/Users/pr/git/junit5/build.gradle':

java 9-ea
Java(TM) SE Runtime Environment (build 9-ea+161)
Java HotSpot(TM) 64-Bit Server VM (build 9-ea+161, mixed mode)

FAILURE: Build failed with an exception.

* What went wrong:
java.lang.ExceptionInInitializerError (no error message)

I have been used the options you described with the additional --show-version to additionally should the actual version. Do you experience the same problem?

@reinhapa Sorry, I haven't tried any of this on this project. But the previous solutions worked for my Gradle Java 9 project. And then this new flag was just announced yesterday.

In order to use the --permit-illegal-access option you need the latest jigsaw build:
https://jdk9.java.net/jigsaw/

▶ java -version
java version "9-ea"
Java(TM) SE Runtime Environment (build 9-ea+161-jigsaw-nightly-h6227-20170321)
Java HotSpot(TM) 64-Bit Server VM (build 9-ea+161-jigsaw-nightly-h6227-20170321, mixed mode)

I think it will soon be added to the regular EA builds as well (https://jdk9.java.net/download/)

@jamespedwards42 hmm, just tried it successfully thanks for the tip.

Thanks for sharing your experiences and tips, everybody!

I've also had a look at the Big Kill Switch for disabling strong encapsulation on Java 9, and we'll be investigating further during the M5 time frame.

775 is dedicated to get JUnit 5 compile and run on Java 9 "classpath style".

We'll need to keep an eye on JigSaw as apparently there is not yet a consensus in the expert group. IBM, RedHat and others will vote "no" in the ballot to release the public review. In the past, we've seen major proposals dropped when they failed to meet Java release dead-lines so the question may well be whether Java 9 has module support or not.

Strict module boundaries and JUnit 5 works like a charm: https://github.com/forax/pro/tree/master/src/test/java

Added Related Pull Requests section to the initial issue description.

The sub project junit-platform-commons-java-9 and the sample project junit5-modular-world tackle most of the deliverables listed in the initial description.

Thus, closing this issue. Please open new issues if you encounter problem testing JPMS modules.

@sormuras, can you please _check off_ the Deliverables you covered and mark the ones you didn't cover with an ❌ so that we know what the status quo is?

_reopened in order to address open deliverables_

All covered, all checked.

Thanks! 👍

Was this page helpful?
0 / 5 - 0 ratings