Skip to content

Commit 50a8240

Browse files
committed
Quote text-based arguments in display names for parameterized tests
This commit introduces quoteTextArguments attributes in @⁠ParameterizedClass and @⁠ParameterizedTest which default to true. This new feature automatically encloses any CharSequence (such as a String) in double quotes (") and any Character in single quotes ('). Special characters will be escaped within quoted text. For example, '\n', '\r', will be escaped as "\\r" and "\\n", respectively. In addition, any ISO control character will be represented as a question mark (?) in the quoted text. For example, given a String argument "line 1\nline 2", the representation in the display name would be "\"line 1\\nline 2\"" (visually "line 1\nline 2") with the newline character escaped as "\\n". Similarly, given a String argument "\t", the representation in the display name would be "\"\\t\"" (visually "\t") instead of a blank string or invisible tab character. The same applies for a character argument '\t', whose representation in the display name would be "'\\t'" (visually '\t'). Closes #4716
1 parent 8bd6888 commit 50a8240

File tree

32 files changed

+597
-171
lines changed

32 files changed

+597
-171
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-RC1.adoc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ repository on GitHub.
4545
[[release-notes-6.0.0-RC1-junit-jupiter-new-features-and-improvements]]
4646
==== New Features and Improvements
4747

48-
* ❓
48+
* Text-based arguments in display names for parameterized tests are now quoted by default.
49+
In addition, special characters are escaped within quoted text. Please refer to the
50+
<<../user-guide/index.adoc#writing-tests-parameterized-tests-display-names-quoted-text,
51+
User Guide>> for details.
4952

5053

5154
[[release-notes-6.0.0-RC1-junit-vintage]]

documentation/src/docs/asciidoc/user-guide/writing-tests.adoc

Lines changed: 111 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1242,26 +1242,26 @@ Executing the above test class yields the following output:
12421242

12431243
....
12441244
FruitTests ✔
1245-
├─ [1] fruit = apple ✔
1245+
├─ [1] fruit = "apple"
12461246
│ └─ QuantityTests ✔
12471247
│ ├─ [1] quantity = 23 ✔
12481248
│ │ └─ test(Duration) ✔
1249-
│ │ ├─ [1] duration = PT1H ✔
1250-
│ │ └─ [2] duration = PT2H ✔
1249+
│ │ ├─ [1] duration = "PT1H"
1250+
│ │ └─ [2] duration = "PT2H"
12511251
│ └─ [2] quantity = 42 ✔
12521252
│ └─ test(Duration) ✔
1253-
│ ├─ [1] duration = PT1H ✔
1254-
│ └─ [2] duration = PT2H ✔
1255-
└─ [2] fruit = banana ✔
1253+
│ ├─ [1] duration = "PT1H"
1254+
│ └─ [2] duration = "PT2H"
1255+
└─ [2] fruit = "banana"
12561256
└─ QuantityTests ✔
12571257
├─ [1] quantity = 23 ✔
12581258
│ └─ test(Duration) ✔
1259-
│ ├─ [1] duration = PT1H ✔
1260-
│ └─ [2] duration = PT2H ✔
1259+
│ ├─ [1] duration = "PT1H"
1260+
│ └─ [2] duration = "PT2H"
12611261
└─ [2] quantity = 42 ✔
12621262
└─ test(Duration) ✔
1263-
├─ [1] duration = PT1H ✔
1264-
└─ [2] duration = PT2H ✔
1263+
├─ [1] duration = "PT1H"
1264+
└─ [2] duration = "PT2H"
12651265
....
12661266

12671267
[[writing-tests-dependency-injection]]
@@ -1649,9 +1649,9 @@ following.
16491649

16501650
....
16511651
palindromes(String) ✔
1652-
├─ [1] candidate = racecar ✔
1653-
├─ [2] candidate = radar ✔
1654-
└─ [3] candidate = able was I ere I saw elba ✔
1652+
├─ [1] candidate = "racecar"
1653+
├─ [2] candidate = "radar"
1654+
└─ [3] candidate = "able was I ere I saw elba"
16551655
....
16561656

16571657
The same `@ValueSource` annotation can be used to specify the source of arguments for a
@@ -1668,13 +1668,13 @@ following.
16681668

16691669
....
16701670
PalindromeTests ✔
1671-
├─ [1] candidate = racecar ✔
1671+
├─ [1] candidate = "racecar"
16721672
│ ├─ palindrome() ✔
16731673
│ └─ reversePalindrome() ✔
1674-
├─ [2] candidate = radar ✔
1674+
├─ [2] candidate = "radar"
16751675
│ ├─ palindrome() ✔
16761676
│ └─ reversePalindrome() ✔
1677-
└─ [3] candidate = able was I ere I saw elba ✔
1677+
└─ [3] candidate = "able was I ere I saw elba"
16781678
├─ palindrome() ✔
16791679
└─ reversePalindrome() ✔
16801680
....
@@ -2139,7 +2139,7 @@ It is also possible to provide a `Stream`, `DoubleStream`, `IntStream`, `LongStr
21392139
iterator is wrapped in a `java.util.function.Supplier`. The following example demonstrates
21402140
how to provide a `Supplier` of a `Stream` of named arguments. This parameterized test
21412141
method will be invoked twice: with the values `"apple"` and `"banana"` and with display
2142-
names `Apple` and `Banana`, respectively.
2142+
names `"Apple"` and `"Banana"`, respectively.
21432143

21442144
[source,java,indent=0]
21452145
----
@@ -2252,10 +2252,10 @@ void testWithCsvSource(String fruit, int rank) {
22522252
The generated display names for the previous example include the CSV header names.
22532253

22542254
----
2255-
[1] FRUIT = apple, RANK = 1
2256-
[2] FRUIT = banana, RANK = 2
2257-
[3] FRUIT = lemon, lime, RANK = 0xF1
2258-
[4] FRUIT = strawberry, RANK = 700_000
2255+
[1] FRUIT = "apple", RANK = "1"
2256+
[2] FRUIT = "banana", RANK = "2"
2257+
[3] FRUIT = "lemon, lime", RANK = "0xF1"
2258+
[4] FRUIT = "strawberry", RANK = "700_000"
22592259
----
22602260

22612261
In contrast to CSV records supplied via the `value` attribute, a text block can contain
@@ -2332,20 +2332,20 @@ The following listing shows the generated display names for the first two parame
23322332
test methods above.
23332333

23342334
----
2335-
[1] country = Sweden, reference = 1
2336-
[2] country = Poland, reference = 2
2337-
[3] country = United States of America, reference = 3
2338-
[4] country = France, reference = 700_000
2335+
[1] country = "Sweden", reference = "1"
2336+
[2] country = "Poland", reference = "2"
2337+
[3] country = "United States of America", reference = "3"
2338+
[4] country = "France", reference = "700_000"
23392339
----
23402340

23412341
The following listing shows the generated display names for the last parameterized test
23422342
method above that uses CSV header names.
23432343

23442344
----
2345-
[1] COUNTRY = Sweden, REFERENCE = 1
2346-
[2] COUNTRY = Poland, REFERENCE = 2
2347-
[3] COUNTRY = United States of America, REFERENCE = 3
2348-
[4] COUNTRY = France, REFERENCE = 700_000
2345+
[1] COUNTRY = "Sweden", REFERENCE = "1"
2346+
[2] COUNTRY = "Poland", REFERENCE = "2"
2347+
[3] COUNTRY = "United States of America", REFERENCE = "3"
2348+
[4] COUNTRY = "France", REFERENCE = "700_000"
23492349
----
23502350

23512351
In contrast to the default syntax used in `@CsvSource`, `@CsvFileSource` uses a double
@@ -2668,11 +2668,18 @@ include::{testDir}/example/ParameterizedTestDemo.java[tags=ArgumentsAggregator_w
26682668
==== Customizing Display Names
26692669

26702670
By default, the display name of a parameterized class or test invocation contains the
2671-
invocation index and the `String` representation of all arguments for that specific
2672-
invocation. Each argument is preceded by its parameter name (unless the argument is only
2673-
available via an `ArgumentsAccessor` or `ArgumentAggregator`), if the parameter name is
2674-
present in the bytecode (for Java, test code must be compiled with the `-parameters`
2675-
compiler flag; for Kotlin, with `-java-parameters`).
2671+
invocation index and a comma-separated list of the `String` representations of all
2672+
arguments for that specific invocation. If parameter names are present in the bytecode,
2673+
each argument will be preceded by its parameter name and an equals sign (unless the
2674+
argument is only available via an `ArgumentsAccessor` or `ArgumentAggregator`) – for
2675+
example, `firstName = "Jane"`.
2676+
2677+
[TIP]
2678+
====
2679+
To ensure that parameter names are present in the bytecode, test code must be compiled
2680+
with the `-parameters` compiler flag for Java or with the `-java-parameters` compiler flag
2681+
for Kotlin.
2682+
====
26762683

26772684
However, you can customize invocation display names via the `name` attribute of the
26782685
`@ParameterizedClass` or `@ParameterizedTest` annotation as in the following example.
@@ -2688,9 +2695,9 @@ the following.
26882695
26892696
....
26902697
Display name of container ✔
2691-
├─ 1 ==> the rank of 'apple' is 1
2692-
├─ 2 ==> the rank of 'banana' is 2
2693-
└─ 3 ==> the rank of 'lemon, lime' is 3
2698+
├─ 1 ==> the rank of "apple" is "1"
2699+
├─ 2 ==> the rank of "banana" is "2"
2700+
└─ 3 ==> the rank of "lemon, lime" is "3"
26942701
....
26952702
======
26962703

@@ -2777,6 +2784,70 @@ Note that `argumentSet(String, Object...)` is a static factory method defined in
27772784
`org.junit.jupiter.params.provider.Arguments` interface.
27782785
====
27792786

2787+
[[writing-tests-parameterized-tests-display-names-quoted-text]]
2788+
===== Quoted Text-based Arguments
2789+
2790+
As of JUnit Jupiter 6.0, text-based arguments in display names for parameterized tests are
2791+
quoted by default. In this context, any `CharSequence` (such as a `String`) or `Character`
2792+
is considered text. A `CharSequence` is wrapped in double quotes (`"`), and a `Character`
2793+
is wrapped in single quotes (`'`).
2794+
2795+
Special characters will be escaped in the quoted text. For example, carriage returns and
2796+
line feeds will be escaped as `\\r` and `\\n`, respectively. In addition, any ISO control
2797+
character will be represented as a question mark (`?`) in the quoted text.
2798+
2799+
[TIP]
2800+
====
2801+
This feature can be disabled by setting the `quoteTextArguments` attributes in
2802+
`@ParameterizedClass` and `@ParameterizedTest` to `false`.
2803+
====
2804+
2805+
For example, given a string argument `"line 1\nline 2"`, the physical representation in
2806+
the display name will be `"\"line 1\\nline 2\""` which is printed as `"line 1\nline 2"`.
2807+
Similarly, given a string argument `"\t"`, the physical representation in the display name
2808+
will be `"\"\\t\""` which is printed as `"\t"` instead of a blank string or invisible tab
2809+
character. The same applies for a character argument `'\t'`, whose physical representation
2810+
in the display name would be `"'\\t'"` which is printed as `'\t'`.
2811+
2812+
For a concrete example, if you run the first `nullEmptyAndBlankStrings(String text)`
2813+
parameterized test method from the
2814+
<<writing-tests-parameterized-tests-sources-null-and-empty>> section above, the following
2815+
display names are generated.
2816+
2817+
----
2818+
[1] text = null
2819+
[2] text = ""
2820+
[3] text = " "
2821+
[4] text = " "
2822+
[5] text = "\t"
2823+
[6] text = "\n"
2824+
----
2825+
2826+
If you run the first `testWithCsvSource(String fruit, int rank)` parameterized test method
2827+
from the <<writing-tests-parameterized-tests-sources-CsvSource>> section above, the
2828+
following display names are generated.
2829+
2830+
----
2831+
[1] fruit = "apple", rank = "1"
2832+
[2] fruit = "banana", rank = "2"
2833+
[3] fruit = "lemon, lime", rank = "0xF1"
2834+
[4] fruit = "strawberry", rank = "700_000"
2835+
----
2836+
2837+
[NOTE]
2838+
====
2839+
The original source arguments are quoted when generating a display name, and this occurs
2840+
before any implicit or explicit argument conversion is performed.
2841+
2842+
For example, if a parameterized test accepts `3.14` as a `float` argument that was
2843+
converted from `"3.14"` as an input string, `"3.14"` will be present in the display name
2844+
instead of `3.14`. You can see the effect of this with the `rank` values in the above
2845+
example.
2846+
====
2847+
2848+
[[writing-tests-parameterized-tests-display-names-default-pattern]]
2849+
===== Default Display Name Pattern
2850+
27802851
If you'd like to set a default name pattern for all parameterized classes and tests in
27812852
your project, you can declare the `junit.jupiter.params.displayname.default` configuration
27822853
parameter in the `junit-platform.properties` file as demonstrated in the following example (see
@@ -2787,6 +2858,9 @@ parameter in the `junit-platform.properties` file as demonstrated in the followi
27872858
junit.jupiter.params.displayname.default = {index}
27882859
----
27892860

2861+
[[writing-tests-parameterized-tests-display-names-precedence-rules]]
2862+
===== Precedence Rules
2863+
27902864
The display name for a parameterized class or test is determined according to the
27912865
following precedence rules:
27922866

documentation/src/test/java/example/ParameterizedTestDemo.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,7 @@ void testWithCustomAggregatorAnnotation(@CsvToPerson Person person) {
582582

583583
// tag::custom_display_names[]
584584
@DisplayName("Display name of container")
585-
@ParameterizedTest(name = "{index} ==> the rank of ''{0}'' is {1}")
585+
@ParameterizedTest(name = "{index} ==> the rank of {0} is {1}")
586586
@CsvSource({ "apple, 1", "banana, 2", "'lemon, lime', 3" })
587587
void testWithCustomDisplayNames(String fruit, int rank) {
588588
}

junit-jupiter-params/src/jmh/java/org/junit/jupiter/params/ParameterizedInvocationNameFormatterBenchmarks.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public void formatTestNames(Blackhole blackhole) throws Exception {
5656
512);
5757
for (int i = 0; i < argumentsList.size(); i++) {
5858
Arguments arguments = argumentsList.get(i);
59-
blackhole.consume(formatter.format(i, EvaluatedArgumentSet.allOf(arguments)));
59+
blackhole.consume(formatter.format(i, EvaluatedArgumentSet.allOf(arguments), false));
6060
}
6161
}
6262

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ int getConsumedLength() {
7575
}
7676

7777
@Nullable
78-
Object[] getConsumedNames() {
79-
return extractFromNamed(this.consumed, Named::getName);
78+
Object[] getConsumedArguments() {
79+
return this.consumed;
8080
}
8181

8282
@Nullable

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,9 +198,50 @@
198198
* a flag rather than a placeholder.
199199
*
200200
* @see java.text.MessageFormat
201+
* @see #quoteTextArguments()
201202
*/
202203
String name() default ParameterizedInvocationNameFormatter.DEFAULT_DISPLAY_NAME;
203204

205+
/**
206+
* Configure whether to enclose text-based argument values in quotes within
207+
* display names.
208+
*
209+
* <p>Defaults to {@code true}.
210+
*
211+
* <p>In this context, any {@link CharSequence} (such as a {@link String})
212+
* or {@link Character} is considered text. A {@code CharSequence} is wrapped
213+
* in double quotes ("), and a {@code Character} is wrapped in single quotes
214+
* (').
215+
*
216+
* <p>Special characters in Java strings and characters will be escaped in the
217+
* quoted text &mdash; for example, carriage returns and line feeds will be
218+
* escaped as {@code \\r} and {@code \\n}, respectively. In addition, any
219+
* {@linkplain Character#isISOControl(char) ISO control character} will be
220+
* represented as a question mark (?) in the quoted text.
221+
*
222+
* <p>For example, given a string argument {@code "line 1\nline 2"}, the
223+
* representation in the display name would be {@code "\"line 1\\nline 2\""}
224+
* (printed as {@code "line 1\nline 2"}) with the newline character escaped as
225+
* {@code "\\n"}. Similarly, given a string argument {@code "\t"}, the
226+
* representation in the display name would be {@code "\"\\t\""} (printed as
227+
* {@code "\t"}) instead of a blank string or invisible tab
228+
* character. The same applies for a character argument {@code '\t'}, whose
229+
* representation in the display name would be {@code "'\\t'"} (printed as
230+
* {@code '\t'}).
231+
*
232+
* <p>Please note that original source arguments are quoted when generating
233+
* a display name, before any implicit or explicit argument conversion is
234+
* performed. For example, if a parameterized class accepts {@code 3.14} as a
235+
* {@code float} argument that was converted from {@code "3.14"} as an input
236+
* string, {@code "3.14"} will be present in the display name instead of
237+
* {@code 3.14}.
238+
*
239+
* @since 6.0
240+
* @see #name()
241+
*/
242+
@API(status = EXPERIMENTAL, since = "6.0")
243+
boolean quoteTextArguments() default true;
244+
204245
/**
205246
* Configure whether all arguments of the parameterized class that implement
206247
* {@link AutoCloseable} will be closed after their corresponding

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ public String getDisplayNamePattern() {
102102
return this.annotation.name();
103103
}
104104

105+
@Override
106+
public boolean quoteTextArguments() {
107+
return this.annotation.quoteTextArguments();
108+
}
109+
105110
@Override
106111
public boolean isAutoClosingArguments() {
107112
return this.annotation.autoCloseArguments();

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ interface ParameterizedDeclarationContext<C> {
2828

2929
String getDisplayNamePattern();
3030

31+
boolean quoteTextArguments();
32+
3133
boolean isAutoClosingArguments();
3234

3335
boolean isAllowingZeroInvocations();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class ParameterizedInvocationContext<T extends ParameterizedDeclarationContext<?
4343
}
4444

4545
public String getDisplayName(int invocationIndex) {
46-
return this.formatter.format(invocationIndex, this.arguments);
46+
return this.formatter.format(invocationIndex, this.arguments, this.declarationContext.quoteTextArguments());
4747
}
4848

4949
public void prepareInvocation(ExtensionContext context) {

0 commit comments

Comments
 (0)