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]