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}