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

vavrtom pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/qpid-broker-j.git


The following commit(s) were added to refs/heads/main by this push:
     new ffe64bf811 QPID-8740: [Broker-J] JUnit 6 migration - 
QpidUnitTestExtension should be stateless (#384)
ffe64bf811 is described below

commit ffe64bf811a6c0241148a33df2c1b0a46334c3c9
Author: Daniil Kirilyuk <[email protected]>
AuthorDate: Wed Apr 1 08:51:45 2026 +0200

    QPID-8740: [Broker-J] JUnit 6 migration - QpidUnitTestExtension should be 
stateless (#384)
---
 qpid-test-utils/README.md                          | 71 ++++++++++++++++++++
 .../utils/LogbackPropertyValueDiscriminator.java   | 21 ++++--
 .../qpid/test/utils/QpidUnitTestExtension.java     | 75 +++++++++++++---------
 3 files changed, 131 insertions(+), 36 deletions(-)

diff --git a/qpid-test-utils/README.md b/qpid-test-utils/README.md
index 784b13b649..3a76e36bb0 100644
--- a/qpid-test-utils/README.md
+++ b/qpid-test-utils/README.md
@@ -2,6 +2,77 @@
 
 This module provides utility classes and JUnit extensions used in Broker-J 
unit tests.
 
+## QpidUnitTestExtension
+
+`QpidUnitTestExtension` is a JUnit extension that logs test method execution 
start and end, and
+routes log output into per-test files using Logback's `SiftingAppender`.
+
+### How it works
+
+The extension sets a `classQualifiedTestName` property on the Logback 
`LoggerContext` at each stage
+of the test lifecycle. `LogbackPropertyValueDiscriminator` reads this property 
and the
+`SiftingAppender` uses it to select the target log file.
+
+| Lifecycle phase | Property value               | Log file                    
            |
+|-----------------|------------------------------|-----------------------------------------|
+| `@BeforeAll`    | `com.example.MyTest`         | 
`TEST-com.example.MyTest.txt`           |
+| `@BeforeEach`   | `com.example.MyTest.myTest`  | 
`TEST-com.example.MyTest.myTest.txt`    |
+| test execution  | `com.example.MyTest.myTest`  | 
`TEST-com.example.MyTest.myTest.txt`    |
+| `@AfterEach`    | `com.example.MyTest`         | 
`TEST-com.example.MyTest.txt`           |
+| `@AfterAll`     | cleared                      | `TEST-testrun.txt` 
(default)            |
+
+Log files are written to `target/surefire-reports/` (configurable via the 
`test.output.dir` system
+property).
+
+### Design note: LogbackPropertyValueDiscriminator vs MDC
+
+The extension sets a property on the Logback `LoggerContext` (a process-wide 
property map) rather
+than using MDC. MDC values are thread-local and would be lost in background 
threads, producing
+incorrect log routing. Since production code frequently performs logging in 
background threads, the
+context-property approach ensures all log output from a test - regardless of 
which thread produces
+it - is routed to the correct file.
+
+### Example: using QpidUnitTestExtension
+
+For tests that cannot extend `UnitTestBase`, apply the extension directly:
+
+```java
+@ExtendWith(QpidUnitTestExtension.class)
+class MyTest
+{
+    @Test
+    void testA()
+    {
+        // test logic ...
+    }
+
+    @Test
+    void testB()
+    {
+        // test logic ...
+    }
+}
+```
+
+### Expected log files
+
+Running the test class `org.example.MyTest` with methods `testA` and `testB` 
produces:
+
+```
+target/surefire-reports/
+    TEST-org.example.MyTest.txt           # class-level log 
(beforeAll/afterAll, cleanup)
+    TEST-org.example.MyTest.testA.txt     # method-level log for testA
+    TEST-org.example.MyTest.testB.txt     # method-level log for testB
+```
+
+Each method-level log file contains:
+
+```
+... INFO  ... ========================= start executing test : MyTest#testA
+... (test output) ...
+... INFO  ... ========================= stop executing test : MyTest#testA
+```
+
 ## TlsResourceExtension
 
 `TlsResourceExtension` injects a `TlsResource` into JUnit lifecycle methods 
and test methods.
diff --git 
a/qpid-test-utils/src/main/java/org/apache/qpid/test/utils/LogbackPropertyValueDiscriminator.java
 
b/qpid-test-utils/src/main/java/org/apache/qpid/test/utils/LogbackPropertyValueDiscriminator.java
index 1d7bff2928..52c68e8441 100644
--- 
a/qpid-test-utils/src/main/java/org/apache/qpid/test/utils/LogbackPropertyValueDiscriminator.java
+++ 
b/qpid-test-utils/src/main/java/org/apache/qpid/test/utils/LogbackPropertyValueDiscriminator.java
@@ -20,10 +20,21 @@
  */
 package org.apache.qpid.test.utils;
 
+import java.util.Optional;
+
 import ch.qos.logback.classic.spi.ILoggingEvent;
 import ch.qos.logback.classic.spi.LoggerContextVO;
 import ch.qos.logback.core.sift.AbstractDiscriminator;
 
+/**
+ * Logback discriminator that reads a property from the {@link 
LoggerContextVO} property map.
+ * <p>
+ * Used with the {@link ch.qos.logback.classic.sift.SiftingAppender 
SiftingAppender} to route log
+ * output into separate files based on the property value set by {@link 
QpidUnitTestExtension}.
+ * <p>
+ * This approach is chosen over MDC because MDC values are thread-local and 
would be
+ * lost in background threads, producing incorrect log routing.
+ */
 public class LogbackPropertyValueDiscriminator extends 
AbstractDiscriminator<ILoggingEvent>
 {
     public static final String CLASS_QUALIFIED_TEST_NAME = 
"classQualifiedTestName";
@@ -34,12 +45,10 @@ public class LogbackPropertyValueDiscriminator extends 
AbstractDiscriminator<ILo
     @Override
     public String getDiscriminatingValue(final ILoggingEvent event)
     {
-        final LoggerContextVO context = event.getLoggerContextVO();
-        if (context != null && context.getPropertyMap() != null && 
context.getPropertyMap().get(_key) != null)
-        {
-            return context.getPropertyMap().get(_key);
-        }
-        return _defaultValue;
+        return Optional.ofNullable(event.getLoggerContextVO())
+                .map(LoggerContextVO::getPropertyMap)
+                .map(properties -> properties.get(_key))
+                .orElse(_defaultValue);
     }
 
     @Override
diff --git 
a/qpid-test-utils/src/main/java/org/apache/qpid/test/utils/QpidUnitTestExtension.java
 
b/qpid-test-utils/src/main/java/org/apache/qpid/test/utils/QpidUnitTestExtension.java
index c1e7dda0be..bd22fa5691 100644
--- 
a/qpid-test-utils/src/main/java/org/apache/qpid/test/utils/QpidUnitTestExtension.java
+++ 
b/qpid-test-utils/src/main/java/org/apache/qpid/test/utils/QpidUnitTestExtension.java
@@ -33,78 +33,93 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * JUnit's extension. Logs test method execution start and end
+ * JUnit extension that logs test method execution start and end.
+ * <p>
+ * Sets the {@code classQualifiedTestName} property on the Logback {@link 
LoggerContext} so that
+ * the {@link ch.qos.logback.classic.sift.SiftingAppender SiftingAppender} 
configured with
+ * {@link LogbackPropertyValueDiscriminator} can route log output into 
per-test files.
+ * <p>
+ * During {@code @BeforeAll}/{@code @AfterAll} the property is set to the 
fully-qualified class
+ * name. During individual test execution it is narrowed to {@code 
className.methodName}, producing
+ * a separate log file per test method.
  */
-public class QpidUnitTestExtension implements AfterAllCallback, 
AfterEachCallback, BeforeAllCallback, BeforeEachCallback
+public class QpidUnitTestExtension implements BeforeAllCallback, 
BeforeEachCallback, AfterEachCallback, AfterAllCallback
 {
-    /** Logger */
     private static final Logger LOGGER = 
LoggerFactory.getLogger(QpidUnitTestExtension.class);
 
-    /** Logger context */
     private static final LoggerContext LOGGER_CONTEXT = 
((ch.qos.logback.classic.Logger) LOGGER).getLoggerContext();
 
-    /** Test class */
-    private Class<?> _testClass;
-
-    /** Test method */
-    private Method _testMethod;
+    private static final String BANNER = "=========================";
 
     /**
-     * Callback executed before all testing methods
+     * Sets the class-qualified test name before any test method runs.
      *
      * @param extensionContext ExtensionContext
      */
     @Override
     public void beforeAll(final ExtensionContext extensionContext)
     {
-        _testClass = TestUtils.getTestClass(extensionContext);
-        setClassQualifiedTestName(_testClass.getName());
+        final Class<?> testClass = extensionContext.getRequiredTestClass();
+        setClassQualifiedTestName(testClass.getName());
     }
 
     /**
-     * Callback executed before after all testing methods
+     * Logs the test start and narrows the log routing to the method-level 
file.
+     * <p>
+     * The first log message is written to the class-level log file (before
+     * {@code setClassQualifiedTestName} switches routing). The second message 
goes to the
+     * method-level file.
      *
      * @param extensionContext ExtensionContext
      */
     @Override
-    public void afterAll(final ExtensionContext extensionContext)
+    public void beforeEach(final ExtensionContext extensionContext)
     {
-        _testClass = null;
-        setClassQualifiedTestName(null);
+        final Class<?> testClass = extensionContext.getRequiredTestClass();
+        final Method testMethod = extensionContext.getRequiredTestMethod();
+        final String testDisplayName = testClass.getSimpleName() + "#" + 
testMethod.getName();
+
+        LOGGER.info("{} executing test : {}", BANNER, testDisplayName);
+        setClassQualifiedTestName(testClass.getName() + "." + 
testMethod.getName());
+        LOGGER.info("{} start executing test : {}", BANNER, testDisplayName);
     }
 
     /**
-     * Callback executed before single testing method
+     * Logs the test end and restores the log routing to the class-level file.
+     * <p>
+     * The first log message is written to the method-level log file. The 
second message goes to
+     * the class-level file (after {@code setClassQualifiedTestName} restores 
class-level routing).
      *
      * @param extensionContext ExtensionContext
      */
     @Override
-    public void beforeEach(final ExtensionContext extensionContext)
+    public void afterEach(final ExtensionContext extensionContext)
     {
-        _testMethod = TestUtils.getTestMethod(extensionContext);
-        LOGGER.info("========================= executing test : {}", 
_testClass.getSimpleName() + "#" + _testMethod.getName());
-        setClassQualifiedTestName(_testClass.getName() + "." + 
_testMethod.getName());
-        LOGGER.info("========================= start executing test : {}", 
_testClass.getSimpleName() + "#" + _testMethod.getName());
+        final Class<?> testClass = extensionContext.getRequiredTestClass();
+        final Method testMethod = extensionContext.getRequiredTestMethod();
+        final String testDisplayName = testClass.getSimpleName() + "#" + 
testMethod.getName();
+
+        LOGGER.info("{} stop executing test : {} ", BANNER, testDisplayName);
+        setClassQualifiedTestName(testClass.getName());
+        LOGGER.info("{} cleaning up test environment for test : {}", BANNER, 
testDisplayName);
     }
 
     /**
-     * Callback executed after single testing method
+     * Clears the class-qualified test name after all test methods complete.
      *
      * @param extensionContext ExtensionContext
      */
     @Override
-    public void afterEach(final ExtensionContext extensionContext)
+    public void afterAll(final ExtensionContext extensionContext)
     {
-        LOGGER.info("========================= stop executing test : {} ", 
_testClass.getSimpleName() + "#" + _testMethod.getName());
-        setClassQualifiedTestName(_testClass.getName());
-        LOGGER.info("========================= cleaning up test environment 
for test : {}", _testClass.getSimpleName() + "#" + _testMethod.getName());
-        _testMethod = null;
+        setClassQualifiedTestName(null);
     }
 
     /**
-     * Sets test name into the logger context
+     * Sets the test name property on the Logback logger context.
+     * {@link LogbackPropertyValueDiscriminator} reads this property to route 
log output.
      *
-     * @param name Test name
+     * @param name fully-qualified test name, or {@code null} to clear
      */
     private void setClassQualifiedTestName(final String name)
     {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to