This is an automated email from the ASF dual-hosted git repository.
ashishvijaywargiya pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git
The following commit(s) were added to refs/heads/trunk by this push:
new bbb254dd71 Skip wasted testdef discovery, honor @DisplayName in
Jupiter reports, and flag classes missing @Order (#1572)
bbb254dd71 is described below
commit bbb254dd71958b333fbc418d8923cfbde98121af
Author: Ashish Vijaywargiya <[email protected]>
AuthorDate: Thu Aug 6 16:37:24 2026 +0530
Skip wasted testdef discovery, honor @DisplayName in Jupiter reports, and
flag classes missing @Order (#1572)
Moves the suitename= filter check in JunitSuiteWrapper before
ModelTestSuite construction, so a testdef file the filter discards no
longer pays for a
Delegator/LocalDispatcher pair or a full JUnit Platform Launcher discovery
pass.
Jupiter test reports (testIntegration console output, JUnit XML/HTML)
now honor @DisplayName on plain @Test methods. The method name stays the
primary, always-present part of the reported name, with a real
@DisplayName appended after " - " instead of replacing it - keeps a report
entry easy
to trace back to its source. @DisplayName added to EntityTestSuite's 34
methods as the first real usage; unit-tests.adoc documents the format.
Adds a report-only flagUnorderedJupiterTests Gradle task that lists any
testdef-registered Jupiter class with more than one test method and zero
@Order annotations at all. Not wired into check, since many multi-method
classes are legitimately order-independent - a human decides what to do
with the list.
Verified: entitytests and example-tests suites via testIntegration
(covers both the @DisplayName and the no-@DisplayName/@ParameterizedTest
cases),
flagUnorderedJupiterTests against the real tree, checkstyleMain/
checkstyleTest, and codenarcMain/codenarcTest all clean.
---
build.gradle | 98 ++++++++++++++++++++++
.../apache/ofbiz/entity/test/EntityTestSuite.java | 35 ++++++++
.../testtools/src/docs/asciidoc/unit-tests.adoc | 6 ++
.../apache/ofbiz/testtools/JunitSuiteWrapper.java | 13 ++-
.../ofbiz/testtools/JupiterTestExtension.java | 56 ++++++++++---
5 files changed, 193 insertions(+), 15 deletions(-)
diff --git a/build.gradle b/build.gradle
index 9ac503e168..c68f8f4187 100644
--- a/build.gradle
+++ b/build.gradle
@@ -501,6 +501,104 @@ task verifyNoBareJupiterExtendWith(group: 'Verification')
{
}
check.dependsOn verifyNoBareJupiterExtendWith
+// junit5-improvements item 3: the 8 real ordering bugs found during the
JUnit3->JUnit5
+// migration were caught reactively (a suite actually failing under a full
testIntegration
+// run), not via a systematic per-file audit - only 7 of 82 migrated classes
were ever
+// spot-checked. A multi-method Jupiter class with zero @Order annotations at
all is
+// implicitly relying on whatever order the JVM happens to return its test
methods in - the
+// exact shape of bug that produced those 8 failures. This task flags that
pattern so it's at
+// least visible, rather than depending on tests happening to catch it a
second time.
+// Deliberately NOT wired into check: plenty of multi-method classes are
legitimately
+// order-independent and would never need @Order, so hard-failing the build
here would be
+// noise, not signal - a human triages the flagged list instead. Reuses
+// verifyTestdefClassNames' testdef-XML-scan + classloader pattern above.
+task flagUnorderedJupiterTests(group: 'Verification', dependsOn: testClasses) {
+ description = 'Lists testdef-registered Jupiter classes with more than one
test method ' +
+ 'and no @Order annotations at all - report-only, never fails the
build'
+ def testdefDirs = getDirectoryInActiveComponentsIfExists('testdef')
+ def testdefXmlFiles = files(testdefDirs.collect { fileTree(it) { include
'**/*.xml' } })
+ def testRuntimeClasspath = sourceSets.test.runtimeClasspath
+ inputs.files(testdefXmlFiles)
+ inputs.files(testRuntimeClasspath)
+ doLast {
+ URL[] classpathUrls = testRuntimeClasspath.files.collect {
it.toURI().toURL() }
+ new URLClassLoader(classpathUrls,
getClass().classLoader).withCloseable { classpathLoader ->
+ def testAnnotation
+ def paramTestAnnotation
+ def orderAnnotation
+ try {
+ testAnnotation =
classpathLoader.loadClass('org.junit.jupiter.api.Test')
+ paramTestAnnotation =
classpathLoader.loadClass('org.junit.jupiter.params.ParameterizedTest')
+ orderAnnotation =
classpathLoader.loadClass('org.junit.jupiter.api.Order')
+ } catch (Throwable t) {
+ logger.lifecycle("flagUnorderedJupiterTests: could not load
JUnit Jupiter annotation "
+ + "classes on the test runtime classpath - skipping
check (${t}).")
+ return
+ }
+ Set<String> seenClasses = []
+ List<String> flagged = []
+ int skipped = 0
+ testdefXmlFiles.each { xmlFile ->
+ def root
+ try {
+ root = new XmlParser(false, false).parse(xmlFile)
+ } catch (Throwable ignored) {
+ skipped++
+ return // malformed/unreadable testdef XML - not this
task's job to fail on
+ }
+ root.depthFirst()
+ .findAll { it.name() == 'jupiter-test-suite' }
+ .each { node ->
+ String className = node.'@class-name'
+ if (!className || !seenClasses.add(className)) {
+ return // no class-name, or already evaluated from
another testdef file
+ }
+ try {
+ Class<?> clz = Class.forName(className, false,
classpathLoader)
+ // declaredMethods (not the inherited-methods
variant): every Jupiter
+ // test method in this codebase is declared
directly on its own test
+ // class today, not inherited from a shared base
class - if a future
+ // class inherits test methods from a common base,
this task would
+ // silently miss them.
+ def testMethods = clz.declaredMethods.findAll {
+ it.isAnnotationPresent(testAnnotation) ||
it.isAnnotationPresent(paramTestAnnotation)
+ }
+ boolean anyOrdered = testMethods.any {
it.isAnnotationPresent(orderAnnotation) }
+ // Flags only classes with zero total @Order
usage. A class with
+ // @Order on some but not all methods is NOT
flagged, even though its
+ // unannotated methods still get an arbitrary
tie-broken position
+ // under the pinned OrderAnnotation orderer - a
clean run here means
+ // "no class has zero @Order," not "every class is
fully ordered."
+ if (testMethods.size() > 1 && !anyOrdered) {
+ flagged << "${className}
(${testMethods.size()} test methods, no @Order) - ${xmlFile}"
+ }
+ } catch (Throwable ignored) {
+ // Class.forName or the reflection calls below it
can throw for a
+ // testdef entry that doesn't resolve cleanly on
this classpath -
+ // verifyTestdefClassNames (above) is what fails
the build for that
+ // condition; this task only needs to not crash on
it, but a silently
+ // dropped class would make a clean run
indistinguishable from one
+ // that just failed to check everything, so it's
counted instead.
+ skipped++
+ }
+ }
+ }
+ String skipNote = skipped > 0
+ ? " (${skipped} class(es) or testdef file(s) could not be
loaded/parsed cleanly and were skipped)"
+ : ''
+ if (flagged.isEmpty()) {
+ logger.lifecycle('flagUnorderedJupiterTests: no
testdef-registered Jupiter class relies on '
+ + "implicit declaration order.${skipNote}")
+ } else {
+ logger.lifecycle('flagUnorderedJupiterTests: the following
classes have more than one test '
+ + 'method and no @Order annotations at all - they are
implicitly relying on '
+ + "declaration order:${skipNote}\n"
+ + flagged.collect { " - ${it}" }.join('\n'))
+ }
+ }
+ }
+}
+
/* ========================================================
* Tasks
* ======================================================== */
diff --git
a/framework/entity/src/test/java/org/apache/ofbiz/entity/test/EntityTestSuite.java
b/framework/entity/src/test/java/org/apache/ofbiz/entity/test/EntityTestSuite.java
index d301795b09..6cdeb5823f 100644
---
a/framework/entity/src/test/java/org/apache/ofbiz/entity/test/EntityTestSuite.java
+++
b/framework/entity/src/test/java/org/apache/ofbiz/entity/test/EntityTestSuite.java
@@ -70,6 +70,7 @@ import org.apache.ofbiz.entity.util.EntitySaxReader;
import org.apache.ofbiz.entity.util.SequenceUtil;
import org.apache.ofbiz.testtools.JunitJupiterTest;
import org.apache.ofbiz.testtools.JupiterTestHelper;
+import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
@@ -92,6 +93,7 @@ public class EntityTestSuite implements JupiterTestHelper {
* Test models.
* @throws Exception the exception
*/
+ @DisplayName("Test entity models")
@Test
@Order(1)
public void testModels() throws Exception {
@@ -112,6 +114,7 @@ public class EntityTestSuite implements JupiterTestHelper {
/**
* Tests storing values with the delegator's .create, .makeValue, and
.storeAll methods
*/
+ @DisplayName("Store values via create, makeValue, and storeAll")
@Test
@Order(2)
public void testMakeValue() throws Exception {
@@ -140,6 +143,7 @@ public class EntityTestSuite implements JupiterTestHelper {
/**
* Tests updating entities by doing a GenericValue .put(key, value) and
.store()
*/
+ @DisplayName("Update an entity via put and store")
@Test
@Order(3)
public void testUpdateValue() throws Exception {
@@ -172,6 +176,7 @@ public class EntityTestSuite implements JupiterTestHelper {
* Test remove value.
* @throws Exception the exception
*/
+ @DisplayName("Remove a value and reject further mutation")
@Test
@Order(4)
public void testRemoveValue() throws Exception {
@@ -203,6 +208,7 @@ public class EntityTestSuite implements JupiterTestHelper {
* Test to load huge entity
* @throws Exception the exception
*/
+ @DisplayName("Batch-create many values at once")
@Test
@Order(5)
public void testCreateAllValues() throws Exception {
@@ -226,6 +232,7 @@ public class EntityTestSuite implements JupiterTestHelper {
* Tests the entity cache
* @throws Exception the exception
*/
+ @DisplayName("Entity cache reflects create, update, and remove")
@Test
@Order(6)
public void testEntityCache() throws Exception {
@@ -361,6 +368,7 @@ public class EntityTestSuite implements JupiterTestHelper {
* Test xml serialization.
* @throws Exception the exception
*/
+ @DisplayName("Round-trip a value through XML serialization")
@Test
@Order(7)
public void testXmlSerialization() throws Exception {
@@ -412,6 +420,7 @@ public class EntityTestSuite implements JupiterTestHelper {
/**
* Tests storing data with the delegator's .create method. Also tests
.findCountByCondition and .getNextSeqId
*/
+ @DisplayName("Create a node tree and verify the count")
@Test
@Order(8)
public void testCreateTree() throws Exception {
@@ -429,6 +438,7 @@ public class EntityTestSuite implements JupiterTestHelper {
/**
* More tests of storing data with .storeAll. Also prepares data for
testing view-entities (see below.)
*/
+ @DisplayName("Add members to the tree via storeAll")
@Test
@Order(9)
public void testAddMembersToTree() throws Exception {
@@ -518,6 +528,7 @@ public class EntityTestSuite implements JupiterTestHelper {
/**
* Tests findByCondition and tests searching on a view-entity
*/
+ @DisplayName("Count matches on a view-entity")
@Test
@Order(10)
public void testCountViews() throws Exception {
@@ -549,6 +560,7 @@ public class EntityTestSuite implements JupiterTestHelper {
/**
* Tests findByCondition and a find by distinct
*/
+ @DisplayName("Find distinct values by condition")
@Test
@Order(11)
public void testFindDistinct() throws Exception {
@@ -589,6 +601,7 @@ public class EntityTestSuite implements JupiterTestHelper {
/**
* Tests a findByCondition using not like
*/
+ @DisplayName("Find by condition using NOT LIKE")
@Test
@Order(12)
public void testNotLike() throws Exception {
@@ -608,6 +621,7 @@ public class EntityTestSuite implements JupiterTestHelper {
/**
* Tests foreign key integrity by trying to remove an entity which has
foreign-key dependencies. Should cause an exception.
*/
+ @DisplayName("Reject create referencing a non-existent foreign key")
@Test
@Order(13)
public void testForeignKeyCreate() {
@@ -634,6 +648,7 @@ public class EntityTestSuite implements JupiterTestHelper {
/**
* Tests foreign key integrity by trying to remove an entity which has
foreign-key dependencies. Should cause an exception.
*/
+ @DisplayName("Reject removal of a node still referenced by children")
@Test
@Order(14)
public void testForeignKeyRemove() throws Exception {
@@ -670,6 +685,7 @@ public class EntityTestSuite implements JupiterTestHelper {
/**
* Tests the .getRelatedOne method and removeAll for removing entities
*/
+ @DisplayName("Remove related entities via getRelatedOne and removeAll")
@Test
@Order(15)
public void testRemoveNodeMemberAndTesting() throws Exception {
@@ -708,6 +724,7 @@ public class EntityTestSuite implements JupiterTestHelper {
/**
* Tests the storeByCondition operation
*/
+ @DisplayName("Update matching rows via storeByCondition")
@Test
@Order(16)
public void testStoreByCondition() throws Exception {
@@ -725,6 +742,7 @@ public class EntityTestSuite implements JupiterTestHelper {
/**
* Tests the .removeByCondition method for removing entities directly
*/
+ @DisplayName("Remove matching rows via removeByCondition")
@Test
@Order(17)
public void testRemoveByCondition() throws Exception {
@@ -740,6 +758,7 @@ public class EntityTestSuite implements JupiterTestHelper {
/**
* Test the .removeByPrimaryKey by using findByCondition and then
retrieving the GenericPk from a GenericValue
*/
+ @DisplayName("Remove rows by primary key")
@Test
@Order(18)
public void testRemoveByPK() throws Exception {
@@ -770,6 +789,7 @@ public class EntityTestSuite implements JupiterTestHelper {
/**
* Tests the .removeAll method only.
*/
+ @DisplayName("Remove all rows of a type via removeAll")
@Test
@Order(19)
public void testRemoveType() throws Exception {
@@ -794,6 +814,7 @@ public class EntityTestSuite implements JupiterTestHelper {
/**
* This test will create a large number of unique items and add them to
the delegator at once
*/
+ @DisplayName("Create many values and store them all at once")
@Test
@Order(20)
public void testCreateManyAndStoreAtOnce() throws Exception {
@@ -823,6 +844,7 @@ public class EntityTestSuite implements JupiterTestHelper {
/**
* This test will create a large number of unique items and add them to
the delegator at once
*/
+ @DisplayName("Create many values and store them one at a time")
@Test
@Order(21)
public void testCreateManyAndStoreOneAtATime() throws Exception {
@@ -850,6 +872,7 @@ public class EntityTestSuite implements JupiterTestHelper {
/**
* This test will use the large number of unique items from above and test
the EntityListIterator looping through the list
*/
+ @DisplayName("Iterate query results with EntityListIterator")
@Test
@Order(22)
public void testEntityListIterator() throws Exception {
@@ -904,6 +927,7 @@ public class EntityTestSuite implements JupiterTestHelper {
/**
* This test will verify transaction rollbacks using TransactionUtil.
*/
+ @DisplayName("Roll back a transaction via TransactionUtil")
@Test
@Order(23)
public void testTransactionUtilRollback() throws Exception {
@@ -919,6 +943,7 @@ public class EntityTestSuite implements JupiterTestHelper {
/**
* This test will verify that a transaction which takes longer than the
pre-set timeout are rolled back.
*/
+ @DisplayName("Roll back a transaction that exceeds its timeout")
@Test
@Order(24)
public void testTransactionUtilMoreThanTimeout() throws Exception {
@@ -941,6 +966,7 @@ public class EntityTestSuite implements JupiterTestHelper {
/**
* This test will verify that the same transaction transaction which takes
less time than timeout will be committed.
*/
+ @DisplayName("Commit a transaction that finishes within its timeout")
@Test
@Order(25)
public void testTransactionUtilLessThanTimeout() throws Exception {
@@ -960,6 +986,7 @@ public class EntityTestSuite implements JupiterTestHelper {
/**
* Tests field types.
*/
+ @DisplayName("Round-trip every supported field type")
@Test
@Order(26)
public void testFieldTypes() throws Exception {
@@ -1076,6 +1103,7 @@ public class EntityTestSuite implements JupiterTestHelper
{
/**
* Tests EntitySaxReader, verification loading data with tag create,
create-update, create-replace, delete
*/
+ @DisplayName("Load data via EntitySaxReader's create tag")
@Test
@Order(27)
public void testEntitySaxReaderCreation() throws Exception {
@@ -1113,6 +1141,7 @@ public class EntityTestSuite implements JupiterTestHelper
{
* Test entity sax reader create skip.
* @throws Exception the exception
*/
+ @DisplayName("EntitySaxReader's create tag skips an existing row")
@Test
@Order(28)
public void testEntitySaxReaderCreateSkip() throws Exception {
@@ -1145,6 +1174,7 @@ public class EntityTestSuite implements JupiterTestHelper
{
* Test entity sax reader update.
* @throws Exception the exception
*/
+ @DisplayName("Load data via EntitySaxReader's create-update tag")
@Test
@Order(29)
public void testEntitySaxReaderUpdate() throws Exception {
@@ -1184,6 +1214,7 @@ public class EntityTestSuite implements JupiterTestHelper
{
* Test entity sax reader replace.
* @throws Exception the exception
*/
+ @DisplayName("Load data via EntitySaxReader's create-replace tag")
@Test
@Order(30)
public void testEntitySaxReaderReplace() throws Exception {
@@ -1221,6 +1252,7 @@ public class EntityTestSuite implements JupiterTestHelper
{
* Test entity sax reader delete.
* @throws Exception the exception
*/
+ @DisplayName("Load data via EntitySaxReader's delete tag")
@Test
@Order(31)
public void testEntitySaxReaderDelete() throws Exception {
@@ -1251,6 +1283,7 @@ public class EntityTestSuite implements JupiterTestHelper
{
/**
* Test sequence value item.
*/
+ @DisplayName("Sequence generator returns increasing ids across a bank
refresh")
@Test
@Order(32)
public void testSequenceValueItem() {
@@ -1272,6 +1305,7 @@ public class EntityTestSuite implements JupiterTestHelper
{
/**
* Test sequence value item with concurrent threads.
*/
+ @DisplayName("Sequence generator returns unique ids under concurrent
access")
@Test
@Order(33)
public void testSequenceValueItemWithConcurrentThreads() {
@@ -1328,6 +1362,7 @@ public class EntityTestSuite implements JupiterTestHelper
{
This test assess that running the same number of sql statements
withing one transaction is faster than running
them with individual transactions.
*/
+ @DisplayName("One big transaction is faster than many small ones")
@Test
@Order(34)
public void testOneBigTransactionIsFasterThanSeveralSmallOnes() {
diff --git a/framework/testtools/src/docs/asciidoc/unit-tests.adoc
b/framework/testtools/src/docs/asciidoc/unit-tests.adoc
index 4838bc80e7..d429d99148 100644
--- a/framework/testtools/src/docs/asciidoc/unit-tests.adoc
+++ b/framework/testtools/src/docs/asciidoc/unit-tests.adoc
@@ -110,6 +110,12 @@ Two things worth knowing before adding a new Jupiter test
class:
Delegator/LocalDispatcher. Without `@Order`, a class is relying on
declaration order only
implicitly - reordering methods, adding one in the middle, or splitting the
file can silently
break it later.
+* *An optional `@DisplayName` on a plain `@Test` method adds to the reported
name, it doesn't
+ replace it.* The method name always stays the primary, always-present part
of the name shown in
+ `testIntegration`'s console output and JUnit XML/HTML reports - a real
`@DisplayName` is appended
+ after `" - "`, e.g. `testTestEntityModels - Test entity models`. A method
with no `@DisplayName`
+ is unaffected. This has no effect on `@ParameterizedTest` invocations - use
+ `@ParameterizedTest(name = ...)` to control those instead.
=== Service
Specific service's name which will be tested in a service-name attribute like
this:
diff --git
a/framework/testtools/src/main/java/org/apache/ofbiz/testtools/JunitSuiteWrapper.java
b/framework/testtools/src/main/java/org/apache/ofbiz/testtools/JunitSuiteWrapper.java
index 6381ee2069..c570a5ac2c 100644
---
a/framework/testtools/src/main/java/org/apache/ofbiz/testtools/JunitSuiteWrapper.java
+++
b/framework/testtools/src/main/java/org/apache/ofbiz/testtools/JunitSuiteWrapper.java
@@ -48,12 +48,17 @@ public class JunitSuiteWrapper {
// TODO create TestSuite object based on this that will
contain its TestCase objects
Element documentElement =
testSuiteDocument.getDocumentElement();
- ModelTestSuite modelTestSuite = new
ModelTestSuite(documentElement, testCase);
-
- // make sure there are test-cases configured for the suite
- if (suiteName != null &&
!modelTestSuite.getSuiteName().equals(suiteName)) {
+ // Filter on suite-name before constructing ModelTestSuite,
not after: the
+ // constructor unconditionally creates a test Delegator +
LocalDispatcher pair,
+ // and for any <jupiter-test-suite> entries inside it also
runs a full JUnit
+ // Platform Launcher discovery - all of that is wasted work
for a testdef file
+ // that suitename= was going to discard anyway. The suite-name
attribute lives on
+ // this same documentElement (ModelTestSuite's constructor
reads it from the
+ // identical element), so it can be read directly here first.
+ if (suiteName != null &&
!documentElement.getAttribute("suite-name").equals(suiteName)) {
continue;
}
+ ModelTestSuite modelTestSuite = new
ModelTestSuite(documentElement, testCase);
if (modelTestSuite.getTestList().size() > 0) {
this.modelTestSuiteList.add(modelTestSuite);
}
diff --git
a/framework/testtools/src/main/java/org/apache/ofbiz/testtools/JupiterTestExtension.java
b/framework/testtools/src/main/java/org/apache/ofbiz/testtools/JupiterTestExtension.java
index 0cce71dc60..050e00af0e 100644
---
a/framework/testtools/src/main/java/org/apache/ofbiz/testtools/JupiterTestExtension.java
+++
b/framework/testtools/src/main/java/org/apache/ofbiz/testtools/JupiterTestExtension.java
@@ -422,14 +422,28 @@ public class JupiterTestExtension implements
ParameterResolver, TestInstancePost
}
/**
- * getLegacyReportingName() reports plain @Test methods as
"methodName(ParamType1, ParamType2)"
- * and @ParameterizedTest invocations as "methodName(ParamType1,
ParamType2)[index]" - the
- * parameter types come from JUnit 5's own default display name, not
from anything meaningful to
- * a report reader here (they're always the
JupiterTestExtension-injected Delegator/LocalDispatcher,
- * or CSV-provided arguments already visible elsewhere in the name).
Stripping them leaves plain
- * JUnit 3 test methods ("testCreateExample") and Jupiter ones
("shouldCreateExample") looking
- * consistent. For @ParameterizedTest invocations, the bare "[index]"
from getLegacyReportingName()
- * is replaced with the test's own @ParameterizedTest(name=...)
display text (e.g. "[1] exampleTypeId=CONTRIVED"
+ * For a plain @Test method, the base name is the method name itself
("testCreateExample",
+ * "shouldCreateExample") - a report reader needs that to go straight
from the report to the
+ * source, and losing it behind a @DisplayName's prose was a real
readability regression once
+ * @DisplayName started being used. getLegacyReportingName() supplies
that raw
+ * "methodName(ParamType1, ParamType2)" signature unconditionally,
regardless of any
+ * @DisplayName on the method (Jupiter's MethodBasedTestDescriptor
overrides
+ * getLegacyReportingBaseName() as final, always returning it). The
parameter types aren't
+ * meaningful to a report reader here (they're always the
JupiterTestExtension-injected
+ * Delegator/LocalDispatcher, or CSV-provided arguments already
visible elsewhere in the name),
+ * so they're stripped. When the method also carries a real
@DisplayName - detected by
+ * getDisplayName() differing from the same method-name shape JUnit
5's default Standard
+ * display name generator would otherwise produce - that text is
appended after " - ", e.g.
+ * "testTestEntityModels - Test entity models". A method with no
@DisplayName is unaffected:
+ * getDisplayName() falls back to that identical default shape, so the
two sides match and only
+ * the bare method name is used.
+ *
+ * <p>For an @ParameterizedTest invocation, getDisplayName()'s shape
is controlled entirely by the
+ * developer's @ParameterizedTest(name=...) pattern - the index can be
anywhere, or absent - so it
+ * can't be used to detect that an identifier is a parameterized
invocation. getLegacyReportingName()
+ * is used for that detection instead: it reliably ends in "[index]"
for every parameterized
+ * invocation regardless of the display-name pattern in use. Once
detected, the bare "[index]" is
+ * replaced with the invocation's own getDisplayName() text (e.g. "[1]
exampleTypeId=CONTRIVED"
* becomes "shouldCreateExampleAcrossTypes[exampleTypeId=CONTRIVED]"),
so each row is identifiable
* without needing to click into it.
*
@@ -447,12 +461,32 @@ public class JupiterTestExtension implements
ParameterResolver, TestInstancePost
* prefix, since {@code classname} can't carry it and bare {@code
name} previously didn't either.
*/
private static String reportingName(TestIdentifier testIdentifier,
Class<?> testClass) {
- String withoutParamTypes =
testIdentifier.getLegacyReportingName().replaceAll("\\([^)]*\\)", "");
- Matcher indexSuffix = INDEX_SUFFIX.matcher(withoutParamTypes);
- String bareName = withoutParamTypes;
+ // getLegacyReportingName() reliably ends in "[index]" for a
parameterized
+ // invocation regardless of whatever @ParameterizedTest(name=...)
pattern the
+ // developer used (unlike getDisplayName(), whose shape is
entirely controlled by
+ // that pattern and may put the index anywhere, or omit it) - so
it's kept here
+ // purely as a structural signal for "is this a parameterized
invocation", not as
+ // the source of the visible name text.
+ String legacyReportingName =
testIdentifier.getLegacyReportingName().replaceAll("\\([^)]*\\)", "");
+ Matcher indexSuffix = INDEX_SUFFIX.matcher(legacyReportingName);
+ String bareName;
if (indexSuffix.matches()) {
+ // Parameterized invocation: the invocation's own display text
(its
+ // @ParameterizedTest(name=...) text, e.g. "[1]
exampleTypeId=CONTRIVED")
+ // replaces the bare "[index]", so each row is identifiable
without clicking in.
+ // A @DisplayName on the parameterized method itself has no
effect here - use
+ // @ParameterizedTest(name = ...) instead to control this text.
String invocationLabel =
testIdentifier.getDisplayName().replaceFirst("^\\[\\d+]\\s*", "");
bareName = indexSuffix.group(1) + "[" + invocationLabel + "]";
+ } else {
+ // Plain @Test method: the method name stays the primary,
always-present part of
+ // the reported name. getDisplayName() is the API that
actually honors a custom
+ // @DisplayName - append its text after " - " only when it's
real, i.e. differs
+ // from the default-generated "methodName(ParamTypes)" shape a
method with no
+ // @DisplayName would otherwise get.
+ String methodName = legacyReportingName;
+ String displayNameText =
testIdentifier.getDisplayName().replaceAll("\\([^)]*\\)$", "");
+ bareName = displayNameText.equals(methodName) ? methodName :
methodName + " - " + displayNameText;
}
return testClass.getSimpleName() + "." + bareName;
}