org.xwiki.test.AllLogRule Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xwiki-commons-tool-test-simple Show documentation
Show all versions of xwiki-commons-tool-test-simple Show documentation
For tests not requiring notion of Components
/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.test;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.util.ContextInitializer;
import ch.qos.logback.core.read.ListAppender;
/**
* Allow capturing Logs output during the execution of the unit test. This is useful for two reasons:
*
* - it allows not outputting log messages in the console which is a bad practice. When a test run it should not
* output anything and if it needs to assert something, it has to be done in the test itself.
* - it allows to assert the output log messages
*
* This code was inspired by code written by Christian Baranowski in a
* blog post.
*
* Example usage:
*
{@code
* @Rule public AllLogRule logRule = new AllLogRule();
* }
*
* @version $Id: 99cdd91537b4da77543a58f7fc7b4b222ad2c9cc $
* @since 7.0M1
*/
public class AllLogRule implements TestRule
{
/**
* The log output is captured in a Logback ListAppender.
*/
private final ListAppender listAppender = new ListAppender<>();
private final Set assertedMessages = new HashSet<>();
private LogLevel level;
/**
* The actual code that executes our capturing logic before the test runs and removes it after it has run.
*/
public class LogStatement extends Statement
{
/**
* @see #LogStatement(org.junit.runners.model.Statement)
*/
private final Statement statement;
/**
* @param statement the wrapping statement that we save so that we can execute it (the statement represents
* the test to execute).
*/
public LogStatement(Statement statement)
{
this.statement = statement;
}
@Override
public void evaluate() throws Throwable
{
// Setup Logback to catch log calls
before();
boolean validate = true;
try {
// Run the test
this.statement.evaluate();
} catch (Throwable t) {
// Don't verify anything if the test did not pass in the first place
validate = false;
throw t;
} finally {
// Remove Logback setup
after(validate);
}
}
/**
* Setup Logback capturing.
*/
private void before() throws Throwable
{
initializeLoggers();
listAppender.start();
}
/**
* Stop Logback capturing.
*/
private void after(boolean verify) throws Throwable
{
listAppender.stop();
uninitializeLogger(verify);
}
}
/**
* Capture INFO log.
*/
public AllLogRule()
{
this(LogLevel.INFO);
}
/**
* Caputure passed log level.
*
* @param level the level of log to capture
*/
public AllLogRule(LogLevel level)
{
this.level = level;
}
@Override
public Statement apply(Statement statement, Description description)
{
return new LogStatement(statement);
}
/**
* @param position the message number in the list of captured logs
* @return the logging event corresponding to the message, allowing to get information such as the level, the
* marker, the formatted string, etc
* @since 10.4RC1
*/
public ILoggingEvent getLogEvent(int position)
{
List list = this.listAppender.list;
if (list.size() <= position) {
throw new RuntimeException(String.format("There are only %s messages in the captured logs", list.size()));
}
this.assertedMessages.add(position);
return list.get(position);
}
/**
* @param position the message number in the list of captured logs
* @return the message at the specified position
*/
public String getMessage(int position)
{
return getLogEvent(position).getFormattedMessage();
}
/**
* @param position the message number in the list of captured logs
* @return the marker at the specified position
* @since 7.0M2
*/
public Marker getMarker(int position)
{
return getLogEvent(position).getMarker();
}
/**
* @return the number of log messages that have been captured
*/
public int size()
{
return listAppender.list.size();
}
/**
* Voluntarily ignore all messages to signify they should not need to be asserted.
*
* @since 10.4RC1
*/
public void ignoreAllMessages()
{
for (int i = 0; i < size(); i++) {
getLogEvent(i);
}
}
/**
* Voluntarily ignore a message to signify it should not need to be asserted.
*
* @param position the message number in the list of captured logs
* @since 10.4RC1
*/
public void ignoreMessage(int position)
{
getLogEvent(position);
}
private void initializeLoggers()
{
// Reinitialize completely Logback
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
context.reset();
// Configure the root logger to use our list appender and to log at the level asked.
Logger logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
logger.addAppender(this.listAppender);
logger.setLevel(this.level.getLevel());
}
private void uninitializeLogger(boolean verify) throws Exception
{
// Reinitialize Logback (by reading its config from the logback-test.xml file in the classpath)
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
context.reset();
ContextInitializer initializer = new ContextInitializer(context);
initializer.autoConfig();
// Verify that all appender list messages have been asserted.
if (this.listAppender.list.size() != this.assertedMessages.size()) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < this.listAppender.list.size(); i++) {
// Has the message been asserted already?
if (!this.assertedMessages.contains(i)) {
builder.append(getMessage(i)).append('\n');
}
}
throw new AssertionError(String.format("Following messages must be asserted: [%s]", builder.toString()));
}
}
}