1 /**
2 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3 */
4 package test.net.sourceforge.pmd.testframework;
5
6 import static org.junit.Assert.assertEquals;
7 import static org.junit.Assert.fail;
8 import net.sourceforge.pmd.PMD;
9 import net.sourceforge.pmd.PMDException;
10 import net.sourceforge.pmd.Report;
11 import net.sourceforge.pmd.Rule;
12 import net.sourceforge.pmd.RuleContext;
13 import net.sourceforge.pmd.RuleSet;
14 import net.sourceforge.pmd.RuleSetFactory;
15 import net.sourceforge.pmd.RuleSetNotFoundException;
16 import net.sourceforge.pmd.RuleSets;
17 import net.sourceforge.pmd.SimpleRuleSetNameMapper;
18 import net.sourceforge.pmd.SourceType;
19 import net.sourceforge.pmd.SourceTypeToRuleLanguageMapper;
20
21 import org.w3c.dom.Document;
22 import org.w3c.dom.Element;
23 import org.w3c.dom.Node;
24 import org.w3c.dom.NodeList;
25 import org.xml.sax.SAXException;
26
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.StringReader;
30 import java.util.Properties;
31
32 import javax.xml.parsers.DocumentBuilder;
33 import javax.xml.parsers.DocumentBuilderFactory;
34 import javax.xml.parsers.FactoryConfigurationError;
35 import javax.xml.parsers.ParserConfigurationException;
36 /**
37 * Advanced methods for test cases
38 */
39 public abstract class RuleTst {
40 public static final SourceType DEFAULT_SOURCE_TYPE = SourceType.JAVA_15;
41
42 /**
43 * Find a rule in a certain ruleset by name
44 */
45 public Rule findRule(String ruleSet, String ruleName) {
46 try {
47 String rules = new SimpleRuleSetNameMapper(ruleSet).getRuleSets();
48 Rule rule = new RuleSetFactory().createRuleSets(rules).getRuleByName(ruleName);
49 if (rule == null) {
50 fail("Rule " + ruleName + " not found in ruleset " + ruleSet);
51 }
52 rule.setRuleSetName(ruleSet);
53 return rule;
54 } catch (RuleSetNotFoundException e) {
55 e.printStackTrace();
56 fail("Couldn't find ruleset " + ruleSet);
57 return null;
58 }
59 }
60
61
62 /**
63 * Run the rule on the given code, and check the expected number of violations.
64 */
65 public void runTest(TestDescriptor test) {
66 Rule rule = test.getRule();
67
68 if (test.getReinitializeRule()) {
69 rule = findRule(rule.getRuleSetName(), rule.getName());
70 }
71
72 Properties ruleProperties = rule.getProperties();
73 Properties oldProperties = (Properties)ruleProperties.clone();
74 try {
75 int res;
76 try {
77 if (test.getProperties() != null) {
78 oldProperties = (Properties)ruleProperties.clone();
79 ruleProperties.putAll(test.getProperties());
80 }
81
82 res = processUsingStringReader(test.getCode(), rule, test.getSourceType()).size();
83 } catch (Throwable t) {
84 t.printStackTrace();
85 throw new RuntimeException('"' + test.getDescription() + "\" failed");
86 }
87 assertEquals('"' + test.getDescription() + "\" resulted in wrong number of failures,",
88 test.getNumberOfProblemsExpected(), res);
89 } finally {
90
91 ruleProperties.clear();
92 ruleProperties.putAll(oldProperties);
93 }
94 }
95
96 private Report processUsingStringReader(String code, Rule rule,
97 SourceType sourceType) throws PMDException {
98 Report report = new Report();
99 runTestFromString(code, rule, report, sourceType);
100 return report;
101 }
102
103 /**
104 * Run the rule on the given code and put the violations in the report.
105 */
106 public void runTestFromString(String code, Rule rule, Report report, SourceType sourceType) throws PMDException {
107 PMD p = new PMD();
108 p.setJavaVersion(sourceType);
109 RuleContext ctx = new RuleContext();
110 ctx.setReport(report);
111 ctx.setSourceCodeFilename("n/a");
112 RuleSet rules = new RuleSet();
113 rules.addRule(rule);
114 rules.setLanguage(SourceTypeToRuleLanguageMapper.getMappedLanguage(sourceType));
115 p.processFile(new StringReader(code), new RuleSets(rules), ctx, sourceType);
116 }
117
118 /**
119 * getResourceAsStream tries to find the XML file in weird locations if the
120 * ruleName includes the package, so we strip it here.
121 */
122 protected String getCleanRuleName(Rule rule) {
123 String fullClassName = rule.getClass().getName();
124 if (fullClassName.equals(rule.getName())) {
125
126 String packageName = rule.getClass().getPackage().getName();
127 return fullClassName.substring(packageName.length()+1);
128 } else {
129 return rule.getName();
130 }
131 }
132
133 /**
134 * Extract a set of tests from an XML file. The file should be
135 * ./xml/RuleName.xml relative to the test class. The format is defined in
136 * test-data.xsd.
137 */
138 public TestDescriptor[] extractTestsFromXml(Rule rule) {
139 String testsFileName = getCleanRuleName(rule);
140
141 return extractTestsFromXml(rule, testsFileName);
142 }
143
144 public TestDescriptor[] extractTestsFromXml(Rule rule, String testsFileName) {
145 return extractTestsFromXml(rule, testsFileName, "xml/");
146 }
147 /**
148 * Extract a set of tests from an XML file with the given name. The file should be
149 * ./xml/[testsFileName].xml relative to the test class. The format is defined in
150 * test-data.xsd.
151 */
152 public TestDescriptor[] extractTestsFromXml(Rule rule, String testsFileName, String baseDirectory) {
153 String testXmlFileName = baseDirectory + testsFileName + ".xml";
154 InputStream inputStream = getClass().getResourceAsStream(testXmlFileName);
155 if (inputStream == null) {
156 throw new RuntimeException("Couldn't find " + testXmlFileName);
157 }
158
159 Document doc;
160 try {
161 DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
162 doc = builder.parse(inputStream);
163 } catch (ParserConfigurationException pce) {
164 pce.printStackTrace();
165 throw new RuntimeException("Couldn't parse " + testXmlFileName + ", due to: " + pce.getMessage());
166 } catch (FactoryConfigurationError fce) {
167 fce.printStackTrace();
168 throw new RuntimeException("Couldn't parse " + testXmlFileName + ", due to: " + fce.getMessage());
169 } catch (IOException ioe) {
170 ioe.printStackTrace();
171 throw new RuntimeException("Couldn't parse " + testXmlFileName + ", due to: " + ioe.getMessage());
172 } catch (SAXException se) {
173 se.printStackTrace();
174 throw new RuntimeException("Couldn't parse " + testXmlFileName + ", due to: " + se.getMessage());
175 }
176
177 return parseTests(rule, doc);
178 }
179
180 private TestDescriptor[] parseTests(Rule rule, Document doc) {
181 Element root = doc.getDocumentElement();
182 NodeList testCodes = root.getElementsByTagName("test-code");
183
184 TestDescriptor[] tests = new TestDescriptor[testCodes.getLength()];
185 for (int i = 0; i < testCodes.getLength(); i++) {
186 Element testCode = (Element)testCodes.item(i);
187
188 boolean reinitializeRule = false;
189 Node reinitializeRuleAttribute = testCode.getAttributes().getNamedItem("reinitializeRule");
190 if (reinitializeRuleAttribute != null) {
191 String reinitializeRuleValue = reinitializeRuleAttribute.getNodeValue();
192 if ("true".equalsIgnoreCase(reinitializeRuleValue) ||
193 "1".equalsIgnoreCase(reinitializeRuleValue)) {
194 reinitializeRule = true;
195 }
196 }
197
198 boolean isRegressionTest = true;
199 Node regressionTestAttribute = testCode.getAttributes().getNamedItem("regressionTest");
200 if (regressionTestAttribute != null) {
201 String reinitializeRuleValue = regressionTestAttribute.getNodeValue();
202 if ("false".equalsIgnoreCase(reinitializeRuleValue)) {
203 isRegressionTest = false;
204 }
205 }
206
207 NodeList ruleProperties = testCode.getElementsByTagName("rule-property");
208 Properties properties = new Properties();
209 for (int j = 0; j < ruleProperties.getLength(); j++) {
210 Node ruleProperty = ruleProperties.item(j);
211 String propertyName = ruleProperty.getAttributes().getNamedItem("name").getNodeValue();
212 properties.setProperty(propertyName, parseTextNode(ruleProperty));
213 }
214 int expectedProblems = Integer.parseInt(getNodeValue(testCode, "expected-problems", true));
215 String description = getNodeValue(testCode, "description", true);
216 String code = getNodeValue(testCode, "code", false);
217 if (code == null) {
218
219 NodeList coderefs = testCode.getElementsByTagName("code-ref");
220 if (coderefs.getLength()==0) {
221 throw new RuntimeException("Required tag is missing from the test-xml. Supply either a code or a code-ref tag");
222 }
223 Node coderef = coderefs.item(0);
224 String referenceId = coderef.getAttributes().getNamedItem("id").getNodeValue();
225 NodeList codeFragments = root.getElementsByTagName("code-fragment");
226 for (int j = 0; j < codeFragments.getLength(); j++) {
227 String fragmentId = codeFragments.item(j).getAttributes().getNamedItem("id").getNodeValue();
228 if (referenceId.equals(fragmentId)) {
229 code = parseTextNode(codeFragments.item(j));
230 }
231 }
232
233 if (code==null) {
234 throw new RuntimeException("No matching code fragment found for coderef");
235 }
236 }
237
238 String sourceTypeString = getNodeValue(testCode, "source-type", false);
239 if (sourceTypeString == null) {
240 tests[i] = new TestDescriptor(code, description, expectedProblems, rule);
241 } else {
242 SourceType sourceType = SourceType.getSourceTypeForId(sourceTypeString);
243 if (sourceType != null) {
244 tests[i] = new TestDescriptor(code, description, expectedProblems, rule, sourceType);
245 } else {
246 throw new RuntimeException("Unknown sourceType for test: " + sourceTypeString);
247 }
248 }
249 tests[i].setReinitializeRule(reinitializeRule);
250 tests[i].setRegressionTest(isRegressionTest);
251 tests[i].setProperties(properties);
252 }
253 return tests;
254 }
255
256 private String getNodeValue(Element parentElm, String nodeName, boolean required) {
257 NodeList nodes = parentElm.getElementsByTagName(nodeName);
258 if (nodes == null || nodes.getLength() == 0) {
259 if (required) {
260 throw new RuntimeException("Required tag is missing from the test-xml: " + nodeName);
261 } else {
262 return null;
263 }
264 }
265 Node node = nodes.item(0);
266 return parseTextNode(node);
267 }
268
269 private static String parseTextNode(Node exampleNode) {
270 StringBuffer buffer = new StringBuffer();
271 for (int i = 0; i < exampleNode.getChildNodes().getLength(); i++) {
272 Node node = exampleNode.getChildNodes().item(i);
273 if (node.getNodeType() == Node.CDATA_SECTION_NODE
274 || node.getNodeType() == Node.TEXT_NODE) {
275 buffer.append(node.getNodeValue());
276 }
277 }
278 return buffer.toString().trim();
279 }
280
281 /**
282 * Run the test using the DEFAULT_SOURCE_TYPE and put the violations in the report.
283 * Convenience method.
284 */
285 public void runTestFromString(String code, Rule rule, Report report) throws PMDException {
286 runTestFromString(code, rule, report, DEFAULT_SOURCE_TYPE);
287 }
288 }