Skip to content

Commit 7c5659f

Browse files
committed
Fix discovery of inherited nested classes
The discovery algorithm introduced in 5.5 remembers already the resolution of each discovery selector. Prior to this commit, nested classes were resolved by adding a ClassSelector for the nested class. If that ClassSelector has been resolved before, discovery is assumed to be finished. Similarly, when resolving MethodSelectors for methods within a nested test class that have been resolved before, discovery will stop. This commit introduces dedicated selectors for nested classes and their methods which take into account the actual enclosing classes that will be present at runtime. These selectors are package-private for now but could be made publicly available in a future release. Fixes #1954.
1 parent fbd90b4 commit 7c5659f

File tree

12 files changed

+392
-23
lines changed

12 files changed

+392
-23
lines changed

documentation/src/docs/asciidoc/release-notes/index.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ authors as well as build tool and IDE vendors.
1515

1616
include::../link-attributes.adoc[]
1717

18+
include::release-notes-5.5.1.adoc[]
19+
1820
include::release-notes-5.5.0.adoc[]
1921

2022
include::release-notes-5.4.2.adoc[]
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
[[release-notes-5.5.1]]
2+
== 5.5.1
3+
4+
*Date of Release:* ❓
5+
6+
*Scope:* Bug fixes since 5.5.0
7+
8+
For a complete list of all _closed_ issues and pull requests for this release, consult
9+
the link:{junit5-repo}+/milestone/42?closed=1+[5.5.1] milestone page in the JUnit repository
10+
on GitHub.
11+
12+
13+
[[release-notes-5.5.1-junit-platform]]
14+
=== JUnit Platform
15+
16+
==== Bug Fixes
17+
18+
* ❓
19+
20+
==== Deprecations and Breaking Changes
21+
22+
* ❓
23+
24+
==== New Features and Improvements
25+
26+
* ❓
27+
28+
29+
[[release-notes-5.5.1-junit-jupiter]]
30+
=== JUnit Jupiter
31+
32+
==== Bug Fixes
33+
34+
* Fix test discovery and execution of inherited `@Nested` classes.
35+
36+
==== Deprecations and Breaking Changes
37+
38+
* ❓
39+
40+
==== New Features and Improvements
41+
42+
* ❓
43+
44+
45+
[[release-notes-5.5.1-junit-vintage]]
46+
=== JUnit Vintage
47+
48+
==== Bug Fixes
49+
50+
* ❓
51+
52+
==== Deprecations and Breaking Changes
53+
54+
* ❓
55+
56+
==== New Features and Improvements
57+
58+
* ❓

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ public final Class<?> getTestClass() {
104104
return this.testClass;
105105
}
106106

107+
public abstract List<Class<?>> getEnclosingTestClasses();
108+
107109
@Override
108110
public Type getType() {
109111
return Type.CONTAINER;

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java

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

1111
package org.junit.jupiter.engine.descriptor;
1212

13+
import static java.util.Collections.emptyList;
1314
import static org.apiguardian.api.API.Status.INTERNAL;
1415
import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.createDisplayNameSupplierForClass;
1516

1617
import java.util.LinkedHashSet;
18+
import java.util.List;
1719
import java.util.Optional;
1820
import java.util.Set;
1921

@@ -56,6 +58,11 @@ public Set<TestTag> getTags() {
5658
return new LinkedHashSet<>(this.tags);
5759
}
5860

61+
@Override
62+
public List<Class<?>> getEnclosingTestClasses() {
63+
return emptyList();
64+
}
65+
5966
// --- Node ----------------------------------------------------------------
6067

6168
@Override

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@
1010

1111
package org.junit.jupiter.engine.descriptor;
1212

13+
import static java.util.Collections.emptyList;
1314
import static org.apiguardian.api.API.Status.INTERNAL;
1415
import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.createDisplayNameSupplierForNestedClass;
1516

17+
import java.util.ArrayList;
1618
import java.util.LinkedHashSet;
19+
import java.util.List;
1720
import java.util.Optional;
1821
import java.util.Set;
1922

@@ -57,6 +60,18 @@ public final Set<TestTag> getTags() {
5760
return allTags;
5861
}
5962

63+
@Override
64+
public List<Class<?>> getEnclosingTestClasses() {
65+
TestDescriptor parent = getParent().orElse(null);
66+
if (parent instanceof ClassBasedTestDescriptor) {
67+
ClassBasedTestDescriptor parentClassDescriptor = (ClassBasedTestDescriptor) parent;
68+
List<Class<?>> result = new ArrayList<>(parentClassDescriptor.getEnclosingTestClasses());
69+
result.add(parentClassDescriptor.getTestClass());
70+
return result;
71+
}
72+
return emptyList();
73+
}
74+
6075
// --- Node ----------------------------------------------------------------
6176

6277
@Override

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@
1616
import static org.junit.platform.commons.support.ReflectionSupport.findNestedClasses;
1717
import static org.junit.platform.commons.util.FunctionUtils.where;
1818
import static org.junit.platform.commons.util.ReflectionUtils.findMethods;
19-
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
20-
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod;
2119
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId;
2220
import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved;
2321

22+
import java.lang.reflect.Method;
23+
import java.util.ArrayList;
2424
import java.util.LinkedHashSet;
25+
import java.util.List;
2526
import java.util.Optional;
2627
import java.util.Set;
2728
import java.util.function.Predicate;
@@ -40,7 +41,6 @@
4041
import org.junit.platform.engine.UniqueId;
4142
import org.junit.platform.engine.discovery.ClassSelector;
4243
import org.junit.platform.engine.discovery.DiscoverySelectors;
43-
import org.junit.platform.engine.discovery.MethodSelector;
4444
import org.junit.platform.engine.discovery.UniqueIdSelector;
4545
import org.junit.platform.engine.support.discovery.SelectorResolver;
4646

@@ -71,12 +71,24 @@ public Resolution resolve(ClassSelector selector, Context context) {
7171
}
7272
}
7373
else if (isNestedTestClass.test(testClass)) {
74-
return toResolution(context.addToParent(() -> selectClass(testClass.getEnclosingClass()),
74+
return toResolution(context.addToParent(() -> DiscoverySelectors.selectClass(testClass.getEnclosingClass()),
7575
parent -> Optional.of(newNestedClassTestDescriptor(parent, testClass))));
7676
}
7777
return unresolved();
7878
}
7979

80+
@Override
81+
public Resolution resolve(DiscoverySelector selector, Context context) {
82+
if (selector instanceof NestedClassSelector) {
83+
NestedClassSelector nestedClassSelector = (NestedClassSelector) selector;
84+
if (isNestedTestClass.test(nestedClassSelector.getNestedClass())) {
85+
return toResolution(context.addToParent(() -> selectClass(nestedClassSelector.getEnclosingClasses()),
86+
parent -> Optional.of(newNestedClassTestDescriptor(parent, nestedClassSelector.getNestedClass()))));
87+
}
88+
}
89+
return unresolved();
90+
}
91+
8092
@Override
8193
public Resolution resolve(UniqueIdSelector selector, Context context) {
8294
UniqueId uniqueId = selector.getUniqueId();
@@ -93,7 +105,6 @@ public Resolution resolve(UniqueIdSelector selector, Context context) {
93105
return toResolution(context.addToParent(() -> selectUniqueId(uniqueId.removeLastSegment()), parent -> {
94106
if (parent instanceof ClassBasedTestDescriptor) {
95107
Class<?> parentTestClass = ((ClassBasedTestDescriptor) parent).getTestClass();
96-
// TODO add test for resolving unique id of inherited nested test class
97108
return ReflectionUtils.findNestedClasses(parentTestClass,
98109
isNestedTestClass.and(
99110
where(Class::getSimpleName, isEqual(simpleClassName)))).stream().findFirst().flatMap(
@@ -120,16 +131,34 @@ private NestedClassTestDescriptor newNestedClassTestDescriptor(TestDescriptor pa
120131
private Resolution toResolution(Optional<? extends ClassBasedTestDescriptor> testDescriptor) {
121132
return testDescriptor.map(it -> {
122133
Class<?> testClass = it.getTestClass();
134+
List<Class<?>> testClasses = new ArrayList<>(it.getEnclosingTestClasses());
135+
testClasses.add(testClass);
123136
// @formatter:off
124137
return Resolution.match(Match.exact(it, () -> {
125-
Stream<MethodSelector> methods = findMethods(testClass, isTestOrTestFactoryOrTestTemplateMethod).stream()
126-
.map(method -> selectMethod(testClass, method));
127-
Stream<ClassSelector> nestedClasses = findNestedClasses(testClass, isNestedTestClass).stream()
128-
.map(DiscoverySelectors::selectClass);
138+
Stream<DiscoverySelector> methods = findMethods(testClass, isTestOrTestFactoryOrTestTemplateMethod).stream()
139+
.map(method -> selectMethod(testClasses, method));
140+
Stream<NestedClassSelector> nestedClasses = findNestedClasses(testClass, isNestedTestClass).stream()
141+
.map(nestedClass -> new NestedClassSelector(testClasses, nestedClass));
129142
return Stream.concat(methods, nestedClasses).collect(toCollection((Supplier<Set<DiscoverySelector>>) LinkedHashSet::new));
130143
}));
131144
// @formatter:on
132145
}).orElse(unresolved());
133146
}
134147

148+
private DiscoverySelector selectClass(List<Class<?>> classes) {
149+
if (classes.size() == 1) {
150+
return DiscoverySelectors.selectClass(classes.get(0));
151+
}
152+
int lastIndex = classes.size() - 1;
153+
return new NestedClassSelector(classes.subList(0, lastIndex), classes.get(lastIndex));
154+
}
155+
156+
private DiscoverySelector selectMethod(List<Class<?>> classes, Method method) {
157+
if (classes.size() == 1) {
158+
return DiscoverySelectors.selectMethod(classes.get(0), method);
159+
}
160+
int lastIndex = classes.size() - 1;
161+
return new NestedMethodSelector(classes.subList(0, lastIndex), classes.get(lastIndex), method);
162+
}
163+
135164
}

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java

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

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

13+
import static java.util.Collections.emptyList;
1314
import static java.util.Collections.emptySet;
1415
import static java.util.stream.Collectors.toList;
1516
import static java.util.stream.Collectors.toSet;
16-
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
1717
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId;
1818
import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.matches;
1919
import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved;
2020

2121
import java.lang.reflect.Method;
2222
import java.util.Arrays;
2323
import java.util.LinkedHashSet;
24+
import java.util.List;
2425
import java.util.Optional;
2526
import java.util.Set;
2627
import java.util.function.Predicate;
@@ -43,6 +44,7 @@
4344
import org.junit.platform.engine.DiscoverySelector;
4445
import org.junit.platform.engine.TestDescriptor;
4546
import org.junit.platform.engine.UniqueId;
47+
import org.junit.platform.engine.discovery.DiscoverySelectors;
4648
import org.junit.platform.engine.discovery.MethodSelector;
4749
import org.junit.platform.engine.discovery.UniqueIdSelector;
4850
import org.junit.platform.engine.support.discovery.SelectorResolver;
@@ -63,9 +65,23 @@ class MethodSelectorResolver implements SelectorResolver {
6365

6466
@Override
6567
public Resolution resolve(MethodSelector selector, Context context) {
68+
return resolve(context, emptyList(), selector.getJavaClass(), selector.getJavaMethod());
69+
}
70+
71+
@Override
72+
public Resolution resolve(DiscoverySelector selector, Context context) {
73+
if (selector instanceof NestedMethodSelector) {
74+
NestedMethodSelector nestedMethodSelector = (NestedMethodSelector) selector;
75+
return resolve(context, nestedMethodSelector.getEnclosingClasses(), nestedMethodSelector.getNestedClass(),
76+
nestedMethodSelector.getMethod());
77+
}
78+
return unresolved();
79+
}
80+
81+
private Resolution resolve(Context context, List<Class<?>> enclosingClasses, Class<?> testClass, Method method) {
6682
// @formatter:off
6783
Set<Match> matches = Arrays.stream(MethodType.values())
68-
.map(methodType -> methodType.resolveMethodSelector(selector, context, configuration))
84+
.map(methodType -> methodType.resolve(enclosingClasses, testClass, method, context, configuration))
6985
.filter(Optional::isPresent)
7086
.map(Optional::get)
7187
.map(testDescriptor -> Match.exact(testDescriptor, expansionCallback(testDescriptor)))
@@ -78,8 +94,7 @@ public Resolution resolve(MethodSelector selector, Context context) {
7894
"Possible configuration error: method [%s] resulted in multiple TestDescriptors %s. "
7995
+ "This is typically the result of annotating a method with multiple competing annotations "
8096
+ "such as @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, etc.",
81-
selector.getJavaMethod().toGenericString(),
82-
testDescriptors.map(d -> d.getClass().getName()).collect(toList()));
97+
method.toGenericString(), testDescriptors.map(d -> d.getClass().getName()).collect(toList()));
8398
});
8499
}
85100
return matches.isEmpty() ? unresolved() : matches(matches);
@@ -159,18 +174,23 @@ protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class<?> testCl
159174
this.dynamicDescendantSegmentTypes = new LinkedHashSet<>(Arrays.asList(dynamicDescendantSegmentTypes));
160175
}
161176

162-
private Optional<TestDescriptor> resolveMethodSelector(MethodSelector selector, Context resolver,
163-
JupiterConfiguration configuration) {
164-
if (!methodPredicate.test(selector.getJavaMethod())) {
177+
private Optional<TestDescriptor> resolve(List<Class<?>> enclosingClasses, Class<?> testClass, Method method,
178+
Context context, JupiterConfiguration configuration) {
179+
if (!methodPredicate.test(method)) {
165180
return Optional.empty();
166181
}
167-
Class<?> testClass = selector.getJavaClass();
168-
Method method = selector.getJavaMethod();
169-
return resolver.addToParent(() -> selectClass(testClass), //
182+
return context.addToParent(() -> selectClass(enclosingClasses, testClass), //
170183
parent -> Optional.of(
171184
createTestDescriptor(createUniqueId(method, parent), testClass, method, configuration)));
172185
}
173186

187+
private DiscoverySelector selectClass(List<Class<?>> enclosingClasses, Class<?> testClass) {
188+
if (enclosingClasses.isEmpty()) {
189+
return DiscoverySelectors.selectClass(testClass);
190+
}
191+
return new NestedClassSelector(enclosingClasses, testClass);
192+
}
193+
174194
private Optional<TestDescriptor> resolveUniqueIdIntoTestDescriptor(UniqueId uniqueId, Context context,
175195
JupiterConfiguration configuration) {
176196
UniqueId.Segment lastSegment = uniqueId.getLastSegment();
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2015-2019 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.jupiter.engine.discovery;
12+
13+
import java.util.List;
14+
import java.util.Objects;
15+
16+
import org.junit.platform.commons.util.Preconditions;
17+
import org.junit.platform.engine.DiscoverySelector;
18+
19+
/**
20+
* @since 5.5.1
21+
*/
22+
class NestedClassSelector implements DiscoverySelector {
23+
24+
private final List<Class<?>> enclosingClasses;
25+
private final Class<?> nestedClass;
26+
27+
NestedClassSelector(List<Class<?>> enclosingClasses, Class<?> nestedClass) {
28+
this.enclosingClasses = Preconditions.notEmpty(enclosingClasses, "enclosingClasses must not be null or empty");
29+
this.nestedClass = Preconditions.notNull(nestedClass, "nestedClass must not be null");
30+
}
31+
32+
List<Class<?>> getEnclosingClasses() {
33+
return enclosingClasses;
34+
}
35+
36+
Class<?> getNestedClass() {
37+
return nestedClass;
38+
}
39+
40+
@Override
41+
public boolean equals(Object o) {
42+
if (this == o) {
43+
return true;
44+
}
45+
if (o == null || getClass() != o.getClass()) {
46+
return false;
47+
}
48+
NestedClassSelector that = (NestedClassSelector) o;
49+
return enclosingClasses.equals(that.enclosingClasses) && nestedClass.equals(that.nestedClass);
50+
}
51+
52+
@Override
53+
public int hashCode() {
54+
return Objects.hash(enclosingClasses, nestedClass);
55+
}
56+
57+
}

0 commit comments

Comments
 (0)