001: /**
002: * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003: */package test.net.sourceforge.pmd.testframework;
004:
005: import static org.junit.Assert.assertEquals;
006: import static org.junit.Assert.fail;
007: import net.sourceforge.pmd.PMD;
008: import net.sourceforge.pmd.PMDException;
009: import net.sourceforge.pmd.Report;
010: import net.sourceforge.pmd.Rule;
011: import net.sourceforge.pmd.RuleContext;
012: import net.sourceforge.pmd.RuleSet;
013: import net.sourceforge.pmd.RuleSetFactory;
014: import net.sourceforge.pmd.RuleSetNotFoundException;
015: import net.sourceforge.pmd.RuleSets;
016: import net.sourceforge.pmd.SimpleRuleSetNameMapper;
017: import net.sourceforge.pmd.SourceType;
018: import net.sourceforge.pmd.SourceTypeToRuleLanguageMapper;
019:
020: import org.w3c.dom.Document;
021: import org.w3c.dom.Element;
022: import org.w3c.dom.Node;
023: import org.w3c.dom.NodeList;
024: import org.xml.sax.SAXException;
025:
026: import java.io.IOException;
027: import java.io.InputStream;
028: import java.io.StringReader;
029: import java.util.Properties;
030:
031: import javax.xml.parsers.DocumentBuilder;
032: import javax.xml.parsers.DocumentBuilderFactory;
033: import javax.xml.parsers.FactoryConfigurationError;
034: import javax.xml.parsers.ParserConfigurationException;
035:
036: /**
037: * Advanced methods for test cases
038: */
039: public abstract class RuleTst {
040: public static final SourceType DEFAULT_SOURCE_TYPE = SourceType.JAVA_15;
041:
042: /**
043: * Find a rule in a certain ruleset by name
044: */
045: public Rule findRule(String ruleSet, String ruleName) {
046: try {
047: String rules = new SimpleRuleSetNameMapper(ruleSet)
048: .getRuleSets();
049: Rule rule = new RuleSetFactory().createRuleSets(rules)
050: .getRuleByName(ruleName);
051: if (rule == null) {
052: fail("Rule " + ruleName + " not found in ruleset "
053: + ruleSet);
054: }
055: rule.setRuleSetName(ruleSet);
056: return rule;
057: } catch (RuleSetNotFoundException e) {
058: e.printStackTrace();
059: fail("Couldn't find ruleset " + ruleSet);
060: return null;
061: }
062: }
063:
064: /**
065: * Run the rule on the given code, and check the expected number of violations.
066: */
067: public void runTest(TestDescriptor test) {
068: Rule rule = test.getRule();
069:
070: if (test.getReinitializeRule()) {
071: rule = findRule(rule.getRuleSetName(), rule.getName());
072: }
073:
074: Properties ruleProperties = rule.getProperties();
075: Properties oldProperties = (Properties) ruleProperties.clone();
076: try {
077: int res;
078: try {
079: if (test.getProperties() != null) {
080: oldProperties = (Properties) ruleProperties.clone();
081: ruleProperties.putAll(test.getProperties());
082: }
083:
084: res = processUsingStringReader(test.getCode(), rule,
085: test.getSourceType()).size();
086: } catch (Throwable t) {
087: t.printStackTrace();
088: throw new RuntimeException('"' + test.getDescription()
089: + "\" failed");
090: }
091: assertEquals('"' + test.getDescription()
092: + "\" resulted in wrong number of failures,", test
093: .getNumberOfProblemsExpected(), res);
094: } finally {
095: //Restore old properties
096: ruleProperties.clear();
097: ruleProperties.putAll(oldProperties);
098: }
099: }
100:
101: private Report processUsingStringReader(String code, Rule rule,
102: SourceType sourceType) throws PMDException {
103: Report report = new Report();
104: runTestFromString(code, rule, report, sourceType);
105: return report;
106: }
107:
108: /**
109: * Run the rule on the given code and put the violations in the report.
110: */
111: public void runTestFromString(String code, Rule rule,
112: Report report, SourceType sourceType) throws PMDException {
113: PMD p = new PMD();
114: p.setJavaVersion(sourceType);
115: RuleContext ctx = new RuleContext();
116: ctx.setReport(report);
117: ctx.setSourceCodeFilename("n/a");
118: RuleSet rules = new RuleSet();
119: rules.addRule(rule);
120: rules.setLanguage(SourceTypeToRuleLanguageMapper
121: .getMappedLanguage(sourceType));
122: p.processFile(new StringReader(code), new RuleSets(rules), ctx,
123: sourceType);
124: }
125:
126: /**
127: * getResourceAsStream tries to find the XML file in weird locations if the
128: * ruleName includes the package, so we strip it here.
129: */
130: protected String getCleanRuleName(Rule rule) {
131: String fullClassName = rule.getClass().getName();
132: if (fullClassName.equals(rule.getName())) {
133: //We got the full class name, so we'll use the stripped name instead
134: String packageName = rule.getClass().getPackage().getName();
135: return fullClassName.substring(packageName.length() + 1);
136: } else {
137: return rule.getName(); //Test is using findRule, smart!
138: }
139: }
140:
141: /**
142: * Extract a set of tests from an XML file. The file should be
143: * ./xml/RuleName.xml relative to the test class. The format is defined in
144: * test-data.xsd.
145: */
146: public TestDescriptor[] extractTestsFromXml(Rule rule) {
147: String testsFileName = getCleanRuleName(rule);
148:
149: return extractTestsFromXml(rule, testsFileName);
150: }
151:
152: public TestDescriptor[] extractTestsFromXml(Rule rule,
153: String testsFileName) {
154: return extractTestsFromXml(rule, testsFileName, "xml/");
155: }
156:
157: /**
158: * Extract a set of tests from an XML file with the given name. The file should be
159: * ./xml/[testsFileName].xml relative to the test class. The format is defined in
160: * test-data.xsd.
161: */
162: public TestDescriptor[] extractTestsFromXml(Rule rule,
163: String testsFileName, String baseDirectory) {
164: String testXmlFileName = baseDirectory + testsFileName + ".xml";
165: InputStream inputStream = getClass().getResourceAsStream(
166: testXmlFileName);
167: if (inputStream == null) {
168: throw new RuntimeException("Couldn't find "
169: + testXmlFileName);
170: }
171:
172: Document doc;
173: try {
174: DocumentBuilder builder = DocumentBuilderFactory
175: .newInstance().newDocumentBuilder();
176: doc = builder.parse(inputStream);
177: } catch (ParserConfigurationException pce) {
178: pce.printStackTrace();
179: throw new RuntimeException("Couldn't parse "
180: + testXmlFileName + ", due to: " + pce.getMessage());
181: } catch (FactoryConfigurationError fce) {
182: fce.printStackTrace();
183: throw new RuntimeException("Couldn't parse "
184: + testXmlFileName + ", due to: " + fce.getMessage());
185: } catch (IOException ioe) {
186: ioe.printStackTrace();
187: throw new RuntimeException("Couldn't parse "
188: + testXmlFileName + ", due to: " + ioe.getMessage());
189: } catch (SAXException se) {
190: se.printStackTrace();
191: throw new RuntimeException("Couldn't parse "
192: + testXmlFileName + ", due to: " + se.getMessage());
193: }
194:
195: return parseTests(rule, doc);
196: }
197:
198: private TestDescriptor[] parseTests(Rule rule, Document doc) {
199: Element root = doc.getDocumentElement();
200: NodeList testCodes = root.getElementsByTagName("test-code");
201:
202: TestDescriptor[] tests = new TestDescriptor[testCodes
203: .getLength()];
204: for (int i = 0; i < testCodes.getLength(); i++) {
205: Element testCode = (Element) testCodes.item(i);
206:
207: boolean reinitializeRule = false;
208: Node reinitializeRuleAttribute = testCode.getAttributes()
209: .getNamedItem("reinitializeRule");
210: if (reinitializeRuleAttribute != null) {
211: String reinitializeRuleValue = reinitializeRuleAttribute
212: .getNodeValue();
213: if ("true".equalsIgnoreCase(reinitializeRuleValue)
214: || "1".equalsIgnoreCase(reinitializeRuleValue)) {
215: reinitializeRule = true;
216: }
217: }
218:
219: boolean isRegressionTest = true;
220: Node regressionTestAttribute = testCode.getAttributes()
221: .getNamedItem("regressionTest");
222: if (regressionTestAttribute != null) {
223: String reinitializeRuleValue = regressionTestAttribute
224: .getNodeValue();
225: if ("false".equalsIgnoreCase(reinitializeRuleValue)) {
226: isRegressionTest = false;
227: }
228: }
229:
230: NodeList ruleProperties = testCode
231: .getElementsByTagName("rule-property");
232: Properties properties = new Properties();
233: for (int j = 0; j < ruleProperties.getLength(); j++) {
234: Node ruleProperty = ruleProperties.item(j);
235: String propertyName = ruleProperty.getAttributes()
236: .getNamedItem("name").getNodeValue();
237: properties.setProperty(propertyName,
238: parseTextNode(ruleProperty));
239: }
240: int expectedProblems = Integer.parseInt(getNodeValue(
241: testCode, "expected-problems", true));
242: String description = getNodeValue(testCode, "description",
243: true);
244: String code = getNodeValue(testCode, "code", false);
245: if (code == null) {
246: //Should have a coderef
247: NodeList coderefs = testCode
248: .getElementsByTagName("code-ref");
249: if (coderefs.getLength() == 0) {
250: throw new RuntimeException(
251: "Required tag is missing from the test-xml. Supply either a code or a code-ref tag");
252: }
253: Node coderef = coderefs.item(0);
254: String referenceId = coderef.getAttributes()
255: .getNamedItem("id").getNodeValue();
256: NodeList codeFragments = root
257: .getElementsByTagName("code-fragment");
258: for (int j = 0; j < codeFragments.getLength(); j++) {
259: String fragmentId = codeFragments.item(j)
260: .getAttributes().getNamedItem("id")
261: .getNodeValue();
262: if (referenceId.equals(fragmentId)) {
263: code = parseTextNode(codeFragments.item(j));
264: }
265: }
266:
267: if (code == null) {
268: throw new RuntimeException(
269: "No matching code fragment found for coderef");
270: }
271: }
272:
273: String sourceTypeString = getNodeValue(testCode,
274: "source-type", false);
275: if (sourceTypeString == null) {
276: tests[i] = new TestDescriptor(code, description,
277: expectedProblems, rule);
278: } else {
279: SourceType sourceType = SourceType
280: .getSourceTypeForId(sourceTypeString);
281: if (sourceType != null) {
282: tests[i] = new TestDescriptor(code, description,
283: expectedProblems, rule, sourceType);
284: } else {
285: throw new RuntimeException(
286: "Unknown sourceType for test: "
287: + sourceTypeString);
288: }
289: }
290: tests[i].setReinitializeRule(reinitializeRule);
291: tests[i].setRegressionTest(isRegressionTest);
292: tests[i].setProperties(properties);
293: }
294: return tests;
295: }
296:
297: private String getNodeValue(Element parentElm, String nodeName,
298: boolean required) {
299: NodeList nodes = parentElm.getElementsByTagName(nodeName);
300: if (nodes == null || nodes.getLength() == 0) {
301: if (required) {
302: throw new RuntimeException(
303: "Required tag is missing from the test-xml: "
304: + nodeName);
305: } else {
306: return null;
307: }
308: }
309: Node node = nodes.item(0);
310: return parseTextNode(node);
311: }
312:
313: private static String parseTextNode(Node exampleNode) {
314: StringBuffer buffer = new StringBuffer();
315: for (int i = 0; i < exampleNode.getChildNodes().getLength(); i++) {
316: Node node = exampleNode.getChildNodes().item(i);
317: if (node.getNodeType() == Node.CDATA_SECTION_NODE
318: || node.getNodeType() == Node.TEXT_NODE) {
319: buffer.append(node.getNodeValue());
320: }
321: }
322: return buffer.toString().trim();
323: }
324:
325: /**
326: * Run the test using the DEFAULT_SOURCE_TYPE and put the violations in the report.
327: * Convenience method.
328: */
329: public void runTestFromString(String code, Rule rule, Report report)
330: throws PMDException {
331: runTestFromString(code, rule, report, DEFAULT_SOURCE_TYPE);
332: }
333: }
|