001    /*
002     * $Id: SmokeRunner.java,v 1.30 2014/04/28 14:56:48 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 29.03.2010 by oliver (ob@oasd.de)
019     */
020    
021    package patterntesting.runtime.junit;
022    
023    import java.lang.annotation.Annotation;
024    import java.lang.reflect.*;
025    import java.util.*;
026    
027    import org.apache.commons.lang.StringUtils;
028    import org.junit.*;
029    import org.junit.internal.AssumptionViolatedException;
030    import org.junit.internal.runners.model.MultipleFailureException;
031    import org.junit.internal.runners.statements.*;
032    import org.junit.runner.Description;
033    import org.junit.runner.notification.*;
034    import org.junit.runners.*;
035    import org.junit.runners.model.*;
036    import org.slf4j.*;
037    
038    import patterntesting.runtime.junit.internal.*;
039    
040    /**
041     * This is the eXtended Runner for JUnit 4 which handles the SmokeTest and
042     * other annotations. In previous (intermediate) version it was called
043     * "XRunner". But because this may be a little bit confusing name it was
044     * renamed to "SmokeRunner" for the final version.
045     * <br/>
046     * It can be used together with the {@code @RunWith} annotation of JUnit 4.
047     *
048     * @author oliver
049     * @since 1.0 (29.03.2010)
050     */
051    @SuppressWarnings("deprecation")
052    public class SmokeRunner extends ParentRunner<FrameworkMethod> {
053    //public class SmokeRunner extends BlockJUnit4ClassRunner {
054    
055        private static final Logger log = LoggerFactory.getLogger(SmokeRunner.class);
056        private final SmokeFilter xfilter = new SmokeFilter();
057    
058        /**
059         * Creates a SmokeRunner to run klass methods in parallel.
060         *
061         * @param klass the test class to run
062         * @throws InitializationError if the test class is malformed
063         */
064        public SmokeRunner(final Class<?> klass) throws InitializationError {
065            super(klass);
066        }
067    
068        /**
069         * The ParentRunner allows us no access to the filter. So we added this
070         * method here (e.g. needed by the ParallelRunner).
071         *
072         * @return the SmokeFilter
073         */
074        protected final SmokeFilter getFilter() {
075            return this.xfilter;
076        }
077    
078    //    /**
079    //     * If we extends from BlockJUnit4CLassRunner we must overwrite this method
080    //     * here because we also support JUnit3 tests. Otherwise we would get an
081    //     * InitializationExcption.
082    //     */
083    //    @Override
084    //    protected void collectInitializationErrors(List<Throwable> errors) {
085    //    }
086    
087        /**
088         * Returns the methods that run tests. Default implementation
089         * returns all methods annotated with {@code @Test} on this
090         * class and superclasses that are not overridden.
091         *
092         * @return the methods that run tests
093         */
094        @Override
095        protected List<FrameworkMethod> getChildren() {
096            TestClass testClass = this.getTestClass();
097            Class<?> javaClass = testClass.getJavaClass();
098            if (ProfiledStatement.isTestCaseClass(testClass)) {
099                return getJUnit3TestMethods(javaClass);
100            }
101            return testClass.getAnnotatedMethods(Test.class);
102        }
103    
104    //    /**
105    //     * This method is private in ParentRunner so we copied it and adapted it
106    //     * to our needs. It is not used by SmokeRunner but by the derived classes
107    //     * ParallelRunner and ParallelProxyRunner (where this class here is the
108    //     * common super class).
109    //     *
110    //     * @return the filtered children list
111    //     */
112    //    protected final List<FrameworkMethod> getFilteredChildren() {
113    //        ArrayList<FrameworkMethod> filtered = new ArrayList<FrameworkMethod>();
114    //        for (FrameworkMethod each : getChildren()) {
115    //            if (this.getFilter().shouldRun(describeChild(each))) {
116    //                filtered.add(each);
117    //            }
118    //        }
119    //        return filtered;
120    //    }
121    
122        /**
123         * Create a <code>Description</code> of a single test named
124         * <code>name</code> in the class <code>clazz</code>. Generally, this will
125         * be a leaf <code>Description</code>. (see also
126         * {@link BlockJUnit4ClassRunner})
127         *
128         * @param child
129         *            the name of the test (a method name for test annotated with
130         *            {@link org.junit.Test})
131         * @return the description
132         * @see org.junit.runners.ParentRunner#describeChild(java.lang.Object)
133         */
134        @Override
135        protected Description describeChild(final FrameworkMethod child) {
136            return Description.createTestDescription(getTestClass().getJavaClass(),
137                    child.getName(), child.getAnnotations());
138        }
139    
140        /**
141         * Checks the annotation of the method marked as "@BeforeClass" and add
142         * (or filters out) the beforeClass method (needed to solve.
143         *
144         * @param statement the statement(s) before
145         * @return the statement(s) after with the BeforeClass method
146         * {@link "https://sourceforge.net/tracker/?func=detail&aid=3034823&group_id=48833&atid=454317"}).
147         * @see org.junit.runners.ParentRunner#withBeforeClasses(org.junit.runners.model.Statement)
148         */
149        @Override
150        protected Statement withBeforeClasses(final Statement statement) {
151            List<FrameworkMethod> filtered = filter(BeforeClass.class);
152            if (filtered.isEmpty()) {
153                return statement;
154            }
155            return new RunBefores(statement, filtered, null);
156        }
157    
158        /**
159         * Checks the annotation of the method marked as "@AfterClass" and add
160         * (or filters out) the afterClass method (needed to solve.
161         *
162         * @param statement the statement(s) before
163         * @return the statement(s) after with the AfterClass method
164         * {@link "https://sourceforge.net/tracker/?func=detail&aid=3034823&group_id=48833&atid=454317"}).
165         * @see org.junit.runners.ParentRunner#withAfterClasses(org.junit.runners.model.Statement)
166         */
167        @Override
168        protected Statement withAfterClasses(final Statement statement) {
169            List<FrameworkMethod> filtered = filter(AfterClass.class);
170            if (filtered.isEmpty()) {
171                return statement;
172            }
173            return new RunAfters(statement, filtered, null);
174        }
175    
176        private List<FrameworkMethod> filter(final Class<? extends Annotation> annotationClass) {
177            List<FrameworkMethod> methods = this.getTestClass().getAnnotatedMethods(annotationClass);
178            return filter(methods);
179        }
180    
181        private List<FrameworkMethod> filter(final List<FrameworkMethod> methods) {
182            List<FrameworkMethod> filtered = new ArrayList<FrameworkMethod>();
183            for (FrameworkMethod fm : methods) {
184                if (!this.xfilter.shouldBeIgnored(describeChild(fm))) {
185                    filtered.add(fm);
186                } else if (log.isTraceEnabled()) {
187                    log.trace(fm.getMethod() + " is filtered out");
188                }
189            }
190            return filtered;
191        }
192    
193    //    /**
194    //     *  Unfortunately sending a "fireTestIgnored" to the notifier does not work
195    //     *  as expected. Neither the test class is displayed as "ignored" in the
196    //     *  JUnit GUI of Eclipse nor the methods are displayed as "ignored".
197    //     *  But more critical is that the count of the method is not correct - e.g.
198    //     *  for the IntegrationTestClassTest with two methods the following is
199    //     *  shown in the GUI:
200    //     *
201    //     *      Runs: 0/2
202    //     *
203    //     *  That means: 0 methods of 2 methods are executed. But the correct display
204    //     *  should be:
205    //     *
206    //     *      Runs: 0/0
207    //     *
208    //     *  The workaround here is to send no "fireTestIgnored" for the class but for
209    //     *  the single methods. So the result is then "Runs: 2/2" - not nice but the
210    //     *  count is correct after running it.
211    //     *
212    //     * @param notifier the notifier
213    //     * @see org.junit.runners.ParentRunner#run(org.junit.runner.notification.RunNotifier)
214    //     */
215    //    @Override
216    //    public void run(final RunNotifier notifier) {
217    //        if (this.shouldBeIgnored()) {
218    //            EachTestNotifier testNotifier = new EachTestNotifier(notifier, getDescription());
219    //            testNotifier.fireTestIgnored();
220    //        } else {
221    //            super.run(notifier);
222    //        }
223    //    }
224    //
225    //    /**
226    //     * Here we handle the annotations like {@code @TestIntegration} or
227    //     * {@code @Broken} for the whole class.
228    //     *
229    //     * @return true if test should be ignored
230    //     */
231    //    private boolean shouldBeIgnored() {
232    //        TestClass testClass = this.getTestClass();
233    //        if (runSmokeTests) {
234    //            return !hasSmokeTests(testClass);
235    //        }
236    //        Annotation[] annotations = testClass.getAnnotations();
237    //        for (int i = 0; i < annotations.length; i++) {
238    //            Annotation a = annotations[i];
239    //            if (a.annotationType().equals(IntegrationTest.class)
240    //                    && isIntegrationTest((IntegrationTest) a)) {
241    //                return true;
242    //            }
243    //        }
244    //        return this.xfilter.shouldBeIgnored(describeTestClass());
245    //    }
246    //
247    //    private final Description describeTestClass() {
248    //        return Description.createSuiteDescription(getTestClass().getName(), getTestClass()
249    //                .getAnnotations());
250    //    }
251    //
252    //    private boolean isIntegrationTest(final IntegrationTest it) {
253    //        if (Environment.integrationTestEnabled) {
254    //            log.trace("all tests including integration tests will be executed");
255    //            return false;
256    //        }
257    //        log.info(this.getTestClass().getName() + " SKIPPED because "
258    //                + it.value() + " (can be activated with '-D"
259    //                + Environment.INTEGRATION_TEST + "')");
260    //        return true;
261    //    }
262    //
263    //    /**
264    //     * Looks for the given test class if it has the SmokeClass annotation or
265    //     * one of its test methods have it.
266    //     *
267    //     * @param testClass the test class
268    //     * @return true if smoke tests are found
269    //     */
270    //    private static boolean hasSmokeTests(final TestClass testClass) {
271    //        if (testClass.getJavaClass().getAnnotation(SmokeTest.class) != null) {
272    //            return true;
273    //        }
274    //        List<FrameworkMethod> smokeTests = testClass
275    //                .getAnnotatedMethods(SmokeTest.class);
276    //        return !smokeTests.isEmpty();
277    //    }
278    
279        /**
280         * Runs the test corresponding to {@code child}, which can be assumed to be
281         * an element of the list returned by {@link ParentRunner#getChildren()}.
282         * Subclasses are responsible for making sure that relevant test events are
283         * reported through {@code notifier}
284         *
285         * @param method the method
286         * @param notifier the notifier
287         */
288        @Override
289        @SuppressWarnings("all")
290        protected void runChild(final FrameworkMethod method,
291                final RunNotifier notifier) {
292            Description description = describeChild(method);
293            try {
294                if (shouldBeIgnored(method)) {
295                    notifier.fireTestIgnored(description);
296                    return;
297                }
298            } catch (IllegalArgumentException iae) {
299                notifier.fireTestStarted(description);
300                fireTestAssumptionFailed(notifier, description, iae);
301                notifier.fireTestFinished(description);
302                return;
303            }
304            notifier.fireTestStarted(description);
305            Statement stmt = methodBlock(method);
306            try {
307                stmt.evaluate();
308            } catch (AssumptionViolatedException ex) {
309                fireTestAssumptionFailed(notifier, description, ex);
310            } catch (Throwable e) {
311                addFailure(notifier, e, description);
312            } finally {
313                logStatement(stmt);
314                notifier.fireTestFinished(description);
315            }
316        }
317    
318        /**
319         * In JUnit 4.5 and newer we can use the fireTestAssumptionFailed(..)
320         * of the {@link RunNotifier} class. But JUnit 4.4 does not provide this
321         * method.
322         * <br/>
323         * We could map it to the {@link RunNotifier#fireTestFailure(Failure)}
324         * method - but this does not work for JUnit 4.5 (some internal JUnit tests
325         * will fail if you try that).
326         * We could compile with JUnit 4.5, run the tests with JUnit 4.4 and see
327         * what will happen. Perhaps we can catch the exception and call the
328         * {@link RunNotifier#fireTestFailure(Failure)} method.
329         * We could also give up because the architecture of JUnit has changed too
330         * much between 4.4 and 4.5 - this is, what we do now.
331         *
332         * @param notifier the notifier
333         * @param description the description
334         * @param ex the ex
335         * @since 1.2.20
336         */
337        protected static void fireTestAssumptionFailed(final RunNotifier notifier,
338                final Description description, final Exception ex) {
339            notifier.fireTestAssumptionFailed(new Failure(description, ex));
340            //notifier.fireTestFailure(new Failure(description, ex));
341        }
342    
343        /**
344         * We will give a subclass (like e.g. ParallelRunner) the chance to report
345         * the statement with its own logger.
346         *
347         * @param stmt the stmt to be logged
348         */
349        protected void logStatement(final Statement stmt) {
350            log.info("{}", stmt);
351        }
352    
353        /**
354         * Here we handle annotations like {@code @Ignore} where the execution of
355         * the test method should be ignored.
356         *
357         * @param method the test method
358         * @return true or false
359         */
360        protected final boolean shouldBeIgnored(final FrameworkMethod method) {
361            Ignore ignore = method.getAnnotation(Ignore.class);
362            if (ignore != null) {
363                if (log.isDebugEnabled()) {
364                    String reason = ignore.value();
365                    if (StringUtils.isNotEmpty(reason)) {
366                        reason = " (" + reason + ")";
367                    }
368                    log.debug(this.getTestClass().getName() + "."
369                            + method.getName() + " ignored" + reason);
370                }
371                return true;
372            }
373            return this.xfilter.shouldBeIgnored(describeChild(method));
374        }
375    
376        /**
377         * Should be run.
378         *
379         * @param method the method
380         * @return true, if successful
381         */
382        protected final boolean shouldBeRun(final FrameworkMethod method) {
383            return !this.xfilter.shouldBeIgnored(describeChild(method));
384        }
385    
386        /**
387         * This method was inspired from an internal JUnit class
388         * (EachTestNotifier#addFailure(Throwable)).
389         *
390         * @param notifier the notifier
391         * @param targetException the target exception
392         * @param description the description
393         */
394        protected final void addFailure(final RunNotifier notifier, final Throwable targetException,
395                final Description description) {
396            if (targetException instanceof MultipleFailureException) {
397                MultipleFailureException mfe = (MultipleFailureException) targetException;
398                for (Throwable each : mfe.getFailures()) {
399                    addFailure(notifier, each, description);
400                }
401                return;
402            }
403            notifier.fireTestFailure(new Failure(description, targetException));
404        }
405    
406        /**
407         * Creates a RunStatement for the given test method.
408         *
409         * @param method the test method
410         * @return a created RunStatement
411         */
412        protected Statement methodBlock(final FrameworkMethod method) {
413            return new ProfiledStatement(this.getTestClass(), method);
414        }
415    
416        /**
417         * Here we look after public void methods with "test" as prefix and with no
418         * arguments.
419         * <br/>
420         * NOTE: This method is public because it is also needed by
421         * patterntesting.concurrent.junit.JUnit3Executor
422         *
423         * @param testClass the test class
424         * @return a list of public methods starting with prefix "test"
425         */
426        public static List<FrameworkMethod> getJUnit3TestMethods(final Class<?> testClass) {
427            List<FrameworkMethod> children = new ArrayList<FrameworkMethod>();
428            Method[] methods = testClass.getDeclaredMethods();
429            for (int i = 0; i < methods.length; i++) {
430                Method method = methods[i];
431                int mod = method.getModifiers();
432                if (Modifier.isPrivate(mod) || Modifier.isStatic(mod)) {
433                    continue;
434                }
435                if (method.getParameterTypes().length > 0) {
436                    continue;
437                }
438                Class<?> returnType = method.getReturnType();
439                if (!returnType.toString().equalsIgnoreCase("void")) {
440                    continue;
441                }
442                String name = method.getName();
443                if (name.startsWith("test")) {
444                    if (Modifier.isPublic(mod)) {
445                        FrameworkMethod child = new FrameworkMethod(method);
446                        children.add(child);
447                    } else {
448                        log.warn(method + " isn't public");
449                    }
450                }
451            }
452            return children;
453        }
454    
455    }
456