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.getCode(), rule, test.getLanguageVersion());
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(String code, Rule rule,
194 LanguageVersion languageVersion) throws PMDException {
195 Report report = new Report();
196 runTestFromString(code, rule, report, languageVersion);
197 return report;
198 }
199
200
201
202
203 public void runTestFromString(String code, Rule rule, Report report, LanguageVersion languageVersion) {
204 try {
205 PMD p = new PMD();
206 p.getConfiguration().setDefaultLanguageVersion(languageVersion);
207 p.getConfiguration().prependClasspath(".");
208 RuleContext ctx = new RuleContext();
209 ctx.setReport(report);
210 ctx.setSourceCodeFilename("n/a");
211 ctx.setLanguageVersion(languageVersion);
212 ctx.setIgnoreExceptions(false);
213 RuleSet rules = new RuleSet();
214 rules.addRule(rule);
215 p.getSourceCodeProcessor().processSourceCode(new StringReader(code), new RuleSets(rules), ctx);
216 } catch (Exception e) {
217 throw new RuntimeException(e);
218 }
219 }
220
221
222
223
224
225 protected String getCleanRuleName(Rule rule) {
226 String fullClassName = rule.getClass().getName();
227 if (fullClassName.equals(rule.getName())) {
228
229 String packageName = rule.getClass().getPackage().getName();
230 return fullClassName.substring(packageName.length()+1);
231 } else {
232 return rule.getName();
233 }
234 }
235
236
237
238
239
240
241 public TestDescriptor[] extractTestsFromXml(Rule rule) {
242 String testsFileName = getCleanRuleName(rule);
243
244 return extractTestsFromXml(rule, testsFileName);
245 }
246
247 public TestDescriptor[] extractTestsFromXml(Rule rule, String testsFileName) {
248 return extractTestsFromXml(rule, testsFileName, "xml/");
249 }
250
251
252
253
254
255 public TestDescriptor[] extractTestsFromXml(Rule rule, String testsFileName, String baseDirectory) {
256 String testXmlFileName = baseDirectory + testsFileName + ".xml";
257 InputStream inputStream = getClass().getResourceAsStream(testXmlFileName);
258 if (inputStream == null) {
259 throw new RuntimeException("Couldn't find " + testXmlFileName);
260 }
261
262 Document doc;
263 try {
264 DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
265 doc = builder.parse(inputStream);
266 } catch (ParserConfigurationException pce) {
267 pce.printStackTrace();
268 throw new RuntimeException("Couldn't parse " + testXmlFileName + ", due to: " + pce.getMessage());
269 } catch (FactoryConfigurationError fce) {
270 fce.printStackTrace();
271 throw new RuntimeException("Couldn't parse " + testXmlFileName + ", due to: " + fce.getMessage());
272 } catch (IOException ioe) {
273 ioe.printStackTrace();
274 throw new RuntimeException("Couldn't parse " + testXmlFileName + ", due to: " + ioe.getMessage());
275 } catch (SAXException se) {
276 se.printStackTrace();
277 throw new RuntimeException("Couldn't parse " + testXmlFileName + ", due to: " + se.getMessage());
278 }
279
280 return parseTests(rule, doc);
281 }
282
283 private TestDescriptor[] parseTests(Rule rule, Document doc) {
284 Element root = doc.getDocumentElement();
285 NodeList testCodes = root.getElementsByTagName("test-code");
286
287 TestDescriptor[] tests = new TestDescriptor[testCodes.getLength()];
288 for (int i = 0; i < testCodes.getLength(); i++) {
289 Element testCode = (Element)testCodes.item(i);
290
291 boolean reinitializeRule = true;
292 Node reinitializeRuleAttribute = testCode.getAttributes().getNamedItem("reinitializeRule");
293 if (reinitializeRuleAttribute != null) {
294 String reinitializeRuleValue = reinitializeRuleAttribute.getNodeValue();
295 if ("false".equalsIgnoreCase(reinitializeRuleValue) ||
296 "0".equalsIgnoreCase(reinitializeRuleValue)) {
297 reinitializeRule = false;
298 }
299 }
300
301 boolean isRegressionTest = true;
302 Node regressionTestAttribute = testCode.getAttributes().getNamedItem("regressionTest");
303 if (regressionTestAttribute != null) {
304 String reinitializeRuleValue = regressionTestAttribute.getNodeValue();
305 if ("false".equalsIgnoreCase(reinitializeRuleValue)) {
306 isRegressionTest = false;
307 }
308 }
309
310 NodeList ruleProperties = testCode.getElementsByTagName("rule-property");
311 Properties properties = new Properties();
312 for (int j = 0; j < ruleProperties.getLength(); j++) {
313 Node ruleProperty = ruleProperties.item(j);
314 String propertyName = ruleProperty.getAttributes().getNamedItem("name").getNodeValue();
315 properties.setProperty(propertyName, parseTextNode(ruleProperty));
316 }
317
318 NodeList expectedMessagesNodes = testCode.getElementsByTagName("expected-messages");
319 List<String> messages = new ArrayList<String>();
320 if (expectedMessagesNodes != null && expectedMessagesNodes.getLength() > 0) {
321 Element item = (Element)expectedMessagesNodes.item(0);
322 NodeList messagesNodes = item.getElementsByTagName("message");
323 for (int j = 0; j < messagesNodes.getLength(); j++) {
324 messages.add(parseTextNode(messagesNodes.item(j)));
325 }
326 }
327
328 NodeList expectedLineNumbersNodes = testCode.getElementsByTagName("expected-linenumbers");
329 List<Integer> expectedLineNumbers = new ArrayList<Integer>();
330 if (expectedLineNumbersNodes != null && expectedLineNumbersNodes.getLength() > 0) {
331 Element item = (Element)expectedLineNumbersNodes.item(0);
332 String numbers = item.getTextContent();
333 for (String n : numbers.split(" *, *")) {
334 expectedLineNumbers.add(Integer.valueOf(n));
335 }
336 }
337
338 String code = getNodeValue(testCode, "code", false);
339 if (code == null) {
340
341 NodeList coderefs = testCode.getElementsByTagName("code-ref");
342 if (coderefs.getLength()==0) {
343 throw new RuntimeException("Required tag is missing from the test-xml. Supply either a code or a code-ref tag");
344 }
345 Node coderef = coderefs.item(0);
346 String referenceId = coderef.getAttributes().getNamedItem("id").getNodeValue();
347 NodeList codeFragments = root.getElementsByTagName("code-fragment");
348 for (int j = 0; j < codeFragments.getLength(); j++) {
349 String fragmentId = codeFragments.item(j).getAttributes().getNamedItem("id").getNodeValue();
350 if (referenceId.equals(fragmentId)) {
351 code = parseTextNode(codeFragments.item(j));
352 }
353 }
354
355 if (code==null) {
356 throw new RuntimeException("No matching code fragment found for coderef");
357 }
358 }
359
360 String description = getNodeValue(testCode, "description", true);
361 int expectedProblems = Integer.parseInt(getNodeValue(testCode, "expected-problems", true));
362
363 String languageVersionString = getNodeValue(testCode, "source-type", false);
364 if (languageVersionString == null) {
365 tests[i] = new TestDescriptor(code, description, expectedProblems, rule);
366 } else {
367 LanguageVersion languageVersion = LanguageRegistry.findLanguageVersionByTerseName(languageVersionString);
368 if (languageVersion != null) {
369 tests[i] = new TestDescriptor(code, description, expectedProblems, rule, languageVersion);
370 } else {
371 throw new RuntimeException("Unknown LanguageVersion for test: " + languageVersionString);
372 }
373 }
374 tests[i].setReinitializeRule(reinitializeRule);
375 tests[i].setRegressionTest(isRegressionTest);
376 tests[i].setExpectedMessages(messages);
377 tests[i].setExpectedLineNumbers(expectedLineNumbers);
378 tests[i].setProperties(properties);
379 tests[i].setNumberInDocument(i);
380 }
381 return tests;
382 }
383
384 private String getNodeValue(Element parentElm, String nodeName, boolean required) {
385 NodeList nodes = parentElm.getElementsByTagName(nodeName);
386 if (nodes == null || nodes.getLength() == 0) {
387 if (required) {
388 throw new RuntimeException("Required tag is missing from the test-xml: " + nodeName);
389 } else {
390 return null;
391 }
392 }
393 Node node = nodes.item(0);
394 return parseTextNode(node);
395 }
396
397 private static String parseTextNode(Node exampleNode) {
398 StringBuffer buffer = new StringBuffer();
399 for (int i = 0; i < exampleNode.getChildNodes().getLength(); i++) {
400 Node node = exampleNode.getChildNodes().item(i);
401 if (node.getNodeType() == Node.CDATA_SECTION_NODE
402 || node.getNodeType() == Node.TEXT_NODE) {
403 buffer.append(node.getNodeValue());
404 }
405 }
406 return buffer.toString().trim();
407 }
408 }