001/* 002 * (C) Copyright 2012-2016 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Sun Seng David TAN <stan@nuxeo.com>, slacoin, jcarsique 018 */ 019package org.nuxeo.runtime.test.runner; 020 021import java.lang.annotation.ElementType; 022import java.lang.annotation.Inherited; 023import java.lang.annotation.Retention; 024import java.lang.annotation.RetentionPolicy; 025import java.lang.annotation.Target; 026import java.util.ArrayList; 027import java.util.List; 028 029import org.apache.log4j.Appender; 030import org.apache.log4j.AppenderSkeleton; 031import org.apache.log4j.Level; 032import org.apache.log4j.Logger; 033import org.apache.log4j.spi.LoggingEvent; 034import org.junit.Assert; 035import org.junit.runners.model.FrameworkMethod; 036 037import com.google.common.base.Strings; 038 039/** 040 * Test feature to capture from a log4j appender to check that some log4j calls have been correctly called.</br> 041 * On a test class or a test method using this feature, a default filter can be configured with the annotation 042 * {@link LogCaptureFeature.FilterOn} or a custom one implementing {@link LogCaptureFeature.Filter} class can be 043 * provided with the annotation {@link LogCaptureFeature.FilterWith} to select the log events to capture.</br> 044 * A {@link LogCaptureFeature.Result} instance is to be injected with {@link Inject} as an attribute of the test.</br> 045 * The method {@link LogCaptureFeature.Result#assertHasEvent()} can then be called from test methods to check that 046 * matching log calls (events) have been captured. 047 * 048 * @since 5.7 049 */ 050public class LogCaptureFeature extends SimpleFeature { 051 052 public class NoLogCaptureFilterException extends Exception { 053 private static final long serialVersionUID = 1L; 054 } 055 056 @Inherited 057 @Retention(RetentionPolicy.RUNTIME) 058 @Target({ ElementType.TYPE, ElementType.METHOD }) 059 public @interface FilterWith { 060 /** 061 * Custom implementation of a filter to select event to capture. 062 */ 063 Class<? extends LogCaptureFeature.Filter> value(); 064 } 065 066 @Inherited 067 @Retention(RetentionPolicy.RUNTIME) 068 @Target({ ElementType.TYPE, ElementType.METHOD }) 069 public @interface FilterOn { 070 /** 071 * Configuration for the default filter 072 */ 073 String loggerName() default ""; 074 075 String logLevel() default ""; 076 } 077 078 private static class DefaultFilter implements LogCaptureFeature.Filter { 079 080 String loggerName; 081 082 Level logLevel; 083 084 public DefaultFilter(String loggerName, String logLevel) { 085 super(); 086 this.loggerName = Strings.emptyToNull(loggerName); 087 if (!"".equals(logLevel)) { 088 this.logLevel = Level.toLevel(logLevel); 089 } 090 } 091 092 @Override 093 public boolean accept(LoggingEvent event) { 094 if (logLevel != null && !logLevel.equals(event.getLevel())) { 095 return false; 096 } 097 if (loggerName != null && !loggerName.equals(event.getLoggerName())) { 098 return false; 099 } 100 return true; 101 } 102 } 103 104 public class Result { 105 protected final ArrayList<LoggingEvent> caughtEvents = new ArrayList<>(); 106 107 protected boolean noFilterFlag = false; 108 109 public void assertHasEvent() throws NoLogCaptureFilterException { 110 if (noFilterFlag) { 111 throw new LogCaptureFeature.NoLogCaptureFilterException(); 112 } 113 Assert.assertFalse("No log result found", caughtEvents.isEmpty()); 114 } 115 116 public void clear() { 117 caughtEvents.clear(); 118 noFilterFlag = false; 119 } 120 121 public List<LoggingEvent> getCaughtEvents() { 122 return caughtEvents; 123 } 124 125 protected void setNoFilterFlag(boolean noFilterFlag) { 126 this.noFilterFlag = noFilterFlag; 127 } 128 } 129 130 public interface Filter { 131 /** 132 * {@link LogCaptureFeature} will capture the event if it does match the implementation condition. 133 */ 134 boolean accept(LoggingEvent event); 135 } 136 137 protected Filter logCaptureFilter; 138 139 protected final Result myResult = new Result(); 140 141 protected Logger rootLogger = Logger.getRootLogger(); 142 143 protected Appender logAppender = new AppenderSkeleton() { 144 @Override 145 public boolean requiresLayout() { 146 return false; 147 } 148 149 @Override 150 public void close() { 151 } 152 153 @Override 154 protected void append(LoggingEvent event) { 155 if (logCaptureFilter == null) { 156 myResult.setNoFilterFlag(true); 157 return; 158 } 159 if (logCaptureFilter.accept(event)) { 160 myResult.caughtEvents.add(event); 161 } 162 } 163 }; 164 165 private Filter setupCaptureFiler; 166 167 @Override 168 public void configure(FeaturesRunner runner, com.google.inject.Binder binder) { 169 binder.bind(Result.class).toInstance(myResult); 170 }; 171 172 @Override 173 public void beforeSetup(FeaturesRunner runner) throws Exception { 174 super.beforeSetup(runner); 175 Filter filter; 176 FilterWith filterProvider = runner.getConfig(FilterWith.class); 177 if (filterProvider.value() == null) { 178 FilterOn defaultFilterConfig = runner.getConfig(FilterOn.class); 179 if (defaultFilterConfig == null) { 180 return; 181 } else { 182 filter = new DefaultFilter(defaultFilterConfig.loggerName(), defaultFilterConfig.logLevel()); 183 } 184 } else { 185 filter = filterProvider.value().newInstance(); 186 } 187 enable(filter); 188 } 189 190 @Override 191 public void afterTeardown(FeaturesRunner runner) throws Exception { 192 disable(); 193 } 194 195 @Override 196 public void beforeMethodRun(FeaturesRunner runner, FrameworkMethod method, Object test) throws Exception { 197 Filter filter; 198 FilterWith filterProvider = runner.getConfig(method, FilterWith.class); 199 if (filterProvider.value() == null) { 200 FilterOn defaultFilterConfig = runner.getConfig(method, FilterOn.class); 201 if (defaultFilterConfig == null) { 202 return; 203 } else { 204 filter = new DefaultFilter(defaultFilterConfig.loggerName(), defaultFilterConfig.logLevel()); 205 } 206 } else { 207 filter = filterProvider.value().newInstance(); 208 } 209 enable(filter); 210 } 211 212 @Override 213 public void afterMethodRun(FeaturesRunner runner, FrameworkMethod method, Object test) throws Exception { 214 disable(); 215 } 216 217 /** 218 * @param filter 219 * @since 8.4 220 */ 221 protected void enable(Filter filter) throws InstantiationException, IllegalAccessException { 222 223 if (logCaptureFilter != null) { 224 setupCaptureFiler = logCaptureFilter; 225 } else { 226 rootLogger.addAppender(logAppender); 227 } 228 logCaptureFilter = filter; 229 } 230 231 /** 232 * @since 6.0 233 */ 234 protected void disable() { 235 if (setupCaptureFiler != null) { 236 logCaptureFilter = setupCaptureFiler; 237 setupCaptureFiler = null; 238 return; 239 } 240 if (logCaptureFilter != null) { 241 myResult.clear(); 242 rootLogger.removeAppender(logAppender); 243 logCaptureFilter = null; 244 } 245 } 246 247}