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              //Restore old properties
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             //We got the full class name, so we'll use the stripped name instead
122             String packageName = rule.getClass().getPackage().getName();
123             return fullClassName.substring(packageName.length()+1);
124         } else {
125             return rule.getName();  //Test is using findRule, smart!
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                 //Should have a coderef
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 }