/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.test.util.impl;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggingEvent;
import org.hamcrest.CoreMatchers;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.StringDescription;
import org.hamcrest.TypeSafeMatcher;
import org.junit.rules.TestRule;
import org.junit.runners.model.Statement;
/**
* @author Yoann Rodiere
*/
public class ExpectedLog4jLog implements TestRule {
/**
* Returns a {@linkplain TestRule rule} that does not mandate any particular log to be produced (identical to
* behavior without this rule).
*/
public static ExpectedLog4jLog create() {
return new ExpectedLog4jLog();
}
private List<Matcher<?>> expectations = new ArrayList<>();
private List<Matcher<?>> absenceExpectations = new ArrayList<>();
private ExpectedLog4jLog() {
}
@Override
public Statement apply(Statement base, org.junit.runner.Description description) {
return new ExpectedLogStatement( base );
}
/**
* Verify that your code produces a log event matching the given matcher.
*/
public void expectEvent(Matcher<? extends LoggingEvent> matcher) {
expectations.add( matcher );
}
/**
* Verify that your code <strong>doesn't</strong> produce a log event matching the given matcher.
*/
public void expectEventMissing(Matcher<? extends LoggingEvent> matcher) {
absenceExpectations.add( matcher );
}
/**
* Verify that your code produces a log message containing the given string.
*/
public void expectMessage(String containedString) {
expectMessage( CoreMatchers.containsString( containedString ) );
}
/**
* Verify that your code <strong>doesn't</strong> produce a log message containing the given string.
*/
public void expectMessageMissing(String containedString) {
expectMessageMissing( CoreMatchers.containsString( containedString ) );
}
/**
* Verify that your code produces a log message containing all of the given string.
*/
public void expectMessage(String containedString, String... otherContainedStrings) {
expectMessage( containsAllStrings( containedString, otherContainedStrings ) );
}
/**
* Verify that your code <strong>doesn't</strong> produce a log message containing all of the given string.
*/
public void expectMessageMissing(String containedString, String... otherContainedStrings) {
expectMessageMissing( containsAllStrings( containedString, otherContainedStrings ) );
}
/**
* Verify that your code produces a log message matches the given Hamcrest matcher.
*/
public void expectMessage(Matcher<String> matcher) {
expectEvent( eventMessageMatcher( matcher ) );
}
/**
* Verify that your code <strong>doesn't</strong> produce a log message matches the given Hamcrest matcher.
*/
public void expectMessageMissing(Matcher<String> matcher) {
expectEventMissing( eventMessageMatcher( matcher ) );
}
private Matcher<String> containsAllStrings(String containedString, String... otherContainedStrings) {
Collection<Matcher<? super String>> matchers = new ArrayList<>();
matchers.add( CoreMatchers.containsString( containedString ) );
for ( String otherContainedString : otherContainedStrings ) {
matchers.add( CoreMatchers.containsString( otherContainedString ) );
}
return CoreMatchers.<String>allOf( matchers );
}
private Matcher<LoggingEvent> eventMessageMatcher(final Matcher<String> messageMatcher) {
return new TypeSafeMatcher<LoggingEvent>() {
@Override
public void describeTo(Description description) {
description.appendText( "a LoggingEvent with message matching " );
messageMatcher.describeTo( description );
}
@Override
protected boolean matchesSafely(LoggingEvent item) {
return messageMatcher.matches( item.getMessage() );
}
};
}
private class TestAppender extends AppenderSkeleton {
private final Set<Matcher<?>> expectationsMet = new HashSet<>();
private final Set<LoggingEvent> unexpectedEvents = new HashSet<>();
@Override
public void close() {
}
@Override
public boolean requiresLayout() {
return false;
}
@Override
protected void append(LoggingEvent event) {
for ( Matcher<?> expectation : ExpectedLog4jLog.this.expectations ) {
if ( !expectationsMet.contains( expectation ) && expectation.matches( event ) ) {
expectationsMet.add( expectation );
}
}
for ( Matcher<?> absenceExpectation : ExpectedLog4jLog.this.absenceExpectations ) {
if ( absenceExpectation.matches( event ) ) {
unexpectedEvents.add( event );
}
}
}
public Set<Matcher<?>> getExpectationsNotMet() {
Set<Matcher<?>> expectationsNotMet = new HashSet<>();
expectationsNotMet.addAll( expectations );
expectationsNotMet.removeAll( expectationsMet );
return expectationsNotMet;
}
public Set<LoggingEvent> getUnexpectedEvents() {
return unexpectedEvents;
}
}
private class ExpectedLogStatement extends Statement {
private final Statement next;
public ExpectedLogStatement(Statement base) {
next = base;
}
@Override
public void evaluate() throws Throwable {
final Logger logger = Logger.getRootLogger();
TestAppender appender = new TestAppender();
logger.addAppender( appender );
try {
next.evaluate();
}
finally {
logger.removeAppender( appender );
}
Set<Matcher<?>> expectationsNotMet = appender.getExpectationsNotMet();
Set<LoggingEvent> unexpectedEvents = appender.getUnexpectedEvents();
if ( !expectationsNotMet.isEmpty() || !unexpectedEvents.isEmpty() ) {
fail( buildFailureMessage( expectationsNotMet, unexpectedEvents ) );
}
}
}
private static String buildFailureMessage(Set<Matcher<?>> missingSet, Set<LoggingEvent> unexpectedEvents) {
Description description = new StringDescription();
description.appendText( "Produced logs did not meet the expectations." );
if ( !missingSet.isEmpty() ) {
description.appendText( "\nMissing logs:" );
for ( Matcher<?> missing : missingSet ) {
description.appendText( "\n\t" );
missing.describeTo( description );
}
}
if ( !unexpectedEvents.isEmpty() ) {
description.appendText( "\nUnexpected logs:" );
for ( LoggingEvent unexpected : unexpectedEvents ) {
description.appendText( "\n\t" );
description.appendText( unexpected.getRenderedMessage() );
}
}
return description.toString();
}
}