Skip to content

Commit 4732d12

Browse files
committed
Apply method predicate before searching type hierarchy
Prior to this commit, findMethods() and streamMethods() in ReflectionSupport as well as findAnnotatedMethods() in AnnotationSupport first searched for all methods in the type hierarchy and then applied the user-supplied predicate (or "is annotated?" predicate) afterwards. This resulted in methods in subclasses incorrectly "shadowing" package-private methods in superclasses (in a different package) even if the predicate would otherwise exclude the method in such a subclass. For example, given a superclass that declares a package-private static @⁠BeforeAll "before()" method and a subclass (in a different package) that declares a @⁠BeforeEach "before()" method, when JUnit Jupiter looked up @⁠BeforeAll methods for the subclass, the @⁠BeforeAll "before()" method in the superclass was not found because the @⁠BeforeEach "before()" method shadowed it based solely on the method signature, ignoring the type of annotation sought. To address that, this commit modifies the internal search algorithms in ReflectionUtils so that method predicates are applied while searching the hierarchy for methods. Closes #3498 Closes #3500
1 parent 1b05c88 commit 4732d12

File tree

5 files changed

+111
-1
lines changed

5 files changed

+111
-1
lines changed

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

+9-1
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,22 @@ JUnit repository on GitHub.
1515

1616
==== Bug Fixes
1717

18-
* ❓
18+
* Method predicates are now applied while searching the type hierarchy. This fixes bugs
19+
in `findMethods(...)` and `streamMethods(...)` in `ReflectionSupport` as well as
20+
`findAnnotatedMethods(...)` in `AnnotationSupport`.
21+
- See link:https://github.com/junit-team/junit5/issues/3498[issue 3498] for details.
1922

2023

2124
[[release-notes-5.10.1-junit-jupiter]]
2225
=== JUnit Jupiter
2326

2427
==== Bug Fixes
2528

29+
* A package-private class-level lifecycle method annotated with `@BeforeAll` or
30+
`@AfterAll` is no longer _shadowed_ by a method-level lifecycle method annotated with
31+
`@BeforeEach` or `@AfterEach` when the method-level lifecycle method resides in a
32+
different package and has the same name as the class-level lifecycle method.
33+
- See link:https://github.com/junit-team/junit5/issues/3498[issue 3498] for details.
2634
* The `RandomNumberExtension` example in the
2735
<<../user-guide/index.adoc#extensions-RandomNumberExtension, User Guide>> has been
2836
updated to properly support `Integer` types as well as non-static field injection.

platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java

+27
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,18 @@
3636
import java.lang.annotation.Target;
3737
import java.lang.reflect.AnnotatedElement;
3838
import java.lang.reflect.Field;
39+
import java.lang.reflect.Method;
3940
import java.math.BigDecimal;
4041
import java.util.List;
4142
import java.util.Optional;
4243
import java.util.function.Predicate;
4344

45+
import org.junit.jupiter.api.BeforeAll;
46+
import org.junit.jupiter.api.BeforeEach;
4447
import org.junit.jupiter.api.Test;
4548
import org.junit.platform.commons.PreconditionViolationException;
49+
import org.junit.platform.commons.util.pkg1.SuperclassWithStaticPackagePrivateBeforeMethod;
50+
import org.junit.platform.commons.util.pkg1.subpkg.SubclassWithNonStaticPackagePrivateBeforeMethod;
4651

4752
/**
4853
* Unit tests for {@link AnnotationUtils}.
@@ -380,6 +385,28 @@ void findAnnotatedMethodsForAnnotationUsedInClassAndSuperclassHierarchyDown() th
380385
assertThat(methods.subList(1, 3)).containsOnly(method1, method3);
381386
}
382387

388+
/**
389+
* @see https://github.com/junit-team/junit5/issues/3498
390+
*/
391+
@Test
392+
void findAnnotatedMethodsAppliesPredicateBeforeSearchingTypeHierarchy() throws Exception {
393+
final String BEFORE = "before";
394+
Class<?> superclass = SuperclassWithStaticPackagePrivateBeforeMethod.class;
395+
Method beforeAllMethod = superclass.getDeclaredMethod(BEFORE);
396+
Class<?> subclass = SubclassWithNonStaticPackagePrivateBeforeMethod.class;
397+
Method beforeEachMethod = subclass.getDeclaredMethod(BEFORE);
398+
399+
// Prerequisite
400+
var methods = findAnnotatedMethods(superclass, BeforeAll.class, TOP_DOWN);
401+
assertThat(methods).containsExactly(beforeAllMethod);
402+
403+
// Actual use cases for this test
404+
methods = findAnnotatedMethods(subclass, BeforeAll.class, TOP_DOWN);
405+
assertThat(methods).containsExactly(beforeAllMethod);
406+
methods = findAnnotatedMethods(subclass, BeforeEach.class, TOP_DOWN);
407+
assertThat(methods).containsExactly(beforeEachMethod);
408+
}
409+
383410
@Test
384411
void findAnnotatedMethodsForAnnotationUsedInInterface() throws Exception {
385412
var interfaceMethod = InterfaceWithAnnotatedDefaultMethod.class.getDeclaredMethod("interfaceMethod");

platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java

+24
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@
7272
import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClass.StaticNestedSiblingClass;
7373
import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClassImplementingInterface.InnerClassImplementingInterface;
7474
import org.junit.platform.commons.util.classes.CustomType;
75+
import org.junit.platform.commons.util.pkg1.SuperclassWithStaticPackagePrivateBeforeMethod;
76+
import org.junit.platform.commons.util.pkg1.subpkg.SubclassWithNonStaticPackagePrivateBeforeMethod;
7577

7678
/**
7779
* Unit tests for {@link ReflectionUtils}.
@@ -1344,6 +1346,28 @@ void findMethodsIgnoresBridgeMethods() throws Exception {
13441346
assertEquals(0, methods.stream().filter(Method::isBridge).count());
13451347
}
13461348

1349+
/**
1350+
* @see https://github.com/junit-team/junit5/issues/3498
1351+
*/
1352+
@Test
1353+
void findMethodsAppliesPredicateBeforeSearchingTypeHierarchy() throws Exception {
1354+
final String BEFORE = "before";
1355+
Class<?> superclass = SuperclassWithStaticPackagePrivateBeforeMethod.class;
1356+
Method staticMethod = superclass.getDeclaredMethod(BEFORE);
1357+
Class<?> subclass = SubclassWithNonStaticPackagePrivateBeforeMethod.class;
1358+
Method nonStaticMethod = subclass.getDeclaredMethod(BEFORE);
1359+
1360+
// Prerequisite
1361+
var methods = findMethods(superclass, ReflectionUtils::isStatic);
1362+
assertThat(methods).containsExactly(staticMethod);
1363+
1364+
// Actual use cases for this test
1365+
methods = findMethods(subclass, ReflectionUtils::isStatic);
1366+
assertThat(methods).containsExactly(staticMethod);
1367+
methods = findMethods(subclass, ReflectionUtils::isNotStatic);
1368+
assertThat(methods).containsExactly(nonStaticMethod);
1369+
}
1370+
13471371
@Test
13481372
void isGeneric() {
13491373
for (var method : Generic.class.getMethods()) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2015-2023 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.platform.commons.util.pkg1;
12+
13+
import org.junit.jupiter.api.BeforeAll;
14+
15+
/**
16+
* @see https://github.com/junit-team/junit5/issues/3498
17+
*/
18+
public class SuperclassWithStaticPackagePrivateBeforeMethod {
19+
20+
@BeforeAll
21+
static void before() {
22+
// no-op
23+
}
24+
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2015-2023 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.platform.commons.util.pkg1.subpkg;
12+
13+
import org.junit.jupiter.api.BeforeEach;
14+
import org.junit.platform.commons.util.pkg1.SuperclassWithStaticPackagePrivateBeforeMethod;
15+
16+
/**
17+
* @see https://github.com/junit-team/junit5/issues/3498
18+
*/
19+
public class SubclassWithNonStaticPackagePrivateBeforeMethod extends SuperclassWithStaticPackagePrivateBeforeMethod {
20+
21+
@BeforeEach
22+
void before() {
23+
// no-op
24+
}
25+
26+
}

0 commit comments

Comments
 (0)