Skip to content

Commit

Permalink
Add JUnit 5 test-method-scoped LoggerContexts
Browse files Browse the repository at this point in the history
  • Loading branch information
vy committed May 8, 2023
1 parent 98c3ceb commit dba68fd
Show file tree
Hide file tree
Showing 6 changed files with 281 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -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();
}

}
Original file line number Diff line number Diff line change
@@ -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;
}

}
Original file line number Diff line number Diff line change
@@ -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 {}
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
@@ -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);

}

}

0 comments on commit dba68fd

Please sign in to comment.