1
2
3
4 package net.sourceforge.pmd.testframework;
5
6 import static org.junit.Assert.assertEquals;
7 import static org.junit.Assert.fail;
8
9 import java.io.IOException;
10 import java.io.InputStream;
11 import java.io.StringReader;
12 import java.io.StringWriter;
13 import java.util.ArrayList;
14 import java.util.Iterator;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.Properties;
18
19 import javax.xml.parsers.DocumentBuilder;
20 import javax.xml.parsers.DocumentBuilderFactory;
21 import javax.xml.parsers.FactoryConfigurationError;
22 import javax.xml.parsers.ParserConfigurationException;
23
24 import net.sourceforge.pmd.PMD;
25 import net.sourceforge.pmd.PMDException;
26 import net.sourceforge.pmd.PropertyDescriptor;
27 import net.sourceforge.pmd.Report;
28 import net.sourceforge.pmd.Rule;
29 import net.sourceforge.pmd.RuleContext;
30 import net.sourceforge.pmd.RuleSet;
31 import net.sourceforge.pmd.RuleSetFactory;
32 import net.sourceforge.pmd.RuleSetNotFoundException;
33 import net.sourceforge.pmd.RuleSets;
34 import net.sourceforge.pmd.RuleViolation;
35 import net.sourceforge.pmd.lang.LanguageRegistry;
36 import net.sourceforge.pmd.lang.LanguageVersion;
37 import net.sourceforge.pmd.renderers.TextRenderer;
38
39 import org.w3c.dom.Document;
40 import org.w3c.dom.Element;
41 import org.w3c.dom.Node;
42 import org.w3c.dom.NodeList;
43 import org.xml.sax.SAXException;
44
45
46
47 public abstract class RuleTst {
48
49
50
51 public Rule findRule(String ruleSet, String ruleName) {
52 try {
53 Rule rule = new RuleSetFactory().createRuleSets(ruleSet).getRuleByName(ruleName);
54 if (rule == null) {
55 fail("Rule " + ruleName + " not found in ruleset " + ruleSet);
56 }
57 rule.setRuleSetName(ruleSet);
58 return rule;
59 } catch (RuleSetNotFoundException e) {
60 e.printStackTrace();
61 fail("Couldn't find ruleset " + ruleSet);
62 return null;
63 }
64 }
65
66
67
68
69
70 @SuppressWarnings("unchecked")
71 public void runTest(TestDescriptor test) {
72 Rule rule = test.getRule();
73
74 if (test.getReinitializeRule()) {
75 rule = findRule(rule.getRuleSetName(), rule.getName());
76 }
77
78 Map<PropertyDescriptor<?>, Object> oldProperties = rule.getPropertiesByPropertyDescriptor();
79 try {
80 int res;
81 Report report;
82 try {
83
84 if (test.getProperties() != null) {
85 for (Map.Entry<Object, Object> entry : test.getProperties().entrySet()) {
86 String propertyName = (String)entry.getKey();
87 PropertyDescriptor propertyDescriptor = rule.getPropertyDescriptor(propertyName);
88 if (propertyDescriptor == null) {
89 throw new IllegalArgumentException("No such property '" + propertyName + "' on Rule " + rule.getName());
90 }
91
92 Object value = propertyDescriptor.valueFrom((String)entry.getValue());
93 rule.setProperty(propertyDescriptor, value);
94 }
95 }
96
97 report = processUsingStringReader(test, rule);
98 res = report.size();
99 } catch (Throwable t) {
100 t.printStackTrace();
101 throw new RuntimeException('"' + test.getDescription() + "\" failed", t);
102 }
103 if (test.getNumberOfProblemsExpected() != res) {
104 printReport(test, report);
105 }
106 assertEquals('"' + test.getDescription() + "\" resulted in wrong number of failures,",
107 test.getNumberOfProblemsExpected(), res);
108 assertMessages(report, test);
109 assertLineNumbers(report, test);
110 } finally {
111
112
113
114 for (Map.Entry entry: oldProperties.entrySet()) {
115 rule.setProperty((PropertyDescriptor)entry.getKey(), entry.getValue());
116 }
117 }
118 }
119
120 private void assertMessages(Report report, TestDescriptor test) {
121 if (report == null || test.getExpectedMessages().isEmpty()) {
122 return;
123 }
124
125 List<String> expectedMessages = test.getExpectedMessages();
126 if (report.size() != expectedMessages.size()) {
127 throw new RuntimeException("Test setup error: number of expected messages doesn't match "
128 + "number of violations for test case '" + test.getDescription() + "'");
129 }
130
131 Iterator<RuleViolation> it = report.iterator();
132 int index = 0;
133 while (it.hasNext()) {
134 RuleViolation violation = it.next();
135 String actual = violation.getDescription();
136 if (!expectedMessages.get(index).equals(actual)) {
137 printReport(test, report);
138 }
139 assertEquals(
140 '"' + test.getDescription() + "\" produced wrong message on violation number " + (index + 1) + ".",
141 expectedMessages.get(index), actual);
142 index++;
143 }
144 }
145
146 private void assertLineNumbers(Report report, TestDescriptor test) {
147 if (report == null || test.getExpectedLineNumbers().isEmpty()) {
148 return;
149 }
150
151 List<Integer> expected = test.getExpectedLineNumbers();
152 if (report.getViolationTree().size() != expected.size()) {
153 throw new RuntimeException("Test setup error: number of execpted line numbers doesn't match "
154 + "number of violations for test case '" + test.getDescription() + "'");
155 }
156
157 Iterator<RuleViolation> it = report.getViolationTree().iterator();
158 int index = 0;
159 while (it.hasNext()) {
160 RuleViolation violation = it.next();
161 Integer actual = violation.getBeginLine();
162 if (expected.get(index) != actual.intValue()) {
163 printReport(test, report);
164 }
165 assertEquals(
166 '"' + test.getDescription() + "\" violation on wrong line number: violation number " + (index + 1) + ".",
167 expected.get(index), actual);
168 index++;
169 }
170 }
171
172 private void printReport(TestDescriptor test, Report report) {
173 System.out.println("--------------------------------------------------------------");
174 System.out.println("Test Failure: " + test.getDescription());
175 System.out.println(" -> Expected " + test.getNumberOfProblemsExpected() + " problem(s), "
176 + report.size() + " problem(s) found.");
177 System.out.println(" -> Expected messages: " + test.getExpectedMessages());
178 System.out.println(" -> Expected line numbers: " + test.getExpectedLineNumbers());
179 System.out.println();
180 TextRenderer renderer = new TextRenderer();
181 renderer.setWriter(new StringWriter());
182 try {
183 renderer.start();
184 renderer.renderFileReport(report);
185 renderer.end();
186 } catch (IOException e) {
187 throw new RuntimeException(e);
188 }
189 System.out.println(renderer.getWriter().toString());
190 System.out.println("--------------------------------------------------------------");
191 }
192
193 private Report processUsingStringReader(TestDescriptor test, Rule rule) throws PMDException {
194 Report report = new Report();
195 runTestFromString(test, rule, report);
196 return report;
197 }
198
199
200
201
202 public void runTestFromString(String code, Rule rule, Report report, LanguageVersion languageVersion) {
203 runTestFromString(code, rule, report, languageVersion, true);
204 }
205
206 public void runTestFromString(String code, Rule rule, Report report, LanguageVersion languageVersion, boolean isUseAuxClasspath) {
207 try {
208 PMD p = new PMD();
209 p.getConfiguration().setDefaultLanguageVersion(languageVersion);
210 if (isUseAuxClasspath) {
211 p.getConfiguration().prependClasspath(".");
212 }
213 RuleContext ctx = new RuleContext();
214 ctx.setReport(report);
215 ctx.setSourceCodeFilename("n/a");
216 ctx.setLanguageVersion(languageVersion);
217 ctx.setIgnoreExceptions(false);
218 RuleSet rules = new RuleSet();
219 rules.addRule(rule);
220 p.getSourceCodeProcessor().processSourceCode(new StringReader(code), new RuleSets(rules), ctx);
221 } catch (Exception e) {
222 throw new RuntimeException(e);
223 }
224 }
225
226 public void runTestFromString(TestDescriptor test, Rule rule, Report report) {
227 runTestFromString(test.getCode(), rule, report, test.getLanguageVersion(), test.isUseAuxClasspath());
228 }
229
230
231
232
233
234 protected String getCleanRuleName(Rule rule) {
235 String fullClassName = rule.getClass().getName();
236 if (fullClassName.equals(rule.getName())) {
237
238 String packageName = rule.getClass().getPackage().getName();
239 return fullClassName.substring(packageName.length()+1);
240 } else {
241 return rule.getName();
242 }
243 }
244
245
246
247
248
249
250 public TestDescriptor[] extractTestsFromXml(Rule rule) {
251 String testsFileName = getCleanRuleName(rule);
252
253 return extractTestsFromXml(rule, testsFileName);
254 }
255
256 public TestDescriptor[] extractTestsFromXml(Rule rule, String testsFileName) {
257 return extractTestsFromXml(rule, testsFileName, "xml/");
258 }
259
260
261
262
263
264 public TestDescriptor[] extractTestsFromXml(Rule rule, String testsFileName, String baseDirectory) {
265 String testXmlFileName = baseDirectory + testsFileName + ".xml";
266 InputStream inputStream = getClass().getResourceAsStream(testXmlFileName);
267 if (inputStream == null) {
268 throw new RuntimeException("Couldn't find " + testXmlFileName);
269 }
270
271 Document doc;
272 try {
273 DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
274 doc = builder.parse(inputStream);
275 } catch (ParserConfigurationException pce) {
276 pce.printStackTrace();
277 throw new RuntimeException("Couldn't parse " + testXmlFileName + ", due to: " + pce.getMessage());
278 } catch (FactoryConfigurationError fce) {
279 fce.printStackTrace();
280 throw new RuntimeException("Couldn't parse " + testXmlFileName + ", due to: " + fce.getMessage());
281 } catch (IOException ioe) {
282 ioe.printStackTrace();
283 throw new RuntimeException("Couldn't parse " + testXmlFileName + ", due to: " + ioe.getMessage());
284 } catch (SAXException se) {
285 se.printStackTrace();
286 throw new RuntimeException("Couldn't parse " + testXmlFileName + ", due to: " + se.getMessage());
287 }
288
289 return parseTests(rule, doc);
290 }
291
292 private TestDescriptor[] parseTests(Rule rule, Document doc) {
293 Element root = doc.getDocumentElement();
294 NodeList testCodes = root.getElementsByTagName("test-code");
295
296 TestDescriptor[] tests = new TestDescriptor[testCodes.getLength()];
297 for (int i = 0; i < testCodes.getLength(); i++) {
298 Element testCode = (Element)testCodes.item(i);
299
300 boolean reinitializeRule = true;
301 Node reinitializeRuleAttribute = testCode.getAttributes().getNamedItem("reinitializeRule");
302 if (reinitializeRuleAttribute != null) {
303 String reinitializeRuleValue = reinitializeRuleAttribute.getNodeValue();
304 if ("false".equalsIgnoreCase(reinitializeRuleValue) ||
305 "0".equalsIgnoreCase(reinitializeRuleValue)) {
306 reinitializeRule = false;
307 }
308 }
309
310 boolean isRegressionTest = true;
311 Node regressionTestAttribute = testCode.getAttributes().getNamedItem("regressionTest");
312 if (regressionTestAttribute != null) {
313 String reinitializeRuleValue = regressionTestAttribute.getNodeValue();
314 if ("false".equalsIgnoreCase(reinitializeRuleValue)) {
315 isRegressionTest = false;
316 }
317 }
318
319 boolean isUseAuxClasspath = true;
320 Node useAuxClasspathAttribute = testCode.getAttributes().getNamedItem("useAuxClasspath");
321 if (useAuxClasspathAttribute != null) {
322 String useAuxClasspathValue = useAuxClasspathAttribute.getNodeValue();
323 if ("false".equalsIgnoreCase(useAuxClasspathValue)) {
324 isUseAuxClasspath = false;
325 }
326 }
327
328 NodeList ruleProperties = testCode.getElementsByTagName("rule-property");
329 Properties properties = new Properties();
330 for (int j = 0; j < ruleProperties.getLength(); j++) {
331 Node ruleProperty = ruleProperties.item(j);
332 String propertyName = ruleProperty.getAttributes().getNamedItem("name").getNodeValue();
333 properties.setProperty(propertyName, parseTextNode(ruleProperty));
334 }
335
336 NodeList expectedMessagesNodes = testCode.getElementsByTagName("expected-messages");
337 List<String> messages = new ArrayList<String>();
338 if (expectedMessagesNodes != null && expectedMessagesNodes.getLength() > 0) {
339 Element item = (Element)expectedMessagesNodes.item(0);
340 NodeList messagesNodes = item.getElementsByTagName("message");
341 for (int j = 0; j < messagesNodes.getLength(); j++) {
342 messages.add(parseTextNode(messagesNodes.item(j)));
343 }
344 }
345
346 NodeList expectedLineNumbersNodes = testCode.getElementsByTagName("expected-linenumbers");
347 List<Integer> expectedLineNumbers = new ArrayList<Integer>();
348 if (expectedLineNumbersNodes != null && expectedLineNumbersNodes.getLength() > 0) {
349 Element item = (Element)expectedLineNumbersNodes.item(0);
350 String numbers = item.getTextContent();
351 for (String n : numbers.split(" *, *")) {
352 expectedLineNumbers.add(Integer.valueOf(n));
353 }
354 }
355
356 String code = getNodeValue(testCode, "code", false);
357 if (code == null) {
358
359 NodeList coderefs = testCode.getElementsByTagName("code-ref");
360 if (coderefs.getLength()==0) {
361 throw new RuntimeException("Required tag is missing from the test-xml. Supply either a code or a code-ref tag");
362 }
363 Node coderef = coderefs.item(0);
364 String referenceId = coderef.getAttributes().getNamedItem("id").getNodeValue();
365 NodeList codeFragments = root.getElementsByTagName("code-fragment");
366 for (int j = 0; j < codeFragments.getLength(); j++) {
367 String fragmentId = codeFragments.item(j).getAttributes().getNamedItem("id").getNodeValue();
368 if (referenceId.equals(fragmentId)) {
369 code = parseTextNode(codeFragments.item(j));
370 }
371 }
372
373 if (code==null) {
374 throw new RuntimeException("No matching code fragment found for coderef");
375 }
376 }
377
378 String description = getNodeValue(testCode, "description", true);
379 int expectedProblems = Integer.parseInt(getNodeValue(testCode, "expected-problems", true));
380
381 String languageVersionString = getNodeValue(testCode, "source-type", false);
382 if (languageVersionString == null) {
383 tests[i] = new TestDescriptor(code, description, expectedProblems, rule);
384 } else {
385 LanguageVersion languageVersion = LanguageRegistry.findLanguageVersionByTerseName(languageVersionString);
386 if (languageVersion != null) {
387 tests[i] = new TestDescriptor(code, description, expectedProblems, rule, languageVersion);
388 } else {
389 throw new RuntimeException("Unknown LanguageVersion for test: " + languageVersionString);
390 }
391 }
392 tests[i].setReinitializeRule(reinitializeRule);
393 tests[i].setRegressionTest(isRegressionTest);
394 tests[i].setUseAuxClasspath(isUseAuxClasspath);
395 tests[i].setExpectedMessages(messages);
396 tests[i].setExpectedLineNumbers(expectedLineNumbers);
397 tests[i].setProperties(properties);
398 tests[i].setNumberInDocument(i);
399 }
400 return tests;
401 }
402
403 private String getNodeValue(Element parentElm, String nodeName, boolean required) {
404 NodeList nodes = parentElm.getElementsByTagName(nodeName);
405 if (nodes == null || nodes.getLength() == 0) {
406 if (required) {
407 throw new RuntimeException("Required tag is missing from the test-xml: " + nodeName);
408 } else {
409 return null;
410 }
411 }
412 Node node = nodes.item(0);
413 return parseTextNode(node);
414 }
415
416 private static String parseTextNode(Node exampleNode) {
417 StringBuffer buffer = new StringBuffer();
418 for (int i = 0; i < exampleNode.getChildNodes().getLength(); i++) {
419 Node node = exampleNode.getChildNodes().item(i);
420 if (node.getNodeType() == Node.CDATA_SECTION_NODE
421 || node.getNodeType() == Node.TEXT_NODE) {
422 buffer.append(node.getNodeValue());
423 }
424 }
425 return buffer.toString().trim();
426 }
427 }