Skip to content

Commit cc2f60c

Browse files
yhkuo41marcphilipp
andauthored
Introduce from and to attributes on @EnumSource (#4221)
The new attributes allow defining the range of enum constants to be tested. Resolves #4185. --------- Co-authored-by: Marc Philipp <[email protected]>
1 parent bebff41 commit cc2f60c

File tree

6 files changed

+215
-31
lines changed

6 files changed

+215
-31
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ JUnit repository on GitHub.
137137
thread dump to `System.out` prior to interrupting a test thread due to a timeout.
138138
* `TestReporter` now allows publishing files for a test method or test class which can be
139139
used to include them in test reports, such as the Open Test Reporting format.
140+
* New `from` and `to` attributes added to `@EnumSource` to support range selection of
141+
enum constants.
140142
* Auto-registered extensions can now be
141143
<<../user-guide/index.adoc#extensions-registration-automatic-filtering, filtered>> using
142144
include and exclude patterns that can be specified as configuration parameters.

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

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1560,14 +1560,28 @@ include::{testDir}/example/ParameterizedTestDemo.java[tags=EnumSource_example_au
15601560
----
15611561

15621562
The annotation provides an optional `names` attribute that lets you specify which
1563-
constants shall be used, like in the following example. If omitted, all constants will be
1564-
used.
1563+
constants shall be used, like in the following example.
15651564

15661565
[source,java,indent=0]
15671566
----
15681567
include::{testDir}/example/ParameterizedTestDemo.java[tags=EnumSource_include_example]
15691568
----
15701569

1570+
In addition to `names`, you can use the `from` and `to` attributes to specify a range of
1571+
constants. The range starts from the constant specified in the `from` attribute and
1572+
includes all subsequent constants up to and including the one specified in the `to`
1573+
attribute, based on the natural order of the enum constants.
1574+
1575+
If `from` and `to` attributes are omitted, they default to the first and last constants
1576+
in the enum type, respectively. If all `names`, `from`, and `to` attributes are omitted,
1577+
all constants will be used. The following example demonstrates how to specify a range of
1578+
constants.
1579+
1580+
[source,java,indent=0]
1581+
----
1582+
include::{testDir}/example/ParameterizedTestDemo.java[tags=EnumSource_range_example]
1583+
----
1584+
15711585
The `@EnumSource` annotation also provides an optional `mode` attribute that enables
15721586
fine-grained control over which constants are passed to the test method. For example, you
15731587
can exclude names from the enum constant pool or specify regular expressions as in the
@@ -1583,6 +1597,14 @@ include::{testDir}/example/ParameterizedTestDemo.java[tags=EnumSource_exclude_ex
15831597
include::{testDir}/example/ParameterizedTestDemo.java[tags=EnumSource_regex_example]
15841598
----
15851599

1600+
You can also combine `mode` with the `from`, `to` and `names` attributes to define a
1601+
range of constants while excluding specific values from that range as shown below.
1602+
1603+
[source,java,indent=0]
1604+
----
1605+
include::{testDir}/example/ParameterizedTestDemo.java[tags=EnumSource_range_exclude_example]
1606+
----
1607+
15861608
[[writing-tests-parameterized-tests-sources-MethodSource]]
15871609
===== @MethodSource
15881610

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,14 @@ void testWithEnumSourceInclude(ChronoUnit unit) {
148148
}
149149
// end::EnumSource_include_example[]
150150

151+
// tag::EnumSource_range_example[]
152+
@ParameterizedTest
153+
@EnumSource(from = "HOURS", to = "DAYS")
154+
void testWithEnumSourceRange(ChronoUnit unit) {
155+
assertTrue(EnumSet.of(ChronoUnit.HOURS, ChronoUnit.HALF_DAYS, ChronoUnit.DAYS).contains(unit));
156+
}
157+
// end::EnumSource_range_example[]
158+
151159
// tag::EnumSource_exclude_example[]
152160
@ParameterizedTest
153161
@EnumSource(mode = EXCLUDE, names = { "ERAS", "FOREVER" })
@@ -164,6 +172,15 @@ void testWithEnumSourceRegex(ChronoUnit unit) {
164172
}
165173
// end::EnumSource_regex_example[]
166174

175+
// tag::EnumSource_range_exclude_example[]
176+
@ParameterizedTest
177+
@EnumSource(from = "HOURS", to = "DAYS", mode = EXCLUDE, names = { "HALF_DAYS" })
178+
void testWithEnumSourceRangeExclude(ChronoUnit unit) {
179+
assertTrue(EnumSet.of(ChronoUnit.HOURS, ChronoUnit.DAYS).contains(unit));
180+
assertFalse(EnumSet.of(ChronoUnit.HALF_DAYS).contains(unit));
181+
}
182+
// end::EnumSource_range_exclude_example[]
183+
167184
// tag::simple_MethodSource_example[]
168185
@ParameterizedTest
169186
@MethodSource("stringProvider")

junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumArgumentsProvider.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,19 @@ protected Stream<? extends Arguments> provideArguments(ExtensionContext context,
4343

4444
private <E extends Enum<E>> Set<? extends E> getEnumConstants(ExtensionContext context, EnumSource enumSource) {
4545
Class<E> enumClass = determineEnumClass(context, enumSource);
46-
return EnumSet.allOf(enumClass);
46+
E[] constants = enumClass.getEnumConstants();
47+
if (constants.length == 0) {
48+
Preconditions.condition(enumSource.from().isEmpty() && enumSource.to().isEmpty(),
49+
"No enum constant in " + enumClass.getSimpleName() + ", but 'from' or 'to' is not empty.");
50+
return EnumSet.noneOf(enumClass);
51+
}
52+
E from = enumSource.from().isEmpty() ? constants[0] : Enum.valueOf(enumClass, enumSource.from());
53+
E to = enumSource.to().isEmpty() ? constants[constants.length - 1] : Enum.valueOf(enumClass, enumSource.to());
54+
Preconditions.condition(from.compareTo(to) <= 0,
55+
() -> String.format(
56+
"Invalid enum range: 'from' (%s) must come before 'to' (%s) in the natural order of enum constants.",
57+
from, to));
58+
return EnumSet.range(from, to);
4759
}
4860

4961
@SuppressWarnings({ "unchecked", "rawtypes" })

junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSource.java

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@
4040
* attribute. Otherwise, the declared type of the first parameter of the
4141
* {@code @ParameterizedTest} method is used.
4242
*
43-
* <p>The set of enum constants can be restricted via the {@link #names} and
44-
* {@link #mode} attributes.
43+
* <p>The set of enum constants can be restricted via the {@link #names},
44+
* {@link #from}, {@link #to} and {@link #mode} attributes.
4545
*
4646
* @since 5.0
4747
* @see org.junit.jupiter.params.provider.ArgumentsSource
@@ -63,6 +63,8 @@
6363
* first parameter of the {@code @ParameterizedTest} method is used.
6464
*
6565
* @see #names
66+
* @see #from
67+
* @see #to
6668
* @see #mode
6769
*/
6870
Class<? extends Enum<?>> value() default NullEnum.class;
@@ -71,19 +73,61 @@
7173
* The names of enum constants to provide, or regular expressions to select
7274
* the names of enum constants to provide.
7375
*
74-
* <p>If no names or regular expressions are specified, all enum constants
75-
* declared in the specified {@linkplain #value enum type} will be provided.
76+
* <p>If no names or regular expressions are specified, and neither {@link #from}
77+
* nor {@link #to} are specified, all enum constants declared in the specified
78+
* {@linkplain #value enum type} will be provided.
79+
*
80+
* <p>If {@link #from} or {@link #to} are specified, the elements in names must
81+
* fall within the range defined by {@link #from} and {@link #to}.
7682
*
7783
* <p>The {@link #mode} determines how the names are interpreted.
7884
*
7985
* @see #value
86+
* @see #from
87+
* @see #to
8088
* @see #mode
8189
*/
8290
String[] names() default {};
8391

92+
/**
93+
* The starting enum constant of the range to be included.
94+
*
95+
* <p>Defaults to an empty string, where the range starts from the first enum
96+
* constant of the specified {@linkplain #value enum type}.
97+
*
98+
* @see #value
99+
* @see #names
100+
* @see #to
101+
* @see #mode
102+
*
103+
* @since 5.12
104+
*/
105+
@API(status = EXPERIMENTAL, since = "5.12")
106+
String from() default "";
107+
108+
/**
109+
* The ending enum constant of the range to be included.
110+
*
111+
* <p>Defaults to an empty string, where the range ends at the last enum
112+
* constant of the specified {@linkplain #value enum type}.
113+
*
114+
* @see #value
115+
* @see #names
116+
* @see #from
117+
* @see #mode
118+
*
119+
* @since 5.12
120+
*/
121+
@API(status = EXPERIMENTAL, since = "5.12")
122+
String to() default "";
123+
84124
/**
85125
* The enum constant selection mode.
86126
*
127+
* <p>The mode only applies to the {@link #names} attribute and does not change
128+
* the behavior of {@link #from} and {@link #to}, which always define a range
129+
* based on the natural order of the enum constants.
130+
*
87131
* <p>Defaults to {@link Mode#INCLUDE INCLUDE}.
88132
*
89133
* @see Mode#INCLUDE
@@ -92,6 +136,8 @@
92136
* @see Mode#MATCH_ANY
93137
* @see Mode#MATCH_NONE
94138
* @see #names
139+
* @see #from
140+
* @see #to
95141
*/
96142
Mode mode() default Mode.INCLUDE;
97143

0 commit comments

Comments
 (0)