From 154616e9f560715309aa1abc7d69df363f424862 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Fri, 8 Sep 2017 10:27:38 +0200 Subject: [PATCH] Create a new JUnit test runner to execute rule tests This runner now also supports Before, After, and (JUnit) Rules. --- .../pmd/testframework/RuleTestRunner.java | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 pmd-test/src/main/java/net/sourceforge/pmd/testframework/RuleTestRunner.java diff --git a/pmd-test/src/main/java/net/sourceforge/pmd/testframework/RuleTestRunner.java b/pmd-test/src/main/java/net/sourceforge/pmd/testframework/RuleTestRunner.java new file mode 100644 index 0000000000..048330dd09 --- /dev/null +++ b/pmd-test/src/main/java/net/sourceforge/pmd/testframework/RuleTestRunner.java @@ -0,0 +1,135 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.testframework; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +import org.junit.After; +import org.junit.Before; +import org.junit.internal.runners.statements.RunAfters; +import org.junit.internal.runners.statements.RunBefores; +import org.junit.rules.RunRules; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.ParentRunner; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.Statement; + +import net.sourceforge.pmd.Rule; + +/** + * A JUnit Runner, that executes all declared rule tests in the class. + * It supports Before and After methods as well as TestRules. + * + * @author Andreas Dangel + */ +public class RuleTestRunner extends ParentRunner { + private ConcurrentHashMap testDescriptions = new ConcurrentHashMap<>(); + private final SimpleAggregatorTst instance; + + public RuleTestRunner(Class testClass) throws InitializationError { + super(testClass); + instance = createTestClass(); + instance.setUp(); + } + + protected Description describeChild(TestDescriptor testCase) { + Description description = testDescriptions.get(testCase); + if (description == null) { + description = Description.createTestDescription(getTestClass().getJavaClass(), + testCase.getRule().getName() + "::" + + testCase.getNumberInDocument() + " " + + testCase.getDescription().replaceAll("\n|\r", " ")); + testDescriptions.putIfAbsent(testCase, description); + } + return description; + }; + + @Override + protected List getChildren() { + List rules = new ArrayList<>(instance.getRules()); + Collections.sort(rules, new Comparator() { + @Override + public int compare(Rule o1, Rule o2) { + return o1.getName().compareTo(o2.getName()); + } + }); + + List tests = new LinkedList<>(); + for (Rule r : rules) { + TestDescriptor[] ruleTests = instance.extractTestsFromXml(r); + for (TestDescriptor t : ruleTests) { + tests.add(t); + } + } + + return tests; + } + + private SimpleAggregatorTst createTestClass() throws InitializationError { + try { + return (SimpleAggregatorTst)getTestClass().getOnlyConstructor().newInstance(); + } catch (Exception e) { + throw new InitializationError(e); + } + } + + @Override + protected void runChild(TestDescriptor testCase, RunNotifier notifier) { + Description description = describeChild(testCase); + if (isIgnored(testCase)) { + notifier.fireTestIgnored(description); + } else { + runLeaf(ruleTestBlock(testCase), description, notifier); + } + } + + /** + * Executes the actual test case. If there are Before, After, or TestRules present, + * they are executed accordingly. + * + * @param testCase the PMD rule test case to be executed + * @return a single statement which includes any rules, if present. + */ + private Statement ruleTestBlock(final TestDescriptor testCase) { + Statement statement = new Statement() { + @Override + public void evaluate() throws Throwable { + instance.runTest(testCase); + } + }; + statement = withBefores(testCase, statement); + statement = withAfters(testCase, statement); + statement = withRules(testCase, statement); + return statement; + } + + private Statement withBefores(final TestDescriptor testCase, Statement statement) { + List befores = getTestClass().getAnnotatedMethods(Before.class); + return befores.isEmpty() ? statement : new RunBefores(statement, befores, instance); + } + + private Statement withAfters(final TestDescriptor testCase, Statement statement) { + List afters = getTestClass().getAnnotatedMethods(After.class); + return afters.isEmpty() ? statement : new RunAfters(statement, afters, instance); + } + + private Statement withRules(final TestDescriptor testCase, Statement statement) { + List testRules = getTestClass().getAnnotatedFieldValues(instance, org.junit.Rule.class, TestRule.class); + return testRules.isEmpty() ? statement : new RunRules(statement, testRules, describeChild(testCase)); + } + + @Override + protected boolean isIgnored(TestDescriptor child) { + return TestDescriptor.inRegressionTestMode() && !child.isRegressionTest(); + } +}