001    /*
002     * $Id: SmokeFilter.java,v 1.3 2012/04/20 18:57:54 oboehm Exp $
003     *
004     * Copyright (c) 2010 by Oliver Boehm
005     *
006     * Licensed under the Apache License, Version 2.0 (the "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *   http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express orimplied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     *
018     * (c)reated 03.05.2010 by oliver (ob@oasd.de)
019     */
020    
021    package patterntesting.runtime.junit.internal;
022    
023    import java.lang.annotation.Annotation;
024    import java.util.*;
025    
026    import org.apache.commons.lang.StringUtils;
027    import org.junit.runner.Description;
028    import org.junit.runner.manipulation.Filter;
029    import org.slf4j.*;
030    
031    import patterntesting.annotation.check.runtime.MayReturnNull;
032    import patterntesting.runtime.NullConstants;
033    import patterntesting.runtime.annotation.*;
034    import patterntesting.runtime.util.*;
035    
036    /**
037     * This filter handles the different annotations like Broken, RunTestOn,
038     * SkipTestOn, IntegrationTest or SmokeTest.
039     *
040     * @author oliver
041     * @since 1.0 (03.05.2010)
042     */
043    public final class SmokeFilter extends Filter {
044    
045        private static final Logger log = LoggerFactory.getLogger(SmokeFilter.class);
046        private Date today = new Date();
047        private String osName = Environment.OS_NAME;
048        private String userName = Environment.USER_NAME;
049        private int filteredOut = 0;
050        private final Dictionary<Description, Boolean> shouldRunCache = new Hashtable<Description, Boolean>();
051    
052        /**
053         * For testing (e.g. to check the past) you can set the today's date.
054         *
055         * @param date the new today's date
056         */
057        public void setToday(final Date date) {
058            this.today = new Date(date.getTime());
059        }
060    
061        /**
062         * For testing you can set the OS name.
063         *
064         * @param name the new os name
065         */
066        public void setOsName(final String name) {
067            this.osName = name;
068        }
069    
070        /**
071         * For testing you can set the user name.
072         *
073         * @param name the new user name
074         */
075        public void setUserName(final String name) {
076            this.userName = name;
077        }
078    
079        /**
080         * Describes the kind of test e.g. "IntegrationTest".
081         *
082         * @return e.g. "all tests including integration tests"
083         * @see org.junit.runner.manipulation.Filter#describe()
084         */
085        @Override
086        public String describe() {
087            if (Environment.isPropertyEnabled(Environment.INTEGRATION_TEST)) {
088                return "all tests including integration tests";
089            }
090            if (Environment.isPropertyEnabled(Environment.RUN_SMOKE_TESTS)) {
091                return "tests marked as @SmokeTest";
092            }
093            return "all tests (except tests marked as @Broken)";
094        }
095    
096        /**
097         * Handles the different annotations like Broken, RunTestOn, SkipTestOn,
098         * IntegrationTest or SmokeTest.
099         * We use a cache (shouldRunCache) here in this implementation to avoid to
100         * much logging of "...run method / skip method..." messages.
101         *
102         * @param description containing the JUnit class and test method
103         * @return depends on the annotation
104         * @see org.junit.runner.manipulation.Filter#shouldRun(org.junit.runner.Description)
105         */
106        @Override
107        public boolean shouldRun(final Description description) {
108            Boolean value = shouldRunCache.get(description);
109            if (value != null) {
110                return value;
111            }
112            try {
113                if (Environment.smokeTestEnabled) {
114                    value = isSmokeTest(description);
115                } else if (Environment.integrationTestEnabled) {
116                    value = true;
117                } else {
118                    value = !isIntegrationTest(description);
119                }
120                shouldRunCache.put(description, value);
121                if (!value) {
122                    filteredOut++;
123                }
124                return value;
125            } catch (IllegalArgumentException iae) {
126                log.warn(description + " has incomplete Annotation(s) - " + iae);
127                return false;
128            }
129        }
130        
131        /**
132         * Should be ignored.
133         *
134         * @param description the description
135         * @return true, if successful
136         */
137        public boolean shouldBeIgnored(final Description description) {
138            if (Environment.smokeTestEnabled) {
139                return !isSmokeTest(description);
140            }
141            if (isSkipTestOn(description) || !isRunTestOn(description) || isBroken(description)) {
142                return true;
143            }
144            if (!Environment.integrationTestEnabled) {
145                return isIntegrationTest(description);
146            }
147            return false;
148        }
149    
150        private boolean isIntegrationTest(final Description description) {
151            IntegrationTest it = getAnnotation(description, IntegrationTest.class);
152            if (it != null) {
153                if (log.isInfoEnabled()) {
154                    log.info(description.getDisplayName() + " SKIPPED because "
155                            + it.value());
156                }
157                return true;
158            }
159            return false;
160        }
161    
162        private boolean isSmokeTest(final Description description) {
163            SmokeTest smokeTest = description.getAnnotation(SmokeTest.class);
164            if (smokeTest != null) {
165                if (log.isInfoEnabled()) {
166                    log.info("run " + description.getDisplayName() + " because "
167                            + smokeTest.value());
168                }
169                return true;
170            }
171            return false;
172        }
173    
174        /**
175         * Returns true if the given method has the RunTestOn annotation and
176         * the condition of it is true.
177         *
178         * @param description with the FrameworkMethod
179         * @return depends on the condition
180         */
181        private static boolean isRunTestOn(final Description description) {
182            RunTestOn runOn = getAnnotation(description, RunTestOn.class);
183            if (runOn == null) {
184                return true;
185            }
186            TestOn testOn = new TestOn();
187            if (testOn.matches(runOn.value(), runOn.osName(), runOn.osArch(), runOn
188                    .osVersion(), runOn.host(), runOn.javaVersion(), runOn
189                    .javaVendor(), runOn.user(), runOn.property())) {
190                log.info(description + " executed " + testOn.getReason());
191                return true;
192            }
193            return false;
194        }
195    
196        /**
197         * Returns true if the given method has the SkipTestOn annotation and
198         * the condition of it is true.
199         *
200         * @param description with the FrameworkMethod
201         * @return depends on the condition
202         */
203        private static boolean isSkipTestOn(final Description description) {
204            SkipTestOn skipOn = getAnnotation(description, SkipTestOn.class);
205            if (skipOn == null) {
206                return false;
207            }
208            TestOn testOn = new TestOn();
209            if (testOn.matches(skipOn.value(), skipOn.osName(), skipOn.osArch(), skipOn
210                    .osVersion(), skipOn.host(), skipOn.javaVersion(), skipOn
211                    .javaVendor(), skipOn.user(), skipOn.property())) {
212                log.info(description + " SKIPPED because " + testOn.getReason());
213                return true;
214            }
215            return false;
216        }
217    
218        private boolean isBroken(final Description description) {
219            Broken broken = getAnnotation(description, Broken.class);
220            if (broken == null) {
221                return false;
222            }
223            return isBroken(DescriptionUtils.getMethodNameOf(description), broken);
224        }
225    
226        @MayReturnNull
227        private static <T extends Annotation> T getAnnotation(final Description description,
228                final Class<T> annotationType) {
229            T annotation = description.getAnnotation(annotationType);
230            if (annotation == null) {
231                annotation = DescriptionUtils.getTestClassOf(description).getAnnotation(annotationType);
232            }
233            return annotation;
234        }
235    
236        /**
237         * Checks for a given method if the condition of the given Broken
238         * annotations is fulfilled.
239         * <br/>
240         * NOTE: The method has default visibility
241         *
242         * @param method the method
243         * @param broken the broken
244         * @return true, if is broken
245         */
246        @SuppressWarnings("deprecation")
247        public boolean isBroken(final String method, final Broken broken) {
248            String why = broken.why();
249            if (StringUtils.isEmpty(why)) {
250                why = broken.value();
251            }
252            Date till = Converter.toDate(broken.till());
253            if ((till != NullConstants.NULL_DATE) && till.after(today)) {
254                log.info(method + "() SKIPPED because " + broken.value()
255                        + " till " + broken.till());
256                return true;
257            }
258            if (StringUtils.isNotEmpty(broken.os()[0])) {
259                log.warn("@Broken(os=...) is deprecated - use @Broken(osName=...)");
260                if (StringHelper.containsIgnoreCase(broken.os(), osName)) {
261                    log.info(method + "() SKIPPED because " + why + " on "
262                            + Converter.toString(broken.os()));
263                    return true;
264                } else {
265                    log.debug(method + "() started on " + osName + ", because " + why + " only on "
266                            + Converter.toString(broken.os()));
267                    return false;
268                }
269            }
270            TestOn testOn = new TestOn();
271            testOn.setOsName(this.osName);
272            testOn.setUserName(this.userName);
273            try {
274                if (testOn.matches(broken.osName(), broken.osArch(), broken
275                        .osVersion(), broken.host(), broken.javaVersion(), broken
276                        .javaVendor(), broken.user(), broken.property())) {
277                    log.info(method + " SKIPPED because " + testOn.getReason());
278                    return true;
279                }
280            } catch (IllegalArgumentException canhappen) {
281                log.trace("{}", canhappen);
282                if (till == NullConstants.NULL_DATE) {
283                    log.info(method + "() SKIPPED because " + why);
284                    return true;
285                } else {
286                    log.debug(method + "() started, because it should be fixed since "
287                            + broken.till());
288                    return false;
289                }
290            }
291            return false;
292        }
293    
294        /**
295         * Checks if there are some methods filtered out.
296         * @return true if some methods were filtered
297         */
298        public boolean hasFiltered() {
299            return this.filteredOut > 0;
300        }
301        
302        /**
303         * Gets the number of methods which was filtered out.
304         *
305         * @return the filtered number
306         */
307        public int getFilteredNumber() {
308            return this.filteredOut;
309        }
310    
311        /**
312         * Prints the description of this filter.
313         *
314         * @return a String including the result of the describe() method
315         * @see java.lang.Object#toString()
316         */
317        @Override
318        public String toString() {
319            return "filter for " + this.describe();
320        }
321    
322    }
323