View Javadoc

1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
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.Map;
14  import java.util.Properties;
15  
16  import javax.xml.parsers.DocumentBuilder;
17  import javax.xml.parsers.DocumentBuilderFactory;
18  import javax.xml.parsers.FactoryConfigurationError;
19  import javax.xml.parsers.ParserConfigurationException;
20  
21  import net.sourceforge.pmd.PMD;
22  import net.sourceforge.pmd.PMDException;
23  import net.sourceforge.pmd.PropertyDescriptor;
24  import net.sourceforge.pmd.Report;
25  import net.sourceforge.pmd.Rule;
26  import net.sourceforge.pmd.RuleContext;
27  import net.sourceforge.pmd.RuleSet;
28  import net.sourceforge.pmd.RuleSetFactory;
29  import net.sourceforge.pmd.RuleSetNotFoundException;
30  import net.sourceforge.pmd.RuleSets;
31  import net.sourceforge.pmd.lang.Language;
32  import net.sourceforge.pmd.lang.LanguageVersion;
33  import net.sourceforge.pmd.renderers.TextRenderer;
34  
35  import org.w3c.dom.Document;
36  import org.w3c.dom.Element;
37  import org.w3c.dom.Node;
38  import org.w3c.dom.NodeList;
39  import org.xml.sax.SAXException;
40  /**
41   * Advanced methods for test cases
42   */
43  public abstract class RuleTst {
44      public static final LanguageVersion DEFAULT_LANGUAGE_VERSION = LanguageVersion.JAVA_15;
45      public static final Language DEFAULT_LANGUAGE = DEFAULT_LANGUAGE_VERSION.getLanguage();
46  
47      /**
48       * Find a rule in a certain ruleset by name
49       */
50      public Rule findRule(String ruleSet, String ruleName) {
51          try {
52              Rule rule = new RuleSetFactory().createRuleSets(ruleSet).getRuleByName(ruleName);
53              if (rule == null) {
54                  fail("Rule " + ruleName + " not found in ruleset " + ruleSet);
55              }
56              rule.setRuleSetName(ruleSet);
57              return rule;
58          } catch (RuleSetNotFoundException e) {
59              e.printStackTrace();
60              fail("Couldn't find ruleset " + ruleSet);
61              return null;
62          }
63      }
64  
65  
66      /**
67       * Run the rule on the given code, and check the expected number of violations.
68       */
69      @SuppressWarnings("unchecked")
70      public void runTest(TestDescriptor test) {
71          Rule rule = test.getRule();
72  
73          if (test.getReinitializeRule()) {
74              rule = findRule(rule.getRuleSetName(), rule.getName());
75          }
76  
77          Map<PropertyDescriptor<?>, Object> oldProperties = rule.getPropertiesByPropertyDescriptor();
78          try {
79              int res;
80              Report report;
81              try {
82          	// Set test specific properties onto the Rule
83                  if (test.getProperties() != null) {
84                      for (Map.Entry<Object, Object> entry : test.getProperties().entrySet()) {
85                  	String propertyName = (String)entry.getKey();
86                  	String strValue = (String)entry.getValue();
87                  	PropertyDescriptor propertyDescriptor = rule.getPropertyDescriptor(propertyName);
88                  	if (propertyDescriptor == null) {
89                              throw new IllegalArgumentException("No such property '" + propertyName + "' on Rule " + rule.getName());
90                  	}
91                  	Object value = propertyDescriptor.valueFrom(strValue);
92                  	rule.setProperty(propertyDescriptor, value);
93                      }
94                  }
95  
96                  report = processUsingStringReader(test.getCode(), rule, test.getLanguageVersion());
97                  res = report.size();
98              } catch (Throwable t) {
99                  t.printStackTrace();
100                 throw new RuntimeException('"' + test.getDescription() + "\" failed", t);
101             }
102             printReport(test, report);
103             assertEquals('"' + test.getDescription() + "\" resulted in wrong number of failures,",
104                     test.getNumberOfProblemsExpected(), res);
105         } finally {
106             //Restore old properties
107             // TODO Tried to use generics here, but there's a compiler bug doing so in a finally block.
108             // Neither 1.5.0_16-b02 or 1.6.0_07-b06 works, but 1.7.0-ea-b34 seems to work.   
109             for (Map.Entry entry: oldProperties.entrySet()) {
110         	rule.setProperty((PropertyDescriptor)entry.getKey(), entry.getValue());
111             }
112         }
113     }
114 
115     private void printReport(TestDescriptor test, Report report) {
116         if (test.getNumberOfProblemsExpected() != report.size()) {
117             System.out.println("--------------------------------------------------------------");
118             System.out.println("Test Failure: " + test.getDescription());
119             System.out.println(" -> Expected " + test.getNumberOfProblemsExpected() + " problem(s), but "
120                     + report.size() + " problem(s) found.");
121             System.out.println();
122             TextRenderer renderer = new TextRenderer();
123             renderer.setWriter(new StringWriter());
124             try {
125                 renderer.start();
126                 renderer.renderFileReport(report);
127                 renderer.end();
128             } catch (IOException e) {
129                 throw new RuntimeException(e);
130             }
131             System.out.println(renderer.getWriter().toString());
132             System.out.println("--------------------------------------------------------------");
133         }
134     }
135 
136     private Report processUsingStringReader(String code, Rule rule,
137    			LanguageVersion languageVersion) throws PMDException {
138         Report report = new Report();
139         runTestFromString(code, rule, report, languageVersion);
140         return report;
141     }
142 
143     /**
144      * Run the rule on the given code and put the violations in the report.
145      */
146     public void runTestFromString(String code, Rule rule, Report report, LanguageVersion languageVersion) throws PMDException {
147         PMD p = new PMD();
148         p.getConfiguration().setDefaultLanguageVersion(languageVersion);
149         RuleContext ctx = new RuleContext();
150         ctx.setReport(report);
151         ctx.setSourceCodeFilename("n/a");
152         ctx.setLanguageVersion(languageVersion);
153         ctx.setIgnoreExceptions(false);
154         RuleSet rules = new RuleSet();
155         rules.addRule(rule);
156         p.getSourceCodeProcessor().processSourceCode(new StringReader(code), new RuleSets(rules), ctx);
157     }
158 
159     /**
160      * getResourceAsStream tries to find the XML file in weird locations if the
161      * ruleName includes the package, so we strip it here.
162      */
163     protected String getCleanRuleName(Rule rule) {
164         String fullClassName = rule.getClass().getName();
165         if (fullClassName.equals(rule.getName())) {
166             //We got the full class name, so we'll use the stripped name instead
167             String packageName = rule.getClass().getPackage().getName();
168             return fullClassName.substring(packageName.length()+1);
169         } else {
170             return rule.getName();  //Test is using findRule, smart!
171         }
172     }
173 
174     /**
175      * Extract a set of tests from an XML file. The file should be
176      * ./xml/RuleName.xml relative to the test class. The format is defined in
177      * test-data.xsd.
178      */
179     public TestDescriptor[] extractTestsFromXml(Rule rule) {
180         String testsFileName = getCleanRuleName(rule);
181 
182         return extractTestsFromXml(rule, testsFileName);
183     }
184 
185     public TestDescriptor[] extractTestsFromXml(Rule rule, String testsFileName) {
186         return extractTestsFromXml(rule, testsFileName, "xml/");
187     }
188     /**
189      * Extract a set of tests from an XML file with the given name. The file should be
190      * ./xml/[testsFileName].xml relative to the test class. The format is defined in
191      * test-data.xsd.
192      */
193     public TestDescriptor[] extractTestsFromXml(Rule rule, String testsFileName, String baseDirectory) {
194         String testXmlFileName = baseDirectory + testsFileName + ".xml";
195         InputStream inputStream = getClass().getResourceAsStream(testXmlFileName);
196         if (inputStream == null) {
197             throw new RuntimeException("Couldn't find " + testXmlFileName);
198         }
199 
200         Document doc;
201         try {
202             DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
203             doc = builder.parse(inputStream);
204         } catch (ParserConfigurationException pce) {
205             pce.printStackTrace();
206             throw new RuntimeException("Couldn't parse " + testXmlFileName + ", due to: " + pce.getMessage());
207         } catch (FactoryConfigurationError fce) {
208             fce.printStackTrace();
209             throw new RuntimeException("Couldn't parse " + testXmlFileName + ", due to: " + fce.getMessage());
210         } catch (IOException ioe) {
211             ioe.printStackTrace();
212             throw new RuntimeException("Couldn't parse " + testXmlFileName + ", due to: " + ioe.getMessage());
213         } catch (SAXException se) {
214             se.printStackTrace();
215             throw new RuntimeException("Couldn't parse " + testXmlFileName + ", due to: " + se.getMessage());
216         }
217 
218         return parseTests(rule, doc);
219     }
220 
221     private TestDescriptor[] parseTests(Rule rule, Document doc) {
222         Element root = doc.getDocumentElement();
223         NodeList testCodes = root.getElementsByTagName("test-code");
224 
225         TestDescriptor[] tests = new TestDescriptor[testCodes.getLength()];
226         for (int i = 0; i < testCodes.getLength(); i++) {
227             Element testCode = (Element)testCodes.item(i);
228 
229             boolean reinitializeRule = true;
230             Node reinitializeRuleAttribute = testCode.getAttributes().getNamedItem("reinitializeRule");
231             if (reinitializeRuleAttribute != null) {
232                 String reinitializeRuleValue = reinitializeRuleAttribute.getNodeValue();
233                 if ("false".equalsIgnoreCase(reinitializeRuleValue) ||
234                         "0".equalsIgnoreCase(reinitializeRuleValue)) {
235                     reinitializeRule = false;
236                 }
237             }
238 
239             boolean isRegressionTest = true;
240             Node regressionTestAttribute = testCode.getAttributes().getNamedItem("regressionTest");
241             if (regressionTestAttribute != null) {
242                 String reinitializeRuleValue = regressionTestAttribute.getNodeValue();
243                 if ("false".equalsIgnoreCase(reinitializeRuleValue)) {
244                     isRegressionTest = false;
245                 }
246             }
247 
248             NodeList ruleProperties = testCode.getElementsByTagName("rule-property");
249             Properties properties = new Properties();
250             for (int j = 0; j < ruleProperties.getLength(); j++) {
251                 Node ruleProperty = ruleProperties.item(j);
252                 String propertyName = ruleProperty.getAttributes().getNamedItem("name").getNodeValue();
253                 properties.setProperty(propertyName, parseTextNode(ruleProperty));
254             }
255             int expectedProblems = Integer.parseInt(getNodeValue(testCode, "expected-problems", true));
256             String description = getNodeValue(testCode, "description", true);
257             String code = getNodeValue(testCode, "code", false);
258             if (code == null) {
259                 //Should have a coderef
260                 NodeList coderefs = testCode.getElementsByTagName("code-ref");
261                 if (coderefs.getLength()==0) {
262                     throw new RuntimeException("Required tag is missing from the test-xml. Supply either a code or a code-ref tag");
263                 }
264                 Node coderef = coderefs.item(0);
265                 String referenceId = coderef.getAttributes().getNamedItem("id").getNodeValue();
266                 NodeList codeFragments = root.getElementsByTagName("code-fragment");
267                 for (int j = 0; j < codeFragments.getLength(); j++) {
268                     String fragmentId = codeFragments.item(j).getAttributes().getNamedItem("id").getNodeValue();
269                     if (referenceId.equals(fragmentId)) {
270                         code = parseTextNode(codeFragments.item(j));
271                     }
272                 }
273 
274                 if (code==null) {
275                     throw new RuntimeException("No matching code fragment found for coderef");
276                 }
277             }
278 
279             String languageVersionString = getNodeValue(testCode, "source-type", false);
280             if (languageVersionString == null) {
281                 tests[i] = new TestDescriptor(code, description, expectedProblems, rule);
282             } else {
283             	 LanguageVersion languageVersion = LanguageVersion.findByTerseName(languageVersionString);
284                 if (languageVersion != null) {
285                     tests[i] = new TestDescriptor(code, description, expectedProblems, rule, languageVersion);
286                 } else {
287                     throw new RuntimeException("Unknown LanguageVersion for test: " + languageVersionString);
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, boolean required) {
298         NodeList nodes = parentElm.getElementsByTagName(nodeName);
299         if (nodes == null || nodes.getLength() == 0) {
300             if (required) {
301                 throw new RuntimeException("Required tag is missing from the test-xml: " + nodeName);
302             } else {
303                 return null;
304             }
305         }
306         Node node = nodes.item(0);
307         return parseTextNode(node);
308     }
309 
310     private static String parseTextNode(Node exampleNode) {
311         StringBuffer buffer = new StringBuffer();
312         for (int i = 0; i < exampleNode.getChildNodes().getLength(); i++) {
313             Node node = exampleNode.getChildNodes().item(i);
314             if (node.getNodeType() == Node.CDATA_SECTION_NODE
315                     || node.getNodeType() == Node.TEXT_NODE) {
316                 buffer.append(node.getNodeValue());
317             }
318         }
319         return buffer.toString().trim();
320     }
321 
322     /**
323      * Run the test using the DEFAULT_LANGUAGE_VERSION and put the violations in the report.
324      * Convenience method.
325      */
326     public void runTestFromString(String code, Rule rule, Report report) throws PMDException {
327         runTestFromString(code, rule, report, DEFAULT_LANGUAGE_VERSION);
328     }
329 }