You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by vy...@apache.org on 2023/05/08 20:38:08 UTC
[logging-log4j2] 01/01: Add JUnit 5 test-method-scoped `LoggerContext`s
This is an automated email from the ASF dual-hosted git repository.
vy pushed a commit to branch Log4jEventRecorder
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit dba68fd3ce19719e711e3ab234a09bf4c5d3add0
Author: Volkan Yazıcı <vo...@yazi.ci>
AuthorDate: Mon May 8 22:37:58 2023 +0200
Add JUnit 5 test-method-scoped `LoggerContext`s
---
.../log4j/core/test/junit/Log4jEventRecorder.java | 86 ++++++++++++++++++++++
.../core/test/junit/Log4jEventRecorderAnchor.java | 56 ++++++++++++++
.../core/test/junit/Log4jEventRecorderEnabled.java | 32 ++++++++
.../junit/Log4jEventRecorderParameterResolver.java | 34 +++++++++
.../test/junit/Log4jEventRecorderTerminator.java | 29 ++++++++
.../core/test/junit/Log4jEventRecorderTest.java | 44 +++++++++++
6 files changed, 281 insertions(+)
diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Log4jEventRecorder.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Log4jEventRecorder.java
new file mode 100644
index 0000000000..a7c305b021
--- /dev/null
+++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Log4jEventRecorder.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.core.test.junit;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.LoggerConfig;
+import org.apache.logging.log4j.core.impl.MutableLogEvent;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public final class Log4jEventRecorder implements AutoCloseable {
+
+ private static final String LOGGER_CONTEXT_NAME_PREFIX = Log4jEventRecorder.class.getSimpleName() + "-LoggerContext-";
+
+ private static final AtomicInteger LOGGER_CONTEXT_COUNTER = new AtomicInteger(0);
+
+ private final InternalLog4jAppender appender;
+
+ private final LoggerContext loggerContext;
+
+ private static final class InternalLog4jAppender extends AbstractAppender {
+
+ private static final PatternLayout LAYOUT = PatternLayout.createDefaultLayout();
+
+ private final List<LogEvent> events;
+
+ private InternalLog4jAppender() {
+ super("ListAppender", null, LAYOUT, false, null);
+ this.events = Collections.synchronizedList(new ArrayList<>());
+ start();
+ }
+
+ @Override
+ public void append(final LogEvent event) {
+ final LogEvent copySafeEvent = event instanceof MutableLogEvent
+ ? ((MutableLogEvent) event).createMemento()
+ : event;
+ events.add(copySafeEvent);
+ }
+
+ }
+
+ Log4jEventRecorder() {
+ this.appender = new InternalLog4jAppender();
+ this.loggerContext = new LoggerContext(LOGGER_CONTEXT_NAME_PREFIX + LOGGER_CONTEXT_COUNTER.getAndIncrement());
+ final LoggerConfig rootConfig = loggerContext.getConfiguration().getRootLogger();
+ rootConfig.setLevel(Level.ALL);
+ rootConfig.getAppenders().values().forEach(appender -> rootConfig.removeAppender(appender.getName()));
+ rootConfig.addAppender(appender, Level.ALL, null);
+ }
+
+ public org.apache.logging.log4j.spi.LoggerContext getLoggerContext() {
+ return loggerContext;
+ }
+
+ public List<LogEvent> getEvents() {
+ return appender.events;
+ }
+
+ @Override
+ public void close() {
+ loggerContext.close();
+ }
+
+}
diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Log4jEventRecorderAnchor.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Log4jEventRecorderAnchor.java
new file mode 100644
index 0000000000..b3dd0dc163
--- /dev/null
+++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Log4jEventRecorderAnchor.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.core.test.junit;
+
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ParameterContext;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+final class Log4jEventRecorderAnchor {
+
+ private Log4jEventRecorderAnchor() {}
+
+ static Log4jEventRecorder recorder(
+ final ExtensionContext extensionContext,
+ final ParameterContext parameterContext) {
+ return recorderByParameterName(extensionContext).computeIfAbsent(
+ parameterContext.getParameter().getName(),
+ ignored -> new Log4jEventRecorder());
+ }
+
+ static Collection<Log4jEventRecorder> recorders(final ExtensionContext extensionContext) {
+ return recorderByParameterName(extensionContext).values();
+ }
+
+ private static Map<String, Log4jEventRecorder> recorderByParameterName(final ExtensionContext extensionContext) {
+ ExtensionContext.Namespace namespace = ExtensionContext.Namespace.create(
+ Log4jEventRecorder.class,
+ extensionContext.getRequiredTestClass(),
+ extensionContext.getRequiredTestMethod());
+ final ExtensionContext.Store store = extensionContext.getStore(namespace);
+ @SuppressWarnings("unchecked")
+ final Map<String, Log4jEventRecorder> recorderByParameterName = store.getOrComputeIfAbsent(
+ "recorderByParameterName",
+ ignored -> new LinkedHashMap<>(),
+ Map.class);
+ return recorderByParameterName;
+ }
+
+}
diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Log4jEventRecorderEnabled.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Log4jEventRecorderEnabled.java
new file mode 100644
index 0000000000..41b5f8a3cb
--- /dev/null
+++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Log4jEventRecorderEnabled.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.core.test.junit;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Enables JUnit support resolving test method parameters of type {@link Log4jEventRecorder}.
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@ExtendWith({Log4jEventRecorderTerminator.class, Log4jEventRecorderParameterResolver.class})
+public @interface Log4jEventRecorderEnabled {}
diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Log4jEventRecorderParameterResolver.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Log4jEventRecorderParameterResolver.java
new file mode 100644
index 0000000000..c2221fcea1
--- /dev/null
+++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Log4jEventRecorderParameterResolver.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.core.test.junit;
+
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.api.extension.ParameterResolutionException;
+import org.junit.jupiter.api.extension.support.TypeBasedParameterResolver;
+
+public final class Log4jEventRecorderParameterResolver extends TypeBasedParameterResolver<Log4jEventRecorder> {
+
+ @Override
+ public Log4jEventRecorder resolveParameter(
+ final ParameterContext parameterContext,
+ final ExtensionContext extensionContext
+ ) throws ParameterResolutionException {
+ return Log4jEventRecorderAnchor.recorder(extensionContext, parameterContext);
+ }
+
+}
diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Log4jEventRecorderTerminator.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Log4jEventRecorderTerminator.java
new file mode 100644
index 0000000000..157521d5ae
--- /dev/null
+++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Log4jEventRecorderTerminator.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.core.test.junit;
+
+import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public final class Log4jEventRecorderTerminator implements AfterTestExecutionCallback {
+
+ @Override
+ public void afterTestExecution(final ExtensionContext extensionContext) {
+ Log4jEventRecorderAnchor.recorders(extensionContext).forEach(Log4jEventRecorder::close);
+ }
+
+}
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/test/junit/Log4jEventRecorderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/test/junit/Log4jEventRecorderTest.java
new file mode 100644
index 0000000000..b881c6ae14
--- /dev/null
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/test/junit/Log4jEventRecorderTest.java
@@ -0,0 +1,44 @@
+package org.apache.logging.log4j.core.test.junit;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LogEvent;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Log4jEventRecorderEnabled
+class Log4jEventRecorderTest {
+
+ @Test
+ void should_succeed_when_run_even_in_parallel(final Log4jEventRecorder eventRecorder) {
+
+ // Log events
+ final int eventCount = 3;//1 + (int) (Math.random() * 1000D);
+ final Logger logger = eventRecorder.getLoggerContext().getLogger(Log4jEventRecorderTest.class);
+ for (int eventIndex = 0; eventIndex < eventCount; eventIndex++) {
+ logger.trace("test message {}", eventIndex);
+ }
+
+ // Verify logged levels
+ final List<LogEvent> events = eventRecorder.getEvents();
+ assertThat(events).allMatch(event -> Level.TRACE.equals(event.getLevel()));
+
+ // Verify logged messages
+ final List<String> expectedMessages = IntStream
+ .range(0, eventCount)
+ .mapToObj(eventIndex -> String.format("test message %d", eventIndex))
+ .collect(Collectors.toList());
+ final List<String> actualMessages = events
+ .stream()
+ .map(event -> event.getMessage().getFormattedMessage())
+ .collect(Collectors.toList());
+ assertThat(actualMessages).containsExactlyElementsOf(expectedMessages);
+
+ }
+
+}