001/* 002 * (C) Copyright 2012-2014 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.Logger; 032import org.apache.log4j.spi.LoggingEvent; 033import org.junit.Assert; 034import org.junit.runners.model.FrameworkMethod; 035 036/** 037 * Test feature to capture from a log4j appender to check that some log4j calls have been correctly called.</br> On a 038 * test class or a test method using this feature, a custom {@link LogCaptureFeature.Filter} class is to be provided 039 * with the annotation {@link LogCaptureFeature.FilterWith} to select the log events to capture.</br> A 040 * {@link LogCaptureFeature.Result} instance is to be injected with {@link Inject} as an attribute of the test.</br> The 041 * method {@link LogCaptureFeature.Result#assertHasEvent()} can then be called from test methods to check that matching 042 * log calls (events) have been captured. 043 * 044 * @since 5.7 045 */ 046public class LogCaptureFeature extends SimpleFeature { 047 048 public class NoLogCaptureFilterException extends Exception { 049 private static final long serialVersionUID = 1L; 050 } 051 052 @Inherited 053 @Retention(RetentionPolicy.RUNTIME) 054 @Target({ ElementType.TYPE, ElementType.METHOD }) 055 public @interface FilterWith { 056 /** 057 * Custom implementation of a filter to select event to capture. 058 */ 059 Class<? extends LogCaptureFeature.Filter> value(); 060 } 061 062 public class Result { 063 protected final ArrayList<LoggingEvent> caughtEvents = new ArrayList<>(); 064 065 protected boolean noFilterFlag = false; 066 067 public void assertHasEvent() throws NoLogCaptureFilterException { 068 if (noFilterFlag) { 069 throw new LogCaptureFeature.NoLogCaptureFilterException(); 070 } 071 Assert.assertFalse("No log result found", caughtEvents.isEmpty()); 072 } 073 074 public void clear() { 075 caughtEvents.clear(); 076 noFilterFlag = false; 077 } 078 079 public List<LoggingEvent> getCaughtEvents() { 080 return caughtEvents; 081 } 082 083 protected void setNoFilterFlag(boolean noFilterFlag) { 084 this.noFilterFlag = noFilterFlag; 085 } 086 } 087 088 public interface Filter { 089 /** 090 * {@link LogCaptureFeature} will capture the event if it does match the implementation condition. 091 */ 092 boolean accept(LoggingEvent event); 093 } 094 095 protected Filter logCaptureFilter; 096 097 protected final Result myResult = new Result(); 098 099 protected Logger rootLogger = Logger.getRootLogger(); 100 101 protected Appender logAppender = new AppenderSkeleton() { 102 @Override 103 public boolean requiresLayout() { 104 return false; 105 } 106 107 @Override 108 public void close() { 109 } 110 111 @Override 112 protected void append(LoggingEvent event) { 113 if (logCaptureFilter == null) { 114 myResult.setNoFilterFlag(true); 115 return; 116 } 117 if (logCaptureFilter.accept(event)) { 118 myResult.caughtEvents.add(event); 119 } 120 } 121 }; 122 123 private Filter setupCaptureFiler; 124 125 @Override 126 public void configure(FeaturesRunner runner, com.google.inject.Binder binder) { 127 binder.bind(Result.class).toInstance(myResult); 128 }; 129 130 @Override 131 public void beforeSetup(FeaturesRunner runner) throws Exception { 132 super.beforeSetup(runner); 133 FilterWith filterProvider = runner.getConfig(FilterWith.class); 134 if (filterProvider.value() == null) { 135 return; 136 } 137 Class<? extends Filter> filterClass = filterProvider.value(); 138 enable(filterClass); 139 } 140 141 @Override 142 public void afterTeardown(FeaturesRunner runner) throws Exception { 143 disable(); 144 } 145 146 @Override 147 public void beforeMethodRun(FeaturesRunner runner, FrameworkMethod method, Object test) throws Exception { 148 FilterWith filterProvider = runner.getConfig(method, FilterWith.class); 149 if (filterProvider.value() == null) { 150 return; 151 } 152 Class<? extends Filter> filterClass = filterProvider.value(); 153 enable(filterClass); 154 } 155 156 @Override 157 public void afterMethodRun(FeaturesRunner runner, FrameworkMethod method, Object test) throws Exception { 158 disable(); 159 } 160 161 /** 162 * @since 6.0 163 */ 164 protected void enable(Class<? extends Filter> filterClass) throws InstantiationException, IllegalAccessException { 165 if (logCaptureFilter != null) { 166 setupCaptureFiler = logCaptureFilter; 167 } else { 168 rootLogger.addAppender(logAppender); 169 } 170 logCaptureFilter = filterClass.newInstance(); 171 } 172 173 /** 174 * @since 6.0 175 */ 176 protected void disable() { 177 if (setupCaptureFiler != null) { 178 logCaptureFilter = setupCaptureFiler; 179 setupCaptureFiler = null; 180 return; 181 } 182 if (logCaptureFilter != null) { 183 myResult.clear(); 184 rootLogger.removeAppender(logAppender); 185 logCaptureFilter = null; 186 } 187 } 188 189}