Skip to content

Commit dc753bd

Browse files
Exclude competing @Deprecated factory methods in fallback String-to-Object converter (#5186)
Extends the algorithm to exclude deprecated methods. This extension occurs after the existing logic completes. Fixes #4996 Co-authored-by: M.P. Korstanje <[email protected]>
1 parent 39899e0 commit dc753bd

File tree

4 files changed

+395
-25
lines changed

4 files changed

+395
-25
lines changed

documentation/modules/ROOT/pages/writing-tests/parameterized-classes-and-tests.adoc

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -913,8 +913,8 @@ integral types: `byte`, `short`, `int`, `long`, and their boxed counterparts.
913913

914914
In addition to implicit conversion from strings to the target types listed in the above
915915
table, JUnit Jupiter also provides a fallback mechanism for automatic conversion from a
916-
`String` to a given target type if the target type declares exactly one suitable _factory
917-
method_ or a _factory constructor_ as defined below.
916+
`String` to a given target type if the target type declares a suitable _factory method_
917+
or _factory constructor_ as defined below.
918918

919919
- __factory method__: a non-private, `static` method declared in the target type that
920920
accepts either a single `String` argument or a single `CharSequence` argument and
@@ -924,9 +924,17 @@ method_ or a _factory constructor_ as defined below.
924924
either a single `String` argument or a single `CharSequence` argument. Note that the
925925
target type must be declared as either a top-level class or as a `static` nested class.
926926

927-
NOTE: If multiple _factory methods_ are discovered, they will be ignored. If a _factory
928-
method_ and a _factory constructor_ are discovered, the factory method will be used
929-
instead of the constructor.
927+
If there are multiple _factory methods_ or _factory constructors_, matching proceeds in the
928+
following order:
929+
930+
1. A single _factory method_ accepting a `String` argument.
931+
2. A single _factory constructor_ accepting a `String` argument.
932+
3. A single _factory method_ accepting a `CharSequence` argument.
933+
4. A single _factory constructor_ accepting a `CharSequence` argument.
934+
5. A single _factory method_ accepting a `String` argument once all `@Deprecated` factory
935+
methods have been removed from the set of methods being considered.
936+
6. A single _factory method_ accepting a `CharSequence` argument once all `@Deprecated` factory
937+
methods have been removed from the set of methods being considered.
930938

931939
For example, in the following `@ParameterizedTest` method, the `Book` argument will be
932940
created by invoking the `Book.fromTitle(String)` factory method and passing `"42 Cats"`

documentation/modules/ROOT/partials/release-notes/release-notes-6.1.0-M2.adoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ repository on GitHub.
4848
* https://www.junit-pioneer.org/[JUnit Pioneer]'s `DefaultLocaleExtension` and
4949
`DefaultTimeZoneExtension` are now part of the JUnit Jupiter. Find examples in the
5050
xref:writing-tests/built-in-extensions.adoc#DefaultLocaleAndTimeZone[User Guide].
51+
* Exclude competing `@Deprecated` factory methods in
52+
xref:writing-tests/parameterized-classes-and-tests.adoc#tests-argument-conversion-implicit-fallback[fallback String-to-Object]
53+
converter.
5154
* Trim internal stack frames from `AssertionFailedError` stack traces.
5255
* Introduce new `trimStacktrace(Class<?>)` and `retainStackTraceElements(int)`
5356
methods for `AssertionFailureBuilder`. These allow user defined assertions to

junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverter.java

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import static org.junit.platform.commons.support.ModifierSupport.isNotStatic;
1616
import static org.junit.platform.commons.support.ReflectionSupport.findMethods;
1717
import static org.junit.platform.commons.support.ReflectionSupport.invokeMethod;
18+
import static org.junit.platform.commons.support.conversion.FallbackStringToObjectConverter.DeprecationStatus.EXCLUDE_DEPRECATED;
19+
import static org.junit.platform.commons.support.conversion.FallbackStringToObjectConverter.DeprecationStatus.INCLUDE_DEPRECATED;
1820
import static org.junit.platform.commons.util.ReflectionUtils.findConstructors;
1921
import static org.junit.platform.commons.util.ReflectionUtils.newInstance;
2022

@@ -32,8 +34,8 @@
3234
/**
3335
* {@code FallbackStringToObjectConverter} is a {@link StringToObjectConverter}
3436
* that provides a fallback conversion strategy for converting from a
35-
* {@link String} to a given target type by invoking a static factory method
36-
* or factory constructor defined in the target type.
37+
* {@link String} or {@link CharSequence} to a given target type by invoking a
38+
* static factory method or factory constructor defined in the target type.
3739
*
3840
* <h2>Search Algorithm</h2>
3941
*
@@ -92,12 +94,29 @@ public boolean canConvertTo(Class<?> targetType) {
9294
private static Function<String, @Nullable Object> findFactoryExecutable(Class<?> targetType) {
9395
return factoryExecutableCache.computeIfAbsent(targetType, type -> {
9496
// First, search for exact String argument matches.
95-
Function<String, @Nullable Object> factory = findFactoryExecutable(type, String.class);
97+
var factory = findFactoryMethodExecutable(type, String.class, INCLUDE_DEPRECATED);
98+
if (factory != null) {
99+
return factory;
100+
}
101+
factory = findFactoryConstructorExecutable(type, String.class);
96102
if (factory != null) {
97103
return factory;
98104
}
99105
// Second, fall back to CharSequence argument matches.
100-
factory = findFactoryExecutable(type, CharSequence.class);
106+
factory = findFactoryMethodExecutable(type, CharSequence.class, INCLUDE_DEPRECATED);
107+
if (factory != null) {
108+
return factory;
109+
}
110+
factory = findFactoryConstructorExecutable(type, CharSequence.class);
111+
if (factory != null) {
112+
return factory;
113+
}
114+
// Third, try factory methods again, but exclude deprecated methods
115+
factory = findFactoryMethodExecutable(type, String.class, EXCLUDE_DEPRECATED);
116+
if (factory != null) {
117+
return factory;
118+
}
119+
factory = findFactoryMethodExecutable(type, CharSequence.class, EXCLUDE_DEPRECATED);
101120
if (factory != null) {
102121
return factory;
103122
}
@@ -106,23 +125,28 @@ public boolean canConvertTo(Class<?> targetType) {
106125
});
107126
}
108127

109-
private static @Nullable Function<String, @Nullable Object> findFactoryExecutable(Class<?> targetType,
110-
Class<?> parameterType) {
111-
112-
Method factoryMethod = findFactoryMethod(targetType, parameterType);
128+
private static @Nullable Function<String, @Nullable Object> findFactoryMethodExecutable(Class<?> targetType,
129+
Class<?> parameterType, DeprecationStatus deprecationStatus) {
130+
Method factoryMethod = findFactoryMethod(targetType, parameterType, deprecationStatus);
113131
if (factoryMethod != null) {
114132
return source -> invokeMethod(factoryMethod, null, source);
115133
}
134+
return null;
135+
}
136+
137+
private static @Nullable Function<String, @Nullable Object> findFactoryConstructorExecutable(Class<?> targetType,
138+
Class<?> parameterType) {
116139
Constructor<?> constructor = findFactoryConstructor(targetType, parameterType);
117140
if (constructor != null) {
118141
return source -> newInstance(constructor, source);
119142
}
120143
return null;
121144
}
122145

123-
private static @Nullable Method findFactoryMethod(Class<?> targetType, Class<?> parameterType) {
124-
List<Method> factoryMethods = findMethods(targetType, new IsFactoryMethod(targetType, parameterType),
125-
BOTTOM_UP);
146+
private static @Nullable Method findFactoryMethod(Class<?> targetType, Class<?> parameterType,
147+
DeprecationStatus deprecationStatus) {
148+
var isFactoryMethod = new IsFactoryMethod(targetType, parameterType, deprecationStatus);
149+
List<Method> factoryMethods = findMethods(targetType, isFactoryMethod, BOTTOM_UP);
126150
if (factoryMethods.size() == 1) {
127151
return factoryMethods.get(0);
128152
}
@@ -138,12 +162,17 @@ public boolean canConvertTo(Class<?> targetType) {
138162
return null;
139163
}
140164

165+
enum DeprecationStatus {
166+
INCLUDE_DEPRECATED, EXCLUDE_DEPRECATED
167+
}
168+
141169
/**
142170
* {@link Predicate} that determines if the {@link Method} supplied to
143171
* {@link #test(Method)} is a non-private static factory method for the
144172
* supplied {@link #targetType} and {@link #parameterType}.
145173
*/
146-
record IsFactoryMethod(Class<?> targetType, Class<?> parameterType) implements Predicate<Method> {
174+
record IsFactoryMethod(Class<?> targetType, Class<?> parameterType, DeprecationStatus deprecationStatus)
175+
implements Predicate<Method> {
147176

148177
@Override
149178
public boolean test(Method method) {
@@ -154,6 +183,10 @@ public boolean test(Method method) {
154183
if (isNotStatic(method)) {
155184
return false;
156185
}
186+
if (deprecationStatus == DeprecationStatus.EXCLUDE_DEPRECATED
187+
&& method.getAnnotation(Deprecated.class) != null) {
188+
return false;
189+
}
157190
return isFactoryCandidate(method, this.parameterType);
158191
}
159192
}

0 commit comments

Comments
 (0)