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