View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 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 com.puppycrawl.tools.checkstyle;
21  
22  import static com.google.common.truth.Truth.assertThat;
23  import static com.google.common.truth.Truth.assertWithMessage;
24  
25  import java.io.ByteArrayOutputStream;
26  import java.io.File;
27  import java.nio.charset.StandardCharsets;
28  import java.util.ArrayList;
29  import java.util.Collections;
30  import java.util.List;
31  import java.util.Set;
32  import java.util.function.BiPredicate;
33  
34  import javax.xml.parsers.ParserConfigurationException;
35  
36  import org.w3c.dom.Document;
37  import org.w3c.dom.NamedNodeMap;
38  import org.w3c.dom.Node;
39  
40  import com.puppycrawl.tools.checkstyle.bdd.InlineConfigParser;
41  import com.puppycrawl.tools.checkstyle.bdd.TestInputConfiguration;
42  import com.puppycrawl.tools.checkstyle.internal.utils.XmlUtil;
43  
44  public abstract class AbstractXmlTestSupport extends AbstractModuleTestSupport {
45  
46      /**
47       * Returns the output stream xml.
48       *
49       * @param outputStream the byte array output stream.
50       * @return returns a raw xml document of the interface {@link Document}.
51       * @throws ParserConfigurationException can cause exception when parsing output stream.
52       */
53      protected static Document getOutputStreamXml(ByteArrayOutputStream outputStream)
54              throws ParserConfigurationException {
55          final String xml = outputStream.toString(StandardCharsets.UTF_8);
56  
57          return XmlUtil.getRawXml("audit output", xml, xml);
58      }
59  
60      /**
61       * Verifies the generated xml.
62       *
63       * @param expectedOutputFile the expected output file.
64       * @param actualOutputStream the byte array output stream.
65       * @param messages an array of expected messages/content of xml document.
66       * @throws Exception can throw ComparisonFailure exception if xml encoding,
67       *     version don't match.
68       */
69      protected static void verifyXml(String expectedOutputFile,
70              ByteArrayOutputStream actualOutputStream, String... messages) throws Exception {
71          verifyXml(expectedOutputFile, actualOutputStream, null, messages);
72      }
73  
74      /**
75       * Verifies the actual generated xml document by comparing with expected document.
76       *
77       * @param expectedOutputFile the expected output file.
78       * @param actualOutputStream the actual byte array output stream.
79       * @param ordered an ordered predicate for xml nodes.
80       * @param messages an array of expected messages/content of xml document.
81       * @throws Exception can throw ComparisonFailure exception if xml encoding,
82       *     version do not match.
83       */
84      protected static void verifyXml(String expectedOutputFile,
85              ByteArrayOutputStream actualOutputStream,
86              BiPredicate<Node, Node> ordered, String... messages) throws Exception {
87          String expectedContents = readFile(expectedOutputFile);
88  
89          for (int i = 0; i < messages.length; i++) {
90              expectedContents = expectedContents.replace("$" + i, messages[i]);
91          }
92  
93          final Document expectedDocument = XmlUtil.getRawXml("audit output", expectedContents,
94                  expectedContents);
95          final Document actualDocument = getOutputStreamXml(actualOutputStream);
96  
97          assertWithMessage("xml encoding should be the same")
98                  .that(actualDocument.getXmlEncoding())
99                  .isEqualTo(expectedDocument.getXmlEncoding());
100 
101         assertWithMessage("xml version should be the same")
102                 .that(actualDocument.getXmlVersion())
103                 .isEqualTo(expectedDocument.getXmlVersion());
104 
105         verifyXmlNode(expectedDocument, actualDocument, "/", ordered);
106     }
107 
108     /**
109      * Verifies if xml nodes in actual xml document and expected xml document match or not.
110      *
111      * @param expected the expected xml node. A {@link Node} interface.
112      * @param actual the actual xml node. A {@link Node} interface.
113      * @param path the path to the current xml node that are compared.
114      * @param ordered an ordered predicate for xml nodes.
115      */
116     private static void verifyXmlNodes(Node expected, Node actual, String path,
117             BiPredicate<Node, Node> ordered) {
118         final Node expectedFirstChild = expected.getFirstChild();
119         final Node actualFirstChild = actual.getFirstChild();
120 
121         if (expectedFirstChild == null) {
122             assertWithMessage("no children nodes should exist: %s", path)
123                     .that(actualFirstChild)
124                     .isNull();
125             assertWithMessage("text should be the same: %s", path)
126                     .that(actual.getNodeValue())
127                     .isEqualTo(expected.getNodeValue());
128         }
129         else {
130             assertWithMessage("children nodes should exist: %s", path)
131                     .that(actualFirstChild)
132                     .isNotNull();
133 
134             if (ordered == null) {
135                 Node actualChild = actualFirstChild;
136 
137                 for (Node expectedChild = expectedFirstChild; expectedChild != null;
138                         expectedChild = expectedChild.getNextSibling()) {
139                     verifyXmlNode(expectedChild, actualChild, path, ordered);
140 
141                     actualChild = actualChild.getNextSibling();
142                 }
143 
144                 assertWithMessage("node have same number of children: %s", path)
145                         .that(actualChild)
146                         .isNull();
147             }
148             else {
149                 final Set<Node> expectedChildren = XmlUtil.getChildrenElements(expected);
150                 final Set<Node> actualChildren = XmlUtil.getChildrenElements(actual);
151 
152                 assertWithMessage("node have same number of children: %s", path)
153                         .that(actualChildren)
154                         .hasSize(expectedChildren.size());
155 
156                 for (Node expectedChild : expectedChildren) {
157                     Node foundChild = null;
158 
159                     for (Node actualChild : actualChildren) {
160                         if (ordered.test(expectedChild, actualChild)) {
161                             foundChild = actualChild;
162                             break;
163                         }
164                     }
165 
166                     assertWithMessage("node should exist: %s%s/", path, expectedChild.getNodeName())
167                             .that(foundChild)
168                             .isNotNull();
169 
170                     verifyXmlNode(expectedChild, foundChild, path, ordered);
171                 }
172             }
173         }
174     }
175 
176     /**
177      * Verifies if a xml node in actual xml document and expected xml document match or not based on
178      * their name, type, and attributes.
179      *
180      * @param expected the expected xml node. A {@link Node} interface.
181      * @param actual the actual xml node. A {@link Node} interface.
182      * @param path the path to the current xml nodes that are compared.
183      * @param ordered an ordered predicate for xml nodes.
184      */
185     private static void verifyXmlNode(Node expected, Node actual, String path,
186             BiPredicate<Node, Node> ordered) {
187         if (expected == null) {
188             if (actual != null) {
189                 assertWithMessage("no node should exist: %s%s/", path, actual.getNodeName())
190                         .fail();
191             }
192         }
193         else {
194             final String newPath = path + expected.getNodeName() + "/";
195 
196             assertWithMessage("node should exist: %s", newPath)
197                     .that(actual)
198                     .isNotNull();
199             assertWithMessage("node should have same name: %s", newPath)
200                     .that(actual.getNodeName())
201                     .isEqualTo(expected.getNodeName());
202             assertWithMessage("node should have same type: %s", newPath)
203                     .that(actual.getNodeType())
204                     .isEqualTo(expected.getNodeType());
205 
206             verifyXmlAttributes(expected.getAttributes(), actual.getAttributes(), newPath);
207 
208             verifyXmlNodes(expected, actual, newPath, ordered);
209         }
210     }
211 
212     /**
213      * Verifies xml attributes of collection of actual nodes by comparing with collection
214      * of expected node attributes.
215      *
216      * @param expected collection of expected nodes. A {@link NamedNodeMap} interface.
217      * @param actual collection of actual nodes. A {@link NamedNodeMap} interface.
218      * @param path the path to these xml nodes that are compared.
219      */
220     private static void verifyXmlAttributes(NamedNodeMap expected, NamedNodeMap actual,
221             String path) {
222         if (expected == null) {
223             assertWithMessage("no attributes should exist: %s", path)
224                     .that(actual)
225                     .isNull();
226         }
227         else {
228             assertWithMessage("attributes should exist: %s", path)
229                     .that(actual)
230                     .isNotNull();
231 
232             for (int i = 0; i < expected.getLength(); i++) {
233                 verifyXmlAttribute(expected.item(i), actual.item(i), path);
234             }
235 
236             assertThat(actual.getLength())
237                     .isEqualTo(expected.getLength());
238             assertWithMessage("node have same number of attributes: %s", path)
239                     .that(actual.getLength())
240                     .isEqualTo(expected.getLength());
241         }
242     }
243 
244     /**
245      * Verifies xml attributes of actual node (like name, and node value) by comparing with
246      * expected node attributes.
247      *
248      * @param expected the expected xml node. A {@link Node} interface.
249      * @param actual the actual xml node. A {@link Node} interface.
250      * @param path the path to the current xml nodes that are compared.
251      */
252     private static void verifyXmlAttribute(Node expected, Node actual, String path) {
253         final String expectedName = expected.getNodeName();
254 
255         assertWithMessage("attribute value for '%s' should not be null: %s", expectedName, path)
256                 .that(actual)
257                 .isNotNull();
258 
259         assertWithMessage("attribute name should match: %s", path)
260                 .that(actual.getNodeName())
261                 .isEqualTo(expectedName);
262 
263         // ignore checkstyle version in xml as it changes each release
264         if (!"/#document/checkstyle".equals(path) && !"version".equals(expectedName)) {
265             assertWithMessage("attribute value for '%s' should match: %s", expectedName, path)
266                     .that(actual.getNodeValue())
267                     .isEqualTo(expected.getNodeValue());
268         }
269     }
270 
271     /**
272      * Verifies XML output using inline configuration parser and XML logger.
273      *
274      * @param filePath the path to the test file
275      * @param expectedXmlReportPath the path to the expected XML report
276      * @throws Exception if an error occurs
277      */
278     protected final void verifyWithInlineConfigParserAndXmlLogger(
279             String filePath, String expectedXmlReportPath) throws Exception {
280         final String configFilePath = getPath(filePath);
281         final TestInputConfiguration testInputConfiguration =
282                 InlineConfigParser.parse(configFilePath);
283         final DefaultConfiguration parsedConfig = testInputConfiguration.createConfiguration();
284         final String basePath = new File(getPath("")).getAbsolutePath();
285 
286         final Checker checker = createChecker(parsedConfig);
287         checker.setBasedir(basePath);
288 
289         final ByteArrayOutputStream actualXmlOutput = new ByteArrayOutputStream();
290         final XMLLogger logger = new XMLLogger(actualXmlOutput,
291                 AbstractAutomaticBean.OutputStreamOptions.CLOSE);
292         checker.addListener(logger);
293         final List<File> filesToCheck = Collections.singletonList(new File(configFilePath));
294         checker.process(filesToCheck);
295 
296         verifyXml(getPath(expectedXmlReportPath), actualXmlOutput);
297     }
298 
299     /**
300      * Verifies the XML output of Checkstyle using a given XML configuration and expected report.
301      * Loads the configuration, processes target files, and compares the output with the
302      * expected XML.
303      *
304      * @param inputFileWithConfig the path of class have xml configuration
305      * @param expectedXmlReportPath the path to the expected XML report
306      * @param targetFilePaths list of file paths to check
307      * @throws Exception if an error occurs
308      */
309     protected final void verifyWithInlineConfigParserAndXmlLogger(
310             String inputFileWithConfig, String expectedXmlReportPath,
311             List<String> targetFilePaths) throws Exception {
312         final String configFilePath = getPath(inputFileWithConfig);
313         final TestInputConfiguration testInputConfiguration =
314                 InlineConfigParser.parse(configFilePath);
315         final DefaultConfiguration parsedConfig = testInputConfiguration.createConfiguration();
316         final String basePath = new File(getPath("")).getAbsolutePath();
317         final Checker checker = createChecker(parsedConfig);
318         checker.setBasedir(basePath);
319         final ByteArrayOutputStream actualXmlOutput = new ByteArrayOutputStream();
320         final XMLLogger logger = new XMLLogger(actualXmlOutput,
321                 AbstractAutomaticBean.OutputStreamOptions.CLOSE);
322         checker.addListener(logger);
323 
324         final List<File> filesToCheck = new ArrayList<>();
325         for (String path : targetFilePaths) {
326             filesToCheck.add(new File(getPath(path)));
327         }
328 
329         checker.process(filesToCheck);
330         verifyXml(getPath(expectedXmlReportPath), actualXmlOutput);
331     }
332 }