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 dc80d7f434 Migrate UEL unit tests (Date/Math/String/Misc) from JUnit3 
testdef to Jupiter(JUnit5) (#1516)
dc80d7f434 is described below

commit dc80d7f434a3b985cb5a0a1becc975d9c65e44eb
Author: Ashish Vijaywargiya <[email protected]>
AuthorDate: Thu Jul 30 11:14:42 2026 +0530

    Migrate UEL unit tests (Date/Math/String/Misc) from JUnit3 testdef to 
Jupiter(JUnit5) (#1516)
    
    UEL Unit Test Migration
    
    1) I migrated the four UEL unit tests (Date, Math, String, Misc) from
    the old JUnit3 testdef XML format to plain JUnit5/Jupiter tests so they
    run through a fast gradlew test instead of the full OFBiz test
    container.
    
    2) UelFunctions.FUNCTION_MAPPER is built once per JVM by scanning
    ComponentConfig, and ComponentConfig is normally only populated by
    ComponentContainer during a real OFBiz boot, so these lightweight
    Jupiter tests needed their own way to trigger just that piece.
    
    3) Added ComponentContainerTestSupport.java as a test-only bridge class
    living in the same package as ComponentContainer, so it can call its
    package-private init(String, Path) method without widening that method's
    visibility in production code.
    
    4) Added UelTestSupport.groovy as the idempotent helper that checks
    whether components are already loaded and triggers the bootstrap through
    ComponentContainerTestSupport exactly once per JVM.
    
    5) Added UelFunctionsBootstrapListener.groovy, registered via
    META-INF/services as a JUnit Platform LauncherSessionListener, so the
    bootstrap runs once before any test in the JVM is discovered, regardless
    of which test class Gradle happens to run first.
    
    6) That listener now loads the real, full component tree into the
    JVM-wide ComponentConfig cache for every test run, so we renamed the
    ComponentContainerTest fixture components from order/accounting to
    test-order/test-accounting to avoid name collisions with the real
    components.
    
    7) Updated ComponentContainerTest.java to filter its assertions down to
    just its own fixture names so the test stays a genuine check of
    dependency sorting regardless of what else is loaded into the shared
    cache.
    
    8) Removed the four uel-*-tests test-cases from
    framework/base/testdef/basetests.xml since they no longer run through
    the old JUnit3 testtools container.
    
    9) Added the junit-platform-launcher dependency to dependencies.gradle
    since that library is what actually discovers and invokes the new
    LauncherSessionListener.
    
    10) I also verified the branch with gradlew test for the UEL and base
    packages, checkstyleTest and codenarcTest, and both a scoped and a full
    testIntegration run, with all suites passing.
---
 dependencies.gradle                                |   1 +
 .../apache/ofbiz/base/test/uel/DateUelTest.groovy  | 118 -------------------
 .../apache/ofbiz/base/test/uel/MathUelTest.groovy  |  90 --------------
 .../ofbiz/base/test/uel/StringUelTest.groovy       |  70 -----------
 .../apache/ofbiz/base/test/uel/DateUelTest.groovy  | 131 +++++++++++++++++++++
 .../apache/ofbiz/base/test/uel/MathUelTest.groovy  | 106 +++++++++++++++++
 .../apache/ofbiz/base/test/uel/MiscUelTest.groovy  |  25 ++--
 .../ofbiz/base/test/uel/StringUelTest.groovy       |  84 +++++++++++++
 .../test/uel/UelFunctionsBootstrapListener.groovy  |  58 +++++++++
 .../ofbiz/base/test/uel/UelTestSupport.groovy      |  59 ++++++++++
 .../base/container/ComponentContainerTest.java     |  14 ++-
 .../container/ComponentContainerTestSupport.java   |  37 ++++++
 ...junit.platform.launcher.LauncherSessionListener |   1 +
 .../ofbiz-component.xml                            |   4 +-
 .../{order => test-order}/ofbiz-component.xml      |   2 +-
 framework/base/testdef/basetests.xml               |  13 --
 16 files changed, 508 insertions(+), 305 deletions(-)

diff --git a/dependencies.gradle b/dependencies.gradle
index 03303d97ae..5b945852f8 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -96,6 +96,7 @@ dependencies {
     testImplementation 'org.junit.jupiter:junit-jupiter-api:6.1.0'
     testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:6.1.0'
     testImplementation 'org.junit.jupiter:junit-jupiter-params:6.1.0'
+    testImplementation 'org.junit.platform:junit-platform-launcher:6.1.0'
     testImplementation 'org.hamcrest:hamcrest-library:2.2'
     testImplementation 'org.mockito:mockito-core:5.23.0'
     testImplementation 'org.jmockit:jmockit:1.50'
diff --git 
a/framework/base/src/main/groovy/org/apache/ofbiz/base/test/uel/DateUelTest.groovy
 
b/framework/base/src/main/groovy/org/apache/ofbiz/base/test/uel/DateUelTest.groovy
deleted file mode 100644
index 69359c8fd5..0000000000
--- 
a/framework/base/src/main/groovy/org/apache/ofbiz/base/test/uel/DateUelTest.groovy
+++ /dev/null
@@ -1,118 +0,0 @@
-/*******************************************************************************
- * 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.base.test.uel
-
-import org.apache.ofbiz.base.util.UtilDateTime
-import org.apache.ofbiz.base.util.string.FlexibleStringExpander
-import org.apache.ofbiz.base.util.string.UelFunctions
-import org.apache.ofbiz.service.testtools.OFBizTestCase
-
-import java.sql.Timestamp
-
-/**
- * ./gradlew 'ofbiz -t component=base -t suitename=basetests'
- */
-/* codenarc-disable GStringExpressionWithinString,ClosureAsLastMethodParameter 
*/
-class DateUelTest extends OFBizTestCase {
-
-    DateUelTest(String name) {
-        super(name)
-    }
-
-    void testDateUel() { // codenarc-disable JUnitTestMethodWithoutAssert
-        doUelDateTest('${date:second(now, timeZone, locale)}', { Timestamp now 
->
-            UtilDateTime.getSecond(now, TimeZone.getDefault(), 
Locale.getDefault())
-        })
-        doUelDateTest('${date:minute(now, timeZone, locale)}', { Timestamp now 
->
-            UtilDateTime.getMinute(now, TimeZone.getDefault(), 
Locale.getDefault())
-        })
-        doUelDateTest('${date:hour(now, timeZone, locale)}', { Timestamp now ->
-            UtilDateTime.getHour(now, TimeZone.getDefault(), 
Locale.getDefault())
-        })
-        doUelDateTest('${date:dayOfMonth(now, timeZone, locale)}', { Timestamp 
now ->
-            UtilDateTime.getDayOfMonth(now, TimeZone.getDefault(), 
Locale.getDefault())
-        })
-        doUelDateTest('${date:dayOfWeek(now, timeZone, locale)}', { Timestamp 
now ->
-            UtilDateTime.getDayOfWeek(now, TimeZone.getDefault(), 
Locale.getDefault())
-        })
-        doUelDateTest('${date:dayOfYear(now, timeZone, locale)}', { Timestamp 
now ->
-            UtilDateTime.getDayOfYear(now, TimeZone.getDefault(), 
Locale.getDefault())
-        })
-        doUelDateTest('${date:week(now, timeZone, locale)}', { Timestamp now ->
-            UtilDateTime.getWeek(now, TimeZone.getDefault(), 
Locale.getDefault())
-        })
-        doUelDateTest('${date:month(now, timeZone, locale)}', { Timestamp now 
->
-            UtilDateTime.getMonth(now, TimeZone.getDefault(), 
Locale.getDefault())
-        })
-        doUelDateTest('${date:year(now, timeZone, locale)}', { Timestamp now ->
-            UtilDateTime.getYear(now, TimeZone.getDefault(), 
Locale.getDefault())
-        })
-        doUelDateTest('${date:dayStart(now, timeZone, locale)}', { Timestamp 
now ->
-            UtilDateTime.getDayStart(now, TimeZone.getDefault(), 
Locale.getDefault())
-        })
-        doUelDateTest('${date:dayEnd(now, timeZone, locale)}', { Timestamp now 
->
-            UtilDateTime.getDayEnd(now, TimeZone.getDefault(), 
Locale.getDefault())
-        })
-        doUelDateTest('${date:weekStart(now, timeZone, locale)}', { Timestamp 
now ->
-            UtilDateTime.getWeekStart(now, TimeZone.getDefault(), 
Locale.getDefault())
-        })
-        doUelDateTest('${date:weekEnd(now, timeZone, locale)}', { Timestamp 
now ->
-            UtilDateTime.getWeekEnd(now, TimeZone.getDefault(), 
Locale.getDefault())
-        })
-        doUelDateTest('${date:monthStart(now, timeZone, locale)}', { Timestamp 
now ->
-            UtilDateTime.getMonthStart(now, TimeZone.getDefault(), 
Locale.getDefault())
-        })
-        doUelDateTest('${date:monthEnd(now, timeZone, locale)}', { Timestamp 
now ->
-            UtilDateTime.getMonthEnd(now, TimeZone.getDefault(), 
Locale.getDefault())
-        })
-        doUelDateTest('${date:yearStart(now, timeZone, locale)}', { Timestamp 
now ->
-            UtilDateTime.getYearStart(now, TimeZone.getDefault(), 
Locale.getDefault())
-        })
-        doUelDateTest('${date:yearEnd(now, timeZone, locale)}', { Timestamp 
now ->
-            UtilDateTime.getYearEnd(now, TimeZone.getDefault(), 
Locale.getDefault())
-        })
-        doUelDateTest('${date:dateStr(now, timeZone, locale)}', { Timestamp 
now ->
-            UelFunctions.dateString(now, TimeZone.getDefault(), 
Locale.getDefault())
-        })
-        doUelDateTest('${date:localizedDateStr(now, timeZone, locale)}', { 
Timestamp now ->
-            UelFunctions.localizedDateString(now, TimeZone.getDefault(), 
Locale.getDefault())
-        })
-        doUelDateTest('${date:localizedDateTimeStr(now, timeZone, locale)}', { 
Timestamp now ->
-            UelFunctions.localizedDateTimeString(now, TimeZone.getDefault(), 
Locale.getDefault())
-        })
-        doUelDateTest('${date:timeStr(now, timeZone, locale)}', { Timestamp 
now ->
-            UelFunctions.timeString(now, TimeZone.getDefault(), 
Locale.getDefault())
-        })
-    }
-
-    void testNowTimestampUel() {
-        FlexibleStringExpander fse = 
FlexibleStringExpander.getInstance('${date:nowTimestamp()}')
-        assert (fse.expand([:]).time - UtilDateTime.nowTimestamp().time).abs() 
< 500 // less than 500 ms apart
-    }
-
-    private void doUelDateTest(String uelInput, Closure uelFunction) {
-        Timestamp now = UtilDateTime.nowTimestamp()
-        Map context = [now: now,
-                       timeZone: TimeZone.getDefault(),
-                       locale: Locale.getDefault()]
-        FlexibleStringExpander fse = 
FlexibleStringExpander.getInstance(uelInput)
-        assert fse.expand(context) == uelFunction(now)
-    }
-
-}
diff --git 
a/framework/base/src/main/groovy/org/apache/ofbiz/base/test/uel/MathUelTest.groovy
 
b/framework/base/src/main/groovy/org/apache/ofbiz/base/test/uel/MathUelTest.groovy
deleted file mode 100644
index 8cf9e7efd7..0000000000
--- 
a/framework/base/src/main/groovy/org/apache/ofbiz/base/test/uel/MathUelTest.groovy
+++ /dev/null
@@ -1,90 +0,0 @@
-/*******************************************************************************
- * 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.base.test.uel
-
-import org.apache.ofbiz.base.util.string.FlexibleStringExpander
-import org.apache.ofbiz.service.testtools.OFBizTestCase
-
-/**
- * ./gradlew 'ofbiz -t component=base -t suitename=basetests'
- */
-/* codenarc-disable GStringExpressionWithinString,ClosureAsLastMethodParameter 
*/
-
-class MathUelTest extends OFBizTestCase {
-
-    MathUelTest(String name) { super(name) }
-
-    void testMathUel() { // codenarc-disable JUnitTestMethodWithoutAssert
-        doMathTest('${math:absDouble(a)}', [a: -0.3, b: null], { a, b -> 
Math.abs(a as double) })
-        doMathTest('${math:absFloat(a)}', [a: -0.3, b: null], { a, b -> 
Math.abs(a as float) })
-        doMathTest('${math:absInt(a)}', [a: -2, b: null], { a, b -> Math.abs(a 
as int) })
-        doMathTest('${math:absLong(a)}', [a: -5, b: null], { a, b -> 
Math.abs(a as long) })
-        doMathTest('${math:acos(a)}', [a: 0.2, b: null], { a, b -> 
Math.acos(a) })
-        doMathTest('${math:asin(a)}', [a: 0.2, b: null], { a, b -> 
Math.asin(a) })
-        doMathTest('${math:atan(a)}', [a: 0.2, b: null], { a, b -> 
Math.atan(a) })
-        doMathTest('${math:atan2(a, b)}', [a: 0.2, b: 0.2], { a, b -> 
Math.atan2(a, b) })
-        doMathTest('${math:cbrt(a)}', [a: 10, b: null], { a, b -> Math.cbrt(a) 
})
-        doMathTest('${math:ceil(a)}', [a: 20, b: null], { a, b -> Math.ceil(a) 
})
-        doMathTest('${math:cos(a)}', [a: 0.2, b: null], { a, b -> Math.cos(a) 
})
-        doMathTest('${math:cosh(a)}', [a: 0.2, b: null], { a, b -> 
Math.cosh(a) })
-        doMathTest('${math:exp(a)}', [a: 29, b: null], { a, b -> Math.exp(a) })
-        doMathTest('${math:expm1(a)}', [a: 20, b: null], { a, b -> 
Math.expm1(a) })
-        doMathTest('${math:floor(a)}', [a: 23.4, b: null], { a, b -> 
Math.floor(a) })
-        doMathTest('${math:hypot(a, b)}', [a: 29, b: 12], { a, b -> 
Math.hypot(a, b) })
-        doMathTest('${math:IEEEremainder(a, b)}', [a: 12, b: 1.3], { a, b -> 
Math.IEEEremainder(a, b) })
-        doMathTest('${math:log(a)}', [a: 20, b: null], { a, b -> Math.log(a) })
-        doMathTest('${math:log10(a)}', [a: 29, b: null], { a, b -> 
Math.log10(a) })
-        doMathTest('${math:log1p(a)}', [a: 12, b: null], { a, b -> 
Math.log1p(a) })
-        doMathTest('${math:maxDouble(a, b)}', [a: 12, b: 13], { a, b -> 
Math.max(a as double, b as double) })
-        doMathTest('${math:maxFloat(a, b)}', [a: 2.4, b: 3.9], { a, b -> 
Math.max(a as float, b as float) })
-        doMathTest('${math:maxInt(a, b)}', [a: 2.6, b: 3.7], { a, b -> 
Math.max(a as int, b as int) })
-        doMathTest('${math:maxLong(a, b)}', [a: 23, b: 32], { a, b -> 
Math.max(a as long, b as long) })
-        doMathTest('${math:minDouble(a, b)}', [a: 10, b: 20], { a, b -> 
Math.min(a as double, b as double) })
-        doMathTest('${math:minFloat(a, b)}', [a: 1.2, b: 2.5], { a, b -> 
Math.min(a as float, b as float) })
-        doMathTest('${math:minInt(a, b)}', [a: 12, b: 13], { a, b -> 
Math.min(a as int, b as int) })
-        doMathTest('${math:minLong(a, b)}', [a: 24, b: 14], { a, b -> 
Math.min(a as long, b as long) })
-        doMathTest('${math:pow(a, b)}', [a: 12, b: 13], { a, b -> Math.pow(a, 
b) })
-        doMathTest('${math:rint(a)}', [a: 29, b: null], { a, b -> Math.rint(a) 
})
-        doMathTest('${math:roundDouble(a)}', [a: 23.4, b: null], { a, b -> 
Math.round(a as double) })
-        doMathTest('${math:roundFloat(a)}', [a: 12.4, b: null], { a, b -> 
Math.round(a as float) })
-        doMathTest('${math:signumDouble(a)}', [a: 23, b: null], { a, b -> 
Math.signum(a as double) })
-        doMathTest('${math:signumFloat(a)}', [a: 23, b: null], { a, b -> 
Math.signum(a as float) })
-        doMathTest('${math:sin(a)}', [a: 12, b: null], { a, b -> Math.sin(a) })
-        doMathTest('${math:sinh(a)}', [a: 12, b: null], { a, b -> Math.sinh(a) 
})
-        doMathTest('${math:sqrt(a)}', [a: 34, b: null], { a, b -> Math.sqrt(a) 
})
-        doMathTest('${math:tan(a)}', [a: 13, b: null], { a, b -> Math.tan(a) })
-        doMathTest('${math:tanh(a)}', [a: 30, b: null], { a, b -> Math.tanh(a) 
})
-        doMathTest('${math:toDegrees(a)}', [a: 30, b: null], { a, b -> 
Math.toDegrees(a) })
-        doMathTest('${math:toRadians(a)}', [a: 12, b: null], { a, b -> 
Math.toRadians(a) })
-        doMathTest('${math:ulpDouble(a)}', [a: 12, b: null], { a, b -> 
Math.ulp(a as double) })
-        doMathTest('${math:ulpFloat(a)}', [a: 30, b: null], { a, b -> 
Math.ulp(a as float) })
-    }
-
-    void testMathRandom() {
-        FlexibleStringExpander fse = 
FlexibleStringExpander.getInstance('${math:random()}')
-        assert fse.expand([:]) instanceof Double
-        assert fse.expand([:]) != BigDecimal.ZERO
-    }
-
-    private void doMathTest(String uelInput, Map context, Closure uelFunction) 
{
-        FlexibleStringExpander fse = 
FlexibleStringExpander.getInstance(uelInput)
-        assert new BigDecimal(fse.expand(context)) == new 
BigDecimal(uelFunction(context.a, context.b))
-    }
-
-}
diff --git 
a/framework/base/src/main/groovy/org/apache/ofbiz/base/test/uel/StringUelTest.groovy
 
b/framework/base/src/main/groovy/org/apache/ofbiz/base/test/uel/StringUelTest.groovy
deleted file mode 100644
index ad24acce02..0000000000
--- 
a/framework/base/src/main/groovy/org/apache/ofbiz/base/test/uel/StringUelTest.groovy
+++ /dev/null
@@ -1,70 +0,0 @@
-/*******************************************************************************
- * 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.base.test.uel
-
-import org.apache.ofbiz.base.util.string.FlexibleStringExpander
-import org.apache.ofbiz.base.util.string.UelFunctions
-import org.apache.ofbiz.service.testtools.OFBizTestCase
-
-/**
- * ./gradlew 'ofbiz -t component=base -t suitename=basetests'
- */
-/* codenarc-disable GStringExpressionWithinString,ClosureAsLastMethodParameter 
*/
-
-class StringUelTest extends OFBizTestCase {
-
-    StringUelTest(String name) { super(name) }
-
-    void testUelString() { // codenarc-disable JUnitTestMethodWithoutAssert
-        doStringTest('${str:endsWith(a, b)}', [a: 'dog', b: 'og', c: null],
-                { a, b, c -> UelFunctions.endsWith(a, b) })
-        doStringTest('${str:indexOf(a, b)}', [a: 'dog', b: 'og', c: null],
-                { a, b, c -> UelFunctions.indexOf(a, b) })
-        doStringTest('${str:lastIndexOf(a, b)}', [a: 'dog', b: 'og', c: null],
-                { a, b, c -> UelFunctions.lastIndexOf(a, b) })
-        doStringTest('${str:length(a)}', [a: 'dog', b: null, c: null],
-                { a, b, c -> UelFunctions.length(a) })
-        doStringTest('${str:replace(a, b, c)}', [a: 'the dog', b: 'dog', c: 
'cat'],
-                { a, b, c -> UelFunctions.replace(a, b, c) })
-        doStringTest('${str:replaceAll(a, b, c)}', [a: 'the dog', b: 'dog', c: 
'cat'],
-                { a, b, c -> UelFunctions.replaceAll(a, b, c) })
-        doStringTest('${str:replaceFirst(a, b, c)}', [a: 'the dog', b: 'dog', 
c: 'cat'],
-                { a, b, c -> UelFunctions.replaceFirst(a, b, c) })
-        doStringTest('${str:startsWith(a, b)}', [a: 'the dog', b: 'the', c: 
null],
-                { a, b, c -> UelFunctions.startsWith(a, b) })
-        doStringTest('${str:endstring(a, b)}', [a: 'the dog', b: 3, c: null],
-                { a, b, c -> UelFunctions.endString(a, b) })
-        doStringTest('${str:substring(a, b, c)}', [a: 'the dog', b: 3, c: 7],
-                { a, b, c -> UelFunctions.subString(a, b, c) })
-        doStringTest('${str:toString(a)}', [a: 'foo', b: null, c: null],
-                { a, b, c -> UelFunctions.toString(a) })
-        doStringTest('${str:toLowerCase(a)}', [a: 'FOO', b: null, c: null],
-                { a, b, c -> UelFunctions.toLowerCase(a) })
-        doStringTest('${str:toUpperCase(a)}', [a: 'foo', b: null, c: null],
-                { a, b, c -> UelFunctions.toUpperCase(a) })
-        doStringTest('${str:trim(a)}', [a: ' foo ', b: null, c: null],
-                { a, b, c -> UelFunctions.trim(a) })
-    }
-
-    private void doStringTest(String uelInput, Map context, Closure 
uelFunction) {
-        FlexibleStringExpander fse = 
FlexibleStringExpander.getInstance(uelInput)
-        assert fse.expand(context) == uelFunction(context.a, context.b, 
context.c)
-    }
-
-}
diff --git 
a/framework/base/src/test/groovy/org/apache/ofbiz/base/test/uel/DateUelTest.groovy
 
b/framework/base/src/test/groovy/org/apache/ofbiz/base/test/uel/DateUelTest.groovy
new file mode 100644
index 0000000000..99394f89e3
--- /dev/null
+++ 
b/framework/base/src/test/groovy/org/apache/ofbiz/base/test/uel/DateUelTest.groovy
@@ -0,0 +1,131 @@
+/*******************************************************************************
+ * 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.base.test.uel
+
+import org.apache.ofbiz.base.util.UtilDateTime
+import org.apache.ofbiz.base.util.string.FlexibleStringExpander
+import org.apache.ofbiz.base.util.string.UelFunctions
+
+import java.sql.Timestamp
+import java.util.stream.Stream
+
+import org.junit.jupiter.api.BeforeAll
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.MethodSource
+
+/**
+ * ./gradlew test --tests "org.apache.ofbiz.base.test.uel.DateUelTest"
+ */
+/* codenarc-disable GStringExpressionWithinString,ClosureAsLastMethodParameter 
*/
+class DateUelTest {
+
+    @BeforeAll
+    static void loadUelFunctions() {
+        UelTestSupport.ensureUelFunctionsLoaded()
+    }
+
+    @ParameterizedTest(name = '{0}')
+    @MethodSource('dateUelExpressions')
+    void dateUelExpressionMatchesExpectedValue(String uelInput, Closure<?> 
expectedFunction) { // codenarc-disable JUnitTestMethodWithoutAssert
+        Timestamp now = UtilDateTime.nowTimestamp()
+        Map context = [now: now,
+                       timeZone: TimeZone.getDefault(),
+                       locale: Locale.getDefault()]
+        FlexibleStringExpander fse = 
FlexibleStringExpander.getInstance(uelInput)
+        assert fse.expand(context) == expectedFunction(now)
+    }
+
+    @Test
+    void nowTimestampUelIsWithinToleranceOfActualNow() {
+        FlexibleStringExpander fse = 
FlexibleStringExpander.getInstance('${date:nowTimestamp()}')
+        assert (fse.expand([:]).time - UtilDateTime.nowTimestamp().time).abs() 
< 500 // less than 500 ms apart
+    }
+
+    @SuppressWarnings('UnusedPrivateMethod')
+    private static Stream<Arguments> dateUelExpressions() {
+        Stream.of(
+            Arguments.of('${date:second(now, timeZone, locale)}', { Timestamp 
now ->
+                UtilDateTime.getSecond(now, TimeZone.getDefault(), 
Locale.getDefault())
+            }),
+            Arguments.of('${date:minute(now, timeZone, locale)}', { Timestamp 
now ->
+                UtilDateTime.getMinute(now, TimeZone.getDefault(), 
Locale.getDefault())
+            }),
+            Arguments.of('${date:hour(now, timeZone, locale)}', { Timestamp 
now ->
+                UtilDateTime.getHour(now, TimeZone.getDefault(), 
Locale.getDefault())
+            }),
+            Arguments.of('${date:dayOfMonth(now, timeZone, locale)}', { 
Timestamp now ->
+                UtilDateTime.getDayOfMonth(now, TimeZone.getDefault(), 
Locale.getDefault())
+            }),
+            Arguments.of('${date:dayOfWeek(now, timeZone, locale)}', { 
Timestamp now ->
+                UtilDateTime.getDayOfWeek(now, TimeZone.getDefault(), 
Locale.getDefault())
+            }),
+            Arguments.of('${date:dayOfYear(now, timeZone, locale)}', { 
Timestamp now ->
+                UtilDateTime.getDayOfYear(now, TimeZone.getDefault(), 
Locale.getDefault())
+            }),
+            Arguments.of('${date:week(now, timeZone, locale)}', { Timestamp 
now ->
+                UtilDateTime.getWeek(now, TimeZone.getDefault(), 
Locale.getDefault())
+            }),
+            Arguments.of('${date:month(now, timeZone, locale)}', { Timestamp 
now ->
+                UtilDateTime.getMonth(now, TimeZone.getDefault(), 
Locale.getDefault())
+            }),
+            Arguments.of('${date:year(now, timeZone, locale)}', { Timestamp 
now ->
+                UtilDateTime.getYear(now, TimeZone.getDefault(), 
Locale.getDefault())
+            }),
+            Arguments.of('${date:dayStart(now, timeZone, locale)}', { 
Timestamp now ->
+                UtilDateTime.getDayStart(now, TimeZone.getDefault(), 
Locale.getDefault())
+            }),
+            Arguments.of('${date:dayEnd(now, timeZone, locale)}', { Timestamp 
now ->
+                UtilDateTime.getDayEnd(now, TimeZone.getDefault(), 
Locale.getDefault())
+            }),
+            Arguments.of('${date:weekStart(now, timeZone, locale)}', { 
Timestamp now ->
+                UtilDateTime.getWeekStart(now, TimeZone.getDefault(), 
Locale.getDefault())
+            }),
+            Arguments.of('${date:weekEnd(now, timeZone, locale)}', { Timestamp 
now ->
+                UtilDateTime.getWeekEnd(now, TimeZone.getDefault(), 
Locale.getDefault())
+            }),
+            Arguments.of('${date:monthStart(now, timeZone, locale)}', { 
Timestamp now ->
+                UtilDateTime.getMonthStart(now, TimeZone.getDefault(), 
Locale.getDefault())
+            }),
+            Arguments.of('${date:monthEnd(now, timeZone, locale)}', { 
Timestamp now ->
+                UtilDateTime.getMonthEnd(now, TimeZone.getDefault(), 
Locale.getDefault())
+            }),
+            Arguments.of('${date:yearStart(now, timeZone, locale)}', { 
Timestamp now ->
+                UtilDateTime.getYearStart(now, TimeZone.getDefault(), 
Locale.getDefault())
+            }),
+            Arguments.of('${date:yearEnd(now, timeZone, locale)}', { Timestamp 
now ->
+                UtilDateTime.getYearEnd(now, TimeZone.getDefault(), 
Locale.getDefault())
+            }),
+            Arguments.of('${date:dateStr(now, timeZone, locale)}', { Timestamp 
now ->
+                UelFunctions.dateString(now, TimeZone.getDefault(), 
Locale.getDefault())
+            }),
+            Arguments.of('${date:localizedDateStr(now, timeZone, locale)}', { 
Timestamp now ->
+                UelFunctions.localizedDateString(now, TimeZone.getDefault(), 
Locale.getDefault())
+            }),
+            Arguments.of('${date:localizedDateTimeStr(now, timeZone, 
locale)}', { Timestamp now ->
+                UelFunctions.localizedDateTimeString(now, 
TimeZone.getDefault(), Locale.getDefault())
+            }),
+            Arguments.of('${date:timeStr(now, timeZone, locale)}', { Timestamp 
now ->
+                UelFunctions.timeString(now, TimeZone.getDefault(), 
Locale.getDefault())
+            })
+        )
+    }
+
+}
diff --git 
a/framework/base/src/test/groovy/org/apache/ofbiz/base/test/uel/MathUelTest.groovy
 
b/framework/base/src/test/groovy/org/apache/ofbiz/base/test/uel/MathUelTest.groovy
new file mode 100644
index 0000000000..54e1e75149
--- /dev/null
+++ 
b/framework/base/src/test/groovy/org/apache/ofbiz/base/test/uel/MathUelTest.groovy
@@ -0,0 +1,106 @@
+/*******************************************************************************
+ * 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.base.test.uel
+
+import org.apache.ofbiz.base.util.string.FlexibleStringExpander
+
+import java.util.stream.Stream
+
+import org.junit.jupiter.api.BeforeAll
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.MethodSource
+
+/**
+ * ./gradlew test --tests "org.apache.ofbiz.base.test.uel.MathUelTest"
+ */
+/* codenarc-disable GStringExpressionWithinString,ClosureAsLastMethodParameter 
*/
+class MathUelTest {
+
+    @BeforeAll
+    static void loadUelFunctions() {
+        UelTestSupport.ensureUelFunctionsLoaded()
+    }
+
+    @ParameterizedTest(name = '{0}')
+    @MethodSource('mathUelExpressions')
+    void mathUelExpressionMatchesExpectedValue(String uelInput, Map context,
+            Closure<?> expectedFunction) { // codenarc-disable 
JUnitTestMethodWithoutAssert
+        FlexibleStringExpander fse = 
FlexibleStringExpander.getInstance(uelInput)
+        assert new BigDecimal(fse.expand(context)) == new 
BigDecimal(expectedFunction(context.a, context.b))
+    }
+
+    @Test
+    void mathRandomUelReturnsNonZeroDouble() {
+        FlexibleStringExpander fse = 
FlexibleStringExpander.getInstance('${math:random()}')
+        assert fse.expand([:]) instanceof Double
+        assert fse.expand([:]) != BigDecimal.ZERO
+    }
+
+    @SuppressWarnings('UnusedPrivateMethod')
+    private static Stream<Arguments> mathUelExpressions() {
+        Stream.of(
+            Arguments.of('${math:absDouble(a)}', [a: -0.3, b: null], { a, b -> 
Math.abs(a as double) }),
+            Arguments.of('${math:absFloat(a)}', [a: -0.3, b: null], { a, b -> 
Math.abs(a as float) }),
+            Arguments.of('${math:absInt(a)}', [a: -2, b: null], { a, b -> 
Math.abs(a as int) }),
+            Arguments.of('${math:absLong(a)}', [a: -5, b: null], { a, b -> 
Math.abs(a as long) }),
+            Arguments.of('${math:acos(a)}', [a: 0.2, b: null], { a, b -> 
Math.acos(a) }),
+            Arguments.of('${math:asin(a)}', [a: 0.2, b: null], { a, b -> 
Math.asin(a) }),
+            Arguments.of('${math:atan(a)}', [a: 0.2, b: null], { a, b -> 
Math.atan(a) }),
+            Arguments.of('${math:atan2(a, b)}', [a: 0.2, b: 0.2], { a, b -> 
Math.atan2(a, b) }),
+            Arguments.of('${math:cbrt(a)}', [a: 10, b: null], { a, b -> 
Math.cbrt(a) }),
+            Arguments.of('${math:ceil(a)}', [a: 20, b: null], { a, b -> 
Math.ceil(a) }),
+            Arguments.of('${math:cos(a)}', [a: 0.2, b: null], { a, b -> 
Math.cos(a) }),
+            Arguments.of('${math:cosh(a)}', [a: 0.2, b: null], { a, b -> 
Math.cosh(a) }),
+            Arguments.of('${math:exp(a)}', [a: 29, b: null], { a, b -> 
Math.exp(a) }),
+            Arguments.of('${math:expm1(a)}', [a: 20, b: null], { a, b -> 
Math.expm1(a) }),
+            Arguments.of('${math:floor(a)}', [a: 23.4, b: null], { a, b -> 
Math.floor(a) }),
+            Arguments.of('${math:hypot(a, b)}', [a: 29, b: 12], { a, b -> 
Math.hypot(a, b) }),
+            Arguments.of('${math:IEEEremainder(a, b)}', [a: 12, b: 1.3], { a, 
b -> Math.IEEEremainder(a, b) }),
+            Arguments.of('${math:log(a)}', [a: 20, b: null], { a, b -> 
Math.log(a) }),
+            Arguments.of('${math:log10(a)}', [a: 29, b: null], { a, b -> 
Math.log10(a) }),
+            Arguments.of('${math:log1p(a)}', [a: 12, b: null], { a, b -> 
Math.log1p(a) }),
+            Arguments.of('${math:maxDouble(a, b)}', [a: 12, b: 13], { a, b -> 
Math.max(a as double, b as double) }),
+            Arguments.of('${math:maxFloat(a, b)}', [a: 2.4, b: 3.9], { a, b -> 
Math.max(a as float, b as float) }),
+            Arguments.of('${math:maxInt(a, b)}', [a: 2.6, b: 3.7], { a, b -> 
Math.max(a as int, b as int) }),
+            Arguments.of('${math:maxLong(a, b)}', [a: 23, b: 32], { a, b -> 
Math.max(a as long, b as long) }),
+            Arguments.of('${math:minDouble(a, b)}', [a: 10, b: 20], { a, b -> 
Math.min(a as double, b as double) }),
+            Arguments.of('${math:minFloat(a, b)}', [a: 1.2, b: 2.5], { a, b -> 
Math.min(a as float, b as float) }),
+            Arguments.of('${math:minInt(a, b)}', [a: 12, b: 13], { a, b -> 
Math.min(a as int, b as int) }),
+            Arguments.of('${math:minLong(a, b)}', [a: 24, b: 14], { a, b -> 
Math.min(a as long, b as long) }),
+            Arguments.of('${math:pow(a, b)}', [a: 12, b: 13], { a, b -> 
Math.pow(a, b) }),
+            Arguments.of('${math:rint(a)}', [a: 29, b: null], { a, b -> 
Math.rint(a) }),
+            Arguments.of('${math:roundDouble(a)}', [a: 23.4, b: null], { a, b 
-> Math.round(a as double) }),
+            Arguments.of('${math:roundFloat(a)}', [a: 12.4, b: null], { a, b 
-> Math.round(a as float) }),
+            Arguments.of('${math:signumDouble(a)}', [a: 23, b: null], { a, b 
-> Math.signum(a as double) }),
+            Arguments.of('${math:signumFloat(a)}', [a: 23, b: null], { a, b -> 
Math.signum(a as float) }),
+            Arguments.of('${math:sin(a)}', [a: 12, b: null], { a, b -> 
Math.sin(a) }),
+            Arguments.of('${math:sinh(a)}', [a: 12, b: null], { a, b -> 
Math.sinh(a) }),
+            Arguments.of('${math:sqrt(a)}', [a: 34, b: null], { a, b -> 
Math.sqrt(a) }),
+            Arguments.of('${math:tan(a)}', [a: 13, b: null], { a, b -> 
Math.tan(a) }),
+            Arguments.of('${math:tanh(a)}', [a: 30, b: null], { a, b -> 
Math.tanh(a) }),
+            Arguments.of('${math:toDegrees(a)}', [a: 30, b: null], { a, b -> 
Math.toDegrees(a) }),
+            Arguments.of('${math:toRadians(a)}', [a: 12, b: null], { a, b -> 
Math.toRadians(a) }),
+            Arguments.of('${math:ulpDouble(a)}', [a: 12, b: null], { a, b -> 
Math.ulp(a as double) }),
+            Arguments.of('${math:ulpFloat(a)}', [a: 30, b: null], { a, b -> 
Math.ulp(a as float) })
+        )
+    }
+
+}
diff --git 
a/framework/base/src/main/groovy/org/apache/ofbiz/base/test/uel/MiscUelTest.groovy
 
b/framework/base/src/test/groovy/org/apache/ofbiz/base/test/uel/MiscUelTest.groovy
similarity index 85%
rename from 
framework/base/src/main/groovy/org/apache/ofbiz/base/test/uel/MiscUelTest.groovy
rename to 
framework/base/src/test/groovy/org/apache/ofbiz/base/test/uel/MiscUelTest.groovy
index cce82265c1..7b5659196c 100644
--- 
a/framework/base/src/main/groovy/org/apache/ofbiz/base/test/uel/MiscUelTest.groovy
+++ 
b/framework/base/src/test/groovy/org/apache/ofbiz/base/test/uel/MiscUelTest.groovy
@@ -20,35 +20,44 @@ package org.apache.ofbiz.base.test.uel
 
 import org.apache.ofbiz.base.util.string.FlexibleStringExpander
 import org.apache.ofbiz.base.util.string.UelFunctions
-import org.apache.ofbiz.service.testtools.OFBizTestCase
+
+import org.junit.jupiter.api.BeforeAll
+import org.junit.jupiter.api.Test
 
 /**
- * ./gradlew 'ofbiz -t component=base -t suitename=basetests'
+ * ./gradlew test --tests "org.apache.ofbiz.base.test.uel.MiscUelTest"
  */
 /* codenarc-disable GStringExpressionWithinString,ClosureAsLastMethodParameter 
*/
-class MiscUelTest extends OFBizTestCase {
+class MiscUelTest {
 
-    MiscUelTest(String name) { super(name) }
+    @BeforeAll
+    static void loadUelFunctions() {
+        UelTestSupport.ensureUelFunctionsLoaded()
+    }
 
-    void testSystemUel() { // codenarc-disable JUnitTestMethodWithoutAssert
+    @Test
+    void systemEnvAndPropertyUelFunctionsWork() { // codenarc-disable 
JUnitTestMethodWithoutAssert
         doUelSystemTest('${sys:getenv("foo")}', 'foo', { String prop -> 
UelFunctions.sysGetEnv(prop) })
         doUelSystemTest('${sys:getProperty("bar")}', 'bar', { String prop -> 
UelFunctions.sysGetProp(prop) })
     }
 
-    void testUtilSizeUel() {
+    @Test
+    void utilSizeUelFunctionReturnsListSize() {
         List foo = [1, 2, 3]
         FlexibleStringExpander fse = 
FlexibleStringExpander.getInstance('${util:size(foo)}')
         assert fse.expand([foo: foo]) == UelFunctions.getSize(foo)
     }
 
-    void testDefaultLocaleAndTimzoneUel() {
+    @Test
+    void defaultLocaleAndTimeZoneUelFunctionsReturnSystemDefaults() {
         FlexibleStringExpander fse = 
FlexibleStringExpander.getInstance('${util:defaultLocale()}')
         assert fse.expand([foo: 'bar']) == Locale.getDefault()
         fse = FlexibleStringExpander.getInstance('${util:defaultTimeZone()}')
         assert fse.expand([foo: 'bar']) == TimeZone.getDefault()
     }
 
-    void testLabelUel() {
+    @Test
+    void labelUelFunctionResolvesPropertyLabel() {
         String labelFile = 'CommonEntityLabels', labelKey = 
'VisualTheme.description.RAINBOWSTONE_AMBER'
         FlexibleStringExpander fse = FlexibleStringExpander.getInstance(
                 
'${util:label(\'CommonEntityLabels\',\'VisualTheme.description.RAINBOWSTONE_AMBER\',
 locale)}')
diff --git 
a/framework/base/src/test/groovy/org/apache/ofbiz/base/test/uel/StringUelTest.groovy
 
b/framework/base/src/test/groovy/org/apache/ofbiz/base/test/uel/StringUelTest.groovy
new file mode 100644
index 0000000000..b114027cad
--- /dev/null
+++ 
b/framework/base/src/test/groovy/org/apache/ofbiz/base/test/uel/StringUelTest.groovy
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * 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.base.test.uel
+
+import org.apache.ofbiz.base.util.string.FlexibleStringExpander
+import org.apache.ofbiz.base.util.string.UelFunctions
+
+import java.util.stream.Stream
+
+import org.junit.jupiter.api.BeforeAll
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.MethodSource
+
+/**
+ * ./gradlew test --tests "org.apache.ofbiz.base.test.uel.StringUelTest"
+ */
+/* codenarc-disable GStringExpressionWithinString,ClosureAsLastMethodParameter 
*/
+class StringUelTest {
+
+    @BeforeAll
+    static void loadUelFunctions() {
+        UelTestSupport.ensureUelFunctionsLoaded()
+    }
+
+    @ParameterizedTest(name = '{0}')
+    @MethodSource('stringUelExpressions')
+    void stringUelExpressionMatchesExpectedValue(String uelInput, Map context,
+            Closure<?> expectedFunction) { // codenarc-disable 
JUnitTestMethodWithoutAssert
+        FlexibleStringExpander fse = 
FlexibleStringExpander.getInstance(uelInput)
+        assert fse.expand(context) == expectedFunction(context.a, context.b, 
context.c)
+    }
+
+    @SuppressWarnings('UnusedPrivateMethod')
+    private static Stream<Arguments> stringUelExpressions() {
+        Stream.of(
+            Arguments.of('${str:endsWith(a, b)}', [a: 'dog', b: 'og', c: null],
+                    { a, b, c -> UelFunctions.endsWith(a, b) }),
+            Arguments.of('${str:indexOf(a, b)}', [a: 'dog', b: 'og', c: null],
+                    { a, b, c -> UelFunctions.indexOf(a, b) }),
+            Arguments.of('${str:lastIndexOf(a, b)}', [a: 'dog', b: 'og', c: 
null],
+                    { a, b, c -> UelFunctions.lastIndexOf(a, b) }),
+            Arguments.of('${str:length(a)}', [a: 'dog', b: null, c: null],
+                    { a, b, c -> UelFunctions.length(a) }),
+            Arguments.of('${str:replace(a, b, c)}', [a: 'the dog', b: 'dog', 
c: 'cat'],
+                    { a, b, c -> UelFunctions.replace(a, b, c) }),
+            Arguments.of('${str:replaceAll(a, b, c)}', [a: 'the dog', b: 
'dog', c: 'cat'],
+                    { a, b, c -> UelFunctions.replaceAll(a, b, c) }),
+            Arguments.of('${str:replaceFirst(a, b, c)}', [a: 'the dog', b: 
'dog', c: 'cat'],
+                    { a, b, c -> UelFunctions.replaceFirst(a, b, c) }),
+            Arguments.of('${str:startsWith(a, b)}', [a: 'the dog', b: 'the', 
c: null],
+                    { a, b, c -> UelFunctions.startsWith(a, b) }),
+            Arguments.of('${str:endstring(a, b)}', [a: 'the dog', b: 3, c: 
null],
+                    { a, b, c -> UelFunctions.endString(a, b) }),
+            Arguments.of('${str:substring(a, b, c)}', [a: 'the dog', b: 3, c: 
7],
+                    { a, b, c -> UelFunctions.subString(a, b, c) }),
+            Arguments.of('${str:toString(a)}', [a: 'foo', b: null, c: null],
+                    { a, b, c -> UelFunctions.toString(a) }),
+            Arguments.of('${str:toLowerCase(a)}', [a: 'FOO', b: null, c: null],
+                    { a, b, c -> UelFunctions.toLowerCase(a) }),
+            Arguments.of('${str:toUpperCase(a)}', [a: 'foo', b: null, c: null],
+                    { a, b, c -> UelFunctions.toUpperCase(a) }),
+            Arguments.of('${str:trim(a)}', [a: ' foo ', b: null, c: null],
+                    { a, b, c -> UelFunctions.trim(a) })
+        )
+    }
+
+}
diff --git 
a/framework/base/src/test/groovy/org/apache/ofbiz/base/test/uel/UelFunctionsBootstrapListener.groovy
 
b/framework/base/src/test/groovy/org/apache/ofbiz/base/test/uel/UelFunctionsBootstrapListener.groovy
new file mode 100644
index 0000000000..33bfd6141b
--- /dev/null
+++ 
b/framework/base/src/test/groovy/org/apache/ofbiz/base/test/uel/UelFunctionsBootstrapListener.groovy
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * 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.base.test.uel
+
+import org.apache.ofbiz.base.util.Debug
+import org.junit.platform.launcher.LauncherSession
+import org.junit.platform.launcher.LauncherSessionListener
+
+/**
+ * Registered via META-INF/services (see the resource file alongside this 
class) so JUnit
+ * Platform's LauncherFactory discovers and invokes launcherSessionOpened() 
exactly once, before
+ * any test in the JVM is discovered or executed - regardless of which test 
class Gradle would
+ * otherwise run first. This is the structural fix for a class-execution-order 
dependency that
+ * UelTestSupport.ensureUelFunctionsLoaded()'s per-class @BeforeAll wiring 
alone can't guarantee
+ * once dozens of unrelated tests elsewhere in the repo can also touch 
UelFunctions first in a
+ * full, unfiltered gradlew test run.
+ *
+ * <p>UelTestSupport.ensureUelFunctionsLoaded() is also called directly from 
each of the 4 UEL
+ * test classes' own {@literal @}BeforeAll. Both call sites are intentionally 
present: this
+ * listener is the fast/global path for a normal full-suite run, while the 
per-class
+ * {@literal @}BeforeAll is what retries the bootstrap - and correctly 
attributes a failure to the
+ * right test class - if this listener's own attempt failed.
+ */
+class UelFunctionsBootstrapListener implements LauncherSessionListener {
+
+    private static final String MODULE = 
UelFunctionsBootstrapListener.getName()
+
+    @Override
+    void launcherSessionOpened(LauncherSession session) {
+        try {
+            UelTestSupport.ensureUelFunctionsLoaded()
+        } catch (Exception e) {
+            // Deliberately swallowed here: an exception from this 
session-wide hook would abort
+            // every test in the JVM, not just the ones that need UEL 
functions. Leaving
+            // COMPONENTS_LOADED unset means the affected UEL test classes' 
own @BeforeAll will
+            // retry and surface this same failure, scoped correctly to just 
those classes.
+            Debug.logError(e, 'UEL function bootstrap failed during session 
startup; ' +
+                    'UEL test classes will retry and report the failure', 
MODULE)
+        }
+    }
+
+}
diff --git 
a/framework/base/src/test/groovy/org/apache/ofbiz/base/test/uel/UelTestSupport.groovy
 
b/framework/base/src/test/groovy/org/apache/ofbiz/base/test/uel/UelTestSupport.groovy
new file mode 100644
index 0000000000..1b01aa1ee4
--- /dev/null
+++ 
b/framework/base/src/test/groovy/org/apache/ofbiz/base/test/uel/UelTestSupport.groovy
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * 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.base.test.uel
+
+import org.apache.ofbiz.base.component.ComponentConfig
+import org.apache.ofbiz.base.container.ComponentContainerTestSupport
+
+import java.nio.file.Path
+import java.nio.file.Paths
+import java.util.concurrent.atomic.AtomicBoolean
+
+/**
+ * UelFunctions.FUNCTION_MAPPER 
(framework/base/.../util/string/UelFunctions.java) is a static
+ * field built exactly once, the first time UelFunctions is touched by any 
code in the JVM, by
+ * scanning ComponentConfig for every component's &lt;uel-mapping&gt; 
declarations. ComponentConfig
+ * is normally populated by ComponentContainer during full OFBiz boot - never 
triggered by plain
+ * gradlew test. ensureUelFunctionsLoaded() runs that one piece standalone 
(metadata/XML scanning
+ * only, no database or web container involved) so UEL-function-based unit 
tests in this package
+ * don't need the ofbiz --test container.
+ *
+ * <p>Idempotent and safe to call from every UEL test class's own {@literal 
@}BeforeAll,
+ * regardless of which class Gradle happens to run first in this JVM - only 
the first call
+ * actually does anything.
+ */
+class UelTestSupport {
+
+    private static final AtomicBoolean COMPONENTS_LOADED = new 
AtomicBoolean(false)
+
+    static void ensureUelFunctionsLoaded() {
+        if (!COMPONENTS_LOADED.get()) {
+            if (!ComponentConfig.componentExists('base')) {
+                Path ofbizHome = Paths.get('').toAbsolutePath().normalize()
+                
ComponentContainerTestSupport.bootstrapComponents('uel-test-bootstrap', 
ofbizHome)
+                if (!ComponentConfig.componentExists('base')) {
+                    throw new IllegalStateException("UEL bootstrap loaded no 
components from ${ofbizHome} - " +
+                            'is the JVM working directory the OFBiz project 
root?')
+                }
+            }
+            COMPONENTS_LOADED.set(true)
+        }
+    }
+
+}
diff --git 
a/framework/base/src/test/java/org/apache/ofbiz/base/container/ComponentContainerTest.java
 
b/framework/base/src/test/java/org/apache/ofbiz/base/container/ComponentContainerTest.java
index cd9b6e40c2..17518c3985 100644
--- 
a/framework/base/src/test/java/org/apache/ofbiz/base/container/ComponentContainerTest.java
+++ 
b/framework/base/src/test/java/org/apache/ofbiz/base/container/ComponentContainerTest.java
@@ -36,8 +36,8 @@ import org.junit.jupiter.api.Test;
 
 
 public final class ComponentContainerTest {
-    private static final Path ORDER_CONFIG = Paths.get("applications", 
"order", "config");
-    private static final Path ACCOUNTING_CONFIG = Paths.get("applications", 
"accounting", "config");
+    private static final Path ORDER_CONFIG = Paths.get("applications", 
"test-order", "config");
+    private static final Path ACCOUNTING_CONFIG = Paths.get("applications", 
"test-accounting", "config");
     private static final Path[] CONFIGS = {ORDER_CONFIG, ACCOUNTING_CONFIG};
 
     private Path ofbizHome = 
Paths.get(ComponentContainerTest.class.getResource("testsdata").toURI())
@@ -65,9 +65,17 @@ public final class ComponentContainerTest {
         ComponentContainer containerObj = new ComponentContainer();
         containerObj.init("component-container", ofbizHome);
 
+        // ComponentConfig's cache is a single JVM-wide static shared with the 
rest of the test
+        // suite (e.g. the UEL bootstrap session listener also loads the real, 
~90-component
+        // project into it). Filter down to just this test's own fixture names 
so the assertion
+        // stays a genuine, order-sensitive check of sortDependencies() - 
accounting depends on
+        // order, so a correct topological sort must place test-order before 
test-accounting even
+        // though alphabetical scan order would visit test-accounting first - 
regardless of
+        // whatever else has been loaded into the shared cache by other tests 
in this JVM.
         List<String> loadedComponents = ComponentConfig.components()
                 .map(ComponentConfig::getGlobalName)
+                .filter(name -> name.equals("test-order") || 
name.equals("test-accounting"))
                 .collect(Collectors.toList());
-        assertEquals(Arrays.asList("order", "accounting"), loadedComponents);
+        assertEquals(Arrays.asList("test-order", "test-accounting"), 
loadedComponents);
     }
 }
diff --git 
a/framework/base/src/test/java/org/apache/ofbiz/base/container/ComponentContainerTestSupport.java
 
b/framework/base/src/test/java/org/apache/ofbiz/base/container/ComponentContainerTestSupport.java
new file mode 100644
index 0000000000..a3c4dcf845
--- /dev/null
+++ 
b/framework/base/src/test/java/org/apache/ofbiz/base/container/ComponentContainerTestSupport.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * 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.base.container;
+
+import java.nio.file.Path;
+
+/**
+ * Test-only bridge exposing ComponentContainer's package-private init(String, 
Path) to other
+ * test packages (e.g. org.apache.ofbiz.base.test.uel's UelTestSupport), 
without widening its
+ * visibility in production code. This class lives in src/test, so it is never 
present on the
+ * production classpath.
+ */
+public final class ComponentContainerTestSupport {
+
+    private ComponentContainerTestSupport() { }
+
+    public static void bootstrapComponents(String name, Path ofbizHome) throws 
ContainerException {
+        new ComponentContainer().init(name, ofbizHome);
+    }
+
+}
diff --git 
a/framework/base/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener
 
b/framework/base/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener
new file mode 100644
index 0000000000..99d083c25d
--- /dev/null
+++ 
b/framework/base/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener
@@ -0,0 +1 @@
+org.apache.ofbiz.base.test.uel.UelFunctionsBootstrapListener
diff --git 
a/framework/base/src/test/resources/org/apache/ofbiz/base/container/testsdata/applications/accounting/ofbiz-component.xml
 
b/framework/base/src/test/resources/org/apache/ofbiz/base/container/testsdata/applications/test-accounting/ofbiz-component.xml
similarity index 92%
rename from 
framework/base/src/test/resources/org/apache/ofbiz/base/container/testsdata/applications/accounting/ofbiz-component.xml
rename to 
framework/base/src/test/resources/org/apache/ofbiz/base/container/testsdata/applications/test-accounting/ofbiz-component.xml
index 83e2534f55..e87479b973 100644
--- 
a/framework/base/src/test/resources/org/apache/ofbiz/base/container/testsdata/applications/accounting/ofbiz-component.xml
+++ 
b/framework/base/src/test/resources/org/apache/ofbiz/base/container/testsdata/applications/test-accounting/ofbiz-component.xml
@@ -18,10 +18,10 @@ specific language governing permissions and limitations
 under the License.
 -->
 
-<ofbiz-component name="accounting"
+<ofbiz-component name="test-accounting"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
         
xsi:noNamespaceSchemaLocation="https://ofbiz.apache.org/dtds/ofbiz-component.xsd";>
-    <depends-on component-name="order" />
+    <depends-on component-name="test-order" />
     <resource-loader name="main" type="component"/>
     <classpath type="dir" location="config"/>
 </ofbiz-component>
diff --git 
a/framework/base/src/test/resources/org/apache/ofbiz/base/container/testsdata/applications/order/ofbiz-component.xml
 
b/framework/base/src/test/resources/org/apache/ofbiz/base/container/testsdata/applications/test-order/ofbiz-component.xml
similarity index 96%
rename from 
framework/base/src/test/resources/org/apache/ofbiz/base/container/testsdata/applications/order/ofbiz-component.xml
rename to 
framework/base/src/test/resources/org/apache/ofbiz/base/container/testsdata/applications/test-order/ofbiz-component.xml
index 1b70403e89..92f90063f8 100644
--- 
a/framework/base/src/test/resources/org/apache/ofbiz/base/container/testsdata/applications/order/ofbiz-component.xml
+++ 
b/framework/base/src/test/resources/org/apache/ofbiz/base/container/testsdata/applications/test-order/ofbiz-component.xml
@@ -17,7 +17,7 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License.
 -->
-<ofbiz-component name="order"
+<ofbiz-component name="test-order"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
         
xsi:noNamespaceSchemaLocation="https://ofbiz.apache.org/dtds/ofbiz-component.xsd";>
     <resource-loader name="main" type="component"/>
diff --git a/framework/base/testdef/basetests.xml 
b/framework/base/testdef/basetests.xml
index 81b1638abd..b01f063cbb 100644
--- a/framework/base/testdef/basetests.xml
+++ b/framework/base/testdef/basetests.xml
@@ -24,17 +24,4 @@
         <junit-test-suite class-name="org.apache.ofbiz.base.test.SimpleTests"/>
     </test-case>
 
-    <test-case case-name="uel-date-tests">
-        <junit-test-suite 
class-name="org.apache.ofbiz.base.test.uel.DateUelTest"/>
-    </test-case>
-    <test-case case-name="uel-math-tests">
-        <junit-test-suite 
class-name="org.apache.ofbiz.base.test.uel.MathUelTest"/>
-    </test-case>
-    <test-case case-name="uel-string-tests">
-        <junit-test-suite 
class-name="org.apache.ofbiz.base.test.uel.StringUelTest"/>
-    </test-case>
-    <test-case case-name="uel-misc-tests">
-        <junit-test-suite 
class-name="org.apache.ofbiz.base.test.uel.MiscUelTest"/>
-    </test-case>
-
 </test-suite>

Reply via email to