001/* 002 * (C) Copyright 2012-2014 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl-2.1.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Sun Seng David TAN <stan@nuxeo.com>, slacoin, jcarsique 016 */ 017package org.nuxeo.runtime.test.runner; 018 019import java.lang.annotation.ElementType; 020import java.lang.annotation.Inherited; 021import java.lang.annotation.Retention; 022import java.lang.annotation.RetentionPolicy; 023import java.lang.annotation.Target; 024import java.util.ArrayList; 025import java.util.List; 026 027import org.apache.log4j.Appender; 028import org.apache.log4j.AppenderSkeleton; 029import org.apache.log4j.Logger; 030import org.apache.log4j.spi.LoggingEvent; 031import org.junit.Assert; 032import org.junit.runners.model.FrameworkMethod; 033 034/** 035 * Test feature to capture from a log4j appender to check that some log4j calls have been correctly called.</br> On a 036 * test class or a test method using this feature, a custom {@link LogCaptureFeature.Filter} class is to be provided 037 * with the annotation {@link LogCaptureFeature.FilterWith} to select the log events to capture.</br> A 038 * {@link LogCaptureFeature.Result} instance is to be injected with {@link Inject} as an attribute of the test.</br> The 039 * method {@link LogCaptureFeature.Result#assertHasEvent()} can then be called from test methods to check that matching 040 * log calls (events) have been captured. 041 * 042 * @since 5.7 043 */ 044public class LogCaptureFeature extends SimpleFeature { 045 046 public class NoLogCaptureFilterException extends Exception { 047 private static final long serialVersionUID = 1L; 048 } 049 050 @Inherited 051 @Retention(RetentionPolicy.RUNTIME) 052 @Target({ ElementType.TYPE, ElementType.METHOD }) 053 public @interface FilterWith { 054 /** 055 * Custom implementation of a filter to select event to capture. 056 */ 057 Class<? extends LogCaptureFeature.Filter> value(); 058 } 059 060 public class Result { 061 protected final ArrayList<LoggingEvent> caughtEvents = new ArrayList<>(); 062 063 protected boolean noFilterFlag = false; 064 065 public void assertHasEvent() throws NoLogCaptureFilterException { 066 if (noFilterFlag) { 067 throw new LogCaptureFeature.NoLogCaptureFilterException(); 068 } 069 Assert.assertFalse("No log result found", caughtEvents.isEmpty()); 070 } 071 072 public void clear() { 073 caughtEvents.clear(); 074 noFilterFlag = false; 075 } 076 077 public List<LoggingEvent> getCaughtEvents() { 078 return caughtEvents; 079 } 080 081 protected void setNoFilterFlag(boolean noFilterFlag) { 082 this.noFilterFlag = noFilterFlag; 083 } 084 } 085 086 public interface Filter { 087 /** 088 * {@link LogCaptureFeature} will capture the event if it does match the implementation condition. 089 */ 090 boolean accept(LoggingEvent event); 091 } 092 093 protected Filter logCaptureFilter; 094 095 protected final Result myResult = new Result(); 096 097 protected Logger rootLogger = Logger.getRootLogger(); 098 099 protected Appender logAppender = new AppenderSkeleton() { 100 @Override 101 public boolean requiresLayout() { 102 return false; 103 } 104 105 @Override 106 public void close() { 107 } 108 109 @Override 110 protected void append(LoggingEvent event) { 111 if (logCaptureFilter == null) { 112 myResult.setNoFilterFlag(true); 113 return; 114 } 115 if (logCaptureFilter.accept(event)) { 116 myResult.caughtEvents.add(event); 117 } 118 } 119 }; 120 121 private Filter setupCaptureFiler; 122 123 @Override 124 public void configure(FeaturesRunner runner, com.google.inject.Binder binder) { 125 binder.bind(Result.class).toInstance(myResult); 126 }; 127 128 @Override 129 public void beforeSetup(FeaturesRunner runner) throws Exception { 130 super.beforeSetup(runner); 131 FilterWith filterProvider = runner.getConfig(FilterWith.class); 132 if (filterProvider.value() == null) { 133 return; 134 } 135 Class<? extends Filter> filterClass = filterProvider.value(); 136 enable(filterClass); 137 } 138 139 @Override 140 public void afterTeardown(FeaturesRunner runner) throws Exception { 141 disable(); 142 } 143 144 @Override 145 public void beforeMethodRun(FeaturesRunner runner, FrameworkMethod method, Object test) throws Exception { 146 FilterWith filterProvider = runner.getConfig(method, FilterWith.class); 147 if (filterProvider.value() == null) { 148 return; 149 } 150 Class<? extends Filter> filterClass = filterProvider.value(); 151 enable(filterClass); 152 } 153 154 @Override 155 public void afterMethodRun(FeaturesRunner runner, FrameworkMethod method, Object test) throws Exception { 156 disable(); 157 } 158 159 /** 160 * @since 6.0 161 */ 162 protected void enable(Class<? extends Filter> filterClass) throws InstantiationException, IllegalAccessException { 163 if (logCaptureFilter != null) { 164 setupCaptureFiler = logCaptureFilter; 165 } else { 166 rootLogger.addAppender(logAppender); 167 } 168 logCaptureFilter = filterClass.newInstance(); 169 } 170 171 /** 172 * @since 6.0 173 */ 174 protected void disable() { 175 if (setupCaptureFiler != null) { 176 logCaptureFilter = setupCaptureFiler; 177 setupCaptureFiler = null; 178 return; 179 } 180 if (logCaptureFilter != null) { 181 myResult.clear(); 182 rootLogger.removeAppender(logAppender); 183 logCaptureFilter = null; 184 } 185 } 186 187}