From d97a2098e387f22d7aa9a5645dff62e9ebc66dec Mon Sep 17 00:00:00 2001 From: Scott Leberknight <174812+sleberknight@users.noreply.github.com> Date: Fri, 9 Feb 2024 18:44:37 +0000 Subject: [PATCH 01/16] Create utils and Jupter extension to reset Logback * Add LogbackTestHelpers utility class. This provides utilities to reset Logback. * Add ResetLogbackLoggingExtension, a Jupiter extension to reset Logback after all tests have run. * Add UncheckedJoranException, which wraps the checked Logback JoranException. * Update several tests that use Dropwizard extensions to use ResetLogbackLoggingExtension to restore logging. Closes #460 Closes #461 --- .../jupiter/ResetLogbackLoggingExtension.java | 71 ++++++++ .../test/logback/LogbackTestHelpers.java | 50 ++++++ .../test/logback/UncheckedJoranException.java | 42 +++++ .../app/DropwizardAppTestsTest.java | 2 + ...esAppTestExtensionConfigOverridesTest.java | 3 + ...resAppTestExtensionCustomPropertyTest.java | 3 + .../app/PostgresAppTestExtensionTest.java | 3 + .../LogbackTestHelpersIntegrationTest.java | 165 ++++++++++++++++++ .../test/logback/LogbackTestHelpersTest.java | 34 ++++ .../logback/UncheckedJoranExceptionTest.java | 32 ++++ .../invalid-logback-test.xml | 18 ++ src/test/resources/logback-test.xml | 8 + 12 files changed, 431 insertions(+) create mode 100644 src/main/java/org/kiwiproject/test/junit/jupiter/ResetLogbackLoggingExtension.java create mode 100644 src/main/java/org/kiwiproject/test/logback/LogbackTestHelpers.java create mode 100644 src/main/java/org/kiwiproject/test/logback/UncheckedJoranException.java create mode 100644 src/test/java/org/kiwiproject/test/logback/LogbackTestHelpersIntegrationTest.java create mode 100644 src/test/java/org/kiwiproject/test/logback/LogbackTestHelpersTest.java create mode 100644 src/test/java/org/kiwiproject/test/logback/UncheckedJoranExceptionTest.java create mode 100644 src/test/resources/LogbackTestHelpersTest/invalid-logback-test.xml diff --git a/src/main/java/org/kiwiproject/test/junit/jupiter/ResetLogbackLoggingExtension.java b/src/main/java/org/kiwiproject/test/junit/jupiter/ResetLogbackLoggingExtension.java new file mode 100644 index 00000000..17333f65 --- /dev/null +++ b/src/main/java/org/kiwiproject/test/junit/jupiter/ResetLogbackLoggingExtension.java @@ -0,0 +1,71 @@ +package org.kiwiproject.test.junit.jupiter; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.kiwiproject.test.logback.LogbackTestHelpers; + +import ch.qos.logback.classic.ClassicConstants; + +/** + * A JUnit Jupiter {@link org.junit.jupiter.api.extension.Extension Extension} to reset + * the Logback logging system after all tests have completed. + *
+ * This is useful if something misbehaves, for example Dropwizard's + * DropwizardAppExtension and + * DropwizardClientExtension + * extensions both stop and detach all appenders after all tests complete! Both of those extensions + * reset Logback in + * DropwizardTestSupport. + * Once this happens, there is no logging output from subsequent tests (since there are no more appenders). + * We consider this to be bad, since logging output is useful to track down causes if + * there are other test failures. And, it's just not nice behavior to completely hijack logging! + *
+ * You can use this extension in tests that are using misbehaving components to ensure that Logback + * is reset after all tests complete, so that subsequent tests have log output. + *
+ * For example to use the default {@code logback-test.xml} as the logging configuration you + * can just use {@code @ExtendWith} on the test class: + *
+ * {@literal @}ExtendWith(DropwizardExtensionsSupport.class) + * {@literal @}ExtendWith(ResetLogbackLoggingExtension.class) + * class CustomClientTest { + * + * // test code that uses DropwizardClientExtension + * } + *+ * Alternatively, you can register the extension programmatically to use a custom + * logging configuration: + *
+ * {@literal @}ExtendWith(DropwizardExtensionsSupport.class) + * class CustomClientTest { + * + * {@literal @}RegisterExtension + * static final ResetLogbackLoggingExtension RESET_LOGBACK = ResetLogbackLoggingExtension.builder() + * .logbackConfigFilePath("acme-special-logback.xml") + * .build(); + * + * // test code that uses DropwizardClientExtension + * } + *+ */ +@Builder +@NoArgsConstructor +@AllArgsConstructor(access = AccessLevel.PACKAGE) +@Slf4j +public class ResetLogbackLoggingExtension implements AfterAllCallback { + + @Builder.Default + private String logbackConfigFilePath = ClassicConstants.TEST_AUTOCONFIG_FILE; + + @Override + public void afterAll(ExtensionContext context) throws Exception { + LogbackTestHelpers.resetLogback(logbackConfigFilePath); + LOG.info("Logback was reset using configuration: {}", logbackConfigFilePath); + } +} diff --git a/src/main/java/org/kiwiproject/test/logback/LogbackTestHelpers.java b/src/main/java/org/kiwiproject/test/logback/LogbackTestHelpers.java new file mode 100644 index 00000000..1ae59edd --- /dev/null +++ b/src/main/java/org/kiwiproject/test/logback/LogbackTestHelpers.java @@ -0,0 +1,50 @@ +package org.kiwiproject.test.logback; + +import com.google.common.io.Resources; + +import lombok.experimental.UtilityClass; + +import ch.qos.logback.classic.ClassicConstants; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; +import ch.qos.logback.core.joran.spi.JoranException; + +import org.slf4j.LoggerFactory; + +/** + * Static test utilities that provide Logback-related functionality. + */ +@UtilityClass +public class LogbackTestHelpers { + + /** + * Reset the Logback logging system using the default test configuration file ({@code logback-test.xml}). + * + * @see ClassicConstants#TEST_AUTOCONFIG_FILE + * @throws UncheckedJoranException if an error occurs resetting Logback + */ + public static void resetLogback() { + resetLogback(ClassicConstants.TEST_AUTOCONFIG_FILE); + } + + /** + * Reset the Logback logging system using the given configuration file. + * + * @param logbackConfigFilePath the location of the custom Logback configuration file + * @throws UncheckedJoranException if an error occurs resetting Logback + */ + public static void resetLogback(String logbackConfigFilePath) { + try { + var loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + loggerContext.stop(); + + var joranConfigurator = new JoranConfigurator(); + joranConfigurator.setContext(loggerContext); + var logbackConfigUrl = Resources.getResource(logbackConfigFilePath); + joranConfigurator.doConfigure(logbackConfigUrl); + loggerContext.start(); + } catch (JoranException e) { + throw new UncheckedJoranException(e); + } + } +} diff --git a/src/main/java/org/kiwiproject/test/logback/UncheckedJoranException.java b/src/main/java/org/kiwiproject/test/logback/UncheckedJoranException.java new file mode 100644 index 00000000..e5a0acd9 --- /dev/null +++ b/src/main/java/org/kiwiproject/test/logback/UncheckedJoranException.java @@ -0,0 +1,42 @@ +package org.kiwiproject.test.logback; + +import static org.kiwiproject.base.KiwiPreconditions.requireNotNull; + +import ch.qos.logback.core.joran.spi.JoranException; + +/** + * Wraps a {@link JoranException} with an unchecked exception. + */ +public class UncheckedJoranException extends RuntimeException { + + /** + * Construct an instance. + * + * @param message the message, which can be null + * @param cause the cause, which cannot be null + * @throws IllegalArgumentException if cause is null + */ + public UncheckedJoranException(String message, JoranException cause) { + super(message, requireNotNull(cause)); + } + + /** + * Construct an instance. + * + * @param cause the cause, which cannot be null + * @throws IllegalArgumentException if cause is null + */ + public UncheckedJoranException(JoranException cause) { + super(requireNotNull(cause)); + } + + /** + * Returns the cause of this exception. + * + * @return the {@link JoranException} which is the cause of this exception + */ + @Override + public JoranException getCause() { + return (JoranException) super.getCause(); + } +} diff --git a/src/test/java/org/kiwiproject/test/dropwizard/app/DropwizardAppTestsTest.java b/src/test/java/org/kiwiproject/test/dropwizard/app/DropwizardAppTestsTest.java index 7ff50e78..0c8f9061 100644 --- a/src/test/java/org/kiwiproject/test/dropwizard/app/DropwizardAppTestsTest.java +++ b/src/test/java/org/kiwiproject/test/dropwizard/app/DropwizardAppTestsTest.java @@ -27,12 +27,14 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.kiwiproject.test.junit.jupiter.ResetLogbackLoggingExtension; import java.util.List; @DisplayName("DropwizardAppTests") @ExtendWith(SoftAssertionsExtension.class) @ExtendWith(DropwizardExtensionsSupport.class) +@ExtendWith(ResetLogbackLoggingExtension.class) class DropwizardAppTestsTest { @Getter diff --git a/src/test/java/org/kiwiproject/test/dropwizard/app/PostgresAppTestExtensionConfigOverridesTest.java b/src/test/java/org/kiwiproject/test/dropwizard/app/PostgresAppTestExtensionConfigOverridesTest.java index a7b9662b..2463b066 100644 --- a/src/test/java/org/kiwiproject/test/dropwizard/app/PostgresAppTestExtensionConfigOverridesTest.java +++ b/src/test/java/org/kiwiproject/test/dropwizard/app/PostgresAppTestExtensionConfigOverridesTest.java @@ -18,9 +18,12 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; +import org.kiwiproject.test.junit.jupiter.ResetLogbackLoggingExtension; @DisplayName("PostgresAppTestExtension: ConfigOverrides") +@ExtendWith(ResetLogbackLoggingExtension.class) class PostgresAppTestExtensionConfigOverridesTest { @Getter diff --git a/src/test/java/org/kiwiproject/test/dropwizard/app/PostgresAppTestExtensionCustomPropertyTest.java b/src/test/java/org/kiwiproject/test/dropwizard/app/PostgresAppTestExtensionCustomPropertyTest.java index 56b47c25..53e51d0c 100644 --- a/src/test/java/org/kiwiproject/test/dropwizard/app/PostgresAppTestExtensionCustomPropertyTest.java +++ b/src/test/java/org/kiwiproject/test/dropwizard/app/PostgresAppTestExtensionCustomPropertyTest.java @@ -16,12 +16,15 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; +import org.kiwiproject.test.junit.jupiter.ResetLogbackLoggingExtension; /** * Tests that we can specify a custom name in the configuration for the DataSourceFactory. */ @DisplayName("PostgresAppTestExtension (custom DataSourceFactory property") +@ExtendWith(ResetLogbackLoggingExtension.class) class PostgresAppTestExtensionCustomPropertyTest { @Getter diff --git a/src/test/java/org/kiwiproject/test/dropwizard/app/PostgresAppTestExtensionTest.java b/src/test/java/org/kiwiproject/test/dropwizard/app/PostgresAppTestExtensionTest.java index 783afb46..c6092702 100644 --- a/src/test/java/org/kiwiproject/test/dropwizard/app/PostgresAppTestExtensionTest.java +++ b/src/test/java/org/kiwiproject/test/dropwizard/app/PostgresAppTestExtensionTest.java @@ -16,9 +16,12 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; +import org.kiwiproject.test.junit.jupiter.ResetLogbackLoggingExtension; @DisplayName("PostgresAppTestExtension") +@ExtendWith(ResetLogbackLoggingExtension.class) class PostgresAppTestExtensionTest { @Getter diff --git a/src/test/java/org/kiwiproject/test/logback/LogbackTestHelpersIntegrationTest.java b/src/test/java/org/kiwiproject/test/logback/LogbackTestHelpersIntegrationTest.java new file mode 100644 index 00000000..7f02e489 --- /dev/null +++ b/src/test/java/org/kiwiproject/test/logback/LogbackTestHelpersIntegrationTest.java @@ -0,0 +1,165 @@ +package org.kiwiproject.test.logback; + +import static java.util.Objects.nonNull; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +import com.codahale.metrics.health.HealthCheck; + +import io.dropwizard.core.Application; +import io.dropwizard.core.Configuration; +import io.dropwizard.core.setup.Environment; +import io.dropwizard.testing.junit5.DropwizardAppExtension; +import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.ExtendWith; +import org.kiwiproject.test.junit.jupiter.ResetLogbackLoggingExtension; +import org.slf4j.LoggerFactory; + +import ch.qos.logback.classic.Logger; + +/** + * This integration test uses DropwizardAppExtension which resets Logback when it + * starts the test application class. It first verifies that there is no appender + * for this class' Logger, and then uses {@link LogbackTestHelpers#resetLogback()} + * to reset Loback to the default logging configuration. Finally, it ensures that + * the InMemoryAppender was reset and that it receives the expected messages. + *
+ * The tests are designed to execute in a specific order and use Jupiter's + * method ordering feature. The first test executes after DropwizardAppExtension + * has reset Logback, so we expect the appender to be null. That test resets + * Logback, after which all subsequent tests should get a non-null appender. + *
+ * In case of failure, this test uses the ResetLogbackLoggingExtension to restore
+ * the Logback logging configuration. However, since that extension simply uses
+ * {@link LogbackTestHelpers}, it might not work if there is actually a bug and
+ * is therefore a bit circular. But, since it uses {@link LogbackTestHelpers#resetLogback(String)}
+ * with {@link ch.qos.logback.classic.ClassicConstants#TEST_AUTOCONFIG_FILE} as its
+ * argument, instead of the no-arg method, it is slightly different than here.
+ */
+@DisplayName("LogbackTestHelpers (Integration Test)")
+@ExtendWith(DropwizardExtensionsSupport.class)
+@ExtendWith(ResetLogbackLoggingExtension.class)
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+@Slf4j
+public class LogbackTestHelpersIntegrationTest {
+
+ private static final String APPENDER_NAME = "LogbackTestHelpersIntegrationTestAppender";
+
+ public static class MyConfig extends Configuration {
+ }
+
+ public static class MyApp extends Application
* The tests are designed to execute in a specific order and use Jupiter's
@@ -40,14 +40,14 @@
* {@link LogbackTestHelpers}, it might not work if there is actually a bug and
* is therefore a bit circular. But, since it uses {@link LogbackTestHelpers#resetLogback(String)}
* with {@link ch.qos.logback.classic.ClassicConstants#TEST_AUTOCONFIG_FILE} as its
- * argument, instead of the no-arg method, it is slightly different than here.
+ * argument, instead of the no-arg method, it is slightly different from here.
*/
@DisplayName("LogbackTestHelpers (Integration Test)")
@ExtendWith(DropwizardExtensionsSupport.class)
@ExtendWith(ResetLogbackLoggingExtension.class)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@Slf4j
-public class LogbackTestHelpersIntegrationTest {
+class LogbackTestHelpersIntegrationTest {
private static final String APPENDER_NAME = "LogbackTestHelpersIntegrationTestAppender";
@@ -69,6 +69,7 @@ protected Result check() {
}
}
+ @SuppressWarnings("unused")
static final DropwizardAppExtension
+ * If you need a custom location (or locations), use {@link #resetLogback(String, String...)}.
*
- * @see ClassicConstants#TEST_AUTOCONFIG_FILE
* @throws UncheckedJoranException if an error occurs resetting Logback
+ * @see ClassicConstants#TEST_AUTOCONFIG_FILE
+ * @see ClassicConstants#AUTOCONFIG_FILE
*/
public static void resetLogback() {
- resetLogback(ClassicConstants.TEST_AUTOCONFIG_FILE);
+ resetLogback(ClassicConstants.TEST_AUTOCONFIG_FILE, ClassicConstants.AUTOCONFIG_FILE);
}
/**
- * Reset the Logback logging system using the given configuration file.
+ * Reset the Logback logging system using the given configuration file or fallback configuration files.
+ *
+ * The fallback configurations are tried in the order they are provided.
*
- * @param logbackConfigFilePath the location of the custom Logback configuration file
+ * @param logbackConfigFile the location of the custom Logback configuration file
+ * @param fallbackConfigFiles additional locations to check for Logback configuration files
* @throws UncheckedJoranException if an error occurs resetting Logback
+ * @throws IllegalArgumentException if none of the Logback configuration files exist
*/
- public static void resetLogback(String logbackConfigFilePath) {
+ public static void resetLogback(String logbackConfigFile, String... fallbackConfigFiles) {
+ checkArgumentNotBlank(logbackConfigFile, "logbackConfigFile must not be blank");
+ checkArgumentNotNull(fallbackConfigFiles, "fallback locations vararg parameter must not be null");
+ checkArgument(isNoneBlank(fallbackConfigFiles), "fallbackConfigFiles must not contain blank locations");
+
try {
var loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.stop();
var joranConfigurator = new JoranConfigurator();
joranConfigurator.setContext(loggerContext);
- var logbackConfigUrl = Resources.getResource(logbackConfigFilePath);
+ var logbackConfigUrl = getFirstLogbackConfigOrThrow(logbackConfigFile, fallbackConfigFiles);
joranConfigurator.doConfigure(logbackConfigUrl);
loggerContext.start();
} catch (JoranException e) {
throw new UncheckedJoranException(e);
}
}
+
+ private static URL getFirstLogbackConfigOrThrow(String logbackConfigFilePath, String... fallbackConfigFilePaths) {
+ var allConfigs = ListUtils.union(
+ List.of(logbackConfigFilePath),
+ List.of(fallbackConfigFilePaths)
+ );
+
+ return allConfigs.stream()
+ .map(LogbackTestHelpers::getResourceOrNull)
+ .filter(Objects::nonNull)
+ .findFirst()
+ .orElseThrow(() ->
+ new IllegalArgumentException("Did not find any of the Logback configurations: " + allConfigs));
+ }
+
+ @Nullable
+ private static URL getResourceOrNull(String resourceName) {
+ try {
+ return Resources.getResource(resourceName);
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
}
diff --git a/src/test/java/org/kiwiproject/test/logback/LogbackTestHelpersTest.java b/src/test/java/org/kiwiproject/test/logback/LogbackTestHelpersTest.java
index 04198f98..803f1010 100644
--- a/src/test/java/org/kiwiproject/test/logback/LogbackTestHelpersTest.java
+++ b/src/test/java/org/kiwiproject/test/logback/LogbackTestHelpersTest.java
@@ -5,9 +5,12 @@
import ch.qos.logback.core.joran.spi.JoranException;
import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
import org.kiwiproject.test.junit.jupiter.ResetLogbackLoggingExtension;
+import org.kiwiproject.test.junit.jupiter.params.provider.MinimalBlankStringSource;
/**
* Unit test for {@link LogbackTestHelpers}. This mainly tests invalid arguments
@@ -18,16 +21,52 @@
@ExtendWith(ResetLogbackLoggingExtension.class)
class LogbackTestHelpersTest {
- @Test
- void shouldThrowIllegalArgument_WhenInvalidLogbackConfigPath() {
- assertThatIllegalArgumentException()
- .isThrownBy(() -> LogbackTestHelpers.resetLogback("dne.xml"));
+ @Nested
+ class ThrowsIllegalArgumentException {
+
+ @Test
+ void whenInvalidLogbackConfigPath() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> LogbackTestHelpers.resetLogback("dne.xml"))
+ .withMessage("Did not find any of the Logback configurations: [dne.xml]");
+ }
+
+ @Test
+ void whenInvalidLogbackConfigPaths() {
+ assertThatIllegalArgumentException().isThrownBy(() ->
+ LogbackTestHelpers.resetLogback("dne1.xml", "dne2.xml", "dne3.xml"))
+ .withMessage("Did not find any of the Logback configurations: [dne1.xml, dne2.xml, dne3.xml]");
+ }
+
+ @ParameterizedTest
+ @MinimalBlankStringSource
+ void whenGivenBlankConfigLocation(String location) {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> LogbackTestHelpers.resetLogback(location))
+ .withMessage("logbackConfigFile must not be blank");
+ }
+
+ @Test
+ void whenExplicitNullFallbackLocationsIsGiven() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> LogbackTestHelpers.resetLogback("acme-logback.xml", (String[]) null))
+ .withMessage("fallback locations vararg parameter must not be null");
+ }
+
+ @ParameterizedTest
+ @MinimalBlankStringSource
+ void whenFallbackLocationIsBlank(String fallbackLocation) {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> LogbackTestHelpers.resetLogback("acme-test-logback.xml", fallbackLocation))
+ .withMessage("fallbackConfigFiles must not contain blank locations");
+ }
}
@Test
void shouldThrowUncheckedJoranException_WhenInvalidLogbackConfig() {
assertThatExceptionOfType(UncheckedJoranException.class)
- .isThrownBy(() -> LogbackTestHelpers.resetLogback("LogbackTestHelpersTest/invalid-logback-test.xml"))
+ .isThrownBy(() ->
+ LogbackTestHelpers.resetLogback("LogbackTestHelpersTest/invalid-logback-test.xml"))
.withCauseExactlyInstanceOf(JoranException.class);
}
}
From 4793c9fa47e0df809bf94d985ca51ee25ed33ca5 Mon Sep 17 00:00:00 2001
From: Scott Leberknight <174812+sleberknight@users.noreply.github.com>
Date: Fri, 9 Feb 2024 15:17:13 -0500
Subject: [PATCH 06/16] Code cleanup: add test for InMemoryAppenderExtension
All it does right now is validate withLogbackConfigFilePath
---
.../logback/InMemoryAppenderExtension.java | 1 +
.../InMemoryAppenderExtensionTest.java | 29 +++++++++++++++++++
2 files changed, 30 insertions(+)
create mode 100644 src/test/java/org/kiwiproject/test/logback/InMemoryAppenderExtensionTest.java
diff --git a/src/main/java/org/kiwiproject/test/logback/InMemoryAppenderExtension.java b/src/main/java/org/kiwiproject/test/logback/InMemoryAppenderExtension.java
index 073e1452..72fa3e13 100644
--- a/src/main/java/org/kiwiproject/test/logback/InMemoryAppenderExtension.java
+++ b/src/main/java/org/kiwiproject/test/logback/InMemoryAppenderExtension.java
@@ -34,6 +34,7 @@ public class InMemoryAppenderExtension implements BeforeEachCallback, AfterEachC
private final String appenderName;
// Use the default Logback test configuration file as our default value.
+ @Getter
private String logbackConfigFilePath = ClassicConstants.TEST_AUTOCONFIG_FILE;
@Getter
diff --git a/src/test/java/org/kiwiproject/test/logback/InMemoryAppenderExtensionTest.java b/src/test/java/org/kiwiproject/test/logback/InMemoryAppenderExtensionTest.java
new file mode 100644
index 00000000..076cd158
--- /dev/null
+++ b/src/test/java/org/kiwiproject/test/logback/InMemoryAppenderExtensionTest.java
@@ -0,0 +1,29 @@
+package org.kiwiproject.test.logback;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import ch.qos.logback.classic.ClassicConstants;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+@DisplayName("InMemoryAppenderExtension")
+@Slf4j
+class InMemoryAppenderExtensionTest {
+
+ @Test
+ void shouldUseLogbackTestFileAsDefaultConfigLocation() {
+ var extension = new InMemoryAppenderExtension(InMemoryAppenderExtensionTest.class);
+ assertThat(extension.getLogbackConfigFilePath())
+ .isEqualTo(ClassicConstants.TEST_AUTOCONFIG_FILE);
+ }
+
+ @Test
+ void shouldAcceptCustomConfigLocation() {
+ var customLocation = "acme-test-logback.xml";
+ var extension = new InMemoryAppenderExtension(InMemoryAppenderExtensionTest.class)
+ .withLogbackConfigFilePath(customLocation);
+
+ assertThat(extension.getLogbackConfigFilePath()).isEqualTo(customLocation);
+ }
+}
From c72d357bc52b96f757eaa5bfa050c0eeb7e18c83 Mon Sep 17 00:00:00 2001
From: Scott Leberknight <174812+sleberknight@users.noreply.github.com>
Date: Fri, 9 Feb 2024 15:20:40 -0500
Subject: [PATCH 07/16] Docs: remove confusing javadoc
---
.../test/logback/LogbackTestHelpersIntegrationTest.java | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/src/test/java/org/kiwiproject/test/logback/LogbackTestHelpersIntegrationTest.java b/src/test/java/org/kiwiproject/test/logback/LogbackTestHelpersIntegrationTest.java
index b24619e2..2a6e2af6 100644
--- a/src/test/java/org/kiwiproject/test/logback/LogbackTestHelpersIntegrationTest.java
+++ b/src/test/java/org/kiwiproject/test/logback/LogbackTestHelpersIntegrationTest.java
@@ -38,9 +38,7 @@
* In case of failure, this test uses the ResetLogbackLoggingExtension to restore
* the Logback logging configuration. However, since that extension simply uses
* {@link LogbackTestHelpers}, it might not work if there is actually a bug and
- * is therefore a bit circular. But, since it uses {@link LogbackTestHelpers#resetLogback(String)}
- * with {@link ch.qos.logback.classic.ClassicConstants#TEST_AUTOCONFIG_FILE} as its
- * argument, instead of the no-arg method, it is slightly different from here.
+ * is therefore a bit circular.
*/
@DisplayName("LogbackTestHelpers (Integration Test)")
@ExtendWith(DropwizardExtensionsSupport.class)
From 347166b2f8d48fdaae102163cf5695d611c127e4 Mon Sep 17 00:00:00 2001
From: Scott Leberknight <174812+sleberknight@users.noreply.github.com>
Date: Fri, 9 Feb 2024 15:26:36 -0500
Subject: [PATCH 08/16] Add test for ResetLogbackLoggingExtension
* Make logbackConfigFilePath final in ResetLogbackLoggingExtension
* Add test that verifies default and custom config locations
---
.../jupiter/ResetLogbackLoggingExtension.java | 5 ++-
.../ResetLogbackLoggingExtensionTest.java | 36 +++++++++++++++++++
2 files changed, 40 insertions(+), 1 deletion(-)
create mode 100644 src/test/java/org/kiwiproject/test/junit/jupiter/ResetLogbackLoggingExtensionTest.java
diff --git a/src/main/java/org/kiwiproject/test/junit/jupiter/ResetLogbackLoggingExtension.java b/src/main/java/org/kiwiproject/test/junit/jupiter/ResetLogbackLoggingExtension.java
index e6268bbe..63393288 100644
--- a/src/main/java/org/kiwiproject/test/junit/jupiter/ResetLogbackLoggingExtension.java
+++ b/src/main/java/org/kiwiproject/test/junit/jupiter/ResetLogbackLoggingExtension.java
@@ -4,6 +4,7 @@
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
+import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.extension.AfterAllCallback;
@@ -56,10 +57,12 @@
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@Slf4j
+@SuppressWarnings("LombokGetterMayBeUsed")
public class ResetLogbackLoggingExtension implements AfterAllCallback {
+ @Getter
@Builder.Default
- private String logbackConfigFilePath = ClassicConstants.TEST_AUTOCONFIG_FILE;
+ private final String logbackConfigFilePath = ClassicConstants.TEST_AUTOCONFIG_FILE;
@Override
public void afterAll(ExtensionContext context) {
diff --git a/src/test/java/org/kiwiproject/test/junit/jupiter/ResetLogbackLoggingExtensionTest.java b/src/test/java/org/kiwiproject/test/junit/jupiter/ResetLogbackLoggingExtensionTest.java
new file mode 100644
index 00000000..9c145b0d
--- /dev/null
+++ b/src/test/java/org/kiwiproject/test/junit/jupiter/ResetLogbackLoggingExtensionTest.java
@@ -0,0 +1,36 @@
+package org.kiwiproject.test.junit.jupiter;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import ch.qos.logback.classic.ClassicConstants;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+@DisplayName("ResetLogbackLoggingExtension")
+class ResetLogbackLoggingExtensionTest {
+
+ @Test
+ void shouldConstructWithLogbackTestFileAsDefaultConfigLocation() {
+ var extension = new ResetLogbackLoggingExtension();
+ assertThat(extension.getLogbackConfigFilePath())
+ .isEqualTo(ClassicConstants.TEST_AUTOCONFIG_FILE);
+ }
+
+ @Test
+ void shouldBuildWithLogbackTestFileAsDefaultConfigLocation() {
+ var extension = ResetLogbackLoggingExtension.builder().build();
+ assertThat(extension.getLogbackConfigFilePath())
+ .isEqualTo(ClassicConstants.TEST_AUTOCONFIG_FILE);
+ }
+
+ @Test
+ void shouldAllowCustomConfigLocation() {
+ var customLocation = "acme-test-logback.xml";
+
+ var extension = ResetLogbackLoggingExtension.builder()
+ .logbackConfigFilePath(customLocation)
+ .build();
+
+ assertThat(extension.getLogbackConfigFilePath()).isEqualTo(customLocation);
+ }
+}
From 6e9883b0f1c945c55d370b90506325be992a0cca Mon Sep 17 00:00:00 2001
From: Scott Leberknight <174812+sleberknight@users.noreply.github.com>
Date: Fri, 9 Feb 2024 17:02:05 -0500
Subject: [PATCH 09/16] Change extensions to use default Logback configs
The reason to do this is to provide more flexibility. Unless
a custom Logback config file is provided, use the
LogbackTestHelpers.resetLogback() method which attempts
both logback-test.xml with logback.xml as a fallback.
---
.../junit/jupiter/ResetLogbackLoggingExtension.java | 12 ++++++++----
.../test/logback/InMemoryAppenderExtension.java | 11 +++++++----
.../jupiter/ResetLogbackLoggingExtensionTest.java | 11 ++++-------
.../test/logback/InMemoryAppenderExtensionTest.java | 4 +---
4 files changed, 20 insertions(+), 18 deletions(-)
diff --git a/src/main/java/org/kiwiproject/test/junit/jupiter/ResetLogbackLoggingExtension.java b/src/main/java/org/kiwiproject/test/junit/jupiter/ResetLogbackLoggingExtension.java
index 63393288..0f842573 100644
--- a/src/main/java/org/kiwiproject/test/junit/jupiter/ResetLogbackLoggingExtension.java
+++ b/src/main/java/org/kiwiproject/test/junit/jupiter/ResetLogbackLoggingExtension.java
@@ -1,6 +1,7 @@
package org.kiwiproject.test.junit.jupiter;
-import ch.qos.logback.classic.ClassicConstants;
+import static org.apache.commons.lang3.StringUtils.isBlank;
+
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
@@ -61,12 +62,15 @@
public class ResetLogbackLoggingExtension implements AfterAllCallback {
@Getter
- @Builder.Default
- private final String logbackConfigFilePath = ClassicConstants.TEST_AUTOCONFIG_FILE;
+ private String logbackConfigFilePath;
@Override
public void afterAll(ExtensionContext context) {
- LogbackTestHelpers.resetLogback(logbackConfigFilePath);
+ if (isBlank(logbackConfigFilePath)) {
+ LogbackTestHelpers.resetLogback();
+ } else {
+ LogbackTestHelpers.resetLogback(logbackConfigFilePath);
+ }
LOG.info("Logback was reset using configuration: {}", logbackConfigFilePath);
}
}
diff --git a/src/main/java/org/kiwiproject/test/logback/InMemoryAppenderExtension.java b/src/main/java/org/kiwiproject/test/logback/InMemoryAppenderExtension.java
index 72fa3e13..0f714ccf 100644
--- a/src/main/java/org/kiwiproject/test/logback/InMemoryAppenderExtension.java
+++ b/src/main/java/org/kiwiproject/test/logback/InMemoryAppenderExtension.java
@@ -1,9 +1,9 @@
package org.kiwiproject.test.logback;
import static java.util.Objects.nonNull;
+import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.assertj.core.api.Assertions.assertThat;
-import ch.qos.logback.classic.ClassicConstants;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
@@ -33,9 +33,8 @@ public class InMemoryAppenderExtension implements BeforeEachCallback, AfterEachC
private final Class> loggerClass;
private final String appenderName;
- // Use the default Logback test configuration file as our default value.
@Getter
- private String logbackConfigFilePath = ClassicConstants.TEST_AUTOCONFIG_FILE;
+ private String logbackConfigFilePath;
@Getter
@Accessors(fluent = true)
@@ -177,7 +176,11 @@ private Appender
+ * This is an instance-based utility class, and is mainly useful if you need to mock
+ * its behavior. By default, it delegates to {@link LogbackTestHelpers} for methods
+ * that have the same signature.
+ */
+public class LogbackTestHelper {
+
+ /**
+ * Resets Logback using either the given config file, or uses the defaults
+ * as provided by {@link LogbackTestHelpers#resetLogback()}.
+ *
+ * @param logbackConfigFile the Logback config file to use, or null
+ */
+ public void resetLogbackWithDefaultOrConfig(@Nullable String logbackConfigFile) {
+ if (isBlank(logbackConfigFile)) {
+ resetLogback();
+ } else {
+ resetLogback(logbackConfigFile);
+ }
+ }
+
+ /**
+ * Delegates to {@link LogbackTestHelpers#resetLogback()}.
+ */
+ public void resetLogback() {
+ LogbackTestHelpers.resetLogback();
+ }
+
+ /**
+ * Delegates to {@link LogbackTestHelpers#resetLogback(String, String...)}.
+ *
+ * @param logbackConfigFile the location of the custom Logback configuration file
+ * @param fallbackConfigFiles additional locations to check for Logback configuration files
+ */
+ public void resetLogback(String logbackConfigFile, String... fallbackConfigFiles) {
+ LogbackTestHelpers.resetLogback(logbackConfigFile, fallbackConfigFiles);
+ }
+}
diff --git a/src/test/java/org/kiwiproject/test/logback/LogbackTestHelperTest.java b/src/test/java/org/kiwiproject/test/logback/LogbackTestHelperTest.java
new file mode 100644
index 00000000..2d46c04a
--- /dev/null
+++ b/src/test/java/org/kiwiproject/test/logback/LogbackTestHelperTest.java
@@ -0,0 +1,42 @@
+package org.kiwiproject.test.logback;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.kiwiproject.test.junit.jupiter.params.provider.MinimalBlankStringSource;
+
+@DisplayName("Logback")
+class LogbackTestHelperTest {
+
+ private LogbackTestHelper helper;
+
+ @BeforeEach
+ void setUp() {
+ helper = spy(new LogbackTestHelper());
+
+ doNothing().when(helper).resetLogback();
+ doNothing().when(helper).resetLogback(anyString());
+ }
+
+ @Test
+ void shouldResetLogbackWithCustomConfiguration() {
+ var customConfig = "test-acme-logback.xml";
+ helper.resetLogbackWithDefaultOrConfig(customConfig);
+
+ verify(helper).resetLogback(customConfig);
+ }
+
+ @ParameterizedTest
+ @MinimalBlankStringSource
+ void shouldUseDefaultWhenCustomConfigurationIsBlank(String configFile) {
+ helper.resetLogbackWithDefaultOrConfig(configFile);
+
+ verify(helper).resetLogback();
+ }
+}
From 19792af83334dc19334a61f5696b25df26b655cf Mon Sep 17 00:00:00 2001
From: Scott Leberknight <174812+sleberknight@users.noreply.github.com>
Date: Fri, 9 Feb 2024 17:52:25 -0500
Subject: [PATCH 11/16] Add "clear-box" tests for
InMemoryAppenderExtension#getAppender
---
.../logback/InMemoryAppenderExtension.java | 3 +-
.../InMemoryAppenderExtensionTest.java | 60 +++++++++++++++++++
2 files changed, 62 insertions(+), 1 deletion(-)
diff --git a/src/main/java/org/kiwiproject/test/logback/InMemoryAppenderExtension.java b/src/main/java/org/kiwiproject/test/logback/InMemoryAppenderExtension.java
index 1b4e8c5d..d065c11e 100644
--- a/src/main/java/org/kiwiproject/test/logback/InMemoryAppenderExtension.java
+++ b/src/main/java/org/kiwiproject/test/logback/InMemoryAppenderExtension.java
@@ -161,8 +161,9 @@ public void beforeEach(ExtensionContext context) {
}
@Nullable
+ @VisibleForTesting
@SuppressWarnings("java:S106")
- private Appender
+ * If this is not set, then the default Logback configuration files are used
+ * in the order {@code logback-test.xml} followed by {@code logback.xml}.
+ */
@Getter
private String logbackConfigFilePath;
diff --git a/src/main/java/org/kiwiproject/test/logback/InMemoryAppenderExtension.java b/src/main/java/org/kiwiproject/test/logback/InMemoryAppenderExtension.java
index d065c11e..befb383c 100644
--- a/src/main/java/org/kiwiproject/test/logback/InMemoryAppenderExtension.java
+++ b/src/main/java/org/kiwiproject/test/logback/InMemoryAppenderExtension.java
@@ -102,9 +102,10 @@ public InMemoryAppenderExtension(Class> loggerClass, String appenderName) {
}
/**
- * The Logback configuration to use if the logging system needs to be reset.
+ * The custom Logback configuration to use if the logging system needs to be reset.
*
* For example:
+ *
*
- * The fallback configurations are tried in the order they are provided.
+ * The fallback configurations are searched in the order they are provided.
*
* @param logbackConfigFile the location of the custom Logback configuration file
* @param fallbackConfigFiles additional locations to check for Logback configuration files
* {@literal @}RegisterExtension
* private final InMemoryAppenderExtension inMemoryAppenderExtension =
@@ -112,6 +113,9 @@ public InMemoryAppenderExtension(Class> loggerClass, String appenderName) {
* .withLogbackConfigFilePath("acme-logback-test.xml");
*
*
+ * If this is not set, then the default Logback configuration files are used
+ * in the order {@code logback-test.xml} followed by {@code logback.xml}.
+ *
* @param logbackConfigFilePath the location of the custom Logback configuration file
* @return this extension, so this can be chained after the constructor
* @see Tests failing because Logback appenders don't exist (#457)
From b65b76217d43bfac0dc20ee4b50e7249b90037b1 Mon Sep 17 00:00:00 2001
From: Scott Leberknight <174812+sleberknight@users.noreply.github.com>
Date: Sat, 10 Feb 2024 16:04:17 -0500
Subject: [PATCH 15/16] Don't stop the LoggerContext unless the config file
exists
---
.../java/org/kiwiproject/test/logback/LogbackTestHelpers.java | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/main/java/org/kiwiproject/test/logback/LogbackTestHelpers.java b/src/main/java/org/kiwiproject/test/logback/LogbackTestHelpers.java
index 03e7ebaa..88e54a99 100644
--- a/src/main/java/org/kiwiproject/test/logback/LogbackTestHelpers.java
+++ b/src/main/java/org/kiwiproject/test/logback/LogbackTestHelpers.java
@@ -55,12 +55,13 @@ public static void resetLogback(String logbackConfigFile, String... fallbackConf
checkArgument(isNoneBlank(fallbackConfigFiles), "fallbackConfigFiles must not contain blank locations");
try {
+ var logbackConfigUrl = getFirstLogbackConfigOrThrow(logbackConfigFile, fallbackConfigFiles);
+
var loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.stop();
var joranConfigurator = new JoranConfigurator();
joranConfigurator.setContext(loggerContext);
- var logbackConfigUrl = getFirstLogbackConfigOrThrow(logbackConfigFile, fallbackConfigFiles);
joranConfigurator.doConfigure(logbackConfigUrl);
loggerContext.start();
} catch (JoranException e) {
From 4b2ff78a7227229df574e9a7b2c97b2d72a8b5eb Mon Sep 17 00:00:00 2001
From: Scott Leberknight <174812+sleberknight@users.noreply.github.com>
Date: Sat, 10 Feb 2024 16:28:30 -0500
Subject: [PATCH 16/16] Clarify javadoc; we only try to reset using the first
one that exists
---
.../org/kiwiproject/test/logback/LogbackTestHelpers.java | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/main/java/org/kiwiproject/test/logback/LogbackTestHelpers.java b/src/main/java/org/kiwiproject/test/logback/LogbackTestHelpers.java
index 88e54a99..35c18080 100644
--- a/src/main/java/org/kiwiproject/test/logback/LogbackTestHelpers.java
+++ b/src/main/java/org/kiwiproject/test/logback/LogbackTestHelpers.java
@@ -40,9 +40,11 @@ public static void resetLogback() {
}
/**
- * Reset the Logback logging system using the given configuration file or fallback configuration files.
+ * Reset the Logback logging system using the given configuration file.
+ * If the primary file does not exist, use the first fallback configuration
+ * file that exists. If the reset fails, an exception is thrown immediately.
*