Skip to content

Commit e575ea3

Browse files
alemendoza-vsbrannen
authored andcommitted
Do not parse {displayName} for @ParameterizedTest using MessageFormat
Prior to this commit, if a @ParameterizedTest used the {displayName} placeholder to generate a display name and the value of the displayName contained an apostrophe (') or something resembling a MessageFormat element (such as {data}), JUnit threw an exception due to failure to parse the display name. This is applicable to method names in Kotlin-based tests or custom display names in general. To fix this bug, instead of replacing the DISPLAY_NAME_PLACEHOLDER before the MessageFormat is evaluated, the DISPLAY_NAME_PLACEHOLDER is now replaced with another temporary placeholder, which is then replaced after the MessageFormat has been evaluated. Closes #3235 Closes #3264
1 parent 82f1f4b commit e575ea3

File tree

4 files changed

+186
-2
lines changed

4 files changed

+186
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
[[release-notes-5.10.0-M1]]
2+
== 5.10.0-M1
3+
4+
*Date of Release:* ❓
5+
6+
*Scope:* ❓
7+
8+
For a complete list of all _closed_ issues and pull requests for this release, consult the
9+
link:{junit5-repo}+/milestone/65?closed=1+[5.10.0-M1] milestone page in the JUnit
10+
repository on GitHub.
11+
12+
13+
[[release-notes-5.10.0-M1-junit-platform]]
14+
=== JUnit Platform
15+
16+
==== Bug Fixes
17+
18+
* ❓
19+
20+
==== Deprecations and Breaking Changes
21+
22+
* Building native images with GraalVM now requires configuring the build arg
23+
`--initialize-at-build-time=org.junit.platform.launcher.core.LauncherConfig`.
24+
25+
==== New Features and Improvements
26+
27+
* Promote various "experimental" APIs that have matured to "stable" including
28+
`ModuleSelector`, `EngineDiscoveryListener`, `EngineDiscoveryRequestResolver`,
29+
`LauncherSession`, `LauncherSessionListener`, parallel execution support classes,
30+
`@Suite` and related annotations, and others.
31+
* All utility methods in `ReflectionSupport` that return a `List` now have counterparts
32+
which return a `Stream`.
33+
* For consistency with JUnit Jupiter lifecycle callbacks, listener method pairs for
34+
started/finished and opened/closed events are now invoked using "wrapping" semantics.
35+
This means that finished/closed event methods are invoked in reverse order compared to
36+
the corresponding started/opened event methods when multiple listeners are registered.
37+
This affects the following listener interfaces:
38+
`TestExecutionListener`, `EngineExecutionListener`, `LauncherDiscoveryListener`, and
39+
`LauncherSessionListener`.
40+
* New `LauncherInterceptor` SPI for intercepting the creation of instances of `Launcher`
41+
and `LauncherSessionlistener` as well as invocations of the `discover` and `execute`
42+
methods of the former. Please refer to the
43+
<<../user-guide/index.adoc#launcher-api-launcher-interceptors-custom, User Guide>> for
44+
details.
45+
* Support for limiting the `max-pool-size-factor` for parallel execution via a configuration parameter.
46+
* The new `testfeed` details mode for `ConsoleLauncher` prints test execution events as
47+
they occur in a concise format.
48+
49+
50+
[[release-notes-5.10.0-M1-junit-jupiter]]
51+
=== JUnit Jupiter
52+
53+
==== Bug Fixes
54+
55+
* The `{displayName}` placeholder of `@ParameterizedTest` is no longer parsed during the
56+
evaluation of the `MessageFormat`, now `@DisplayName` and Kotlin method names can contain
57+
single apostrophes and `MessageFormat` elements, such as `{data}`.
58+
59+
==== Deprecations and Breaking Changes
60+
61+
* The `dynamic` parallel execution strategy now allows the thread pool to be saturated by
62+
default.
63+
64+
==== New Features and Improvements
65+
66+
* Promote various "experimental" APIs that have matured to "stable" including
67+
`MethodOrderer`, `ClassOrderer`, `InvocationInterceptor`,
68+
`LifecycleMethodExecutionExceptionHandler`, `@TempDir`, parallel execution annotations,
69+
and others.
70+
* `@RepeatedTest` can now be configured with a failure threshold which signifies the
71+
number of failures after which remaining repetitions will be automatically skipped. See
72+
the <<../user-guide/index.adoc#writing-tests-repeated-tests, User Guide>> for details.
73+
* New `ArgumentsAccessor.getInvocationIndex()` method that supplies the index of a
74+
`@ParameterizedTest` invocation.
75+
* `@EmptySource` now supports additional types, including `Collection` and `Map` subtypes
76+
with a public no-arg constructor.
77+
* `DisplayNameGenerator` methods are now allowed to return `null`, in order to signal to
78+
fall back to the default display name generator.
79+
* New `AnnotationBasedArgumentsProvider` convenience base class which implements both
80+
`ArgumentsProvider` and `AnnotationConsumer`.
81+
* New `AnnotationBasedArgumentConverter` convenience base class which implements both
82+
`ArgumentConverter` and `AnnotationConsumer`.
83+
* New `junit.jupiter.execution.parallel.config.dynamic.max-pool-size-factor` configuration
84+
parameter to set the maximum pool size factor.
85+
* New `junit.jupiter.execution.parallel.config.dynamic.saturate` configuration
86+
parameter to disable pool saturation.
87+
88+
89+
[[release-notes-5.10.0-M1-junit-vintage]]
90+
=== JUnit Vintage
91+
92+
==== Bug Fixes
93+
94+
* ❓
95+
96+
==== Deprecations and Breaking Changes
97+
98+
* ❓
99+
100+
==== New Features and Improvements
101+
102+
* ❓

junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
class ParameterizedTestNameFormatter {
3232

3333
private static final char ELLIPSIS = '\u2026';
34+
private static final String DISPLAY_NAME_TEMPORARY_PLACEHOLDER = "__DISPLAY_NAME__";
3435

3536
private final String pattern;
3637
private final String displayName;
@@ -61,7 +62,8 @@ private String formatSafely(int invocationIndex, Object[] arguments) {
6162
String pattern = prepareMessageFormatPattern(invocationIndex, namedArguments);
6263
MessageFormat format = new MessageFormat(pattern);
6364
Object[] humanReadableArguments = makeReadable(format, namedArguments);
64-
return format.format(humanReadableArguments);
65+
String formatted = format.format(humanReadableArguments);
66+
return formatted.replace(DISPLAY_NAME_TEMPORARY_PLACEHOLDER, this.displayName);
6567
}
6668

6769
private Object[] extractNamedArguments(Object[] arguments) {
@@ -72,7 +74,7 @@ private Object[] extractNamedArguments(Object[] arguments) {
7274

7375
private String prepareMessageFormatPattern(int invocationIndex, Object[] arguments) {
7476
String result = pattern//
75-
.replace(DISPLAY_NAME_PLACEHOLDER, this.displayName)//
77+
.replace(DISPLAY_NAME_PLACEHOLDER, DISPLAY_NAME_TEMPORARY_PLACEHOLDER)//
7678
.replace(INDEX_PLACEHOLDER, String.valueOf(invocationIndex));
7779

7880
if (result.contains(ARGUMENTS_WITH_NAMES_PLACEHOLDER)) {

junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java

+18
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,24 @@ void formatsDisplayName() {
6060
assertEquals("enigma", formatter.format(2));
6161
}
6262

63+
@Test
64+
void formatsDisplayNameWithApostrophe() {
65+
String displayName = "display'Zero";
66+
var formatter = formatter(DISPLAY_NAME_PLACEHOLDER, "display'Zero");
67+
68+
assertEquals(displayName, formatter.format(1));
69+
assertEquals(displayName, formatter.format(2));
70+
}
71+
72+
@Test
73+
void formatsDisplayNameContainingFormatElements() {
74+
String displayName = "{enigma} {0} '{1}'";
75+
var formatter = formatter(DISPLAY_NAME_PLACEHOLDER, displayName);
76+
77+
assertEquals(displayName, formatter.format(1));
78+
assertEquals(displayName, formatter.format(2));
79+
}
80+
6381
@Test
6482
void formatsInvocationIndex() {
6583
var formatter = formatter(INDEX_PLACEHOLDER, "enigma");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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+
import org.junit.jupiter.api.Assertions.assertEquals
11+
import org.junit.jupiter.api.TestInfo
12+
import org.junit.jupiter.params.ParameterizedTest
13+
import org.junit.jupiter.params.provider.ValueSource
14+
15+
class ParameterizedTestNameFormatterIntegrationTests {
16+
17+
@ValueSource(strings = ["foo", "bar"])
18+
@ParameterizedTest
19+
fun `implicit'Name`(param: String, info: TestInfo) {
20+
if (param.equals("foo")) {
21+
assertEquals("[1] foo", info.displayName)
22+
} else {
23+
assertEquals("[2] bar", info.displayName)
24+
}
25+
}
26+
27+
@ValueSource(strings = ["foo", "bar"])
28+
@ParameterizedTest(name = "{0}")
29+
fun `zero'Only`(param: String, info: TestInfo) {
30+
if (param.equals("foo")) {
31+
assertEquals("foo", info.displayName)
32+
} else {
33+
assertEquals("bar", info.displayName)
34+
}
35+
}
36+
37+
@ValueSource(strings = ["foo", "bar"])
38+
@ParameterizedTest(name = "{displayName}")
39+
fun `displayName'Only`(param: String, info: TestInfo) {
40+
assertEquals("displayName'Only(String, TestInfo)", info.displayName)
41+
}
42+
43+
@ValueSource(strings = ["foo", "bar"])
44+
@ParameterizedTest(name = "{displayName} - {0}")
45+
fun `displayName'Zero`(param: String, info: TestInfo) {
46+
if (param.equals("foo")) {
47+
assertEquals("displayName'Zero(String, TestInfo) - foo", info.displayName)
48+
} else {
49+
assertEquals("displayName'Zero(String, TestInfo) - bar", info.displayName)
50+
}
51+
}
52+
53+
@ValueSource(strings = ["foo", "bar"])
54+
@ParameterizedTest(name = "{0} - {displayName}")
55+
fun `zero'DisplayName`(param: String, info: TestInfo) {
56+
if (param.equals("foo")) {
57+
assertEquals("foo - zero'DisplayName(String, TestInfo)", info.displayName)
58+
} else {
59+
assertEquals("bar - zero'DisplayName(String, TestInfo)", info.displayName)
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)