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 b9dfe95aa1 Fix webtools Run Suite/Run Case: missing prerequisites and 
shared ambient transaction (#1655)
b9dfe95aa1 is described below

commit b9dfe95aa10b2857110d6d7c0db2a689d0d1fb65
Author: Ashish Vijaywargiya <[email protected]>
AuthorDate: Sat Aug 15 12:30:34 2026 +0530

    Fix webtools Run Suite/Run Case: missing prerequisites and shared ambient 
transaction (#1655)
    
    ## Problem
    
    Webtools' "Run Suite" / "Run Case" (`TestSuiteInfo` ->
    `RunTest/{compName}/{suiteName}[/{caseName}]`) had two bugs that didn't
    show up via the CLI's `ofbiz --test`:
    
    1. Requesting a single case (`case=`) ran only that exact case, silently
    skipping sibling setup cases it depends on (e.g. `party-tests` needs the
    data `party-tests-data-load` creates), so a filtered run could fail for
    missing data that the full suite run never exposed.
    2. `entity`/`service`/`ecommerce` full-suite runs failed or misbehaved:
    `RunTestEvents` -> `TestRunContainer` runs a whole testdef suite's worth
    of otherwise-independent test-cases inside one
    `JavaEventHandler`-invoked event call, and `JavaEventHandler`
    unconditionally wraps that whole call in one ambient transaction. A
    `require-new-transaction` service invoked by one test-case couldn't see
    data a prior test-case's own entity-xml load had "committed" (that
    commit was a no-op participant in the still-open ambient transaction),
    and one test-case marking that shared transaction rollback-only poisoned
    every test-case after it in the same run. Neither symptom is possible
    via `ofbiz --test`, which runs `TestRunContainer` as a bare `Container`
    with no event/HTTP layer and no ambient transaction at all.
    
    ## Fix
    
    **`ModelTestSuite`**: `case=` filtering now auto-includes only genuine
    data-load prerequisites (`entity-xml`/`entity-xml-assert` with
    `action="load"`) declared before the requested case - not every
    preceding case. An earlier, broader version of this fix (running
    everything declared before the target) over-included independent sibling
    test classes, real `service-test` cases, and `entity-xml
    action="assert"` checks - `entitytests.xml` bundles five unrelated
    `jupiter-test-suite` classes in one file, and requesting one used to
    also run the others, including a deliberately slow one. Covered by unit
    tests in `ModelTestSuiteTest`.
    
    **`JavaEventHandler`**: honors the existing `global-transaction="false"`
    `<event>` attribute (previously only read by `ServiceMultiEventHandler`,
    unused anywhere in the codebase until this change).
    **`controller.xml`**: declares `global-transaction="false"` on both
    `RunTest` request-maps in webtools, opting only that one event out of
    the default whole-invocation ambient transaction.
    
    ## Testing
    
    - New unit tests in `ModelTestSuiteTest` (case-filtering logic).
    - `./gradlew test checkstyleMain checkstyleTest codenarcMain
    codenarcTest verifyTestdefClassNames verifyNoBareJupiterExtendWith` -
    all clean.
    - `./gradlew loadAll testIntegration` (full CLI run, every testdef suite
    in the repo): 43 suites, 652 tests, one pre-existing unrelated failure
    (`facilitytests`/`InventoryTests.testGetInventoryAvailableByFacility`)
    reproduced identically on `trunk` with none of this PR's changes,
    confirming it predates this PR.
    - Manually exercised `party`, `entity`, `service`, `ecommerce` through
    the actual webtools UI (`RunTest/...` URLs) before and after each change
    to confirm the fix end to end, not just via the CLI.
---
 build.gradle                                       |  11 +-
 .../org/apache/ofbiz/testtools/ModelTestSuite.java | 109 ++++++++++++--
 .../apache/ofbiz/testtools/ModelTestSuiteTest.java | 160 +++++++++++++++++++++
 .../ofbiz/webapp/event/JavaEventHandler.java       |  18 ++-
 .../webapp/webtools/WEB-INF/controller.xml         |  18 ++-
 5 files changed, 299 insertions(+), 17 deletions(-)

diff --git a/build.gradle b/build.gradle
index 587a87a84f..1b191128c0 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1396,8 +1396,15 @@ tasks.addRule('Pattern: ofbizBackground <Commands>: 
Execute OFBiz startup comman
 def createOfbizCommandTask(taskName, arguments) {
     task(type: JavaExec, dependsOn: classes, taskName) {
         jvmArgs(application.applicationDefaultJvmArgs)
+        // Every ofbiz-launched dev server needs test classes on its 
classpath, not just one
+        // started with --test/-t: webtools' "Run Test" screen (RunTestEvents 
-> TestRunContainer)
+        // loads jupiter-test-suite/junit-test-suite classes by name inside 
whatever JVM is already
+        // running, regardless of how that JVM was started. OFBIZ-13402 moved 
those classes out of
+        // src/main/* (and so out of sourceSets.main) to keep them out of 
release artifacts; this
+        // task is a local dev-run launcher, never a release-packaging task, 
so widening its
+        // classpath here doesn't reintroduce test code into anything that 
gets distributed.
+        classpath = sourceSets.main.runtimeClasspath + 
sourceSets.test.runtimeClasspath
         if (taskName ==~ /^ofbiz.*(--test|-t).*/) {
-            classpath = sourceSets.main.runtimeClasspath + 
sourceSets.test.runtimeClasspath
             // TestRunContainer.java writes one XML per suite as 
"<suite.getName()>.xml" and only
             // ever overwrites the suites a run actually re-executes - nothing 
here deletes the
             // rest first. Left alone, a narrower run (suitename=... filtering 
to one component) or
@@ -1412,8 +1419,6 @@ def createOfbizCommandTask(taskName, arguments) {
             }
             finalizedBy(createTestReport)
             finalizedBy(createFramedTestReport)
-        } else {
-            classpath = sourceSets.main.runtimeClasspath
         }
         mainClass = application.mainClass
         args arguments
diff --git 
a/framework/testtools/src/main/java/org/apache/ofbiz/testtools/ModelTestSuite.java
 
b/framework/testtools/src/main/java/org/apache/ofbiz/testtools/ModelTestSuite.java
index 9cfdddcbdc..b34a3d11be 100644
--- 
a/framework/testtools/src/main/java/org/apache/ofbiz/testtools/ModelTestSuite.java
+++ 
b/framework/testtools/src/main/java/org/apache/ofbiz/testtools/ModelTestSuite.java
@@ -63,23 +63,112 @@ public class ModelTestSuite {
         this.delegator = 
DelegatorFactory.getDelegator(DELEGATOR_NAME).makeTestDelegator(DELEGATOR_NAME 
+ uniqueSuffix);
         this.dispatcher = ServiceContainer.getLocalDispatcher(DISPATCHER_NAME 
+ uniqueSuffix, delegator);
 
-        for (Element testCaseElement : UtilXml.childElementList(mainElement, 
UtilMisc.toSet("test-case", "test-group"))) {
+        List<Element> testCaseElements = 
List.copyOf(UtilXml.childElementList(mainElement, UtilMisc.toSet("test-case", 
"test-group")));
+        for (Element testCaseElement : 
selectTestCaseElements(testCaseElements, testCase)) {
             String caseName = testCaseElement.getAttribute("case-name");
             String nodeName = testCaseElement.getNodeName();
-            if (testCase == null || caseName.equals(testCase)) {
-                if ("test-case".equals(nodeName)) {
-                    parseTestElement(caseName, 
UtilXml.firstChildElement(testCaseElement));
-                } else if ("test-group".equals(nodeName)) {
-                    int i = 0;
-                    for (Element childElement: 
UtilXml.childElementList(testCaseElement)) {
-                        parseTestElement(caseName + '-' + i, childElement);
-                        i++;
-                    }
+            if ("test-case".equals(nodeName)) {
+                parseTestElement(caseName, 
UtilXml.firstChildElement(testCaseElement));
+            } else if ("test-group".equals(nodeName)) {
+                int i = 0;
+                for (Element childElement: 
UtilXml.childElementList(testCaseElement)) {
+                    parseTestElement(caseName + '-' + i, childElement);
+                    i++;
                 }
             }
         }
     }
 
+    /**
+     * Selects which of a suite's ordered test-case/test-group elements should 
run for a given
+     * {@code case=} filter.
+     *
+     * <p>A single case is rarely self-contained: every testdef file in this 
repo follows the
+     * convention of a data-load case (an {@code entity-xml action="load"}, 
typically named
+     * {@code *-data-load}/{@code load*TestData}) declared immediately before 
the case(s) that consume
+     * it - see e.g. applications/party/testdef/PartyTests.xml's {@code 
party-tests-data-load} feeding
+     * {@code party-tests}. Filtering down to only the exact requested 
case-name, the way this method
+     * used to work, silently drops that prerequisite and leaves the requested 
case failing for lack of
+     * data it never asked to be responsible for loading itself.
+     *
+     * <p>Only that one shape - an {@code entity-xml}/{@code 
entity-xml-assert} case whose
+     * {@code action} is {@code "load"} - is auto-included as a prerequisite; 
every other preceding
+     * case is skipped unless it is itself the requested case. That is 
deliberately narrower than
+     * "run everything declared before the requested case": a {@code load} is 
additive and
+     * side-effect-free relative to every other case in the suite, so running 
it unconditionally is
+     * safe, but nothing else in a testdef file has that property -
+     * <ul>
+     * <li>A {@code jupiter-test-suite}/{@code junit-test-suite} case, or a 
{@code test-group} (a
+     * repo-wide scan found 14 of 15 {@code test-group} children are themselves
+     * {@code jupiter-test-suite}), is a whole independent test class in its 
own right. Several
+     * testdef files (e.g. framework/entity/testdef/entitytests.xml) bundle 
multiple such classes into
+     * one {@code <test-suite>} purely for organization, with no data 
relationship between them -
+     * requesting entity-crypto-tests (declared third) used to also run the 
unrelated, much larger and
+     * deliberately slow entity-tests/entity-util-tests classes ahead of it, 
turning a single-case
+     * request into most of the suite's runtime.</li>
+     * <li>A {@code service-test} is always a real functional test in every 
testdef file in this repo,
+     * never data loading (see framework/service/testdef/servicetests.xml's
+     * service-dead-lock-retry-test and friends).</li>
+     * <li>An {@code entity-xml} whose action is {@code "assert"} (the default 
when unset) is a check,
+     * not a load - in servicetests.xml, 
service-eca-global-event-exec-assert-data verifies the side
+     * effects of the service-test case declared immediately before it. 
Auto-including an assert case
+     * without the real test whose effects it checks would assert against data 
that was never
+     * created - trading one bug (a missing prerequisite) for another (a 
spurious failure) rather than
+     * fixing it.</li>
+     * </ul>
+     *
+     * <p>Nothing declared after the requested case runs, so this is not 
simply "ignore the filter":
+     * a case still can't see data set up by a case that only runs after it in 
the suite's own
+     * declared order.
+     *
+     * <p>When {@code testCase} is {@code null} (no filter - run the whole 
suite), every element is
+     * selected unchanged. When {@code testCase} names a case-name that isn't 
present in this
+     * particular document at all, nothing is selected - unchanged from the 
old exact-match
+     * behavior, and still what lets JunitSuiteWrapper's suite-name filtering 
keep a testdef file
+     * that simply doesn't define the requested case from contributing 
anything.
+     * @param testCaseElements the suite's test-case/test-group elements, in 
declared order
+     * @param testCase the requested case-name, or {@code null} to select 
every element
+     * @return the elements to run, in the same order
+     */
+    static List<Element> selectTestCaseElements(List<Element> 
testCaseElements, String testCase) {
+        if (testCase == null) {
+            return testCaseElements;
+        }
+        List<Element> selected = new ArrayList<>();
+        for (Element element : testCaseElements) {
+            boolean isTarget = 
testCase.equals(element.getAttribute("case-name"));
+            if (isTarget) {
+                selected.add(element);
+                return selected;
+            }
+            if (isDataLoadCase(element)) {
+                selected.add(element);
+            }
+        }
+        return List.of();
+    }
+
+    /**
+     * True for a {@code <test-case>} whose sole child is an {@code 
entity-xml}/{@code entity-xml-assert}
+     * element with {@code action="load"}: the one shape safe to auto-include 
as a prerequisite ahead
+     * of a requested case. See selectTestCaseElements()'s javadoc for why 
every other shape - including
+     * the same elements with a different action - is excluded instead.
+     * @param testCaseElement one of a suite's test-case/test-group elements
+     * @return true if this element is a genuine data-load case
+     */
+    private static boolean isDataLoadCase(Element testCaseElement) {
+        if (!"test-case".equals(testCaseElement.getNodeName())) {
+            return false;
+        }
+        Element child = UtilXml.firstChildElement(testCaseElement);
+        if (child == null) {
+            return false;
+        }
+        String nodeName = child.getNodeName();
+        boolean isEntityXml = "entity-xml".equals(nodeName) || 
"entity-xml-assert".equals(nodeName);
+        return isEntityXml && "load".equals(child.getAttribute("action"));
+    }
+
     private void parseTestElement(String caseName, Element testElement) {
         String nodeName = testElement.getNodeName();
         if ("junit-test-suite".equals(nodeName)) {
diff --git 
a/framework/testtools/src/test/java/org/apache/ofbiz/testtools/ModelTestSuiteTest.java
 
b/framework/testtools/src/test/java/org/apache/ofbiz/testtools/ModelTestSuiteTest.java
new file mode 100644
index 0000000000..026d8921ef
--- /dev/null
+++ 
b/framework/testtools/src/test/java/org/apache/ofbiz/testtools/ModelTestSuiteTest.java
@@ -0,0 +1,160 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ 
*******************************************************************************/
+package org.apache.ofbiz.testtools;
+
+import java.util.List;
+import java.util.Set;
+
+import org.apache.ofbiz.base.util.UtilXml;
+import org.junit.jupiter.api.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * Exercises ModelTestSuite.selectTestCaseElements() directly: the piece of 
the case-filtering logic
+ * that decides which of a suite's test-case/test-group elements run for a 
given {@code case=} filter,
+ * with no Delegator/LocalDispatcher or class-loading involved - unlike the 
constructor itself (see
+ * TestRunContainerTest's class javadoc for why that needs a full ofbiz --test 
container bootstrap).
+ *
+ * <p>The fixture mirrors every shape actually found in this repo's testdef 
files (a full repo-wide
+ * scan backs each case here):
+ * <ul>
+ * <li>{@code data-load} - an {@code entity-xml action="load"} case, the one 
shape that genuinely
+ * behaves like setup: additive, side-effect-free relative to every other 
case, safe to run
+ * unconditionally. This is the only shape auto-included as a 
prerequisite.</li>
+ * <li>{@code assert-data} - an {@code entity-xml action="assert"} case. 
Despite also being
+ * {@code entity-xml}, this is a check, not a load - and in the real suite it 
is modeled on
+ * (framework/service/testdef/servicetests.xml's *-assert-data cases), it 
verifies the side effects of
+ * the {@code service-test} case declared immediately before it. 
Auto-including it without that
+ * service-test would assert against data that was never created, turning a 
clean single-case run into
+ * a spurious failure - so it is treated the same as an independent test, not 
as setup.</li>
+ * <li>{@code service-call} - a {@code service-test} case, modeled on 
servicetests.xml's
+ * 
service-dead-lock-retry-test/service-own-tx-sub-service-after-set-rollback-only-in-parent/
+ * service-eca-global-event-exec: always a real functional test in every 
testdef file in this repo,
+ * never data loading.</li>
+ * <li>{@code first-tests}/{@code second-tests} - independent {@code 
jupiter-test-suite} classes, the
+ * entitytests.xml shape this whole rule exists for (see 
caseFilterSkipsAnIndependentJupiterTestClass
+ * DeclaredBeforeIt()'s comment).</li>
+ * <li>{@code grouped-tests} - a {@code test-group}. A repo-wide scan of every 
{@code test-group} in
+ * this codebase found 14 of 15 child elements are {@code jupiter-test-suite} 
(accounting/product/scrum
+ * testdef files bundle several independent classes into one group) and only 1 
is {@code entity-xml} -
+ * so a test-group reads as "more independent classes," not setup, and is 
excluded like one.</li>
+ * <li>{@code third-tests} - a {@code junit-test-suite} case, the 
legacy-JUnit-3 equivalent of
+ * first-tests/second-tests.</li>
+ * </ul>
+ */
+class ModelTestSuiteTest {
+
+    private static final String SUITE_XML =
+            "<test-suite suite-name=\"sample\">"
+                    + "<test-case case-name=\"data-load\"><entity-xml 
action=\"load\" entity-xml-url=\"x\"/></test-case>"
+                    + "<test-case case-name=\"assert-data\"><entity-xml 
action=\"assert\" entity-xml-url=\"x\"/></test-case>"
+                    + "<test-case case-name=\"service-call\"><service-test 
service-name=\"x\"/></test-case>"
+                    + "<test-case 
case-name=\"first-tests\"><jupiter-test-suite 
class-name=\"a.First\"/></test-case>"
+                    + "<test-case 
case-name=\"second-tests\"><jupiter-test-suite 
class-name=\"a.Second\"/></test-case>"
+                    + "<test-group case-name=\"grouped-tests\">"
+                    + "<jupiter-test-suite class-name=\"a.GroupA\"/>"
+                    + "<jupiter-test-suite class-name=\"a.GroupB\"/>"
+                    + "</test-group>"
+                    + "<test-case case-name=\"third-tests\"><junit-test-suite 
class-name=\"a.Third\"/></test-case>"
+                    + "</test-suite>";
+
+    @Test
+    void nullCaseFilterSelectsEveryElementUnchanged() throws Exception {
+        List<Element> selected = 
ModelTestSuite.selectTestCaseElements(caseElements(), null);
+
+        assertThat(caseNamesOf(selected), contains(
+                "data-load", "assert-data", "service-call", "first-tests", 
"second-tests", "grouped-tests", "third-tests"));
+    }
+
+    @Test
+    void caseFilterIncludesAGenuineLoadCaseDeclaredBeforeIt() throws Exception 
{
+        List<Element> selected = 
ModelTestSuite.selectTestCaseElements(caseElements(), "assert-data");
+
+        assertThat(caseNamesOf(selected), contains("data-load", 
"assert-data"));
+    }
+
+    @Test
+    void caseFilterExcludesAnEntityXmlAssertCaseThatIsNotTheTarget() throws 
Exception {
+        // assert-data would run against data only service-call creates - 
since service-call is
+        // itself excluded (it's a real test, not setup), running assert-data 
too would be a spurious
+        // failure, not a harmless extra. Modeled on servicetests.xml's 
service-eca-global-event-exec
+        // -assert-data, which checks service-eca-global-event-exec's side 
effects.
+        List<Element> selected = 
ModelTestSuite.selectTestCaseElements(caseElements(), "first-tests");
+
+        assertThat(caseNamesOf(selected), contains("data-load", 
"first-tests"));
+    }
+
+    @Test
+    void caseFilterSkipsAnIndependentJupiterTestClassDeclaredBeforeIt() throws 
Exception {
+        // Regression test: entitytests.xml bundles five independent 
jupiter-test-suite classes with
+        // no data-load relationship between them. Requesting 
entity-crypto-tests (declared third)
+        // must not also run the unrelated entity-tests/entity-util-tests 
classes ahead of it - that
+        // silently multiplied a single-case run into the whole suite's worth 
of work, including
+        // EntityTestSuite's deliberately slow bulk-operation tests.
+        List<Element> selected = 
ModelTestSuite.selectTestCaseElements(caseElements(), "second-tests");
+
+        assertThat(caseNamesOf(selected), contains("data-load", 
"second-tests"));
+    }
+
+    @Test
+    void caseFilterExcludesEveryNonLoadShapeAheadOfTheTarget() throws 
Exception {
+        // third-tests is declared last: only the one genuine entity-xml 
action="load" case ahead of
+        // it survives. assert-data, service-call, first-tests, second-tests, 
and grouped-tests are
+        // all excluded - none of them is data loading, whatever shape they 
otherwise take.
+        List<Element> selected = 
ModelTestSuite.selectTestCaseElements(caseElements(), "third-tests");
+
+        assertThat(caseNamesOf(selected), contains("data-load", 
"third-tests"));
+    }
+
+    @Test
+    void caseFilterOnATestGroupSelectsOnlyThatGroup() throws Exception {
+        List<Element> selected = 
ModelTestSuite.selectTestCaseElements(caseElements(), "grouped-tests");
+
+        assertThat(caseNamesOf(selected), contains("data-load", 
"grouped-tests"));
+    }
+
+    @Test
+    void caseFilterOnTheFirstElementSelectsOnlyThatElement() throws Exception {
+        List<Element> selected = 
ModelTestSuite.selectTestCaseElements(caseElements(), "data-load");
+
+        assertThat(caseNamesOf(selected), contains("data-load"));
+    }
+
+    @Test
+    void caseFilterNotPresentInThisDocumentSelectsNothing() throws Exception {
+        List<Element> selected = 
ModelTestSuite.selectTestCaseElements(caseElements(), "not-here-at-all");
+
+        assertThat(selected, is(empty()));
+    }
+
+    private static List<Element> caseElements() throws Exception {
+        Document document = UtilXml.readXmlDocument(SUITE_XML, false);
+        return 
List.copyOf(UtilXml.childElementList(document.getDocumentElement(), 
Set.of("test-case", "test-group")));
+    }
+
+    private static List<String> caseNamesOf(List<Element> elements) {
+        return elements.stream().map(element -> 
element.getAttribute("case-name")).toList();
+    }
+}
diff --git 
a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/event/JavaEventHandler.java
 
b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/event/JavaEventHandler.java
index a101770d76..e2da1dcde3 100644
--- 
a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/event/JavaEventHandler.java
+++ 
b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/event/JavaEventHandler.java
@@ -76,10 +76,24 @@ public class JavaEventHandler implements EventHandler {
         if (Debug.verboseOn()) {
             Debug.logVerbose("[Processing]: Java Event", MODULE);
         }
+        // A type="java" event defaults to running inside one transaction 
spanning the whole
+        // invoke() call - the same global-transaction="false" opt-out 
ServiceMultiEventHandler
+        // already honors for type="service-multi" events. Without it, an 
event whose own method
+        // deliberately manages several independent units of work (e.g. 
RunTestEvents running a
+        // whole testdef suite's worth of otherwise-independent test-cases 
through TestRunContainer)
+        // has every one of those units silently folded into this one ambient 
transaction instead:
+        // an entity-xml load's own begin()/commit() inside that event becomes 
a no-op participant
+        // rather than a real commit, so a require-new-transaction service 
called by a later,
+        // unrelated test-case in the same suite can't see data an earlier one 
just "committed",
+        // and one test-case marking the transaction rollback-only poisons 
every test-case after it
+        // in the same event invocation - see the 2026-08-15 
ecommerce/entity/service RunTest
+        // investigation this comment was added for.
         boolean began = false;
         try {
-            int timeout = Integer.max(event.getTransactionTimeout(), 0);
-            began = TransactionUtil.begin(timeout);
+            if (event.isGlobalTransaction()) {
+                int timeout = Integer.max(event.getTransactionTimeout(), 0);
+                began = TransactionUtil.begin(timeout);
+            }
             Method m = k.getMethod(event.getInvoke(), HttpServletRequest.class,
                                    HttpServletResponse.class);
             String ret = (String) m.invoke(null, request, response);
diff --git a/framework/webtools/webapp/webtools/WEB-INF/controller.xml 
b/framework/webtools/webapp/webtools/WEB-INF/controller.xml
index dcdf7ab396..c579a9206a 100644
--- a/framework/webtools/webapp/webtools/WEB-INF/controller.xml
+++ b/framework/webtools/webapp/webtools/WEB-INF/controller.xml
@@ -445,13 +445,27 @@ under the License.
 
     <request-map uri="RunTest/{compName}/{suiteName}">
         <security https="true" auth="true"/>
-        <event type="java" 
path="org.apache.ofbiz.webtools.artifactinfo.RunTestEvents" invoke="runTest"/>
+        <!-- TestRunContainer runs a whole testdef suite's worth of 
otherwise-independent
+             test-cases; each manages its own transaction the same way it does 
when run via
+             the ofbiz command line's own test option, which never runs inside 
an event's
+             ambient transaction to begin with. Without 
global-transaction="false" here,
+             JavaEventHandler's default whole-invoke() transaction folds every 
one of those
+             test-cases into a single ambient transaction instead, breaking 
both data
+             visibility between test-cases and per-suite rollback-only 
isolation. -->
+        <event type="java" 
path="org.apache.ofbiz.webtools.artifactinfo.RunTestEvents" invoke="runTest" 
global-transaction="false"/>
         <response name="success" type="request" value="TestSuiteInfo"/>
         <response name="error" type="request" value="TestSuiteInfo"/>
     </request-map>
     <request-map uri="RunTest/{compName}/{suiteName}/{caseName}">
         <security https="true" auth="true"/>
-        <event type="java" 
path="org.apache.ofbiz.webtools.artifactinfo.RunTestEvents" invoke="runTest"/>
+        <!-- TestRunContainer runs a whole testdef suite's worth of 
otherwise-independent
+             test-cases; each manages its own transaction the same way it does 
when run via
+             the ofbiz command line's own test option, which never runs inside 
an event's
+             ambient transaction to begin with. Without 
global-transaction="false" here,
+             JavaEventHandler's default whole-invoke() transaction folds every 
one of those
+             test-cases into a single ambient transaction instead, breaking 
both data
+             visibility between test-cases and per-suite rollback-only 
isolation. -->
+        <event type="java" 
path="org.apache.ofbiz.webtools.artifactinfo.RunTestEvents" invoke="runTest" 
global-transaction="false"/>
         <response name="success" type="request" value="TestSuiteInfo"/>
         <response name="error" type="request" value="TestSuiteInfo"/>
     </request-map>

Reply via email to