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