This is an automated email from the ASF dual-hosted git repository.

veithen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ws-axiom.git


The following commit(s) were added to refs/heads/master by this push:
     new 36e9fce6b Add TestNode tree design for LDAP-style filtering with JUnit 
5 dynamic tests
36e9fce6b is described below

commit 36e9fce6b3ae9e9368ee93bcdba6401d4ac3532c
Author: Andreas Veithen-Knowles <[email protected]>
AuthorDate: Sun Feb 22 13:54:24 2026 +0000

    Add TestNode tree design for LDAP-style filtering with JUnit 5 dynamic tests
---
 docs/design/test-suite-pattern.md | 139 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 139 insertions(+)

diff --git a/docs/design/test-suite-pattern.md 
b/docs/design/test-suite-pattern.md
index 67000a4bb..10db85273 100644
--- a/docs/design/test-suite-pattern.md
+++ b/docs/design/test-suite-pattern.md
@@ -217,6 +217,145 @@ The larger suites present additional considerations:
     output into `DynamicTest` instances would allow consuming modules to 
migrate to
     JUnit 5 runners without rewriting test case classes.
 
+### Replacement for MatrixTestSuiteBuilder: TestNode tree
+
+Since `DynamicContainer` and `DynamicTest` are `final` in JUnit 5, they cannot 
be
+subclassed to attach test parameters for LDAP-style filtering. Instead, a 
parallel
+class hierarchy can act as a factory for `DynamicNode` instances while 
carrying the
+parameters needed for exclusion filtering.
+
+#### Class hierarchy
+
+```java
+/**
+ * Base class mirroring {@link DynamicNode}. Represents a node in the test tree
+ * that can be filtered before conversion to JUnit 5's dynamic test API.
+ */
+abstract class TestNode {
+    private final String displayName;
+
+    TestNode(String displayName) {
+        this.displayName = displayName;
+    }
+
+    String getDisplayName() {
+        return displayName;
+    }
+
+    abstract DynamicNode toDynamicNode(Dictionary<String, String> 
inheritedParameters,
+            List<Filter> excludes);
+}
+```
+
+```java
+/**
+ * Mirrors {@link DynamicContainer}. Represents a parameterized grouping level
+ * in the test tree (e.g. a SOAP version, a serialization strategy).
+ *
+ * <p>Each {@code TestContainer} carries a single test parameter (name/value 
pair).
+ * The full parameter dictionary for any leaf {@code TestCase} is the 
accumulation
+ * of parameters from its ancestor {@code TestContainer} chain.
+ */
+class TestContainer extends TestNode {
+    private final String parameterName;
+    private final String parameterValue;
+    private final List<TestNode> children = new ArrayList<>();
+
+    TestContainer(String displayName, String parameterName, String 
parameterValue) {
+        super(displayName);
+        this.parameterName = parameterName;
+        this.parameterValue = parameterValue;
+    }
+
+    void addChild(TestNode child) {
+        children.add(child);
+    }
+
+    @Override
+    DynamicNode toDynamicNode(Dictionary<String, String> inheritedParameters,
+            List<Filter> excludes) {
+        Hashtable<String, String> params = new Hashtable<>();
+        // Copy inherited parameters from ancestor containers
+        for (Enumeration<String> e = inheritedParameters.keys(); 
e.hasMoreElements(); ) {
+            String key = e.nextElement();
+            params.put(key, inheritedParameters.get(key));
+        }
+        params.put(parameterName, parameterValue);
+        return DynamicContainer.dynamicContainer(getDisplayName(),
+                children.stream()
+                        .map(child -> child.toDynamicNode(params, excludes))
+                        .filter(Objects::nonNull));
+    }
+}
+```
+
+```java
+/**
+ * Mirrors {@link DynamicTest}. A leaf test case with an executable body.
+ * Filtering is applied based on the accumulated parameters from ancestor
+ * containers plus the test class name.
+ */
+class TestCase extends TestNode {
+    private final Class<?> testClass;
+    private final Executable executable;
+
+    TestCase(String displayName, Class<?> testClass, Executable executable) {
+        super(displayName);
+        this.testClass = testClass;
+        this.executable = executable;
+    }
+
+    @Override
+    DynamicNode toDynamicNode(Dictionary<String, String> inheritedParameters,
+            List<Filter> excludes) {
+        for (Filter exclude : excludes) {
+            if (exclude.matches(testClass, inheritedParameters)) {
+                return null; // Excluded
+            }
+        }
+        return DynamicTest.dynamicTest(getDisplayName(), executable);
+    }
+}
+```
+
+#### How filtering works
+
+Each `TestContainer` level represents one test dimension and carries a single 
parameter.
+As the tree is converted to `DynamicNode` instances via `toDynamicNode()`, 
parameters
+accumulate from the root down:
+
+```
+TestContainer("SOAP11", "spec", "soap11")          → params: {spec=soap11}
+  TestContainer("Text", "strategy", "text")        → params: {spec=soap11, 
strategy=text}
+    TestCase("serializeToWriter", ...)              → filtered against 
{spec=soap11, strategy=text}
+    TestCase("serializeToStream", ...)              → filtered against 
{spec=soap11, strategy=text}
+```
+
+Consumers apply exclusions exactly as they do today:
+
+```java
+class OMImplementationTests {
+    @TestFactory
+    Stream<DynamicNode> omTests() {
+        TestContainer root = new OMTestTreeBuilder(metaFactory).build();
+        List<Filter> excludes = new ArrayList<>();
+        excludes.add(Filter.forClass(TestSerialize.class, "(spec=soap12)"));
+        return root.toDynamicNode(new Hashtable<>(), excludes)
+                .children(); // unwrap the root container
+    }
+}
+```
+
+#### Benefits over MatrixTestSuiteBuilder
+
+*   Produces a hierarchical test tree in the IDE (grouped by dimension) 
instead of a
+    flat list with parameter suffixes in the test name.
+*   Parameters are distributed across the tree (one per container level) 
rather than
+    accumulated on each leaf test case, making the structure explicit.
+*   Uses standard JUnit 5 `DynamicNode` for execution while keeping the 
filtering
+    infrastructure in the intermediate `TestNode` layer.
+*   The LDAP-style filter mechanism is preserved unchanged.
+
 ### Hybrid approach: JUnit 5 adapter for MatrixTestSuiteBuilder
 
 A pragmatic intermediate step would be to create a JUnit 5 adapter that 
converts a

Reply via email to