-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
ClasspathScanner.getRootUrisForPackage returns empty list when package name conflicts with module name #2500
Comments
…h other packages. See junit-team/junit5#2500
@oobles Thanks for the report! Could you please provide a small reproducer project? |
Here's a couple of modules created in eclipse that demonstrates the issue. Running ProjectTest directly results in the test running. Running tests on com.project.test results in no test executing. This was run on Eclipse 2020-12 using JDK15 (installed by eclipse). |
@noopur2507, can you provide any input for this? Is it perhaps an issue due to the module-path? |
FYI: running the JUnit Console Launcher on the module-path and various test-related IDEA run configurations that make use of the module-path do run fine. With modules that packages that use the same name as their modules. How does the command line generated by Eclipse look like? |
When searching test classes within a Java module, a tool (here Eclipse) should make use of the |
Just to demonstrate this a little clearer, the following fails.
This fails because Java's BuiltinClassLoader.findResources starts with these two lines:
pn becomes "com.project" instead of "com.project.test". It picks up the wrong module. It succeeds when you put a / at the end.
|
Thanks for the details, David. Seems that appending a missing |
I can not reproduce it using Java's module API and JUnit's internal scanner support. Having this modular JAR file (
Yields a successful test like this: @Test
// #2500
void scanForClassesInPackageWithinModuleSharingNames() throws Exception {
var jarfile = getClass().getResource("/[email protected]");
var module = "com.greetings";
var before = ModuleFinder.of();
var finder = ModuleFinder.of(Path.of(jarfile.toURI()));
var boot = ModuleLayer.boot();
var configuration = boot.configuration().resolveAndBind(before, finder, Set.of(module));
var parent = ClassLoader.getPlatformClassLoader();
var layer = ModuleLayer.defineModulesWithOneLoader(configuration, List.of(boot), parent).layer();
var classpathScanner = new ClasspathScanner(() -> layer.findLoader(module), ReflectionUtils::tryToLoadClass);
var classes = classpathScanner.scanForClassesInPackage("com.greetings", allClasses);
var classNames = classes.stream().map(Class::getName).collect(Collectors.toList());
assertThat(classNames).hasSize(1).contains("com.greetings.Main");
} Perhaps, I didn't reproduce your setup correctly or it's maybe an issue within Eclipse's way of handling (exploded) modules? |
@sormuras it doesn't look like your test includes two modules. Create another module with the package com.greetings.test package that requires com.greetings. |
Well. Now I'm able to reproduce this bug. With a module named
...the updated test case: @Test
// #2500
void scanForClassesInPackageWithinModuleSharingNames() throws Exception {
var greetings = getClass().getResource("/[email protected]").toURI();
var tests = getClass().getResource("/[email protected]").toURI();
var module = "com.greetings.test";
var before = ModuleFinder.of();
var finder = ModuleFinder.of(Path.of(greetings), Path.of(tests));
var boot = ModuleLayer.boot();
var configuration = boot.configuration().resolveAndBind(before, finder, Set.of(module));
var parent = ClassLoader.getPlatformClassLoader();
var layer = ModuleLayer.defineModulesWithOneLoader(configuration, List.of(boot), parent).layer();
var classpathScanner = new ClasspathScanner(() -> layer.findLoader(module), ReflectionUtils::tryToLoadClass);
{
var classes = classpathScanner.scanForClassesInPackage("com.greetings", allClasses);
var classNames = classes.stream().map(Class::getName).collect(Collectors.toList());
assertThat(classNames).hasSize(2).contains("com.greetings.Main", "com.greetings.test.Tests");
}
{
var classes = classpathScanner.scanForClassesInPackage("com.greetings.test", allClasses);
var classNames = classes.stream().map(Class::getName).collect(Collectors.toList());
assertThat(classNames).hasSize(1).contains("com.greetings.test.Tests");
}
} ...in the last line due to an empty list of class names. After adding the missing // ClasspathScanner.java line 213
private static String packagePath(String packageName) {
return packageName.replace(PACKAGE_SEPARATOR_CHAR, CLASSPATH_RESOURCE_PATH_SEPARATOR)
+ CLASSPATH_RESOURCE_PATH_SEPARATOR;
} ...the assertion is met, i.e. class @marcphilipp @sbrannen - before I continue with this fix, I want make sure that it is the intended behaviour of |
As documented at the entry point in Lines 122 to 123 in 3f7fed6
Thus, it should find all classes in package |
Prior to this commit no trailing `/` character was appended to the computed package path. Now, except for the default package `""`, a `/` is appended to package path. This leads to corrected and documented behavior even if two modules start with the same name elements. Fixes #2500
Prior to this commit no trailing `/` character was appended to the computed package path. Now, except for the default package `""`, a `/` is appended to package path. This leads to corrected and documented behavior even if two modules start with the same name elements. Fixes #2500
Prior to this commit no trailing `/` character was appended to the computed package path. Now, except for the default package `""`, a `/` is appended to package path. This leads to corrected and documented behavior even if two modules start with the same name elements. Fixes #2500
Prior to this commit no trailing `/` character was appended to the computed package path. Now, except for the default package `""`, a `/` is appended to package path. This leads to corrected and documented behavior even if two modules start with the same name elements. Fixes #2500
Prior to this commit no trailing `/` character was appended to the computed package path. Now, except for the default package `""`, a `/` is appended to package path. This leads to corrected and documented behavior even if two modules start with the same name elements. Fixes #2500
Prior to this commit no trailing `/` character was appended to the computed package path. Now, except for the default package `""`, a `/` is appended to package path. This leads to corrected and documented behavior even if two modules start with the same name elements. Fixes #2500
Prior to this commit no trailing `/` character was appended to the computed package path. Now, except for the default package `""`, a `/` is appended to package path. This leads to corrected and documented behavior even if two modules start with the same name elements. Fixes #2500 # Conflicts: # documentation/src/docs/asciidoc/release-notes/release-notes-5.8.0-M1.adoc
Reopen this issue due to unwanted side-effects produced by the fix. See #2600 for details. |
New Repro-Setup
import java.net.*;
import java.util.*;
class Main {
public static void main(String[] args) {
System.out.printf("Java %s%n", System.getProperty("java.version"));
printRootUrisForPackage("");
printRootUrisForPackage("/");
printRootUrisForPackage("foo");
printRootUrisForPackage("foo/");
printRootUrisForPackage("foo/test");
printRootUrisForPackage("foo/test/");
}
static void printRootUrisForPackage(String basePackageName) {
try {
System.out.printf("basePackageName: '%s'%n", basePackageName);
Enumeration<URL> resources = Main.class.getClassLoader().getResources(basePackageName);
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
System.out.printf(" %s%n", resource);
}
} catch(Exception exception) {
System.out.println(" " + exception);
}
}
} Java 8 (-classpath)
Java 16 (--class-path)
Java 16 (--module-path)
|
Fix `getRootUrisForPackage()` in class `ClasspathScanner` by looking for two "wordings" of a package name. For example, package `foo.bar` is expanded to `foo/bar` and `foo/bar/`. The latter allows find package `foo.bar` in a module called `foo.bar`, and not `foo`. Fixes #2500 Fixes #2600 Fixes #2612
Fix `getRootUrisForPackage()` in class `ClasspathScanner` by looking for two "wordings" of a package name. For example, package `foo.bar` is expanded to `foo/bar` and `foo/bar/`. The latter allows find package `foo.bar` in a module called `foo.bar`, and not `foo`. This commit also ensures, that there's no regression (compared to #2531) by launching test runs using the standalone artifact with `--select-package` and `--scan-class-path`. Fixes #2500 Fixes #2600 Fixes #2612
Fix `getRootUrisForPackage()` in class `ClasspathScanner` by looking for two "wordings" of a package name. For example, package `foo.bar` is expanded to `foo/bar` and `foo/bar/`. The latter allows find package `foo.bar` in a module called `foo.bar`, and not `foo`. This commit also ensures, that there's no regression (compared to #2531) by launching test runs using the standalone artifact with `--select-package` and `--scan-class-path`. Fixes #2500 Fixes #2600 Fixes #2612
Prior to this commit no trailing `/` character was appended to the computed package path. Now, except for the default package `""`, a `/` is appended to package path. This leads to corrected and documented behavior even if two modules start with the same name elements. Fixes junit-team#2500
Fix `getRootUrisForPackage()` in class `ClasspathScanner` by looking for two "wordings" of a package name. For example, package `foo.bar` is expanded to `foo/bar` and `foo/bar/`. The latter allows find package `foo.bar` in a module called `foo.bar`, and not `foo`. This commit also ensures, that there's no regression (compared to junit-team#2531) by launching test runs using the standalone artifact with `--select-package` and `--scan-class-path`. Fixes junit-team#2500 Fixes junit-team#2600 Fixes junit-team#2612
When developing a multi-module project that has similar package names it is possible that
getRootUrisForPackage
will fail to return a valid list.Steps to reproduce
Project1.
Project2.
In eclipse (2020-09,2020-12 and other versions) when selecting the package
io.litterat.pep.test
no JUnit 5 tests are found or executed. In 2020-12 no errors or console output is shown.The root cause is that
getRootUrisForPackage( "io.litterat.pep.test" )
results in callinggetClassLoader().getResources( "io/litterat/pep/test" )
. This results in callingClassLoaders$BootClassLoader findResources
which callsResources.toPackageName("io/litterat/pep/test" )
which results in package name"io.litterat.pep"
(the name of the first module). The loader then resolves the name to the wrongLoadedModule
which does not have the selected package. This results in no resources being returned.Using Eclipse and attempting to execute all tests on either source directory or project results in all tests in the
io.litterat.pep.test
package being skipped. However, tests in sub-packages (e.g.io.litterat.pep.test.extra
) are executed correctly.Changing the name of the package so that the second project does not use the other module package as a base corrects the problem (e.g.
io.litterat.peptest
).Context
Deliverables
It's not clear if this is a JDK bug or an issue with the way
getResources
is being called. Given that no output or feedback is provided this has taken quite a long time to figure out. Either issue should be documented or fixed.The text was updated successfully, but these errors were encountered: