Skip to content

Commit 8bc798f

Browse files
committed
Support explicit ClassLoader in NestedMethodSelector
Resolves #3298.
1 parent a30437b commit 8bc798f

File tree

5 files changed

+145
-26
lines changed

5 files changed

+145
-26
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-5.10.0-M1.adoc

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,10 @@ repository on GitHub.
3737
`ClassLoader`. This allows parameter types to be resolved with custom `ClassLoader`
3838
arrangements (such as OSGi). Consequently, `DiscoverySelectors.selectMethod(Class<?>,
3939
String, String)` also now works properly with custom `ClassLoader` arrangements.
40-
* New overloaded constructors for `ClassSelector`, `NestedClassSelector`, and
41-
`MethodSelector` that take an explicit `ClassLoader` as a parameter, allowing selectors
42-
to select classes in custom `ClassLoader` arrangements like in OSGi.
40+
* New overloaded constructors for `ClassSelector`, `NestedClassSelector`,
41+
`MethodSelector`, and `NestedMethodSelector` that take an explicit `ClassLoader` as a
42+
parameter, allowing selectors to select classes in custom `ClassLoader` arrangements
43+
like in OSGi.
4344
* For consistency with JUnit Jupiter lifecycle callbacks, listener method pairs for
4445
started/finished and opened/closed events are now invoked using "wrapping" semantics.
4546
This means that finished/closed event methods are invoked in reverse order compared to

junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -669,11 +669,29 @@ public static NestedClassSelector selectNestedClass(List<String> enclosingClassN
669669
@API(status = STABLE, since = "1.6")
670670
public static NestedMethodSelector selectNestedMethod(List<String> enclosingClassNames, String nestedClassName,
671671
String methodName) {
672+
return selectNestedMethod(enclosingClassNames, nestedClassName, methodName, (ClassLoader) null);
673+
}
672674

675+
/**
676+
* Create a {@code NestedMethodSelector} for the supplied nested class name, method name,
677+
* and class loader.
678+
*
679+
* @param enclosingClassNames the names of the enclosing classes; never {@code null} or empty
680+
* @param nestedClassName the name of the nested class to select; never {@code null} or blank
681+
* @param methodName the name of the method to select; never {@code null} or blank
682+
* @param classLoader the class loader to use to load the method's declaring
683+
* class, or {@code null} to signal that the default {@code ClassLoader}
684+
* should be used
685+
* @since 1.10
686+
* @see NestedMethodSelector
687+
*/
688+
@API(status = EXPERIMENTAL, since = "1.10")
689+
public static NestedMethodSelector selectNestedMethod(List<String> enclosingClassNames, String nestedClassName,
690+
String methodName, ClassLoader classLoader) throws PreconditionViolationException {
673691
Preconditions.notEmpty(enclosingClassNames, "Enclosing class names must not be null or empty");
674692
Preconditions.notBlank(nestedClassName, "Nested class name must not be null or blank");
675693
Preconditions.notBlank(methodName, "Method name must not be null or blank");
676-
return new NestedMethodSelector(enclosingClassNames, nestedClassName, methodName);
694+
return new NestedMethodSelector(enclosingClassNames, nestedClassName, methodName, classLoader);
677695
}
678696

679697
/**
@@ -696,12 +714,35 @@ public static NestedMethodSelector selectNestedMethod(List<String> enclosingClas
696714
@API(status = STABLE, since = "1.6")
697715
public static NestedMethodSelector selectNestedMethod(List<String> enclosingClassNames, String nestedClassName,
698716
String methodName, String methodParameterTypes) {
717+
return selectNestedMethod(enclosingClassNames, nestedClassName, methodName, methodParameterTypes, null);
718+
}
719+
720+
/**
721+
* Create a {@code NestedMethodSelector} for the supplied nested class name, method name,
722+
* method parameter types, and class loader.
723+
*
724+
* @param enclosingClassNames the names of the enclosing classes; never {@code null} or empty
725+
* @param nestedClassName the name of the nested class to select; never {@code null} or blank
726+
* @param methodName the name of the method to select; never {@code null} or blank
727+
* @param methodParameterTypes the method parameter types as a single string; never
728+
* {@code null} though potentially an empty string if the method does not accept
729+
* arguments
730+
* @param classLoader the class loader to use to load the method's declaring
731+
* class, or {@code null} to signal that the default {@code ClassLoader}
732+
* should be used
733+
* @since 1.10
734+
* @see #selectNestedMethod(List, String, String, String)
735+
*/
736+
@API(status = EXPERIMENTAL, since = "1.10")
737+
public static NestedMethodSelector selectNestedMethod(List<String> enclosingClassNames, String nestedClassName,
738+
String methodName, String methodParameterTypes, ClassLoader classLoader) {
699739

700740
Preconditions.notEmpty(enclosingClassNames, "Enclosing class names must not be null or empty");
701741
Preconditions.notBlank(nestedClassName, "Nested class name must not be null or blank");
702742
Preconditions.notBlank(methodName, "Method name must not be null or blank");
703743
Preconditions.notNull(methodParameterTypes, "Parameter types must not be null");
704-
return new NestedMethodSelector(enclosingClassNames, nestedClassName, methodName, methodParameterTypes);
744+
return new NestedMethodSelector(enclosingClassNames, nestedClassName, methodName, methodParameterTypes,
745+
classLoader);
705746
}
706747

707748
/**

junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
package org.junit.platform.engine.discovery;
1212

13+
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
1314
import static org.apiguardian.api.API.Status.STABLE;
1415

1516
import java.lang.reflect.Method;
@@ -55,15 +56,15 @@ public class NestedMethodSelector implements DiscoverySelector {
5556
private final NestedClassSelector nestedClassSelector;
5657
private final MethodSelector methodSelector;
5758

58-
NestedMethodSelector(List<String> enclosingClassNames, String nestedClassName, String methodName) {
59-
this.nestedClassSelector = new NestedClassSelector(enclosingClassNames, nestedClassName);
60-
this.methodSelector = new MethodSelector(nestedClassName, methodName);
59+
NestedMethodSelector(List<String> enclosingClassNames, String nestedClassName, String methodName,
60+
ClassLoader classLoader) {
61+
this(enclosingClassNames, nestedClassName, methodName, "", classLoader);
6162
}
6263

6364
NestedMethodSelector(List<String> enclosingClassNames, String nestedClassName, String methodName,
64-
String methodParameterTypes) {
65-
this.nestedClassSelector = new NestedClassSelector(enclosingClassNames, nestedClassName);
66-
this.methodSelector = new MethodSelector(nestedClassName, methodName, methodParameterTypes);
65+
String methodParameterTypes, ClassLoader classLoader) {
66+
this.nestedClassSelector = new NestedClassSelector(enclosingClassNames, nestedClassName, classLoader);
67+
this.methodSelector = new MethodSelector(nestedClassName, methodName, methodParameterTypes, classLoader);
6768
}
6869

6970
NestedMethodSelector(List<Class<?>> enclosingClasses, Class<?> nestedClass, String methodName) {
@@ -82,6 +83,16 @@ public class NestedMethodSelector implements DiscoverySelector {
8283
this.methodSelector = new MethodSelector(nestedClass, method);
8384
}
8485

86+
/**
87+
* Get the {@link ClassLoader} used to load the nested class.
88+
*
89+
* @since 1.10
90+
*/
91+
@API(status = EXPERIMENTAL, since = "1.10")
92+
public ClassLoader getClassLoader() {
93+
return this.nestedClassSelector.getClassLoader();
94+
}
95+
8596
/**
8697
* Get the names of the classes enclosing the nested class
8798
* containing the selected method.
@@ -182,6 +193,7 @@ public String toString() {
182193
.append("nestedClassName", getNestedClassName()) //
183194
.append("methodName", getMethodName()) //
184195
.append("methodParameterTypes", getMethodParameterTypes()) //
196+
.append("classLoader", getClassLoader()) //
185197
.toString();
186198
}
187199

platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,30 @@ void selectNestedMethodByEnclosingClassNamesAndMethodName() throws Exception {
747747
assertThat(selector.getMethodName()).isEqualTo(methodName);
748748
}
749749

750+
@Test
751+
void selectNestedMethodByEnclosingClassNamesAndMethodNameAndClassLoader() throws Exception {
752+
var testClasses = List.of(AbstractClassWithNestedInnerClass.class, ClassWithNestedInnerClass.class,
753+
AbstractClassWithNestedInnerClass.NestedClass.class);
754+
try (var testClassLoader = TestClassLoader.forClasses(testClasses.toArray(Class[]::new))) {
755+
756+
var selector = selectNestedMethod(List.of(enclosingClassName), nestedClassName, methodName,
757+
testClassLoader);
758+
759+
assertThat(selector.getEnclosingClasses()).doesNotContain(ClassWithNestedInnerClass.class);
760+
assertThat(selector.getEnclosingClasses()).extracting(Class::getName).containsOnly(enclosingClassName);
761+
assertThat(selector.getNestedClass()).isNotEqualTo(AbstractClassWithNestedInnerClass.NestedClass.class);
762+
assertThat(selector.getNestedClass().getName()).isEqualTo(nestedClassName);
763+
764+
assertThat(selector.getClassLoader()).isSameAs(testClassLoader);
765+
assertThat(selector.getEnclosingClasses()).extracting(Class::getClassLoader).containsOnly(
766+
testClassLoader);
767+
assertThat(selector.getNestedClass().getClassLoader()).isSameAs(testClassLoader);
768+
assertSame(testClassLoader, selector.getNestedClass().getClassLoader());
769+
770+
assertThat(selector.getMethodName()).isEqualTo(methodName);
771+
}
772+
}
773+
750774
@Test
751775
void selectNestedMethodByEnclosingClassesAndMethodName() throws Exception {
752776
var selector = selectNestedMethod(List.of(ClassWithNestedInnerClass.class),
@@ -776,6 +800,31 @@ void selectNestedMethodByEnclosingClassNamesAndMethodNameWithParameterTypes() th
776800
assertThat(selector.getMethodName()).isEqualTo(methodName);
777801
}
778802

803+
@Test
804+
void selectNestedMethodByEnclosingClassNamesAndMethodNameWithParameterTypesAndClassLoader() throws Exception {
805+
var testClasses = List.of(AbstractClassWithNestedInnerClass.class, ClassWithNestedInnerClass.class,
806+
AbstractClassWithNestedInnerClass.NestedClass.class);
807+
try (var testClassLoader = TestClassLoader.forClasses(testClasses.toArray(Class[]::new))) {
808+
809+
var selector = selectNestedMethod(List.of(enclosingClassName), nestedClassName, methodName,
810+
String.class.getName(), testClassLoader);
811+
812+
assertThat(selector.getEnclosingClasses()).doesNotContain(ClassWithNestedInnerClass.class);
813+
assertThat(selector.getEnclosingClasses()).extracting(Class::getName).containsOnly(enclosingClassName);
814+
assertThat(selector.getNestedClass()).isNotEqualTo(AbstractClassWithNestedInnerClass.NestedClass.class);
815+
assertThat(selector.getNestedClass().getName()).isEqualTo(nestedClassName);
816+
817+
assertThat(selector.getClassLoader()).isSameAs(testClassLoader);
818+
assertThat(selector.getEnclosingClasses()).extracting(Class::getClassLoader).containsOnly(
819+
testClassLoader);
820+
assertThat(selector.getNestedClass().getClassLoader()).isSameAs(testClassLoader);
821+
assertSame(testClassLoader, selector.getNestedClass().getClassLoader());
822+
823+
assertThat(selector.getMethodName()).isEqualTo(methodName);
824+
assertThat(selector.getMethodParameterTypes()).isEqualTo(String.class.getName());
825+
}
826+
}
827+
779828
@Test
780829
void selectDoubleNestedMethodByEnclosingClassNamesAndMethodName() throws Exception {
781830
var doubleNestedMethodName = "doubleNestedTest";
@@ -808,7 +857,8 @@ void selectNestedMethodPreconditions() {
808857
assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), "ClassName", null, "int"));
809858
assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), "ClassName", " "));
810859
assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), "ClassName", " ", "int"));
811-
assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), "ClassName", "methodName", null));
860+
assertViolatesPrecondition(
861+
() -> selectNestedMethod(List.of("ClassName"), "ClassName", "methodName", (String) null));
812862
}
813863

814864
abstract class AbstractClassWithNestedInnerClass {

platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedMethodSelectorTests.java

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,32 +29,33 @@ class NestedMethodSelectorTests extends AbstractEqualsAndHashCodeTests {
2929

3030
@Test
3131
void equalsAndHashCode() {
32-
var selector1 = new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "method",
33-
"int, boolean");
34-
var selector2 = new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "method",
35-
"int, boolean");
32+
var selector1 = new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "method", "int, boolean",
33+
null);
34+
var selector2 = new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "method", "int, boolean",
35+
null);
3636

3737
assertEqualsAndHashCode(selector1, selector2,
38-
new NestedMethodSelector(List.of("X"), "NestedTestClass", "method", "int, boolean"));
38+
new NestedMethodSelector(List.of("X"), "NestedTestClass", "method", "int, boolean", null));
3939
assertEqualsAndHashCode(selector1, selector2,
40-
new NestedMethodSelector(List.of("X"), "NestedTestClass", "method"));
40+
new NestedMethodSelector(List.of("X"), "NestedTestClass", "method", "", null));
4141
assertEqualsAndHashCode(selector1, selector2,
42-
new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "method", "int"));
42+
new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "method", "int", null));
4343
assertEqualsAndHashCode(selector1, selector2,
44-
new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "method"));
44+
new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "method", "", null));
4545
assertEqualsAndHashCode(selector1, selector2,
46-
new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "X", "int, boolean"));
46+
new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "X", "int, boolean", null));
4747
assertEqualsAndHashCode(selector1, selector2,
48-
new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "X"));
48+
new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "X", "", null));
4949
assertEqualsAndHashCode(selector1, selector2,
50-
new NestedMethodSelector(List.of("EnclosingClass"), "X", "method", "int, boolean"));
50+
new NestedMethodSelector(List.of("EnclosingClass"), "X", "method", "int, boolean", null));
5151
assertEqualsAndHashCode(selector1, selector2,
52-
new NestedMethodSelector(List.of("EnclosingClass"), "X", "method"));
52+
new NestedMethodSelector(List.of("EnclosingClass"), "X", "method", "", null));
5353
}
5454

5555
@Test
5656
void preservesOriginalExceptionWhenTryingToLoadEnclosingClass() {
57-
var selector = new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "method", "int, boolean");
57+
var selector = new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "method", "int, boolean",
58+
null);
5859

5960
var exception = assertThrows(PreconditionViolationException.class, selector::getEnclosingClasses);
6061

@@ -64,12 +65,26 @@ void preservesOriginalExceptionWhenTryingToLoadEnclosingClass() {
6465

6566
@Test
6667
void preservesOriginalExceptionWhenTryingToLoadNestedClass() {
67-
var selector = new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "method", "int, boolean");
68+
var selector = new NestedMethodSelector(List.of("EnclosingClass"), "NestedTestClass", "method", "int, boolean",
69+
null);
6870

6971
var exception = assertThrows(PreconditionViolationException.class, selector::getNestedClass);
7072

7173
assertThat(exception).hasMessage("Could not load class with name: NestedTestClass") //
7274
.hasCauseInstanceOf(ClassNotFoundException.class);
7375
}
7476

77+
@Test
78+
void usesClassClassLoader() {
79+
var selector = new NestedMethodSelector(List.of(getClass()), NestedTestCase.class, "method", "");
80+
81+
assertThat(selector.getClassLoader()).isNotNull().isSameAs(getClass().getClassLoader());
82+
}
83+
84+
@SuppressWarnings("InnerClassMayBeStatic")
85+
class NestedTestCase {
86+
void method() {
87+
}
88+
}
89+
7590
}

0 commit comments

Comments
 (0)