View Javadoc
1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.testframework;
5   
6   import java.util.ArrayList;
7   import java.util.Collections;
8   import java.util.Comparator;
9   import java.util.Iterator;
10  import java.util.List;
11  
12  import net.sourceforge.pmd.Rule;
13  
14  import org.junit.Test;
15  import org.junit.runner.Description;
16  import org.junit.runner.Runner;
17  import org.junit.runner.manipulation.Filter;
18  import org.junit.runner.manipulation.Filterable;
19  import org.junit.runner.manipulation.NoTestsRemainException;
20  import org.junit.runner.notification.Failure;
21  import org.junit.runner.notification.RunNotifier;
22  import org.junit.runners.BlockJUnit4ClassRunner;
23  import org.junit.runners.model.InitializationError;
24  import org.junit.runners.model.TestClass;
25  
26  /**
27   * A test runner for rule tests. Unlike {@link SimpleAggregatorTst.CustomXmlTestClassMethodsRunner}
28   * it also reports the successful executed tests and allows to selectively execute single test cases
29   * (it is {@link Filterable}).
30   * <p>
31   * In order to use it, you'll need to subclass {@link SimpleAggregatorTst} and annotate your test
32   * class with RunWith:
33   * <pre>
34   * {@code @}RunWith(PMDTestRunner.class)
35   * public class MyRuleSetTest extends SimpleAggregatorTst {
36   * ...
37   * }
38   * </pre>
39   * </p>
40   */
41  public class PMDTestRunner extends Runner implements Filterable {
42      private final Description desc;
43      private final Class<? extends SimpleAggregatorTst> klass;
44      private final List<TestDescriptor> allTests = new ArrayList<>();
45      private BlockJUnit4ClassRunner chainedRunner;
46  
47      /**
48       * Creates a new {@link PMDTestRunner} for the given test class.
49       * @param klass the test class that is under test
50       * @throws InitializationError any error
51       */
52      public PMDTestRunner(final Class<? extends SimpleAggregatorTst> klass) throws InitializationError {
53          this.klass = klass;
54  
55          desc = Description.createSuiteDescription(klass);
56          configureRuleTests();
57          configureUnitTests();
58      }
59  
60      private void configureRuleTests() throws InitializationError {
61          Description root = Description.createSuiteDescription("Rule Tests");
62          try {
63              SimpleAggregatorTst test = createTestClass();
64              test.setUp();
65  
66              List<Rule> rules = new ArrayList<>(test.getRules());
67              Collections.sort(rules, new Comparator<Rule>() {
68                  @Override
69                  public int compare(Rule o1, Rule o2) {
70                      return o1.getName().compareTo(o2.getName());
71                  }
72              });
73  
74              for (Rule r : rules) {
75                  Description ruleDescription = Description.createSuiteDescription(r.getName());
76                  root.addChild(ruleDescription);
77                  
78                  TestDescriptor[] ruleTests = test.extractTestsFromXml(r);
79                  for (TestDescriptor t : ruleTests) {
80                      Description d = createTestDescription(t);
81                      ruleDescription.addChild(d);
82                      allTests.add(t);
83                  }
84              }
85              if (!root.getChildren().isEmpty()) {
86                  desc.addChild(root);
87              }
88          } catch (Exception e) {
89              throw new InitializationError(e);
90          }
91      }
92  
93      private SimpleAggregatorTst createTestClass() {
94          try {
95              return klass.getConstructor().newInstance();
96          } catch (Exception e) {
97              throw new RuntimeException(e);
98          }
99      }
100 
101     private void configureUnitTests() throws InitializationError {
102         TestClass tclass = new TestClass(klass);
103         if (!tclass.getAnnotatedMethods(Test.class).isEmpty()) {
104             Description unitTests = Description.createSuiteDescription("Unit tests");
105             chainedRunner = new BlockJUnit4ClassRunner(klass);
106             for (Description d : chainedRunner.getDescription().getChildren()) {
107                 unitTests.addChild(d);
108             }
109             desc.addChild(unitTests);
110         }
111     }
112 
113     @Override
114     public Description getDescription() {
115         return desc;
116     }
117 
118     @Override
119     public void run(RunNotifier notifier) {
120         SimpleAggregatorTst test = createTestClass();
121         boolean regressionTestMode = TestDescriptor.inRegressionTestMode();
122 
123         for (TestDescriptor t : allTests) {
124             Description d = createTestDescription(t);
125             notifier.fireTestStarted(d);
126             try {
127                 if (!regressionTestMode || t.isRegressionTest()) {
128                     test.runTest(t);
129                 } else {
130                     notifier.fireTestIgnored(d);
131                 }
132             } catch (Throwable e) {
133                 notifier.fireTestFailure(new Failure(d, e));
134             } finally {
135                 notifier.fireTestFinished(d);
136             }
137         }
138         if (chainedRunner != null) {
139             chainedRunner.run(notifier);
140         }
141     }
142 
143     private Description createTestDescription(TestDescriptor t) {
144         String d = t.getDescription().replaceAll("\n|\r", " ");
145         return Description.createTestDescription(klass, t.getRule().getName() + "::" + t.getNumberInDocument() + " " + d);
146     }
147 
148     @Override
149     public void filter(Filter filter) throws NoTestsRemainException {
150         Iterator<TestDescriptor> it = allTests.iterator();
151         while (it.hasNext()) {
152             TestDescriptor t = it.next();
153             Description testDesc = createTestDescription(t);
154             if (filter.shouldRun(testDesc)) {
155                 try {
156                     filter.apply(t);
157                 } catch (NoTestsRemainException e) {
158                     it.remove();
159                 }
160             } else {
161                 it.remove();
162             }
163         }
164 
165         boolean chainIsEmpty = false;
166         try {
167             if (chainedRunner != null) {
168                 chainedRunner.filter(filter);
169             } else {
170                 chainIsEmpty = true;
171             }
172         } catch (NoTestsRemainException e) {
173             chainIsEmpty = true;
174         }
175 
176         if (allTests.isEmpty() && chainIsEmpty) {
177             throw new NoTestsRemainException();
178         }
179     }
180 }