You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by ma...@apache.org on 2020/08/29 19:43:49 UTC

[logging-log4j2] 02/02: [LOG4J2-2653] Migrate layout tests to JUnit 5

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

mattsicker pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 103f126dfd6ff36d278d4537bc96ad4377b298f8
Author: Matt Sicker <bo...@gmail.com>
AuthorDate: Sat Aug 29 14:16:56 2020 -0500

    [LOG4J2-2653] Migrate layout tests to JUnit 5
    
    Backported from 3.x.
    
    Signed-off-by: Matt Sicker <bo...@gmail.com>
---
 .../java/org/apache/logging/log4j/LogManager.java  |   9 +-
 .../apache/logging/log4j/spi/LoggerContext.java    |  24 +++++
 .../logging/log4j/CloseableThreadContextTest.java  |  17 +--
 .../log4j/ThreadContextInheritanceTest.java        |  17 +--
 .../apache/logging/log4j/ThreadContextTest.java    |  17 +--
 .../junit/LogManagerLoggerContextFactoryRule.java  |   2 +-
 .../log4j/junit/LoggerContextFactoryExtension.java |   2 +-
 .../log4j/junit/StatusLoggerLevelExtension.java    |   2 +-
 .../logging/log4j/junit/StatusLoggerRule.java      |   2 +-
 ...lExtension.java => ThreadContextExtension.java} |  45 ++++----
 .../logging/log4j/junit/ThreadContextMapRule.java  |   3 +
 .../logging/log4j/junit/ThreadContextRule.java     |   3 +
 .../log4j/junit/ThreadContextStackRule.java        |   2 +
 .../logging/log4j/junit/UsingAnyThreadContext.java |  19 ++--
 .../logging/log4j/junit/UsingThreadContextMap.java |  19 ++--
 .../log4j/junit/UsingThreadContextStack.java       |  19 ++--
 .../org/apache/logging/log4j/core/LoggerTest.java  |   5 +-
 .../apache/logging/log4j/core/jmx/ServerTest.java  |   4 +-
 .../core/layout/AbstractStringLayoutTest.java      |  30 +++---
 .../ConcurrentLoggingWithGelfLayoutTest.java       |  48 ++++-----
 .../logging/log4j/core/layout/GelfLayoutTest.java  |  19 ++--
 .../logging/log4j/core/layout/GelfLayoutTest2.java |  21 ++--
 .../logging/log4j/core/layout/GelfLayoutTest3.java |  35 +++---
 .../logging/log4j/core/layout/HtmlLayoutTest.java  |  42 ++++----
 .../log4j/core/layout/Log4j2_2195_Test.java        |  53 +++++----
 .../core/layout/PatternLayoutLookupDateTest.java   |  20 ++--
 .../layout/PatternLayoutMainMapLookupTest.java     |  40 +++----
 .../core/layout/PatternLayoutNoLookupDateTest.java |  21 ++--
 .../log4j/core/layout/PatternLayoutTest.java       |  71 ++++++-------
 .../log4j/core/layout/PatternSelectorTest.java     |  16 +--
 .../log4j/core/layout/Rfc5424LayoutTest.java       | 100 +++++++++--------
 .../core/layout/StringBuilderEncoderTest.java      | 118 ++++++++++-----------
 .../log4j/core/layout/SyslogLayoutTest.java        |  30 +++---
 .../core/pattern/MdcPatternConverterTest.java      |  12 +--
 .../core/pattern/NdcPatternConverterTest.java      |  17 +--
 .../log4j/core/pattern/RegexReplacementTest.java   |  11 +-
 .../logging/log4j/junit/LoggerContextResolver.java |  28 +++--
 .../logging/log4j/junit/LoggerContextSource.java   |  22 +++-
 .../java/org/apache/logging/log4j/junit/Named.java |   7 ++
 .../logging/log4j/junit/ReconfigurationPolicy.java |  30 +++---
 40 files changed, 491 insertions(+), 511 deletions(-)

diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java b/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java
index 51d783b..7716c74 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java
@@ -426,11 +426,6 @@ public class LogManager {
         }
     }
 
-    private static String toLoggerName(final Class<?> cls) {
-        final String canonicalName = cls.getCanonicalName();
-        return canonicalName != null ? canonicalName : cls.getName();
-    }
-
     /**
      * Returns the current LoggerContextFactory.
      *
@@ -599,7 +594,7 @@ public class LogManager {
      */
     public static Logger getLogger(final Class<?> clazz) {
         final Class<?> cls = callerClass(clazz);
-        return getContext(cls.getClassLoader(), false).getLogger(toLoggerName(cls));
+        return getContext(cls.getClassLoader(), false).getLogger(cls);
     }
 
     /**
@@ -615,7 +610,7 @@ public class LogManager {
      */
     public static Logger getLogger(final Class<?> clazz, final MessageFactory messageFactory) {
         final Class<?> cls = callerClass(clazz);
-        return getContext(cls.getClassLoader(), false).getLogger(toLoggerName(cls), messageFactory);
+        return getContext(cls.getClassLoader(), false).getLogger(cls, messageFactory);
     }
 
     /**
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContext.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContext.java
index 25b0071..eab7b51 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContext.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContext.java
@@ -89,6 +89,17 @@ public interface LoggerContext {
     ExtendedLogger getLogger(String name);
 
     /**
+     * Returns an ExtendedLogger using the fully qualified name of the Class as the Logger name.
+     * @param cls The Class whose name should be used as the Logger name.
+     * @return The logger.
+     * @since 2.14.0
+     */
+    default ExtendedLogger getLogger(Class<?> cls) {
+        final String canonicalName = cls.getCanonicalName();
+        return getLogger(canonicalName != null ? canonicalName : cls.getName());
+    }
+
+    /**
      * Returns an ExtendedLogger.
      * @param name The name of the Logger to return.
      * @param messageFactory The message factory is used only when creating a logger, subsequent use does not change
@@ -98,6 +109,19 @@ public interface LoggerContext {
     ExtendedLogger getLogger(String name, MessageFactory messageFactory);
 
     /**
+     * Returns an ExtendedLogger using the fully qualified name of the Class as the Logger name.
+     * @param cls The Class whose name should be used as the Logger name.
+     * @param messageFactory The message factory is used only when creating a logger, subsequent use does not change the
+     *                       logger but will log a warning if mismatched.
+     * @return The logger.
+     * @since 2.14.0
+     */
+    default ExtendedLogger getLogger(Class<?> cls, MessageFactory messageFactory) {
+        final String canonicalName = cls.getCanonicalName();
+        return getLogger(canonicalName != null ? canonicalName : cls.getName(), messageFactory);
+    }
+
+    /**
      * Detects if a Logger with the specified name exists.
      * @param name The Logger name to search for.
      * @return true if the Logger exists, false otherwise.
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/CloseableThreadContextTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/CloseableThreadContextTest.java
index ce06907..54ea122 100644
--- a/log4j-api/src/test/java/org/apache/logging/log4j/CloseableThreadContextTest.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/CloseableThreadContextTest.java
@@ -16,8 +16,7 @@
  */
 package org.apache.logging.log4j;
 
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
+import org.apache.logging.log4j.junit.UsingAnyThreadContext;
 import org.junit.jupiter.api.Test;
 
 import java.util.HashMap;
@@ -31,24 +30,12 @@ import static org.junit.jupiter.api.Assertions.*;
  *
  * @since 2.6
  */
+@UsingAnyThreadContext
 public class CloseableThreadContextTest {
 
     private final String key = "key";
     private final String value = "value";
 
-    private ThreadContextHolder threadContextHolder;
-
-    @BeforeEach
-    void clearThreadContext() {
-        threadContextHolder = new ThreadContextHolder(true, true);
-        ThreadContext.clearAll();
-    }
-
-    @AfterEach
-    void restoreThreadContext() {
-        threadContextHolder.restore();
-    }
-
     @Test
     public void shouldAddAnEntryToTheMap() {
         try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.put(key, value)) {
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextInheritanceTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextInheritanceTest.java
index 5bcc1eb..f1cc91e 100644
--- a/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextInheritanceTest.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextInheritanceTest.java
@@ -16,11 +16,10 @@
  */
 package org.apache.logging.log4j;
 
+import org.apache.logging.log4j.junit.UsingAnyThreadContext;
 import org.apache.logging.log4j.spi.DefaultThreadContextMap;
 import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 
@@ -29,21 +28,9 @@ import static org.junit.jupiter.api.Assertions.*;
 /**
  * Tests {@link ThreadContext}.
  */
+@UsingAnyThreadContext
 public class ThreadContextInheritanceTest {
 
-    private ThreadContextHolder threadContextHolder;
-
-    @BeforeEach
-    void clearThreadContext() {
-        threadContextHolder = new ThreadContextHolder(true, true);
-        ThreadContext.clearAll();
-    }
-
-    @AfterEach
-    void restoreThreadContext() {
-        threadContextHolder.restore();
-    }
-
     @BeforeAll
     public static void setupClass() {
         System.setProperty(DefaultThreadContextMap.INHERITABLE_MAP, "true");
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextTest.java
index 636b53d..3b6fb3a 100644
--- a/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextTest.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextTest.java
@@ -20,31 +20,18 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
+import org.apache.logging.log4j.junit.UsingAnyThreadContext;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.*;
 
+@UsingAnyThreadContext
 public class ThreadContextTest {
     public static void reinitThreadContext() {
         ThreadContext.init();
     }
 
-    private ThreadContextHolder threadContextHolder;
-
-    @BeforeEach
-    void clearThreadContext() {
-        threadContextHolder = new ThreadContextHolder(true, true);
-        ThreadContext.clearAll();
-    }
-
-    @AfterEach
-    void restoreThreadContext() {
-        threadContextHolder.restore();
-    }
-
     @Test
     public void testPush() {
         ThreadContext.push("Hello");
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/junit/LogManagerLoggerContextFactoryRule.java b/log4j-api/src/test/java/org/apache/logging/log4j/junit/LogManagerLoggerContextFactoryRule.java
index c7c157f..060e553 100644
--- a/log4j-api/src/test/java/org/apache/logging/log4j/junit/LogManagerLoggerContextFactoryRule.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/junit/LogManagerLoggerContextFactoryRule.java
@@ -24,7 +24,7 @@ import org.junit.rules.ExternalResource;
  * Sets the {@link LogManager}'s {@link LoggerContextFactory} to the given instance before the test and restores it to
  * the original value after the test.
  *
- * @deprecated Use {@link LoggerContextFactoryExtension}
+ * @deprecated Use {@link LoggerContextFactoryExtension} with JUnit 5
  */
 public class LogManagerLoggerContextFactoryRule extends ExternalResource {
 
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/junit/LoggerContextFactoryExtension.java b/log4j-api/src/test/java/org/apache/logging/log4j/junit/LoggerContextFactoryExtension.java
index 9cb557a..1131e1b 100644
--- a/log4j-api/src/test/java/org/apache/logging/log4j/junit/LoggerContextFactoryExtension.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/junit/LoggerContextFactoryExtension.java
@@ -26,7 +26,7 @@ import org.junit.jupiter.api.extension.ExtensionContext;
 /**
  * JUnit 5 extension that sets a particular {@link LoggerContextFactory} for the entire run of tests in a class.
  *
- * @since 3.0.0
+ * @since 2.14.0
  */
 public class LoggerContextFactoryExtension implements BeforeAllCallback, AfterAllCallback {
 
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/junit/StatusLoggerLevelExtension.java b/log4j-api/src/test/java/org/apache/logging/log4j/junit/StatusLoggerLevelExtension.java
index f112652..5eb7de2 100644
--- a/log4j-api/src/test/java/org/apache/logging/log4j/junit/StatusLoggerLevelExtension.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/junit/StatusLoggerLevelExtension.java
@@ -26,7 +26,7 @@ import org.junit.jupiter.api.extension.ExtensionContext;
 /**
  * JUnit 5 test extension that sets a specific StatusLogger logging level for each test.
  *
- * @since 3.0.0
+ * @since 2.14.0
  */
 public class StatusLoggerLevelExtension implements BeforeEachCallback, AfterEachCallback {
 
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/junit/StatusLoggerRule.java b/log4j-api/src/test/java/org/apache/logging/log4j/junit/StatusLoggerRule.java
index a424933..a14114b 100644
--- a/log4j-api/src/test/java/org/apache/logging/log4j/junit/StatusLoggerRule.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/junit/StatusLoggerRule.java
@@ -25,7 +25,7 @@ import org.junit.rules.ExternalResource;
  * Log4j configuration file.
  *
  * @since 2.8
- * @deprecated Use {@link StatusLoggerLevelExtension}
+ * @deprecated Use {@link StatusLoggerLevelExtension} with JUnit 5
  */
 public class StatusLoggerRule extends ExternalResource {
 
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/junit/StatusLoggerLevelExtension.java b/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextExtension.java
similarity index 50%
copy from log4j-api/src/test/java/org/apache/logging/log4j/junit/StatusLoggerLevelExtension.java
copy to log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextExtension.java
index f112652..e163578 100644
--- a/log4j-api/src/test/java/org/apache/logging/log4j/junit/StatusLoggerLevelExtension.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextExtension.java
@@ -17,40 +17,41 @@
 
 package org.apache.logging.log4j.junit;
 
-import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.ThreadContextHolder;
 import org.junit.jupiter.api.extension.AfterEachCallback;
 import org.junit.jupiter.api.extension.BeforeEachCallback;
 import org.junit.jupiter.api.extension.ExtensionContext;
 
-/**
- * JUnit 5 test extension that sets a specific StatusLogger logging level for each test.
- *
- * @since 3.0.0
- */
-public class StatusLoggerLevelExtension implements BeforeEachCallback, AfterEachCallback {
-
-    private static final String KEY = "previousLevel";
-    private final Level level;
-
-    public StatusLoggerLevelExtension(Level level) {
-        this.level = level;
-    }
-
+class ThreadContextExtension implements BeforeEachCallback, AfterEachCallback {
     @Override
     public void beforeEach(ExtensionContext context) throws Exception {
-        final StatusLogger logger = StatusLogger.getLogger();
-        getStore(context).put(KEY, logger.getLevel());
-        logger.setLevel(level);
+        final Class<?> testClass = context.getRequiredTestClass();
+        final ThreadContextHolder holder;
+        if (testClass.isAnnotationPresent(UsingAnyThreadContext.class)) {
+            holder = new ThreadContextHolder(true, true);
+            ThreadContext.clearAll();
+        } else if (testClass.isAnnotationPresent(UsingThreadContextMap.class)) {
+            holder = new ThreadContextHolder(true, false);
+            ThreadContext.clearMap();
+        } else if (testClass.isAnnotationPresent(UsingThreadContextStack.class)) {
+            holder = new ThreadContextHolder(false, true);
+            ThreadContext.clearStack();
+        } else {
+            return;
+        }
+        getStore(context).put(ThreadContextHolder.class, holder);
     }
 
     @Override
     public void afterEach(ExtensionContext context) throws Exception {
-        StatusLogger.getLogger().setLevel(getStore(context).get(KEY, Level.class));
+        final ThreadContextHolder holder = getStore(context).get(ThreadContextHolder.class, ThreadContextHolder.class);
+        if (holder != null) {
+            holder.restore();
+        }
     }
 
     private ExtensionContext.Store getStore(ExtensionContext context) {
-        return context.getStore(ExtensionContext.Namespace
-                .create(getClass(), context.getRequiredTestInstance(), context.getRequiredTestMethod()));
+        return context.getStore(ExtensionContext.Namespace.create(getClass(), context.getRequiredTestInstance()));
     }
 }
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextMapRule.java b/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextMapRule.java
index 4066303..6b153fb 100644
--- a/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextMapRule.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextMapRule.java
@@ -25,7 +25,10 @@ package org.apache.logging.log4j.junit;
  * &#64;Rule
  * public final ThreadContextMapRule threadContextRule = new ThreadContextMapRule();
  * </pre>
+ *
+ * @deprecated use {@link UsingThreadContextMap} with JUnit 5
  */
+@Deprecated
 public class ThreadContextMapRule extends ThreadContextRule {
 
     /**
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextRule.java b/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextRule.java
index dad7b5b..1d95bf7 100644
--- a/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextRule.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextRule.java
@@ -29,7 +29,10 @@ import org.junit.rules.ExternalResource;
  * &#64;Rule
  * public final ThreadContextRule threadContextRule = new ThreadContextRule();
  * </pre>
+ *
+ * @deprecated use {@link UsingAnyThreadContext} with JUnit 5
  */
+@Deprecated
 public class ThreadContextRule extends ExternalResource {
 
     private final boolean restoreMap;
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextStackRule.java b/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextStackRule.java
index 1581dc8..b51c0b9 100644
--- a/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextStackRule.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextStackRule.java
@@ -25,7 +25,9 @@ package org.apache.logging.log4j.junit;
  * &#64;Rule
  * public final ThreadContextStackRule threadContextRule = new ThreadContextStackRule();
  * </pre>
+ * @deprecated use {@link UsingThreadContextStack} with JUnit 5
  */
+@Deprecated
 public class ThreadContextStackRule extends ThreadContextRule {
 
     /**
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/junit/Named.java b/log4j-api/src/test/java/org/apache/logging/log4j/junit/UsingAnyThreadContext.java
similarity index 71%
copy from log4j-core/src/test/java/org/apache/logging/log4j/junit/Named.java
copy to log4j-api/src/test/java/org/apache/logging/log4j/junit/UsingAnyThreadContext.java
index fd3e4f4..935df61 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/junit/Named.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/junit/UsingAnyThreadContext.java
@@ -17,18 +17,25 @@
 
 package org.apache.logging.log4j.junit;
 
+import org.junit.jupiter.api.extension.ExtendWith;
+
 import java.lang.annotation.Documented;
 import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+/**
+ * Marks a test class as using {@link org.apache.logging.log4j.ThreadContext} APIs. This will automatically clear and restore
+ * both the thread context map and stack for each test invocation.
+ *
+ * @since 2.14.0
+ */
 @Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.PARAMETER)
+@Target(ElementType.TYPE)
 @Documented
-public @interface Named {
-    /**
-     * Specifies the name of the configuration item to inject. If blank, uses the name of the annotated parameter.
-     */
-    String value() default "";
+@Inherited
+@ExtendWith(ThreadContextExtension.class)
+public @interface UsingAnyThreadContext {
 }
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/junit/Named.java b/log4j-api/src/test/java/org/apache/logging/log4j/junit/UsingThreadContextMap.java
similarity index 71%
copy from log4j-core/src/test/java/org/apache/logging/log4j/junit/Named.java
copy to log4j-api/src/test/java/org/apache/logging/log4j/junit/UsingThreadContextMap.java
index fd3e4f4..df608f8 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/junit/Named.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/junit/UsingThreadContextMap.java
@@ -17,18 +17,25 @@
 
 package org.apache.logging.log4j.junit;
 
+import org.junit.jupiter.api.extension.ExtendWith;
+
 import java.lang.annotation.Documented;
 import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+/**
+ * Marks a test class as using {@link org.apache.logging.log4j.spi.ThreadContextMap} APIs. This will automatically clear and
+ * restore the thread context map (MDC) for each test invocation.
+ *
+ * @since 2.14.0
+ */
 @Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.PARAMETER)
+@Target(ElementType.TYPE)
 @Documented
-public @interface Named {
-    /**
-     * Specifies the name of the configuration item to inject. If blank, uses the name of the annotated parameter.
-     */
-    String value() default "";
+@Inherited
+@ExtendWith(ThreadContextExtension.class)
+public @interface UsingThreadContextMap {
 }
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/junit/Named.java b/log4j-api/src/test/java/org/apache/logging/log4j/junit/UsingThreadContextStack.java
similarity index 71%
copy from log4j-core/src/test/java/org/apache/logging/log4j/junit/Named.java
copy to log4j-api/src/test/java/org/apache/logging/log4j/junit/UsingThreadContextStack.java
index fd3e4f4..7d47e49 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/junit/Named.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/junit/UsingThreadContextStack.java
@@ -17,18 +17,25 @@
 
 package org.apache.logging.log4j.junit;
 
+import org.junit.jupiter.api.extension.ExtendWith;
+
 import java.lang.annotation.Documented;
 import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+/**
+ * Marks a test class as using {@link org.apache.logging.log4j.spi.ThreadContextStack} APIs. This will automatically clear and
+ * restore the thread context stack (NDC) for each test invocation.
+ *
+ * @since 2.14.0
+ */
 @Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.PARAMETER)
+@Target(ElementType.TYPE)
 @Documented
-public @interface Named {
-    /**
-     * Specifies the name of the configuration item to inject. If blank, uses the name of the annotated parameter.
-     */
-    String value() default "";
+@Inherited
+@ExtendWith(ThreadContextExtension.class)
+public @interface UsingThreadContextStack {
 }
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/LoggerTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/LoggerTest.java
index 6d6f276..9ee22f1 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/LoggerTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/LoggerTest.java
@@ -26,6 +26,7 @@ import org.apache.logging.log4j.core.config.Configurator;
 import org.apache.logging.log4j.core.config.LoggerConfig;
 import org.apache.logging.log4j.junit.LoggerContextSource;
 import org.apache.logging.log4j.junit.Named;
+import org.apache.logging.log4j.junit.ReconfigurationPolicy;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.MessageFactory;
 import org.apache.logging.log4j.message.ParameterizedMessageFactory;
@@ -51,7 +52,7 @@ import java.util.concurrent.TimeUnit;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.jupiter.api.Assertions.*;
 
-@LoggerContextSource(value = LoggerTest.CONFIG, reconfigure = true)
+@LoggerContextSource(value = LoggerTest.CONFIG, reconfigure = ReconfigurationPolicy.AFTER_EACH)
 public class LoggerTest {
 
     static final String CONFIG = "log4j-test2.xml";
@@ -84,7 +85,7 @@ public class LoggerTest {
         final List<LogEvent> events = app.getEvents();
         assertEventCount(events, 3);
         assertEquals(
-                "org.apache.logging.log4j.core.LoggerTest.builder(LoggerTest.java:80)", events.get(0).getSource().toString(),
+                "org.apache.logging.log4j.core.LoggerTest.builder(LoggerTest.java:81)", events.get(0).getSource().toString(),
                 "Incorrect location");
         assertEquals(Level.DEBUG, events.get(0).getLevel(), "Incorrect Level");
         MatcherAssert.assertThat("Incorrect message", events.get(1).getMessage().getFormattedMessage(), equalTo("Hello John"));
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/jmx/ServerTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/jmx/ServerTest.java
index 57c8910..e90f943 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/jmx/ServerTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/jmx/ServerTest.java
@@ -19,9 +19,9 @@ package org.apache.logging.log4j.core.jmx;
 
 import javax.management.ObjectName;
 
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
 
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
 
 /**
  * Unit tests for the Server class.
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/AbstractStringLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/AbstractStringLayoutTest.java
index 04505d4..b29b25d 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/AbstractStringLayoutTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/AbstractStringLayoutTest.java
@@ -18,9 +18,9 @@ package org.apache.logging.log4j.core.layout;/*
 import java.nio.charset.Charset;
 
 import org.apache.logging.log4j.core.LogEvent;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
 
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
 
 /**
  * Tests AbstractStringLayout.
@@ -48,30 +48,32 @@ public class AbstractStringLayoutTest {
     public void testGetStringBuilderCapacityRestrictedToMax() throws Exception {
         final StringBuilder sb = ConcreteStringLayout.getStringBuilder();
         final int initialCapacity = sb.capacity();
-        assertEquals("initial capacity", ConcreteStringLayout.DEFAULT_STRING_BUILDER_SIZE, sb.capacity());
+        assertEquals(ConcreteStringLayout.DEFAULT_STRING_BUILDER_SIZE, sb.capacity(), "initial capacity");
 
         final int SMALL = 100;
         final String smallMessage = new String(new char[SMALL]);
         sb.append(smallMessage);
-        assertTrue("capacity not grown", sb.capacity() == initialCapacity);
-        assertEquals("length=msg length", SMALL, sb.length());
+        assertEquals(initialCapacity, sb.capacity(), "capacity not grown");
+        assertEquals(SMALL, sb.length(), "length=msg length");
 
         final StringBuilder sb2 = ConcreteStringLayout.getStringBuilder();
-        assertEquals("capacity unchanged", sb2.capacity(), initialCapacity);
-        assertEquals("empty, ready for use", 0, sb2.length());
+        assertEquals(sb2.capacity(), initialCapacity, "capacity unchanged");
+        assertEquals(0, sb2.length(), "empty, ready for use");
 
         final int LARGE = ConcreteStringLayout.MAX_STRING_BUILDER_SIZE * 2;
         final String largeMessage = new String(new char[LARGE]);
         sb2.append(largeMessage);
-        assertTrue("capacity grown to fit msg length", sb2.capacity() >= LARGE);
-        assertTrue("capacity is now greater than max length", sb2.capacity() >= ConcreteStringLayout.MAX_STRING_BUILDER_SIZE);
-        assertEquals("length=msg length", LARGE, sb2.length());
+        assertTrue(sb2.capacity() >= LARGE, "capacity grown to fit msg length");
+        assertTrue(sb2.capacity() >= ConcreteStringLayout.MAX_STRING_BUILDER_SIZE,
+                "capacity is now greater than max length");
+        assertEquals(LARGE, sb2.length(), "length=msg length");
         sb2.setLength(0); // set 0 before next getStringBuilder() call
-        assertEquals("empty, cleared", 0, sb2.length());
-        assertTrue("capacity remains very large", sb2.capacity() >= ConcreteStringLayout.MAX_STRING_BUILDER_SIZE);
+        assertEquals(0, sb2.length(), "empty, cleared");
+        assertTrue(sb2.capacity() >= ConcreteStringLayout.MAX_STRING_BUILDER_SIZE, "capacity remains very large");
 
         final StringBuilder sb3 = ConcreteStringLayout.getStringBuilder();
-        assertEquals("capacity, trimmed to MAX_STRING_BUILDER_SIZE", ConcreteStringLayout.MAX_STRING_BUILDER_SIZE, sb3.capacity());
-        assertEquals("empty, ready for use", 0, sb3.length());
+        assertEquals(ConcreteStringLayout.MAX_STRING_BUILDER_SIZE, sb3.capacity(),
+                "capacity, trimmed to MAX_STRING_BUILDER_SIZE");
+        assertEquals(0, sb3.length(), "empty, ready for use");
     }
 }
\ No newline at end of file
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/ConcurrentLoggingWithGelfLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/ConcurrentLoggingWithGelfLayoutTest.java
index af04b35..7f16909 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/ConcurrentLoggingWithGelfLayoutTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/ConcurrentLoggingWithGelfLayoutTest.java
@@ -16,9 +16,11 @@
  */
 package org.apache.logging.log4j.core.layout;
 
-import java.io.File;
+import java.io.IOException;
 import java.nio.charset.Charset;
 import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
@@ -26,35 +28,36 @@ import java.util.List;
 import java.util.Set;
 
 import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.junit.LoggerContextRule;
-import org.junit.AfterClass;
-import org.junit.ClassRule;
-import org.junit.Test;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.junit.LoggerContextSource;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
 
 import static org.hamcrest.CoreMatchers.endsWith;
 import static org.hamcrest.CoreMatchers.startsWith;
-import static org.junit.Assert.*;
+import static org.hamcrest.MatcherAssert.*;
 
 /**
  * Test for LOG4J2-1769, kind of.
  *
  * @since 2.8
  */
+@Tag("concurrency")
+@LoggerContextSource("log4j2-gelf-layout.xml")
 public class ConcurrentLoggingWithGelfLayoutTest {
-    @ClassRule
-    public static LoggerContextRule context = new LoggerContextRule("log4j2-gelf-layout.xml");
-    private static final String PATH = "target/test-gelf-layout.log";
+    private static final Path PATH = Paths.get("target", "test-gelf-layout.log");
 
-    @AfterClass
-    public static void after() {
-        new File(PATH).delete();
+    @AfterAll
+    static void after() throws IOException {
+        Files.deleteIfExists(PATH);
     }
 
     @Test
-    public void testConcurrentLogging() throws Throwable {
-        final Logger log = context.getLogger(ConcurrentLoggingWithGelfLayoutTest.class);
-        final Set<Thread> threads = Collections.synchronizedSet(new HashSet<Thread>());
-        final List<Throwable> thrown = Collections.synchronizedList(new ArrayList<Throwable>());
+    public void testConcurrentLogging(final LoggerContext context) throws Throwable {
+        final Logger log = context.getLogger(ConcurrentLoggingWithGelfLayoutTest.class.getName());
+        final Set<Thread> threads = Collections.synchronizedSet(new HashSet<>());
+        final List<Throwable> thrown = Collections.synchronizedList(new ArrayList<>());
 
         for (int x = 0; x < Runtime.getRuntime().availableProcessors() * 2; x++) {
             final Thread t = new LoggingThread(threads, log);
@@ -62,12 +65,7 @@ public class ConcurrentLoggingWithGelfLayoutTest {
 
             // Appender is configured with ignoreExceptions="false";
             // any exceptions are propagated to the caller, so we can catch them here.
-            t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
-                @Override
-                public void uncaughtException(final Thread t, final Throwable e) {
-                    thrown.add(e);
-                }
-            });
+            t.setUncaughtExceptionHandler((t1, e) -> thrown.add(e));
             t.start();
         }
 
@@ -82,8 +80,8 @@ public class ConcurrentLoggingWithGelfLayoutTest {
         }
 
         // simple test to ensure content is not corrupted
-        if (new File(PATH).exists()) {
-            final List<String> lines = Files.readAllLines(new File(PATH).toPath(), Charset.defaultCharset());
+        if (Files.exists(PATH)) {
+            final List<String> lines = Files.readAllLines(PATH, Charset.defaultCharset());
             for (final String line : lines) {
                 assertThat(line, startsWith("{\"version\":\"1.1\",\"host\":\"myself\",\"timestamp\":"));
                 assertThat(line, endsWith("\"}"));
@@ -91,7 +89,7 @@ public class ConcurrentLoggingWithGelfLayoutTest {
         }
     }
 
-    private class LoggingThread extends Thread {
+    private static class LoggingThread extends Thread {
         private final Set<Thread> threads;
         private final Logger log;
 
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest.java
index 3cc0a3a..4a6d08d 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest.java
@@ -30,13 +30,12 @@ import org.apache.logging.log4j.core.layout.GelfLayout.CompressionType;
 import org.apache.logging.log4j.core.lookup.JavaLookup;
 import org.apache.logging.log4j.core.util.KeyValuePair;
 import org.apache.logging.log4j.core.util.NetUtils;
-import org.apache.logging.log4j.junit.ThreadContextRule;
+import org.apache.logging.log4j.junit.UsingAnyThreadContext;
 import org.apache.logging.log4j.test.appender.EncodingListAppender;
 import org.apache.logging.log4j.test.appender.ListAppender;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Rule;
-import org.junit.Test;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
@@ -46,8 +45,9 @@ import java.util.zip.GZIPInputStream;
 import java.util.zip.InflaterInputStream;
 
 import static net.javacrumbs.jsonunit.JsonAssert.assertJsonEquals;
-import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.*;
 
+@UsingAnyThreadContext
 public class GelfLayoutTest {
 
     static ConfigurationFactory configFactory = new BasicConfigurationFactory();
@@ -64,15 +64,12 @@ public class GelfLayoutTest {
     private static final String MDCVALUE2 = "MdcValue2";
     private static final String VALUE1 = "Value1";
 
-    @Rule
-    public final ThreadContextRule threadContextRule = new ThreadContextRule();
-
-    @AfterClass
+    @AfterAll
     public static void cleanupClass() {
         ConfigurationFactory.removeConfigurationFactory(configFactory);
     }
 
-    @BeforeClass
+    @BeforeAll
     public static void setupClass() {
         ConfigurationFactory.setConfigurationFactory(configFactory);
         final LoggerContext ctx = LoggerContext.getContext();
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest2.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest2.java
index ed0fb4c..2b3f429 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest2.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest2.java
@@ -21,23 +21,24 @@ import java.io.IOException;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.lookup.JavaLookup;
-import org.apache.logging.log4j.junit.LoggerContextRule;
-import org.junit.ClassRule;
-import org.junit.Test;
+import org.apache.logging.log4j.junit.LoggerContextSource;
+import org.apache.logging.log4j.junit.Named;
+import org.apache.logging.log4j.test.appender.ListAppender;
+import org.junit.jupiter.api.Test;
 
-import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.*;
 
+@LoggerContextSource("GelfLayoutTest2.xml")
 public class GelfLayoutTest2 {
 
-    @ClassRule
-    public static LoggerContextRule context = new LoggerContextRule("GelfLayoutTest2.xml");
-
     @Test
-    public void gelfLayout() throws IOException {
-        final Logger logger = context.getLogger();
+    public void gelfLayout(final LoggerContext context, @Named final ListAppender list) throws IOException {
+        list.clear();
+        final Logger logger = context.getLogger(getClass());
         logger.info("Message");
-        final String gelf = context.getListAppender("list").getMessages().get(0);
+        final String gelf = list.getMessages().get(0);
         final ObjectMapper mapper = new ObjectMapper();
         final JsonNode json = mapper.readTree(gelf);
         assertEquals("Message", json.get("short_message").asText());
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest3.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest3.java
index 9e02b65..fc9a404 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest3.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest3.java
@@ -20,36 +20,31 @@ import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.ThreadContext;
-import org.apache.logging.log4j.core.lookup.JavaLookup;
-import org.apache.logging.log4j.junit.LoggerContextRule;
-import org.junit.After;
-import org.junit.ClassRule;
-import org.junit.Test;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.junit.LoggerContextSource;
+import org.apache.logging.log4j.junit.Named;
+import org.apache.logging.log4j.junit.UsingAnyThreadContext;
+import org.apache.logging.log4j.test.appender.ListAppender;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
 
 import java.io.IOException;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.*;
 
+@LoggerContextSource("GelfLayoutTest3.xml")
+@UsingAnyThreadContext
+@Tag("json")
 public class GelfLayoutTest3 {
 
-    @ClassRule
-    public static LoggerContextRule context = new LoggerContextRule("GelfLayoutTest3.xml");
-
-    @After
-    public void teardown() throws Exception {
-        ThreadContext.clearMap();
-    }
-
     @Test
-    public void gelfLayout() throws IOException {
-        final Logger logger = context.getLogger();
+    public void gelfLayout(final LoggerContext context, @Named final ListAppender list) throws IOException {
+        list.clear();
+        final Logger logger = context.getLogger(getClass());
         ThreadContext.put("loginId", "rgoers");
         ThreadContext.put("internalId", "12345");
         logger.info("My Test Message");
-        final String gelf = context.getListAppender("list").getMessages().get(0);
+        final String gelf = list.getMessages().get(0);
         final ObjectMapper mapper = new ObjectMapper();
         final JsonNode json = mapper.readTree(gelf);
         assertEquals("My Test Message", json.get("short_message").asText());
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/HtmlLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/HtmlLayoutTest.java
index 20a8dde..b6ad4c4 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/HtmlLayoutTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/HtmlLayoutTest.java
@@ -27,35 +27,29 @@ import org.apache.logging.log4j.core.BasicConfigurationFactory;
 import org.apache.logging.log4j.core.Logger;
 import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.config.ConfigurationFactory;
-import org.apache.logging.log4j.junit.ThreadContextRule;
+import org.apache.logging.log4j.junit.UsingAnyThreadContext;
 import org.apache.logging.log4j.test.appender.ListAppender;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Rule;
-import org.junit.Test;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
 
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
 
-/**
- *
- */
+@UsingAnyThreadContext
 public class HtmlLayoutTest {
-    LoggerContext ctx = LoggerContext.getContext();
-    Logger root = ctx.getRootLogger();
+    private final LoggerContext ctx = LoggerContext.getContext();
+    private final Logger root = ctx.getRootLogger();
 
     static ConfigurationFactory cf = new BasicConfigurationFactory();
 
-    @Rule
-    public final ThreadContextRule threadContextRule = new ThreadContextRule();
-
-    @BeforeClass
+    @BeforeAll
     public static void setupClass() {
         ConfigurationFactory.setConfigurationFactory(cf);
         final LoggerContext ctx = LoggerContext.getContext();
         ctx.reconfigure();
     }
 
-    @AfterClass
+    @AfterAll
     public static void cleanupClass() {
         ConfigurationFactory.removeConfigurationFactory(cf);
     }
@@ -140,17 +134,17 @@ public class HtmlLayoutTest {
             sb.append(string);
         }
         final String html = sb.toString();
-        assertTrue("Incorrect number of lines. Require at least 85 " + list.size(), list.size() > 85);
+        assertTrue(list.size() > 85, "Incorrect number of lines. Require at least 85 " + list.size());
         final String string = list.get(3);
-        assertTrue("Incorrect header: " + string, string.equals("<meta charset=\"UTF-8\"/>"));
-        assertTrue("Incorrect title", list.get(4).equals("<title>Log4j Log Messages</title>"));
-        assertTrue("Incorrect footer", list.get(list.size() - 1).equals("</body></html>"));
+        assertEquals("<meta charset=\"UTF-8\"/>", string, "Incorrect header: " + string);
+        assertEquals("<title>Log4j Log Messages</title>", list.get(4), "Incorrect title");
+        assertEquals("</body></html>", list.get(list.size() - 1), "Incorrect footer");
         if (includeLocation) {
-            assertTrue("Incorrect multiline", list.get(50).equals(multiLine));
-            assertTrue("Missing location", html.contains("HtmlLayoutTest.java:"));
-            assertTrue("Incorrect body", list.get(71).equals(body));
+            assertEquals(list.get(50), multiLine, "Incorrect multiline");
+            assertTrue(html.contains("HtmlLayoutTest.java:"), "Missing location");
+            assertEquals(list.get(71), body, "Incorrect body");
         } else {
-            assertFalse("Location should not be in the output table", html.contains("<td>HtmlLayoutTest.java:"));
+            assertFalse(html.contains("<td>HtmlLayoutTest.java:"), "Location should not be in the output table");
         }
         for (final Appender app : appenders.values()) {
             root.addAppender(app);
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Log4j2_2195_Test.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Log4j2_2195_Test.java
index 5112e5d..ea2433e 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Log4j2_2195_Test.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Log4j2_2195_Test.java
@@ -20,43 +20,38 @@ package org.apache.logging.log4j.core.layout;
 import java.io.Serializable;
 import java.util.List;
 
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.layout.AbstractStringLayout.Serializer;
-import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.junit.LoggerContextSource;
+import org.apache.logging.log4j.junit.Named;
 import org.apache.logging.log4j.test.appender.ListAppender;
-import org.junit.Assert;
-import org.junit.ClassRule;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
 
-public class Log4j2_2195_Test {
-
-    @ClassRule
-    public static final LoggerContextRule loggerContextRule = new LoggerContextRule(
-            "src/test/resources/LOG4J-2195/log4j2.xml");
+import static org.junit.jupiter.api.Assertions.*;
 
-    private static final Logger logger = LogManager.getLogger(Log4j2_2195_Test.class);
+@LoggerContextSource("LOG4J-2195/log4j2.xml")
+public class Log4j2_2195_Test {
 
     @Test
-    public void test() {
-        logger.info("This is a test.", new Exception("Test exception!"));
-        final ListAppender listAppender = loggerContextRule.getListAppender("ListAppender");
-        Assert.assertNotNull(listAppender);
-        final List<String> events = listAppender.getMessages();
-        Assert.assertNotNull(events);
-        Assert.assertEquals(1, events.size());
-        final String logEvent = events.get(0);
-        Assert.assertNotNull(logEvent);
-        Assert.assertFalse("\"org.junit\" should not be here", logEvent.contains("org.junit"));
-        Assert.assertFalse("\"org.eclipse\" should not be here", logEvent.contains("org.eclipse"));
+    public void test(final LoggerContext context, @Named("ListAppender") final ListAppender listAppender) {
+        listAppender.clear();
+        context.getLogger(getClass()).info("This is a test.", new Exception("Test exception!"));
+        assertNotNull(listAppender);
+        List<String> events = listAppender.getMessages();
+        assertNotNull(events);
+        assertEquals(1, events.size());
+        String logEvent = events.get(0);
+        assertNotNull(logEvent);
+        assertFalse(logEvent.contains("org.junit"), "\"org.junit\" should not be here");
+        assertFalse(logEvent.contains("org.eclipse"), "\"org.eclipse\" should not be here");
         //
-        final Layout<? extends Serializable> layout = listAppender.getLayout();
-        final PatternLayout pLayout = (PatternLayout) layout;
-        Assert.assertNotNull(pLayout);
-        final Serializer eventSerializer = pLayout.getEventSerializer();
-        Assert.assertNotNull(eventSerializer);
+        Layout<? extends Serializable> layout = listAppender.getLayout();
+        PatternLayout pLayout = (PatternLayout) layout;
+        assertNotNull(pLayout);
+        Serializer eventSerializer = pLayout.getEventSerializer();
+        assertNotNull(eventSerializer);
         //
-        Assert.assertTrue("Missing \"|\"", logEvent.contains("|"));
+        assertTrue(logEvent.contains("|"), "Missing \"|\"");
     }
 }
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutLookupDateTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutLookupDateTest.java
index 402ea1b..d814d1d 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutLookupDateTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutLookupDateTest.java
@@ -16,29 +16,29 @@
  */
 package org.apache.logging.log4j.core.layout;
 
-import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.junit.LoggerContextSource;
+import org.apache.logging.log4j.junit.Named;
 import org.apache.logging.log4j.test.appender.ListAppender;
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
 
 /**
  * See (LOG4J2-905) Ability to disable (date) lookup completely, compatibility issues with other libraries like camel.
  *
  * This shows the behavior this user wants to disable.
  */
+@LoggerContextSource("log4j-list.xml")
 public class PatternLayoutLookupDateTest {
 
-    @Rule
-    public final LoggerContextRule context = new LoggerContextRule("log4j-list.xml");
-
     @Test
-    public void testDateLookupInMessage() {
+    public void testDateLookupInMessage(final LoggerContext context, @Named("List") final ListAppender listAppender) {
+        listAppender.clear();
         final String template = "${date:YYYY-MM-dd}";
         context.getLogger(PatternLayoutLookupDateTest.class.getName()).info(template);
-        final ListAppender listAppender = context.getListAppender("List");
         final String string = listAppender.getMessages().get(0);
-        Assert.assertFalse(string, string.contains(template));
+        assertFalse(string.contains(template), string);
     }
 
 }
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutMainMapLookupTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutMainMapLookupTest.java
index 120af89..69b8aa3 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutMainMapLookupTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutMainMapLookupTest.java
@@ -17,20 +17,26 @@
 
 package org.apache.logging.log4j.core.layout;
 
-import java.util.List;
-
 import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.appender.FileAppender;
 import org.apache.logging.log4j.core.lookup.MainMapLookup;
-import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.junit.LoggerContextSource;
+import org.apache.logging.log4j.junit.Named;
+import org.apache.logging.log4j.junit.ReconfigurationPolicy;
 import org.apache.logging.log4j.test.appender.ListAppender;
-import org.junit.Assert;
-import org.junit.ClassRule;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 
 /**
  * Tests LOG4j2-962.
  */
+@LoggerContextSource(value = "log4j2-962.xml", reconfigure = ReconfigurationPolicy.BEFORE_EACH)
 public class PatternLayoutMainMapLookupTest {
 
     static {
@@ -38,29 +44,25 @@ public class PatternLayoutMainMapLookupTest {
         MainMapLookup.setMainArguments("value0", "value1", "value2");
     }
 
-    @ClassRule
-    public static LoggerContextRule context = new LoggerContextRule("log4j2-962.xml");
-
     @Test
-    public void testFileName() {
-        final FileAppender fileApp = (FileAppender) context.getRequiredAppender("File");
+    public void testFileName(@Named("File") final FileAppender fileApp) {
         final String name = fileApp.getFileName();
-        Assert.assertEquals("target/value0.log", name);
+        assertEquals("target/value0.log", name);
     }
 
     @Test
-    public void testHeader() {
-        final ListAppender listApp = context.getListAppender("List");
-        final Logger logger = context.getLogger(this.getClass().getName());
+    public void testHeader(final LoggerContext context, @Named("List") final ListAppender listApp) {
+        final Logger logger = context.getLogger(getClass());
         logger.info("Hello World");
         final List<String> initialMessages = listApp.getMessages();
-        Assert.assertFalse(initialMessages.isEmpty());
+        assertFalse(initialMessages.isEmpty());
         final String messagesStr = initialMessages.toString();
-        Assert.assertEquals(messagesStr, "Header: value0", initialMessages.get(0));
+        assertEquals("Header: value0", initialMessages.get(0), messagesStr);
         listApp.stop();
         final List<String> finalMessages = listApp.getMessages();
-        Assert.assertEquals(3, finalMessages.size());
-        Assert.assertEquals("Footer: value1", finalMessages.get(2));
+        assertEquals(3, finalMessages.size());
+        assertEquals("Footer: value1", finalMessages.get(2));
+        listApp.clear();
     }
 
 }
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutNoLookupDateTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutNoLookupDateTest.java
index 4dcd942..78ae6dc 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutNoLookupDateTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutNoLookupDateTest.java
@@ -16,27 +16,26 @@
  */
 package org.apache.logging.log4j.core.layout;
 
-import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.junit.LoggerContextSource;
+import org.apache.logging.log4j.junit.Named;
 import org.apache.logging.log4j.test.appender.ListAppender;
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
 
 /**
  * See (LOG4J2-905) Ability to disable (date) lookup completely, compatibility issues with other libraries like camel.
  */
+@LoggerContextSource("log4j-list-nolookups.xml")
 public class PatternLayoutNoLookupDateTest {
 
-    @Rule
-    public final LoggerContextRule context = new LoggerContextRule("log4j-list-nolookups.xml");
-
     @Test
-    public void testDateLookupInMessage() {
+    public void testDateLookupInMessage(final LoggerContext context, @Named("List") final ListAppender listAppender) {
+        listAppender.clear();
         final String template = "${date:YYYY-MM-dd}";
-        context.getLogger(PatternLayoutNoLookupDateTest.class.getName()).info(template);
-        final ListAppender listAppender = context.getListAppender("List");
+        context.getLogger(PatternLayoutNoLookupDateTest.class).info(template);
         final String string = listAppender.getMessages().get(0);
-        Assert.assertTrue(string, string.contains(template));
+        Assertions.assertTrue(string.contains(template), string);
     }
 
 }
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutTest.java
index 4749546..7f429c0 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutTest.java
@@ -16,11 +16,6 @@
  */
 package org.apache.logging.log4j.core.layout;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
 import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
@@ -36,18 +31,17 @@ import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.config.ConfigurationFactory;
 import org.apache.logging.log4j.core.impl.Log4jLogEvent;
 import org.apache.logging.log4j.core.lookup.MainMapLookup;
-import org.apache.logging.log4j.junit.ThreadContextRule;
+import org.apache.logging.log4j.junit.UsingAnyThreadContext;
 import org.apache.logging.log4j.message.SimpleMessage;
 import org.apache.logging.log4j.util.Strings;
-import org.junit.BeforeClass;
-import org.junit.Rule;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
 
-/**
- *
- */
+import static org.junit.jupiter.api.Assertions.*;
+
+@UsingAnyThreadContext
 public class PatternLayoutTest {
-    public class FauxLogger {
+    public static class FauxLogger {
         public String formatEvent(final LogEvent event, final Layout<?> layout) {
             return new String(layout.toByteArray(event));
         }
@@ -63,7 +57,7 @@ public class PatternLayoutTest {
         ConfigurationFactory.removeConfigurationFactory(cf);
     }
 
-    @BeforeClass
+    @BeforeAll
     public static void setupClass() {
         ConfigurationFactory.setConfigurationFactory(cf);
         final LoggerContext ctx = LoggerContext.getContext();
@@ -74,9 +68,6 @@ public class PatternLayoutTest {
 
     Logger root = ctx.getRootLogger();
 
-    @Rule
-    public final ThreadContextRule threadContextRule = new ThreadContextRule();
-
     private static class Destination implements ByteBufferDestination {
         ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[2048]);
         @Override
@@ -145,24 +136,24 @@ public class PatternLayoutTest {
         final PatternLayout layout = PatternLayout.newBuilder().withConfiguration(ctx.getConfiguration())
                 .withHeader("Header: " + pattern).withFooter("Footer: " + pattern).build();
         final byte[] header = layout.getHeader();
-        assertNotNull("No header", header);
+        assertNotNull(header, "No header");
         final String headerStr = new String(header);
-        assertTrue(headerStr, headerStr.contains("Header: "));
-        assertTrue(headerStr, headerStr.contains("Java version "));
-        assertTrue(headerStr, headerStr.contains("(build "));
-        assertTrue(headerStr, headerStr.contains(" from "));
-        assertTrue(headerStr, headerStr.contains(" architecture: "));
-        assertFalse(headerStr, headerStr.contains("%d{UNIX}"));
+        assertTrue(headerStr.contains("Header: "), headerStr);
+        assertTrue(headerStr.contains("Java version "), headerStr);
+        assertTrue(headerStr.contains("(build "), headerStr);
+        assertTrue(headerStr.contains(" from "), headerStr);
+        assertTrue(headerStr.contains(" architecture: "), headerStr);
+        assertFalse(headerStr.contains("%d{UNIX}"), headerStr);
         //
         final byte[] footer = layout.getFooter();
-        assertNotNull("No footer", footer);
+        assertNotNull(footer, "No footer");
         final String footerStr = new String(footer);
-        assertTrue(footerStr, footerStr.contains("Footer: "));
-        assertTrue(footerStr, footerStr.contains("Java version "));
-        assertTrue(footerStr, footerStr.contains("(build "));
-        assertTrue(footerStr, footerStr.contains(" from "));
-        assertTrue(footerStr, footerStr.contains(" architecture: "));
-        assertFalse(footerStr, footerStr.contains("%d{UNIX}"));
+        assertTrue(footerStr.contains("Footer: "), footerStr);
+        assertTrue(footerStr.contains("Java version "), footerStr);
+        assertTrue(footerStr.contains("(build "), footerStr);
+        assertTrue(footerStr.contains(" from "), footerStr);
+        assertTrue(footerStr.contains(" architecture: "), footerStr);
+        assertFalse(footerStr.contains("%d{UNIX}"), footerStr);
     }
 
     /**
@@ -174,14 +165,14 @@ public class PatternLayoutTest {
         final PatternLayout layout = PatternLayout.newBuilder().withConfiguration(ctx.getConfiguration())
                 .withHeader("${main:0}").withFooter("${main:2}").build();
         final byte[] header = layout.getHeader();
-        assertNotNull("No header", header);
+        assertNotNull(header, "No header");
         final String headerStr = new String(header);
-        assertTrue(headerStr, headerStr.contains("value0"));
+        assertTrue(headerStr.contains("value0"), headerStr);
         //
         final byte[] footer = layout.getFooter();
-        assertNotNull("No footer", footer);
+        assertNotNull(footer, "No footer");
         final String footerStr = new String(footer);
-        assertTrue(footerStr, footerStr.contains("value2"));
+        assertTrue(footerStr.contains("value2"), footerStr);
     }
 
     @Test
@@ -192,9 +183,9 @@ public class PatternLayoutTest {
         ThreadContext.put("header", "Hello world Header");
         ThreadContext.put("footer", "Hello world Footer");
         final byte[] header = layout.getHeader();
-        assertNotNull("No header", header);
-        assertTrue("expected \"Hello world Header\", actual " + Strings.dquote(new String(header)),
-                new String(header).equals(new String("Hello world Header")));
+        assertNotNull(header, "No header");
+        assertEquals("Hello world Header", new String(header),
+                "expected \"Hello world Header\", actual " + Strings.dquote(new String(header)));
     }
 
     private void testMdcPattern(final String patternStr, final String expectedStr, final boolean useThreadContext)
@@ -258,14 +249,14 @@ public class PatternLayoutTest {
                 .setMessage(new SimpleMessage("entry")).build();
         final String result1 = new FauxLogger().formatEvent(event1, layout);
         final String expectPattern1 = String.format(".*====== PatternLayoutTest.testPatternSelector:\\d+ entry ======%n");
-        assertTrue("Unexpected result: " + result1, result1.matches(expectPattern1));
+        assertTrue(result1.matches(expectPattern1), "Unexpected result: " + result1);
         final LogEvent event2 = Log4jLogEvent.newBuilder() //
                 .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") //
                 .setLevel(Level.INFO) //
                 .setMessage(new SimpleMessage("Hello, world 1!")).build();
         final String result2 = new String(layout.toByteArray(event2));
         final String expectSuffix2 = String.format("Hello, world 1!%n");
-        assertTrue("Unexpected result: " + result2, result2.endsWith(expectSuffix2));
+        assertTrue(result2.endsWith(expectSuffix2), "Unexpected result: " + result2);
     }
 
     @Test
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternSelectorTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternSelectorTest.java
index 67e2d4c..5c36c5e 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternSelectorTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternSelectorTest.java
@@ -16,8 +16,6 @@
  */
 package org.apache.logging.log4j.core.layout;
 
-import static org.junit.Assert.assertTrue;
-
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.MarkerManager;
 import org.apache.logging.log4j.core.Layout;
@@ -25,11 +23,13 @@ import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.impl.Log4jLogEvent;
 import org.apache.logging.log4j.message.SimpleMessage;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
 
 public class PatternSelectorTest {
 
-    public class FauxLogger {
+    public static class FauxLogger {
         public String formatEvent(final LogEvent event, final Layout<?> layout) {
             return new String(layout.toByteArray(event));
         }
@@ -52,14 +52,14 @@ public class PatternSelectorTest {
                 .setMessage(new SimpleMessage("entry")).build();
         final String result1 = new FauxLogger().formatEvent(event1, layout);
         final String expectSuffix1 = String.format("====== PatternSelectorTest.testMarkerPatternSelector:53 entry ======%n");
-        assertTrue("Unexpected result: " + result1, result1.endsWith(expectSuffix1));
+        assertTrue(result1.endsWith(expectSuffix1), "Unexpected result: " + result1);
         final LogEvent event2 = Log4jLogEvent.newBuilder() //
                 .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") //
                 .setLevel(Level.INFO) //
                 .setMessage(new SimpleMessage("Hello, world 1!")).build();
         final String result2 = new String(layout.toByteArray(event2));
         final String expectSuffix2 = String.format("Hello, world 1!%n");
-        assertTrue("Unexpected result: " + result2, result2.endsWith(expectSuffix2));
+        assertTrue(result2.endsWith(expectSuffix2), "Unexpected result: " + result2);
     }
 
 
@@ -77,14 +77,14 @@ public class PatternSelectorTest {
                 .setMessage(new SimpleMessage("entry")).build();
         final String result1 = new FauxLogger().formatEvent(event1, layout);
         final String expectSuffix1 = String.format("====== PatternSelectorTest.testLevelPatternSelector:78 entry ======%n");
-        assertTrue("Unexpected result: " + result1, result1.endsWith(expectSuffix1));
+        assertTrue(result1.endsWith(expectSuffix1), "Unexpected result: " + result1);
         final LogEvent event2 = Log4jLogEvent.newBuilder() //
                 .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") //
                 .setLevel(Level.INFO) //
                 .setMessage(new SimpleMessage("Hello, world 1!")).build();
         final String result2 = new String(layout.toByteArray(event2));
         final String expectSuffix2 = String.format("Hello, world 1!%n");
-        assertTrue("Unexpected result: " + result2, result2.endsWith(expectSuffix2));
+        assertTrue(result2.endsWith(expectSuffix2), "Unexpected result: " + result2);
     }
 
 }
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Rfc5424LayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Rfc5424LayoutTest.java
index 0b9b50d..9dece77 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Rfc5424LayoutTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Rfc5424LayoutTest.java
@@ -31,21 +31,20 @@ import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.config.ConfigurationFactory;
 import org.apache.logging.log4j.core.net.Facility;
 import org.apache.logging.log4j.core.util.KeyValuePair;
-import org.apache.logging.log4j.junit.ThreadContextRule;
+import org.apache.logging.log4j.junit.UsingAnyThreadContext;
 import org.apache.logging.log4j.message.StructuredDataCollectionMessage;
 import org.apache.logging.log4j.message.StructuredDataMessage;
 import org.apache.logging.log4j.status.StatusLogger;
 import org.apache.logging.log4j.test.appender.ListAppender;
 import org.apache.logging.log4j.util.ProcessIdUtil;
 import org.apache.logging.log4j.util.Strings;
-import org.junit.AfterClass;
-import org.junit.Assert;
-import org.junit.BeforeClass;
-import org.junit.Rule;
-import org.junit.Test;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
 
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
 
+@UsingAnyThreadContext
 public class Rfc5424LayoutTest {
     LoggerContext ctx = LoggerContext.getContext();
     Logger root = ctx.getRootLogger();
@@ -72,10 +71,7 @@ public class Rfc5424LayoutTest {
 
     static ConfigurationFactory cf = new BasicConfigurationFactory();
 
-    @Rule
-    public final ThreadContextRule threadContextRule = new ThreadContextRule();
-
-    @BeforeClass
+    @BeforeAll
     public static void setupClass() {
         StatusLogger.getLogger().setLevel(Level.OFF);
         ConfigurationFactory.setConfigurationFactory(cf);
@@ -83,7 +79,7 @@ public class Rfc5424LayoutTest {
         ctx.reconfigure();
     }
 
-    @AfterClass
+    @AfterAll
     public static void cleanupClass() {
         ConfigurationFactory.removeConfigurationFactory(cf);
     }
@@ -130,10 +126,10 @@ public class Rfc5424LayoutTest {
 
             List<String> list = appender.getMessages();
 
-            assertTrue("Expected line 1 to end with: " + line1 + " Actual " + list.get(0), list.get(0).endsWith(line1));
-            assertTrue("Expected line 2 to end with: " + line2 + " Actual " + list.get(1), list.get(1).endsWith(line2));
-            assertTrue("Expected line 3 to end with: " + line3 + " Actual " + list.get(2), list.get(2).endsWith(line3));
-            assertTrue("Expected line 4 to end with: " + line4 + " Actual " + list.get(3), list.get(3).endsWith(line4));
+            assertTrue(list.get(0).endsWith(line1), "Expected line 1 to end with: " + line1 + " Actual " + list.get(0));
+            assertTrue(list.get(1).endsWith(line2), "Expected line 2 to end with: " + line2 + " Actual " + list.get(1));
+            assertTrue(list.get(2).endsWith(line3), "Expected line 3 to end with: " + line3 + " Actual " + list.get(2));
+            assertTrue(list.get(3).endsWith(line4), "Expected line 4 to end with: " + line4 + " Actual " + list.get(3));
 
             for (final String frame : list) {
                 int length = -1;
@@ -146,7 +142,7 @@ public class Rfc5424LayoutTest {
                     assertEquals(frameLength, messageLength.length() + length);
                 }
                 catch (final NumberFormatException e) {
-                    assertTrue("Not a valid RFC 5425 frame", false);
+                    fail("Not a valid RFC 5425 frame");
                 }
             }
 
@@ -157,9 +153,8 @@ public class Rfc5424LayoutTest {
             root.debug("This is a test");
 
             list = appender.getMessages();
-            assertTrue("No messages expected, found " + list.size(), list.isEmpty());
+            assertTrue(list.isEmpty(), "No messages expected, found " + list.size());
         } finally {
-            ThreadContext.clearMap();
             root.removeAppender(appender);
             appender.stop();
         }
@@ -202,16 +197,17 @@ public class Rfc5424LayoutTest {
 
             root.info(MarkerManager.getMarker("EVENT"), collectionMessage);
 
-            final List<String> list = appender.getMessages();
-            final String result = list.get(0);
-            assertTrue("Expected line to contain " + collectionLine1 + ", Actual " + result,
-                    result.contains(collectionLine1));
-            assertTrue("Expected line to contain " + collectionLine2 + ", Actual " + result,
-                    result.contains(collectionLine2));
-            assertTrue("Expected line to contain " + collectionLine3 + ", Actual " + result,
-                    result.contains(collectionLine3));
-            assertTrue("Expected line to end with: " + collectionEndOfLine + " Actual " + result,
-                    result.endsWith(collectionEndOfLine));
+            List<String> list = appender.getMessages();
+            String result = list.get(0);
+            assertTrue(
+                    result.contains(collectionLine1), "Expected line to contain " + collectionLine1 + ", Actual " + result);
+            assertTrue(
+                    result.contains(collectionLine2), "Expected line to contain " + collectionLine2 + ", Actual " + result);
+            assertTrue(
+                    result.contains(collectionLine3), "Expected line to contain " + collectionLine3 + ", Actual " + result);
+            assertTrue(
+                    result.endsWith(collectionEndOfLine),
+                    "Expected line to end with: " + collectionEndOfLine + " Actual " + result);
 
             for (final String frame : list) {
                 int length = -1;
@@ -224,13 +220,12 @@ public class Rfc5424LayoutTest {
                     assertEquals(frameLength, messageLength.length() + length);
                 }
                 catch (final NumberFormatException e) {
-                    assertTrue("Not a valid RFC 5425 frame", false);
+                    fail("Not a valid RFC 5425 frame");
                 }
             }
 
             appender.clear();
         } finally {
-            ThreadContext.clearMap();
             root.removeAppender(appender);
             appender.stop();
         }
@@ -277,10 +272,12 @@ public class Rfc5424LayoutTest {
 
             List<String> list = appender.getMessages();
 
-            assertTrue("Expected line 1 to end with: " + line1 + " Actual " + list.get(0), list.get(0).endsWith(line1));
-            assertTrue("Expected line 2 to end with: " + line2 + " Actual " + list.get(1), list.get(1).endsWith(line2));
-            assertTrue("Expected line 3 to end with: " + lineEscaped3 + " Actual " + list.get(2), list.get(2).endsWith(lineEscaped3));
-            assertTrue("Expected line 4 to end with: " + lineEscaped4 + " Actual " + list.get(3), list.get(3).endsWith(lineEscaped4));
+            assertTrue(list.get(0).endsWith(line1), "Expected line 1 to end with: " + line1 + " Actual " + list.get(0));
+            assertTrue(list.get(1).endsWith(line2), "Expected line 2 to end with: " + line2 + " Actual " + list.get(1));
+            assertTrue(list.get(2).endsWith(lineEscaped3),
+                    "Expected line 3 to end with: " + lineEscaped3 + " Actual " + list.get(2));
+            assertTrue(list.get(3).endsWith(lineEscaped4),
+                    "Expected line 4 to end with: " + lineEscaped4 + " Actual " + list.get(3));
 
             appender.clear();
 
@@ -289,7 +286,7 @@ public class Rfc5424LayoutTest {
             root.debug("This is a test");
 
             list = appender.getMessages();
-            assertTrue("No messages expected, found " + list.size(), list.isEmpty());
+            assertTrue(list.isEmpty(), "No messages expected, found " + list.size());
         } finally {
             root.removeAppender(appender);
             appender.stop();
@@ -323,9 +320,9 @@ public class Rfc5424LayoutTest {
 
             final List<String> list = appender.getMessages();
 
-            assertTrue("Not enough list entries", list.size() > 1);
+            assertTrue(list.size() > 1, "Not enough list entries");
             final String string = list.get(1);
-			      assertTrue("No Exception in " + string, string.contains("IllegalArgumentException"));
+			      assertTrue(string.contains("IllegalArgumentException"), "No Exception in " + string);
 
             appender.clear();
         } finally {
@@ -364,8 +361,8 @@ public class Rfc5424LayoutTest {
         try {
 
             final List<String> list = appender.getMessages();
-            assertTrue("Not enough list entries", list.size() > 0);
-            assertTrue("No class/method", list.get(0).contains("Rfc5424LayoutTest.testMDCLoggerFields"));
+            assertTrue(list.size() > 0, "Not enough list entries");
+            assertTrue(list.get(0).contains("Rfc5424LayoutTest.testMDCLoggerFields"), "No class/method");
 
             appender.clear();
         } finally {
@@ -408,11 +405,11 @@ public class Rfc5424LayoutTest {
         try {
 
             final List<String> list = appender.getMessages();
-            assertTrue("Not enough list entries", list.size() > 0);
+            assertTrue(list.size() > 0, "Not enough list entries");
             final String message =  list.get(0);
-            assertTrue("No class/method", message.contains("Rfc5424LayoutTest.testLoggerFields"));
+            assertTrue(message.contains("Rfc5424LayoutTest.testLoggerFields"), "No class/method");
             for (final String value : expectedToContain) {
-                Assert.assertTrue("Message expected to contain " + value + " but did not", message.contains(value));
+                assertTrue(message.contains(value), "Message expected to contain " + value + " but did not");
             }
             appender.clear();
         } finally {
@@ -455,11 +452,11 @@ public class Rfc5424LayoutTest {
         try {
 
             final List<String> list = appender.getMessages();
-            assertTrue("Not enough list entries", list.size() > 0);
+            assertTrue(list.size() > 0, "Not enough list entries");
             final String message =  list.get(0);
-            Assert.assertTrue("SD-ID should have been discarded", !message.contains("SD-ID"));
-            Assert.assertTrue("BAZ should have been included", message.contains("BAZ"));
-            Assert.assertTrue(mdcId + "should have been included", message.contains(mdcId));
+            assertFalse(message.contains("SD-ID"), "SD-ID should have been discarded");
+            assertTrue(message.contains("BAZ"), "BAZ should have been included");
+            assertTrue(message.contains(mdcId), mdcId + "should have been included");
             appender.clear();
         } finally {
             root.removeAppender(appender);
@@ -489,9 +486,9 @@ public class Rfc5424LayoutTest {
 
         try {
             final List<String> list = appender.getMessages();
-            assertTrue("Not enough list entries", list.size() > 0);
+            assertTrue(list.size() > 0, "Not enough list entries");
             final String message =  list.get(0);
-            Assert.assertTrue("Not the expected message received", message.contains(expectedToContain));
+            assertTrue(message.contains(expectedToContain), "Not the expected message received");
             appender.clear();
         } finally {
             root.removeAppender(appender);
@@ -517,9 +514,10 @@ public class Rfc5424LayoutTest {
         root.info("Hello {}", "World");
         try {
             final List<String> list = appender.getMessages();
-            assertTrue("Not enough list entries", list.size() > 0);
+            assertTrue(list.size() > 0, "Not enough list entries");
             final String message =  list.get(0);
-            assertTrue("Incorrect message. Expected - Hello World, Actual - " + message, message.contains("Hello World"));
+            assertTrue(message.contains("Hello World"),
+                    "Incorrect message. Expected - Hello World, Actual - " + message);
         } finally {
             root.removeAppender(appender);
             appender.stop();
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/StringBuilderEncoderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/StringBuilderEncoderTest.java
index 51faed9..575f838 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/StringBuilderEncoderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/StringBuilderEncoderTest.java
@@ -19,9 +19,9 @@ import java.nio.CharBuffer;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
 
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
 
 /**
  * Tests the {@code TextEncoderHelper} class.
@@ -35,11 +35,11 @@ public class StringBuilderEncoderTest {
         final SpyByteBufferDestination destination = new SpyByteBufferDestination(17, 17);
         helper.encode(text, destination);
 
-        assertEquals("drained", 0, destination.drainPoints.size());
-        assertEquals("destination.buf.pos", text.length(), destination.buffer.position());
+        assertEquals(0, destination.drainPoints.size(), "drained");
+        assertEquals(text.length(), destination.buffer.position(), "destination.buf.pos");
 
         for (int i = 0; i < text.length(); i++) {
-            assertEquals("char at " + i, (byte) text.charAt(i), destination.buffer.get(i));
+            assertEquals((byte) text.charAt(i), destination.buffer.get(i), "char at " + i);
         }
     }
 
@@ -50,19 +50,19 @@ public class StringBuilderEncoderTest {
         final SpyByteBufferDestination destination = new SpyByteBufferDestination(14, 15);
         helper.encode(text, destination);
 
-        assertEquals("drained", 1, destination.drainPoints.size());
-        assertEquals("drained[0].from", 0, destination.drainPoints.get(0).position);
-        assertEquals("drained[0].to", destination.buffer.capacity(), destination.drainPoints.get(0).limit);
-        assertEquals("drained[0].length", destination.buffer.capacity(), destination.drainPoints.get(0).length());
-        assertEquals("destination.buf.pos", text.length() - destination.buffer.capacity(),
-                destination.buffer.position());
+        assertEquals(1, destination.drainPoints.size(), "drained");
+        assertEquals(0, destination.drainPoints.get(0).position, "drained[0].from");
+        assertEquals(destination.buffer.capacity(), destination.drainPoints.get(0).limit, "drained[0].to");
+        assertEquals(destination.buffer.capacity(), destination.drainPoints.get(0).length(), "drained[0].length");
+        assertEquals(text.length() - destination.buffer.capacity(),
+                destination.buffer.position(), "destination.buf.pos");
 
         for (int i = 0; i < destination.buffer.capacity(); i++) {
-            assertEquals("char at " + i, (byte) text.charAt(i), destination.drained.get(i));
+            assertEquals((byte) text.charAt(i), destination.drained.get(i), "char at " + i);
         }
         for (int i = destination.buffer.capacity(); i < text.length(); i++) {
             final int bufIx = i - destination.buffer.capacity();
-            assertEquals("char at " + i, (byte) text.charAt(i), destination.buffer.get(bufIx));
+            assertEquals((byte) text.charAt(i), destination.buffer.get(bufIx), "char at " + i);
         }
     }
 
@@ -73,25 +73,25 @@ public class StringBuilderEncoderTest {
         final SpyByteBufferDestination destination = new SpyByteBufferDestination(4, 20);
         helper.encode(text, destination);
 
-        assertEquals("drained", 3, destination.drainPoints.size());
-        assertEquals("drained[0].from", 0, destination.drainPoints.get(0).position);
-        assertEquals("drained[0].to", destination.buffer.capacity(), destination.drainPoints.get(0).limit);
-        assertEquals("drained[0].length", destination.buffer.capacity(), destination.drainPoints.get(0).length());
-        assertEquals("drained[1].from", 0, destination.drainPoints.get(1).position);
-        assertEquals("drained[1].to", destination.buffer.capacity(), destination.drainPoints.get(1).limit);
-        assertEquals("drained[1].length", destination.buffer.capacity(), destination.drainPoints.get(1).length());
-        assertEquals("drained[2].from", 0, destination.drainPoints.get(2).position);
-        assertEquals("drained[2].to", destination.buffer.capacity(), destination.drainPoints.get(2).limit);
-        assertEquals("drained[2].length", destination.buffer.capacity(), destination.drainPoints.get(2).length());
-        assertEquals("destination.buf.pos", text.length() - 3 * destination.buffer.capacity(),
-                destination.buffer.position());
+        assertEquals(3, destination.drainPoints.size(), "drained");
+        assertEquals(0, destination.drainPoints.get(0).position, "drained[0].from");
+        assertEquals(destination.buffer.capacity(), destination.drainPoints.get(0).limit, "drained[0].to");
+        assertEquals(destination.buffer.capacity(), destination.drainPoints.get(0).length(), "drained[0].length");
+        assertEquals(0, destination.drainPoints.get(1).position, "drained[1].from");
+        assertEquals(destination.buffer.capacity(), destination.drainPoints.get(1).limit, "drained[1].to");
+        assertEquals(destination.buffer.capacity(), destination.drainPoints.get(1).length(), "drained[1].length");
+        assertEquals(0, destination.drainPoints.get(2).position, "drained[2].from");
+        assertEquals(destination.buffer.capacity(), destination.drainPoints.get(2).limit, "drained[2].to");
+        assertEquals(destination.buffer.capacity(), destination.drainPoints.get(2).length(), "drained[2].length");
+        assertEquals(text.length() - 3 * destination.buffer.capacity(),
+                destination.buffer.position(), "destination.buf.pos");
 
         for (int i = 0; i < 3 * destination.buffer.capacity(); i++) {
-            assertEquals("char at " + i, (byte) text.charAt(i), destination.drained.get(i));
+            assertEquals((byte) text.charAt(i), destination.drained.get(i), "char at " + i);
         }
         for (int i = 3 * destination.buffer.capacity(); i < text.length(); i++) {
             final int bufIx = i - 3 * destination.buffer.capacity();
-            assertEquals("char at " + i, (byte) text.charAt(i), destination.buffer.get(bufIx));
+            assertEquals((byte) text.charAt(i), destination.buffer.get(bufIx), "char at " + i);
         }
     }
 
@@ -102,11 +102,11 @@ public class StringBuilderEncoderTest {
         final SpyByteBufferDestination destination = new SpyByteBufferDestination(17, 17);
         helper.encode(text, destination);
 
-        assertEquals("drained", 0, destination.drainPoints.size());
-        assertEquals("destination.buf.pos", text.length(), destination.buffer.position());
+        assertEquals(0, destination.drainPoints.size(), "drained");
+        assertEquals(text.length(), destination.buffer.position(), "destination.buf.pos");
 
         for (int i = 0; i < text.length(); i++) {
-            assertEquals("char at " + i, (byte) text.charAt(i), destination.buffer.get(i));
+            assertEquals((byte) text.charAt(i), destination.buffer.get(i), "char at " + i);
         }
     }
 
@@ -118,12 +118,12 @@ public class StringBuilderEncoderTest {
         final SpyByteBufferDestination destination = new SpyByteBufferDestination(50, 50);
         helper.encode(text, destination);
 
-        assertEquals("drained", 0, destination.drainPoints.size());
+        assertEquals(0, destination.drainPoints.size(), "drained");
         destination.drain(destination.getByteBuffer());
 
         final byte[] utf8 = text.toString().getBytes(StandardCharsets.UTF_8);
         for (int i = 0; i < utf8.length; i++) {
-            assertEquals("byte at " + i, utf8[i], destination.drained.get(i));
+            assertEquals(utf8[i], destination.drained.get(i), "byte at " + i);
         }
     }
 
@@ -136,12 +136,12 @@ public class StringBuilderEncoderTest {
         final SpyByteBufferDestination destination = new SpyByteBufferDestination(50, 50);
         helper.encode(text, destination);
 
-        assertEquals("drained", 0, destination.drainPoints.size());
+        assertEquals(0, destination.drainPoints.size(), "drained");
         destination.drain(destination.getByteBuffer());
 
         final byte[] bytes = text.toString().getBytes(SHIFT_JIS);
         for (int i = 0; i < bytes.length; i++) {
-            assertEquals("byte at " + i, bytes[i], destination.drained.get(i));
+            assertEquals(bytes[i], destination.drained.get(i), "byte at " + i);
         }
     }
 
@@ -152,14 +152,14 @@ public class StringBuilderEncoderTest {
         final SpyByteBufferDestination destination = new SpyByteBufferDestination(3, 17);
         helper.encode(text, destination);
 
-        assertEquals("drained", 4, destination.drainPoints.size());
-        assertEquals("destination.buf.pos", 3, destination.buffer.position());
+        assertEquals(4, destination.drainPoints.size(), "drained");
+        assertEquals(3, destination.buffer.position(), "destination.buf.pos");
 
         for (int i = 0; i < text.length() - 3; i++) {
-            assertEquals("char at " + i, (byte) text.charAt(i), destination.drained.get(i));
+            assertEquals((byte) text.charAt(i), destination.drained.get(i), "char at " + i);
         }
         for (int i = 0; i < 3; i++) {
-            assertEquals("char at " + (12 + i), (byte) text.charAt(12 + i), destination.buffer.get(i));
+            assertEquals((byte) text.charAt(12 + i), destination.buffer.get(i), "char at " + (12 + i));
         }
     }
 
@@ -171,12 +171,12 @@ public class StringBuilderEncoderTest {
         final SpyByteBufferDestination destination = new SpyByteBufferDestination(3, 50);
         helper.encode(text, destination);
 
-        assertEquals("drained", 7, destination.drainPoints.size());
+        assertEquals(7, destination.drainPoints.size(), "drained");
         destination.drain(destination.getByteBuffer());
 
         final byte[] utf8 = text.toString().getBytes(StandardCharsets.UTF_8);
         for (int i = 0; i < utf8.length; i++) {
-            assertEquals("byte at " + i, utf8[i], destination.drained.get(i));
+            assertEquals(utf8[i], destination.drained.get(i), "byte at " + i);
         }
     }
 
@@ -188,12 +188,12 @@ public class StringBuilderEncoderTest {
         final SpyByteBufferDestination destination = new SpyByteBufferDestination(3, 50);
         helper.encode(text, destination);
 
-        assertEquals("drained", 15, destination.drainPoints.size());
+        assertEquals(15, destination.drainPoints.size(), "drained");
         destination.drain(destination.getByteBuffer());
 
         final byte[] utf8 = text.toString().getBytes(StandardCharsets.UTF_8);
         for (int i = 0; i < utf8.length; i++) {
-            assertEquals("byte at " + i, utf8[i], destination.drained.get(i));
+            assertEquals(utf8[i], destination.drained.get(i), "byte at " + i);
         }
     }
 
@@ -210,7 +210,7 @@ public class StringBuilderEncoderTest {
 
         final byte[] bytes = text.toString().getBytes(SHIFT_JIS);
         for (int i = 0; i < bytes.length; i++) {
-            assertEquals("byte at " + i, bytes[i], destination.drained.get(i));
+            assertEquals(bytes[i], destination.drained.get(i), "byte at " + i);
         }
     }
 
@@ -227,7 +227,7 @@ public class StringBuilderEncoderTest {
 
         final byte[] bytes = text.toString().getBytes(SHIFT_JIS);
         for (int i = 0; i < bytes.length; i++) {
-            assertEquals("byte at " + i, bytes[i], destination.drained.get(i));
+            assertEquals(bytes[i], destination.drained.get(i), "byte at " + i);
         }
     }
 
@@ -236,11 +236,11 @@ public class StringBuilderEncoderTest {
         final CharBuffer buff = CharBuffer.wrap(new char[16]);
         final StringBuilder text = createText(15);
         final int length = TextEncoderHelper.copy(text, 0, buff);
-        assertEquals("everything fits", text.length(), length);
+        assertEquals(text.length(), length, "everything fits");
         for (int i = 0; i < length; i++) {
-            assertEquals("char at " + i, text.charAt(i), buff.get(i));
+            assertEquals(text.charAt(i), buff.get(i), "char at " + i);
         }
-        assertEquals("position moved by length", text.length(), buff.position());
+        assertEquals(text.length(), buff.position(), "position moved by length");
     }
 
     @Test
@@ -248,27 +248,27 @@ public class StringBuilderEncoderTest {
         final CharBuffer buff = CharBuffer.wrap(new char[3]);
         final StringBuilder text = createText(15);
         final int length = TextEncoderHelper.copy(text, 0, buff);
-        assertEquals("partial copy", buff.capacity(), length);
+        assertEquals(buff.capacity(), length, "partial copy");
         for (int i = 0; i < length; i++) {
-            assertEquals("char at " + i, text.charAt(i), buff.get(i));
+            assertEquals(text.charAt(i), buff.get(i), "char at " + i);
         }
-        assertEquals("no space remaining", 0, buff.remaining());
-        assertEquals("position at end", buff.capacity(), buff.position());
+        assertEquals(0, buff.remaining(), "no space remaining");
+        assertEquals(buff.capacity(), buff.position(), "position at end");
     }
 
     @Test
     public void testCopyDoesNotWriteBeyondStringText() throws Exception {
         final CharBuffer buff = CharBuffer.wrap(new char[5]);
-        assertEquals("initial buffer position", 0, buff.position());
+        assertEquals(0, buff.position(), "initial buffer position");
         final StringBuilder text = createText(2);
         final int length = TextEncoderHelper.copy(text, 0, buff);
-        assertEquals("full copy", text.length(), length);
+        assertEquals(text.length(), length, "full copy");
         for (int i = 0; i < length; i++) {
-            assertEquals("char at " + i, text.charAt(i), buff.get(i));
+            assertEquals(text.charAt(i), buff.get(i), "char at " + i);
         }
-        assertEquals("resulting buffer position", text.length(), buff.position());
+        assertEquals(text.length(), buff.position(), "resulting buffer position");
         for (int i = length; i < buff.capacity(); i++) {
-            assertEquals("unset char at " + i, 0, buff.get(i));
+            assertEquals(0, buff.get(i), "unset char at " + i);
         }
     }
 
@@ -279,11 +279,11 @@ public class StringBuilderEncoderTest {
         buff.position(START_POSITION); // set start position
         final StringBuilder text = createText(15);
         final int length = TextEncoderHelper.copy(text, 0, buff);
-        assertEquals("partial copy", buff.capacity() - START_POSITION, length);
+        assertEquals(buff.capacity() - START_POSITION, length, "partial copy");
         for (int i = 0; i < length; i++) {
-            assertEquals("char at " + i, text.charAt(i), buff.get(START_POSITION + i));
+            assertEquals(text.charAt(i), buff.get(START_POSITION + i), "char at " + i);
         }
-        assertEquals("buffer position at end", buff.capacity(), buff.position());
+        assertEquals(buff.capacity(), buff.position(), "buffer position at end");
     }
 
     @Test
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/SyslogLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/SyslogLayoutTest.java
index d9bd5b0..fd710b8 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/SyslogLayoutTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/SyslogLayoutTest.java
@@ -28,19 +28,16 @@ import org.apache.logging.log4j.core.Logger;
 import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.config.ConfigurationFactory;
 import org.apache.logging.log4j.core.net.Facility;
-import org.apache.logging.log4j.junit.ThreadContextRule;
+import org.apache.logging.log4j.junit.UsingAnyThreadContext;
 import org.apache.logging.log4j.message.StructuredDataMessage;
 import org.apache.logging.log4j.test.appender.ListAppender;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Rule;
-import org.junit.Test;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
 
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
 
-/**
- *
- */
+@UsingAnyThreadContext
 public class SyslogLayoutTest {
     LoggerContext ctx = LoggerContext.getContext();
     Logger root = ctx.getRootLogger();
@@ -54,17 +51,14 @@ public class SyslogLayoutTest {
 
     static ConfigurationFactory cf = new BasicConfigurationFactory();
 
-    @Rule
-    public final ThreadContextRule threadContextRule = new ThreadContextRule();
-
-    @BeforeClass
+    @BeforeAll
     public static void setupClass() {
         ConfigurationFactory.setConfigurationFactory(cf);
         final LoggerContext ctx = LoggerContext.getContext();
         ctx.reconfigure();
     }
 
-    @AfterClass
+    @AfterAll
     public static void cleanupClass() {
         ConfigurationFactory.removeConfigurationFactory(cf);
     }
@@ -115,9 +109,9 @@ public class SyslogLayoutTest {
 
         final List<String> list = appender.getMessages();
 
-        assertTrue("Expected line 1 to end with: " + line1 + " Actual " + list.get(0), list.get(0).endsWith(line1));
-        assertTrue("Expected line 2 to end with: " + line2 + " Actual " + list.get(1), list.get(1).endsWith(line2));
-        assertTrue("Expected line 3 to end with: " + line3 + " Actual " + list.get(2), list.get(2).endsWith(line3));
-        assertTrue("Expected line 4 to end with: " + line4 + " Actual " + list.get(3), list.get(3).endsWith(line4));
+        assertTrue(list.get(0).endsWith(line1), "Expected line 1 to end with: " + line1 + " Actual " + list.get(0));
+        assertTrue(list.get(1).endsWith(line2), "Expected line 2 to end with: " + line2 + " Actual " + list.get(1));
+        assertTrue(list.get(2).endsWith(line3), "Expected line 3 to end with: " + line3 + " Actual " + list.get(2));
+        assertTrue(list.get(3).endsWith(line4), "Expected line 4 to end with: " + line4 + " Actual " + list.get(3));
     }
 }
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MdcPatternConverterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MdcPatternConverterTest.java
index b984253..2a94e47 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MdcPatternConverterTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MdcPatternConverterTest.java
@@ -18,34 +18,26 @@ package org.apache.logging.log4j.core.pattern;
 
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.ThreadContext;
-import org.apache.logging.log4j.ThreadContextHolder;
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.impl.Log4jLogEvent;
+import org.apache.logging.log4j.junit.UsingThreadContextMap;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.SimpleMessage;
-import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.*;
 
+@UsingThreadContextMap
 public class MdcPatternConverterTest {
 
-    private final ThreadContextHolder threadContextHolder = new ThreadContextHolder(true, false);
-
     @BeforeEach
     public void setup() {
-        ThreadContext.clearMap();
         ThreadContext.put("subject", "I");
         ThreadContext.put("verb", "love");
         ThreadContext.put("object", "Log4j");
     }
 
-    @AfterEach
-    public void tearDown() {
-        threadContextHolder.restore();
-    }
-
     @Test
     public void testConverter() {
         final Message msg = new SimpleMessage("Hello");
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/NdcPatternConverterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/NdcPatternConverterTest.java
index 4af2ad9..7ed39b7 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/NdcPatternConverterTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/NdcPatternConverterTest.java
@@ -18,31 +18,18 @@ package org.apache.logging.log4j.core.pattern;
 
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.ThreadContext;
-import org.apache.logging.log4j.ThreadContextHolder;
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.impl.Log4jLogEvent;
+import org.apache.logging.log4j.junit.UsingThreadContextStack;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.SimpleMessage;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.*;
 
+@UsingThreadContextStack
 public class NdcPatternConverterTest {
 
-    private final ThreadContextHolder holder = new ThreadContextHolder(false, true);
-
-    @BeforeEach
-    void setUp() {
-        ThreadContext.clearStack();
-    }
-
-    @AfterEach
-    void tearDown() {
-        holder.restore();
-    }
-
     @Test
     public void testEmpty() {
         testConverter("[]");
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/RegexReplacementTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/RegexReplacementTest.java
index 7d4e76d..541ae19 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/RegexReplacementTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/RegexReplacementTest.java
@@ -17,13 +17,12 @@
 package org.apache.logging.log4j.core.pattern;
 
 import org.apache.logging.log4j.ThreadContext;
-import org.apache.logging.log4j.ThreadContextHolder;
 import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.junit.LoggerContextSource;
 import org.apache.logging.log4j.junit.Named;
+import org.apache.logging.log4j.junit.UsingThreadContextMap;
 import org.apache.logging.log4j.test.appender.ListAppender;
 import org.apache.logging.log4j.util.Strings;
-import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Test;
 
 import java.util.List;
@@ -31,8 +30,8 @@ import java.util.List;
 import static org.junit.jupiter.api.Assertions.*;
 
 @LoggerContextSource("log4j-replace.xml")
+@UsingThreadContextMap
 public class RegexReplacementTest {
-    private final ThreadContextHolder holder = new ThreadContextHolder(true, false);
     private final ListAppender app;
     private final ListAppender app2;
     private final org.apache.logging.log4j.Logger logger;
@@ -46,12 +45,6 @@ public class RegexReplacementTest {
         logger2 = context.getLogger("ReplacementTest");
         this.app = app.clear();
         this.app2 = app2.clear();
-        ThreadContext.clearMap();
-    }
-
-    @AfterEach
-    void tearDown() {
-        holder.restore();
     }
 
     @Test
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/junit/LoggerContextResolver.java b/log4j-core/src/test/java/org/apache/logging/log4j/junit/LoggerContextResolver.java
index 0859bbd..fb0f367 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/junit/LoggerContextResolver.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/junit/LoggerContextResolver.java
@@ -35,10 +35,10 @@ class LoggerContextResolver extends TypeBasedParameterResolver<LoggerContext> im
     @Override
     public void beforeAll(ExtensionContext context) throws Exception {
         final Class<?> testClass = context.getRequiredTestClass();
-        final LoggerContextSource source = testClass.getAnnotation(LoggerContextSource.class);
-        if (source != null) {
+        final LoggerContextSource testSource = testClass.getAnnotation(LoggerContextSource.class);
+        if (testSource != null) {
             final LoggerContext loggerContext =
-                    Configurator.initialize(context.getDisplayName(), testClass.getClassLoader(), source.value());
+                    Configurator.initialize(context.getDisplayName(), testClass.getClassLoader(), testSource.value());
             getTestClassStore(context).put(LoggerContext.class, loggerContext);
         }
     }
@@ -53,10 +53,21 @@ class LoggerContextResolver extends TypeBasedParameterResolver<LoggerContext> im
 
     @Override
     public void beforeEach(ExtensionContext context) throws Exception {
+        final Class<?> testClass = context.getRequiredTestClass();
+        final LoggerContextSource testSource = testClass.getAnnotation(LoggerContextSource.class);
+        if (testSource != null && testSource.reconfigure() == ReconfigurationPolicy.BEFORE_EACH) {
+            final LoggerContext loggerContext = getTestClassStore(context).get(LoggerContext.class, LoggerContext.class);
+            if (loggerContext == null) {
+                throw new IllegalStateException(
+                        "Specified test class reconfiguration policy of BEFORE_EACH, but no LoggerContext found for test class " +
+                                testClass.getCanonicalName());
+            }
+            loggerContext.reconfigure();
+        }
         final LoggerContextSource source = context.getRequiredTestMethod().getAnnotation(LoggerContextSource.class);
         if (source != null) {
             final LoggerContext loggerContext = Configurator
-                    .initialize(context.getDisplayName(), context.getRequiredTestClass().getClassLoader(), source.value());
+                    .initialize(context.getDisplayName(), testClass.getClassLoader(), source.value());
             getTestInstanceStore(context).put(LoggerContext.class, loggerContext);
         }
     }
@@ -71,11 +82,14 @@ class LoggerContextResolver extends TypeBasedParameterResolver<LoggerContext> im
         // reloadable variant
         final Class<?> testClass = context.getRequiredTestClass();
         final LoggerContextSource source = testClass.getAnnotation(LoggerContextSource.class);
-        if (source != null && source.reconfigure()) {
+        if (source != null && source.reconfigure() == ReconfigurationPolicy.AFTER_EACH) {
             final LoggerContext loggerContext = getTestClassStore(context).get(LoggerContext.class, LoggerContext.class);
-            if (loggerContext != null) {
-                loggerContext.reconfigure();
+            if (loggerContext == null) {
+                throw new IllegalStateException(
+                        "Specified test class reconfiguration policy of AFTER_EACH, but no LoggerContext found for test class " +
+                                testClass.getCanonicalName());
             }
+            loggerContext.reconfigure();
         }
     }
 
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/junit/LoggerContextSource.java b/log4j-core/src/test/java/org/apache/logging/log4j/junit/LoggerContextSource.java
index baf9eb8..a6cedfb 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/junit/LoggerContextSource.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/junit/LoggerContextSource.java
@@ -17,7 +17,9 @@
 
 package org.apache.logging.log4j.junit;
 
+import org.apache.logging.log4j.core.Appender;
 import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configuration;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.extension.ExtendWith;
 
@@ -30,10 +32,20 @@ import java.lang.annotation.Target;
 
 /**
  * Specifies a configuration file to use for unit tests. This configuration file will be loaded once and used for all tests
- * executed in the annotated test class unless otherwise specified by {@link #reconfigure()}. When annotated on a test method, this
- * will override the class-level configuration if provided for that method.
+ * executed in the annotated test class unless otherwise specified by {@link #reconfigure()}. When annotated on a test method,
+ * this will override the class-level configuration if provided for that method. By using this JUnit 5 extension, the following
+ * types can be injected into tests via constructor or method parameters:
  *
- * @since 3.0.0
+ * <ul>
+ *     <li>{@link LoggerContext};</li>
+ *     <li>{@link Configuration};</li>
+ *     <li>any subclass of {@link Appender} paired with a {@link Named} annotation to select the appender by name.</li>
+ * </ul>
+ *
+ * Tests using this extension will automatically be tagged as {@code functional} to indicate they perform functional tests that
+ * rely on configuration files and production code.
+ *
+ * @since 2.14.0
  */
 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.TYPE, ElementType.METHOD})
@@ -50,7 +62,7 @@ public @interface LoggerContextSource {
     String value();
 
     /**
-     * Specifies whether or not to {@linkplain LoggerContext#reconfigure() reconfigure} after each test.
+     * Specifies when to {@linkplain LoggerContext#reconfigure() reconfigure} the logging system.
      */
-    boolean reconfigure() default false;
+    ReconfigurationPolicy reconfigure() default ReconfigurationPolicy.NEVER;
 }
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/junit/Named.java b/log4j-core/src/test/java/org/apache/logging/log4j/junit/Named.java
index fd3e4f4..fe776d7 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/junit/Named.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/junit/Named.java
@@ -23,6 +23,13 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+/**
+ * Specifies the name of an {@link org.apache.logging.log4j.core.Appender} to inject into JUnit 5 tests from the specified
+ * configuration.
+ *
+ * @see LoggerContextSource
+ * @since 2.14.0
+ */
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.PARAMETER)
 @Documented
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextMapRule.java b/log4j-core/src/test/java/org/apache/logging/log4j/junit/ReconfigurationPolicy.java
similarity index 60%
copy from log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextMapRule.java
copy to log4j-core/src/test/java/org/apache/logging/log4j/junit/ReconfigurationPolicy.java
index 4066303..3b872bd 100644
--- a/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextMapRule.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/junit/ReconfigurationPolicy.java
@@ -14,24 +14,22 @@
  * See the license for the specific language governing permissions and
  * limitations under the license.
  */
+
 package org.apache.logging.log4j.junit;
 
+import org.apache.logging.log4j.core.LoggerContext;
+
 /**
- * Restores the ThreadContext to it's initial map values after a JUnit test.
- * 
- * Usage:
- * 
- * <pre>
- * &#64;Rule
- * public final ThreadContextMapRule threadContextRule = new ThreadContextMapRule();
- * </pre>
+ * Indicates when to {@linkplain LoggerContext#reconfigure() reconfigure} the logging system during unit tests.
+ *
+ * @see LoggerContextSource
+ * @since 2.14.0
  */
-public class ThreadContextMapRule extends ThreadContextRule {
-
-    /**
-     * Constructs an initialized instance.
-     */
-    public ThreadContextMapRule() {
-        super(true, false);
-    }
+public enum ReconfigurationPolicy {
+    /** Performs no reconfiguration of the logging system for the entire run of tests in a test class. This is the default. */
+    NEVER,
+    /** Performs a reconfiguration before executing each test. */
+    BEFORE_EACH,
+    /** Performs a reconfiguration after executing each test. */
+    AFTER_EACH
 }