Junit5: PackageUtils do not work with modules on Java 9

Created on 12 Dec 2016  Â·  18Comments  Â·  Source: junit-team/junit5

Background

Invoking clazz.getPackage().getImplementationVersion() can currently return null on Java 9 while returning a valid implementation version on Java 8. See the discussion in #598 for details.

For example PackageUtils.getAttribute(Object.class, Package::getImplementationVersion) returns an empty Optional on Java 9 when passed a class that resides in a module (e.g., java.base).

Analysis

Package versioning is NOT supported for _Modular JAR Files_ (i.e., JARs containing module-info.class).

Excerpt from the JAR File Specification for Java 9:

JAR files that are deployed as modules declare (explicitly or implicitly) their dependencies and may declare service providers too. The JAR Manifest is ignored. On the other hand, JAR files on the class path use the META-INF directory for security, versioning, or service configuration.

Deliverables

  • [x] Determine if classes such as Object which reside in the java.base module even have such metadata associated with their packages.

    • See _Analysis_ section above.

    • [x] Find an alternative to PackageUtils.getAttribute(...) for module versioning on Java 9.

Platform Java 9+10+11... bug

All 18 comments

Overhauled this issue's description based on my _Analysis_.

The special case, retrieving an attribute from Object.class'es package object, used in a test of #598 should be changed to a more robust assertion route, that already works on Java 8 and won't break on Java 9. I'm preparing a PR that uses ValueWrapper from opentest4j.jar as a probe for an externally loaded class -- which does not result in a undefined state on Java 9.

Side note: http://download.java.net/java/jdk9/docs/api/java/lang/Runtime.Version.html represents a version string for an implementation of the Java SE Platform from 9 on.

that already works on Java 8 and won't break on Java 9.

Well... AFAIK, if we were ever to load opentest4j.jar via the module-path within the Gradle build or within the IDE then the test that loads the Package for ValueWrapper would also start to fail. 😉

Side note: http://download.java.net/java/jdk9/docs/api/java/lang/Runtime.Version.html represents a version string for an implementation of the Java SE Platform from 9 on.

Yeah... looking forward to being able to use Runtime.version()! But JUnit 5 only requires Java 8, so we can't use that yet.

True.

A foresee a ModuleUtils helper and VersionUtils - the later deciding contains switch that decides who can answer the "implemenation version" query. Or, shift the versioning text creation duty to the engine supplier: make TestEngine.getVersion() and friends abstract (or return an empty Optional) to force a hard-coded, a dynamic, a package/module-scanned, what-ever implementation version.

Yeah... looking forward to being able to use Runtime.version()! But JUnit 5 only requires Java 8, so we can't use that yet.

If you hide the version access behind an API you could build a Multi-Release JAR. Then you could use Runtime.version() on Java 9+ and are only required to do special shenanigans for Java 8.

Getting the version from the Java runtime was only required by an already removed test case. See #601.

The main purpose of introducing PackageUtils is to provide an convenient default for the TestEngine.getMetadataAttributeForLogging interface methods. It should ease the work for TestEngine vendors when implementing their test engines.

ModuleDescriptor is here to help:

  • Optional<String> rawVersion​() Returns the string with the possibly-unparseable version of the module
  • toNameAndVersion Returns a string containing the module name and, if present, its version.

The following Java 9 snippet:

Class<?> type = org.apiguardian.api.API.class;
System.out.println("type             = " + type);
System.out.println("module           = " + type.getModule());
System.out.println("descriptor       = " + type.getModule().getDescriptor());
System.out.println("toNameAndVersion = " + type.getModule().getDescriptor().toNameAndVersion());
System.out.println("rawVerion        = " + type.getModule().getDescriptor().rawVersion());
System.out.println("version          = " + type.getModule().getDescriptor().version());

yields:

type             = interface org.apiguardian.api.API
module           = module org.apiguardian.api
descriptor       = module { name: [email protected], [mandated java.base] }
toNameAndVersion = [email protected]
rawVerion        = Optional[1.0.0]
version          = Optional[1.0.0]

Looks good.....

But it obviously requires Java 9 libraries. 😜

So.

  • Some reflection-action in Java 8 to test for Java 9 types and methods.
  • ServiceLoader... VersionService SPI. Feels like an overkill for the purpose.

So we'd have to switch the build to JDK 9 and use reflective hacks to ensure that that code path is avoided when executing on Java 8.

Looks like we were typing simultaneously... but you hit "Comment" first.

I'd stay on Java 8 for now. If 9 is the runtime, use some reflective magic to call the methods available here.

But, yeah.... _there's more than one way to skin a cat_.

I'd stay on Java 8 for. If 9 is the runtime, use some reflective magic to call the methods available here.

Sure

Viel Spass! / Have Fun! 🎉

't was fun and it works.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

PeterWippermann picture PeterWippermann  Â·  5Comments

lessthanoptimal picture lessthanoptimal  Â·  4Comments

marcphilipp picture marcphilipp  Â·  3Comments

mkobit picture mkobit  Â·  5Comments

netzwerg picture netzwerg  Â·  6Comments