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 d16edd0146 Fix stale api-runs data polluting test report archive 
counts (#1802)
d16edd0146 is described below

commit d16edd014620d7609d0dab02abb7572f25169a82
Author: Ashish Vijaywargiya <[email protected]>
AuthorDate: Fri Aug 28 17:05:21 2026 +0530

    Fix stale api-runs data polluting test report archive counts (#1802)
    
    JUnitXmlCounter and TestReportArchiver recursed into
    runtime/logs/test-results/api-runs/ when counting and copying results
    for a gradle-triggered test/testIntegration archive. That directory
    holds independently-archived API-triggered run results, so its old XML
    files were getting summed into the current run's manifest.json counts
    and duplicated into the archived results/ folder. Both now skip that
    directory.
    
    Also added a console summary line to createTestReport so a run's
    pass/fail totals are visible without opening the HTML report or reading
    the raw log.
    
    Added regression tests for the api-runs exclusion in JUnitXmlCounterTest
    and TestReportArchiverTest.
---
 .../ofbiz/testtools/report/JUnitXmlCounter.java    | 15 +++++++++++
 .../ofbiz/testtools/report/TestReportArchiver.java |  9 +++++++
 .../testtools/report/JUnitXmlCounterTest.java      | 18 ++++++++++++++
 .../testtools/report/TestReportArchiverTest.java   | 29 ++++++++++++++++++++++
 test-reports.gradle                                | 27 ++++++++++++++++----
 5 files changed, 93 insertions(+), 5 deletions(-)

diff --git 
a/framework/testtools/src/main/java/org/apache/ofbiz/testtools/report/JUnitXmlCounter.java
 
b/framework/testtools/src/main/java/org/apache/ofbiz/testtools/report/JUnitXmlCounter.java
index 4bffcf3b25..50540cacf4 100644
--- 
a/framework/testtools/src/main/java/org/apache/ofbiz/testtools/report/JUnitXmlCounter.java
+++ 
b/framework/testtools/src/main/java/org/apache/ofbiz/testtools/report/JUnitXmlCounter.java
@@ -43,6 +43,18 @@ public final class JUnitXmlCounter {
 
     private static final String MODULE = JUnitXmlCounter.class.getName();
 
+    /**
+     * Name of the directory {@code 
org.apache.ofbiz.testtools.TestRunServices} accumulates one
+     * subdirectory per API-triggered run under (each already archived 
independently, under its
+     * own runId). A gradle-triggered {@code test}/{@code testIntegration} run 
is handed the
+     * shared parent directory (e.g. {@code runtime/logs/test-results}) as its 
own resultsDir,
+     * which sits right next to this one - recursing into it here would keep 
summing every past
+     * API-triggered run's counts into every future gradle run's manifest too. 
{@link
+     * org.apache.ofbiz.testtools.report.TestReportArchiver#copyRecursive} 
skips it for the same
+     * reason when copying resultsDir into the archived run folder.
+     */
+    static final String API_RUNS_DIR_NAME = "api-runs";
+
     private JUnitXmlCounter() {
     }
 
@@ -106,6 +118,9 @@ public final class JUnitXmlCounter {
         }
         for (File child : children) {
             if (child.isDirectory()) {
+                if (API_RUNS_DIR_NAME.equals(child.getName())) {
+                    continue;
+                }
                 result.addAll(listXmlFilesRecursively(child));
             } else if (child.getName().endsWith(".xml")) {
                 result.add(child);
diff --git 
a/framework/testtools/src/main/java/org/apache/ofbiz/testtools/report/TestReportArchiver.java
 
b/framework/testtools/src/main/java/org/apache/ofbiz/testtools/report/TestReportArchiver.java
index d267825fba..523b5dda5c 100644
--- 
a/framework/testtools/src/main/java/org/apache/ofbiz/testtools/report/TestReportArchiver.java
+++ 
b/framework/testtools/src/main/java/org/apache/ofbiz/testtools/report/TestReportArchiver.java
@@ -119,6 +119,15 @@ public final class TestReportArchiver {
             Files.createDirectories(dest);
             try (var children = Files.list(source)) {
                 for (Path child : (Iterable<Path>) children::iterator) {
+                    // See JUnitXmlCounter.API_RUNS_DIR_NAME's javadoc: 
api-runs/ holds every past
+                    // API-triggered run's own results, already archived 
independently under its
+                    // own runId. Copying it into every gradle-triggered 
archive too would
+                    // duplicate that entire (ever-growing) history into 
results/ on every single
+                    // test/testIntegration run, on top of corrupting the 
counts JUnitXmlCounter
+                    // sums from this same tree.
+                    if 
(JUnitXmlCounter.API_RUNS_DIR_NAME.equals(child.getFileName().toString())) {
+                        continue;
+                    }
                     copyRecursive(child, 
dest.resolve(child.getFileName().toString()));
                 }
             }
diff --git 
a/framework/testtools/src/test/java/org/apache/ofbiz/testtools/report/JUnitXmlCounterTest.java
 
b/framework/testtools/src/test/java/org/apache/ofbiz/testtools/report/JUnitXmlCounterTest.java
index 6cb4d97e8e..a8aed40fa5 100644
--- 
a/framework/testtools/src/test/java/org/apache/ofbiz/testtools/report/JUnitXmlCounterTest.java
+++ 
b/framework/testtools/src/test/java/org/apache/ofbiz/testtools/report/JUnitXmlCounterTest.java
@@ -59,6 +59,24 @@ class JUnitXmlCounterTest {
         assertThat(counts.getFailed(), is(0));
     }
 
+    @Test
+    void excludesApiRunsDirectoryFromTheRecursiveWalk(@TempDir File 
resultsDir) throws IOException {
+        // runtime/logs/test-results/api-runs/<runId>/ accumulates one 
subdirectory per
+        // API-triggered run (TestRunServices), each already archived under 
its own runId. A
+        // gradle-triggered run (test/testIntegration) is handed the shared 
parent directory as
+        // its resultsDir, which sits right next to api-runs/ - without this 
exclusion, every past
+        // API run's counts would keep getting summed into every future gradle 
run's manifest too.
+        writeSuiteXml(new File(resultsDir, "SuiteA.xml"), 10, 1, 1, 0);
+        File apiRun = new File(resultsDir, 
"api-runs/11111111-1111-1111-1111-111111111111");
+        apiRun.mkdirs();
+        writeSuiteXml(new File(apiRun, "contenttests.xml"), 12, 4, 2, 0);
+
+        TestRunManifest.Counts counts = JUnitXmlCounter.count(resultsDir);
+
+        assertThat(counts.getTotal(), is(10));
+        assertThat(counts.getFailed(), is(2));
+    }
+
     @Test
     void returnsAllZeroesWhenDirectoryDoesNotExist() {
         TestRunManifest.Counts counts = JUnitXmlCounter.count(new 
File("/no/such/dir"));
diff --git 
a/framework/testtools/src/test/java/org/apache/ofbiz/testtools/report/TestReportArchiverTest.java
 
b/framework/testtools/src/test/java/org/apache/ofbiz/testtools/report/TestReportArchiverTest.java
index 4ba60ffcf7..4ae3b31b4d 100644
--- 
a/framework/testtools/src/test/java/org/apache/ofbiz/testtools/report/TestReportArchiverTest.java
+++ 
b/framework/testtools/src/test/java/org/apache/ofbiz/testtools/report/TestReportArchiverTest.java
@@ -88,6 +88,35 @@ class TestReportArchiverTest {
         assertThat(new File(runFolder, "results/test-report.html").exists(), 
is(true));
     }
 
+    @Test
+    void excludesApiRunsDirectoryFromCountsAndFromTheCopiedResults(@TempDir 
File tmp) throws IOException {
+        // Same scenario JUnitXmlCounterTest exercises at the counter level, 
checked end-to-end
+        // here: a testIntegration-style resultsDir (the whole 
runtime/logs/test-results/ tree)
+        // that happens to have api-runs/ sitting in it from earlier 
API-triggered runs must not
+        // let that unrelated history inflate this run's counts, nor get 
duplicated into this
+        // run's archived results/ directory.
+        File baseDir = new File(tmp, "runtime/test-reports");
+        File resultsDir = new File(tmp, "runtime/logs/test-results");
+        resultsDir.mkdirs();
+        Files.writeString(new File(resultsDir, "SomeSuite.xml").toPath(),
+                "<testsuite name=\"x\" tests=\"3\" failures=\"0\" errors=\"0\" 
skipped=\"0\"></testsuite>");
+        File apiRun = new File(resultsDir, 
"api-runs/11111111-1111-1111-1111-111111111111");
+        apiRun.mkdirs();
+        Files.writeString(new File(apiRun, "contenttests.xml").toPath(),
+                "<testsuite name=\"contenttests\" tests=\"12\" failures=\"4\" 
errors=\"2\" skipped=\"0\"></testsuite>");
+
+        TestReportArchiver.ArchiveRequest request = new 
TestReportArchiver.ArchiveRequest(
+                baseDir, tmp, "testIntegration", "testIntegration", "PASSED", 
resultsDir, null);
+        TestRunManifest manifest = TestReportArchiver.archive(request);
+
+        assertThat(manifest.getCounts().getTotal(), is(3));
+        assertThat(manifest.getCounts().getFailed(), is(0));
+
+        File runFolder = new File(manifest.getResultsLocation());
+        assertThat(new File(runFolder, "results/SomeSuite.xml").exists(), 
is(true));
+        assertThat(new File(runFolder, "results/api-runs").exists(), 
is(false));
+    }
+
     @Test
     void runFolderNameFollowsDateTimeSuiteConvention(@TempDir File tmp) throws 
IOException {
         File baseDir = new File(tmp, "runtime/test-reports");
diff --git a/test-reports.gradle b/test-reports.gradle
index 23aaaeffcb..f649658f68 100644
--- a/test-reports.gradle
+++ b/test-reports.gradle
@@ -251,7 +251,9 @@ def renderStatCards(Map counts, Closure statusHrefFn, List 
extraCards = [], Stri
     writer.toString()
 }
 
-def buildOverallSummaryTable(List suites, Closure statusHrefFn) {
+// Shared by buildOverallSummaryTable (the HTML report's own summary cards) 
and createTestReport's
+// console println below, so the two can never disagree about what "the 
totals" are.
+def computeOverallCounts(List suites) {
     def toInt = { String s -> (s ==~ /\d+/) ? s.toInteger() : 0 }
     def toSeconds = { String s -> (s ==~ /-?[0-9]*\.?[0-9]+/) ? s.toDouble() : 
0.0 }
     // List.sum(Closure) returns null (not 0) on an empty list, unlike the 
no-arg sum() - discovered
@@ -266,10 +268,12 @@ def buildOverallSummaryTable(List suites, Closure 
statusHrefFn) {
     def successRate = totalTests > 0
             ? String.format('%.2f%%', ((totalTests - totalFailures - 
totalErrors) / (double) totalTests) * 100)
             : 'N/A'
-    renderStatCards([tests: totalTests, failures: totalFailures, errors: 
totalErrors,
-                      skipped: totalSkipped, successRate: successRate,
-                      time: String.format('%.3f', totalTime)],
-            statusHrefFn, [], 'overall-summary')
+    [tests: totalTests, failures: totalFailures, errors: totalErrors, skipped: 
totalSkipped,
+     successRate: successRate, time: String.format('%.3f', totalTime)]
+}
+
+def buildOverallSummaryTable(List suites, Closure statusHrefFn) {
+    renderStatCards(computeOverallCounts(suites), statusHrefFn, [], 
'overall-summary')
 }
 
 def buildSummaryTable(List suites, Closure hrefFn) {
@@ -722,6 +726,19 @@ task createTestReport(group: sysadminGroup,
         delete file('./runtime/logs/test-results/modern-report.html')
         reportFile.text = renderModernReport(suites)
         println "Test report written to ${reportFile}"
+
+        // Nothing else in this run's console/log output ever states the 
totals in one place - a
+        // reader otherwise has to open the HTML report, or hand-sum every 
suite's own logged
+        // result, to learn whether the run actually passed. Same counts the 
report's own overall
+        // summary cards show, via the same computeOverallCounts, so the two 
can never disagree.
+        def counts = computeOverallCounts(suites)
+        def passed = counts.tests - counts.failures - counts.errors
+        def parseErrorCount = suites.count { it.parseError }
+        def parseErrorNote = parseErrorCount > 0
+                ? " (${parseErrorCount} suite(s) could not be parsed and are 
not included in these counts - see the report)"
+                : ''
+        println "Tests: ${counts.tests}, Passed: ${passed}, Failed: 
${counts.failures}, " +
+                "Errors: ${counts.errors}, Skipped: 
${counts.skipped}${parseErrorNote}"
     }
 }
 

Reply via email to