1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package org.checkstyle.suppressionxpathfilter;
21  
22  import static org.junit.Assert.assertEquals;
23  
24  import java.io.File;
25  import java.io.Writer;
26  import java.nio.charset.StandardCharsets;
27  import java.nio.file.Files;
28  import java.util.List;
29  import java.util.Locale;
30  import java.util.regex.Matcher;
31  import java.util.regex.Pattern;
32  import java.util.stream.Collectors;
33  
34  import org.junit.Rule;
35  import org.junit.rules.TemporaryFolder;
36  
37  import com.google.checkstyle.test.base.AbstractModuleTestSupport;
38  import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
39  import com.puppycrawl.tools.checkstyle.JavaParser;
40  import com.puppycrawl.tools.checkstyle.TreeWalker;
41  import com.puppycrawl.tools.checkstyle.api.DetailAST;
42  import com.puppycrawl.tools.checkstyle.api.FileText;
43  import com.puppycrawl.tools.checkstyle.filters.SuppressionXpathFilter;
44  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
45  import com.puppycrawl.tools.checkstyle.xpath.XpathQueryGenerator;
46  
47  public abstract class AbstractXpathTestSupport extends AbstractModuleTestSupport {
48  
49      private static final int DEFAULT_TAB_WIDTH = 4;
50  
51      private static final String DELIMITER = " | \n";
52  
53      private static final Pattern LINE_COLUMN_NUMBER_REGEX =
54              Pattern.compile("([0-9]+):([0-9]+):");
55  
56      @Rule
57      public final TemporaryFolder temporaryFolder = new TemporaryFolder();
58  
59      protected abstract String getCheckName();
60  
61      @Override
62      protected String getPackageLocation() {
63          final String subpackage = getCheckName().toLowerCase(Locale.ENGLISH)
64                  .replace("check", "");
65          return "org/checkstyle/suppressionxpathfilter" + "/" + subpackage;
66      }
67  
68      private static List<String> generateXpathQueries(File fileToProcess,
69                                                       ViolationPosition position)
70              throws Exception {
71          final FileText fileText = new FileText(fileToProcess,
72                  StandardCharsets.UTF_8.name());
73          final DetailAST rootAst = JavaParser.parseFile(fileToProcess,
74                  JavaParser.Options.WITH_COMMENTS);
75          final XpathQueryGenerator queryGenerator = new XpathQueryGenerator(rootAst,
76                  position.violationLineNumber, position.violationColumnNumber,
77                  fileText, DEFAULT_TAB_WIDTH);
78  
79          return queryGenerator.generate();
80      }
81  
82      private static void verifyXpathQueries(List<String> generatedXpathQueries,
83                                             List<String> expectedXpathQueries) {
84          assertEquals("Generated queries do not match expected ones", expectedXpathQueries,
85                  generatedXpathQueries);
86      }
87  
88      private String createSuppressionsXpathConfigFile(String checkName,
89                                                       List<String> xpathQueries)
90              throws Exception {
91  
92          final File suppressionsXpathConfigFile = temporaryFolder.newFile();
93          try (Writer bw = Files.newBufferedWriter(suppressionsXpathConfigFile.toPath(),
94                  StandardCharsets.UTF_8)) {
95              bw.write("<?xml version=\"1.0\"?>\n");
96              bw.write("<!DOCTYPE suppressions PUBLIC\n");
97              bw.write("    \"-//Checkstyle//DTD SuppressionXpathFilter ");
98              bw.write("Experimental Configuration 1.2//EN\"\n");
99              bw.write("    \"https://checkstyle.org/dtds/");
100             bw.write("suppressions_1_2_xpath_experimental.dtd\">\n");
101             bw.write("<suppressions>\n");
102             bw.write("   <suppress-xpath\n");
103             bw.write("       checks=\"");
104             bw.write(checkName);
105             bw.write("\"\n");
106             bw.write("       query=\"");
107             bw.write(xpathQueries.stream().collect(Collectors.joining(DELIMITER)));
108             bw.write("\"/>\n");
109             bw.write("</suppressions>");
110         }
111 
112         return suppressionsXpathConfigFile.getPath();
113     }
114 
115     private DefaultConfiguration createSuppressionXpathFilter(String checkName,
116                                            List<String> xpathQueries) throws Exception {
117         final DefaultConfiguration suppressionXpathFilterConfig =
118                 createModuleConfig(SuppressionXpathFilter.class);
119         suppressionXpathFilterConfig.addAttribute("file",
120                 createSuppressionsXpathConfigFile(checkName, xpathQueries));
121 
122         return suppressionXpathFilterConfig;
123     }
124 
125     private static ViolationPosition extractLineAndColumnNumber(String... expectedViolations) {
126         ViolationPosition violation = null;
127         final Matcher matcher =
128                 LINE_COLUMN_NUMBER_REGEX.matcher(expectedViolations[0]);
129         if (matcher.find()) {
130             final int violationLineNumber = Integer.parseInt(matcher.group(1));
131             final int violationColumnNumber = Integer.parseInt(matcher.group(2));
132             violation = new ViolationPosition(violationLineNumber, violationColumnNumber);
133         }
134         return violation;
135     }
136 
137     /**
138      * Runs three verifications:
139      * First one executes checker with defined module configuration and compares output with
140      * expected violations.
141      * Second one generates xpath queries based on violation message and compares them with expected
142      * xpath queries.
143      * Third one constructs new configuration with {@code SuppressionXpathFilter} using generated
144      * xpath queries, executes checker and checks if no violation occurred.
145      * @param moduleConfig module configuration.
146      * @param fileToProcess input class file.
147      * @param expectedViolations expected violation messages.
148      * @param expectedXpathQueries expected generated xpath queries.
149      * @throws Exception if an error occurs
150      */
151     protected void runVerifications(DefaultConfiguration moduleConfig,
152                                   File fileToProcess,
153                                   String[] expectedViolations,
154                                   List<String> expectedXpathQueries) throws Exception {
155         final ViolationPosition position =
156                 extractLineAndColumnNumber(expectedViolations);
157         final List<String> generatedXpathQueries =
158                 generateXpathQueries(fileToProcess, position);
159 
160         final DefaultConfiguration treeWalkerConfigWithXpath =
161                 createModuleConfig(TreeWalker.class);
162         treeWalkerConfigWithXpath.addChild(moduleConfig);
163         treeWalkerConfigWithXpath.addChild(createSuppressionXpathFilter(moduleConfig.getName(),
164                 generatedXpathQueries));
165 
166         final Integer[] warnList = getLinesWithWarn(fileToProcess.getPath());
167         verify(moduleConfig, fileToProcess.getPath(), expectedViolations, warnList);
168         verifyXpathQueries(generatedXpathQueries, expectedXpathQueries);
169         verify(treeWalkerConfigWithXpath, fileToProcess.getPath(), CommonUtil.EMPTY_STRING_ARRAY);
170     }
171 
172     private static final class ViolationPosition {
173         private final int violationLineNumber;
174         private final int violationColumnNumber;
175 
176         /* package */ ViolationPosition(int violationLineNumber,
177                               int violationColumnNumber) {
178             this.violationLineNumber = violationLineNumber;
179             this.violationColumnNumber = violationColumnNumber;
180         }
181     }
182 }