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ı <[email protected]> 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); + + } + +}
