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