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 }