Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make ArchUnit JUnit 5 support Java Modules compatible #827

Merged
merged 3 commits into from
Apr 22, 2022

Conversation

codecholeric
Copy link
Collaborator

@codecholeric codecholeric commented Mar 5, 2022

So far, even though we added Automatic-Module-Name to the MANIFEST to make it compatible with Java Modules, ArchUnit's JUnit 5 support could not be used on the modulepath. The reason is that all three modules archunit-junit5-api, archunit-junit5-engine and archunit-junit5-engine-api export the same package com.tngtech.archunit.junit.

We now fix this by splitting the three modules into separate packages. This is a little tricky since there is also code that is shared with ArchUnit JUnit 4 support and we want to keep as many internals package private as possible. I tried to find a good compromise between backwards compatibility and public surface, the result is

  • API stays the same, i.e. toplevel c.t.archunit.junit
  • Engine and shared internal infrastructure are moved to c.t.archunit.junit.internal (this causes no problem, because engine dependencies were always only transparent runtime dependencies via ServiceLoader, never compile time dependencies)
  • Engine-API is moved to c.t.archunit.junit.engine_api. This is a breaking change, however the engine-api is a very specific artifact that is only needed for frameworks to integrate with JUnit Platform. So it should only affect a very limited number of users.

Unfortunately the JUnit 4 design makes it very hard to cleanly split API and implementation. I.e. by making the Runner type public API (having to explicitly declare it as @RunWith(..)) tends to cause a chain reaction pulling more and more into the public scope. In particular if now the classes that are used internally (like ClassCache) don't reside in the same package anymore. I tried to find some compromise by creating an internal delegate that is loaded via Reflection. This way we can keep the things inside of internal conceiled with package-private scope. Hopefully this will not cause any problem in the long term.

Note that it will still not possible to put both JUnit 4 and JUnit 5 support on the modulepath together, but I also don't see any reason why anybody should want to do this, since they serve exactly the same purpose and JUnit 5 support is just more future proof.

Resolves: #206

Since we use the `MANIFEST` entry `Automatic-Module-Name` the JUnit 5 modules are principally ready to be used on the modulepath. However, we automatically expose **all** packages declared, which means that the modules `junit5-api`, `junit5-engine` and `junit5-engine-api` all export the same package `com.tngtech.archunit.junit`, which is illegal for Java Modules. As a first step we move all classes contained in `junit5-engine-api` to a separate package to resolve part of the conflict. Unfortunately, this demands that we make some methods public that did not have to be before.

Signed-off-by: Peter Gafert <peter.gafert@tngtech.com>
Since we use the `MANIFEST` entry `Automatic-Module-Name` the JUnit 5 modules are principally ready to be used on the modulepath. However, we automatically expose **all** packages declared, which means that the modules `junit5-api` and `junit5-engine` all export the same package `com.tngtech.archunit.junit`, which is illegal for Java Modules. We move all implementation details to a subpackage `internal` and only keep API toplevel. This way the module `junit5-engine` does not expose the root package `com.tngtech.archunit.junit` anymore, because all the contained classes are `internal`. While this separation works nicely and cleanly for `junit5`, the separation for `junit4` is a lot less pretty. Because the architecture of JUnit 4 makes it impossible to hide implementation details in a clean way, e.g. by demanding to annotate a `Runner` implementation, which should be pure hidden implementation detail. I added some separation by introducing `ArchUnitRunnerInternal` so we do not need to make all classes public, but have one (obviously not mean for outside usage) public gateway into the `internal` part.

Signed-off-by: Peter Gafert <peter.gafert@tngtech.com>
@codecholeric codecholeric force-pushed the make-junit5-support-module-compatible branch from e3096ca to 606b737 Compare April 22, 2022 09:19
Unfortunately it is not trivial to test that the final artifacts work in a Java modules project. To keep the effort low we just add a test that catches the most probable error: The dependencies change and suddenly two artifacts comprising the JUnit 5 support export the same package. To simplify this even further we can just assume that each JUnit 5 artifact exports exactly one package and by asserting that this is exactly the expected package we effectively prevent collisions. If we use automatic module names we have to assume that artifacts will export all of their packages on the module path.

Signed-off-by: Peter Gafert <peter.gafert@tngtech.com>
@codecholeric codecholeric force-pushed the make-junit5-support-module-compatible branch from 8a8d599 to ff3bc43 Compare April 22, 2022 09:43
@codecholeric codecholeric merged commit c9a6b7b into main Apr 22, 2022
@codecholeric codecholeric deleted the make-junit5-support-module-compatible branch April 22, 2022 11:35
@codecholeric codecholeric added this to the 1.0.0 milestone Jun 18, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add module description for java 9+ compatibility
1 participant