/*
* (C) Copyright 2012-2016 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed 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.
*
* Contributors:
* Sun Seng David TAN <stan@nuxeo.com>, slacoin, jcarsique
*/
package org.nuxeo.runtime.test.runner;
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;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Appender;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggingEvent;
import org.junit.Assert;
import org.junit.runners.model.FrameworkMethod;
import com.google.common.base.Strings;
/**
* Test feature to capture from a log4j appender to check that some log4j calls have been correctly called.</br>
* On a test class or a test method using this feature, a default filter can be configured with the annotation
* {@link LogCaptureFeature.FilterOn} or a custom one implementing {@link LogCaptureFeature.Filter} class can be
* provided with the annotation {@link LogCaptureFeature.FilterWith} to select the log events to capture.</br>
* A {@link LogCaptureFeature.Result} instance is to be injected with {@link Inject} as an attribute of the test.</br>
* The method {@link LogCaptureFeature.Result#assertHasEvent()} can then be called from test methods to check that
* matching log calls (events) have been captured.
*
* @since 5.7
*/
public class LogCaptureFeature extends SimpleFeature {
public class NoLogCaptureFilterException extends Exception {
private static final long serialVersionUID = 1L;
}
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface FilterWith {
/**
* Custom implementation of a filter to select event to capture.
*/
Class<? extends LogCaptureFeature.Filter> value();
}
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface FilterOn {
/**
* Configuration for the default filter
*/
String loggerName() default "";
String logLevel() default "";
}
private static class DefaultFilter implements LogCaptureFeature.Filter {
String loggerName;
Level logLevel;
public DefaultFilter(String loggerName, String logLevel) {
super();
this.loggerName = Strings.emptyToNull(loggerName);
if (!"".equals(logLevel)) {
this.logLevel = Level.toLevel(logLevel);
}
}
@Override
public boolean accept(LoggingEvent event) {
if (logLevel != null && !logLevel.equals(event.getLevel())) {
return false;
}
if (loggerName != null && !loggerName.equals(event.getLoggerName())) {
return false;
}
return true;
}
}
public class Result {
protected final ArrayList<LoggingEvent> caughtEvents = new ArrayList<>();
protected boolean noFilterFlag = false;
public void assertHasEvent() throws NoLogCaptureFilterException {
if (noFilterFlag) {
throw new LogCaptureFeature.NoLogCaptureFilterException();
}
Assert.assertFalse("No log result found", caughtEvents.isEmpty());
}
public void clear() {
caughtEvents.clear();
noFilterFlag = false;
}
public List<LoggingEvent> getCaughtEvents() {
return caughtEvents;
}
protected void setNoFilterFlag(boolean noFilterFlag) {
this.noFilterFlag = noFilterFlag;
}
}
public interface Filter {
/**
* {@link LogCaptureFeature} will capture the event if it does match the implementation condition.
*/
boolean accept(LoggingEvent event);
}
protected Filter logCaptureFilter;
protected final Result myResult = new Result();
protected Logger rootLogger = Logger.getRootLogger();
protected Appender logAppender = new AppenderSkeleton() {
@Override
public boolean requiresLayout() {
return false;
}
@Override
public void close() {
}
@Override
protected void append(LoggingEvent event) {
if (logCaptureFilter == null) {
myResult.setNoFilterFlag(true);
return;
}
if (logCaptureFilter.accept(event)) {
myResult.caughtEvents.add(event);
}
}
};
private Filter setupCaptureFiler;
@Override
public void configure(FeaturesRunner runner, com.google.inject.Binder binder) {
binder.bind(Result.class).toInstance(myResult);
};
@Override
public void beforeSetup(FeaturesRunner runner) throws Exception {
super.beforeSetup(runner);
Filter filter;
FilterWith filterProvider = runner.getConfig(FilterWith.class);
if (filterProvider.value() == null) {
FilterOn defaultFilterConfig = runner.getConfig(FilterOn.class);
if (defaultFilterConfig == null) {
return;
} else {
filter = new DefaultFilter(defaultFilterConfig.loggerName(), defaultFilterConfig.logLevel());
}
} else {
filter = filterProvider.value().newInstance();
}
enable(filter);
}
@Override
public void afterTeardown(FeaturesRunner runner) throws Exception {
disable();
}
@Override
public void beforeMethodRun(FeaturesRunner runner, FrameworkMethod method, Object test) throws Exception {
Filter filter;
FilterWith filterProvider = runner.getConfig(method, FilterWith.class);
if (filterProvider.value() == null) {
FilterOn defaultFilterConfig = runner.getConfig(method, FilterOn.class);
if (defaultFilterConfig == null) {
return;
} else {
filter = new DefaultFilter(defaultFilterConfig.loggerName(), defaultFilterConfig.logLevel());
}
} else {
filter = filterProvider.value().newInstance();
}
enable(filter);
}
@Override
public void afterMethodRun(FeaturesRunner runner, FrameworkMethod method, Object test) throws Exception {
disable();
}
/**
* @param filter
* @since 8.4
*/
protected void enable(Filter filter) throws InstantiationException, IllegalAccessException {
if (logCaptureFilter != null) {
setupCaptureFiler = logCaptureFilter;
} else {
rootLogger.addAppender(logAppender);
}
logCaptureFilter = filter;
}
/**
* @since 6.0
*/
protected void disable() {
if (setupCaptureFiler != null) {
logCaptureFilter = setupCaptureFiler;
setupCaptureFiler = null;
return;
}
if (logCaptureFilter != null) {
myResult.clear();
rootLogger.removeAppender(logAppender);
logCaptureFilter = null;
}
}
}