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