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