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;
         }

Reply via email to