Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
In a modular Java project, where the main code is supplied from the modulepath instead of the classpath, the class scanning was unreliable. In fact the behavior was sporadic at first sight. To elaborate, consider the following structure: ``` src |-- main | |-- java | |-- module-info.java | |-- com/root/First.java | |-- com/otherroot/Second.java |-- test |-- java |-- com/root/ArchUnitTest.java ``` In this scenario `com.otherroot.Second` would be found by ArchUnit's scanning, but surprisingly `com.root.First` would not be found. The reason for this behavior was an internal fallback to `ClassLoader.getResources(..)` to locate the class (e.g. `/com/otherroot/Second.class`), but this only worked, if the respective resource path had not been overwritten via `--patch-module`. Surprisingly `--patch-module` causes the original resource path to be hidden (compare http://mail.openjdk.java.net/pipermail/core-libs-dev/2018-July/054228.html). Thus in this example case `ArchUnitTest` could find `com.otherroot.Second`, as long as only `com/root` was patched. I.e. Maven would add `--patch-module example_module=com/root` to patch the test class into the module. But `ClassLoader.getResources(..)` would then not find `/com/root/First.class` anymore, since `target/classes/com/root` had been overwritten with `target/test-classes/com/root`. On the other hand `ClassLoader.getResources(..)` would still look into `target/classes/com/otherroot`, since that path had not been patched. To fix this behavior I have tried to eliminate the root cause, namely to make the fallback to `ClassLoader.getResources(..)` unnecessary for classes on the module path. So far in the first step only the configured classpath and the system modulepath have been read in `ModuleLocationResolver`. I have extended this to also parse the configured modulepath (through the system property `jdk.module.path`) and scan the content of these modules analogously to the system modules. It provided quite challenging to add a test for this behavior, since ArchUnit itself is not written as Java Modules and Gradle's support for Java Modules has proven to still lack some usability (i.e. I could not find a way without tinkering around with compiler and JVM args to get this working in a modular way, but this also might be because of the somewhat suboptimal ArchUnit build). To make the setup easier I have added the `org.javamodularity.moduleplugin` (compare https://github.com/java9-modularity/gradle-modules-plugin) to a separate Gradle module, which has the only purpose to try to scan a class from the module path that has been overridden by a `--patch-module` (since the test resides in the same package). However, this test only has any significance if run with Gradle, since IntelliJ seems to simply dump all classes on the classpath to run tests, no matter if the code base is written as Java Modules. Thus I've added a simple assertion to the test to make sure that the respective `main` class is actually not present on the classpath, since the test would then just always be successful, even if modulepath scanning would be broken. This assertion is a little brittle, because it uses a lot of Reflection, but I wanted to reuse ArchUnit's main code to resolve the classpath and I didn't want to make these internal parts visible for external use, so I decided this is still the best solution, considering all tradeoffs. In any case modulepath scanning probably still doesn't work in all scenarios now (e.g. if `--patch-module` and other options are supplied), but it should be good enough for most standard use cases. And since this issue has only been brought up over 3 years after ArchUnit + Java 9 have been out there together, I would guess that exotic combinations (like "I want to use `--patch-module` to patch in test code that I want to scan with ArchUnit" or similar) are not that common. Besides that it is always possible to just dump all classes on the classpath together to execute the ArchUnit test and avoid the modulepath issues altogether (like Gradle or IntelliJ seem to do by default anyway to execute unit tests). Signed-off-by: Peter Gafert <[email protected]>
- Loading branch information