View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 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.filters;
21  
22  import static com.google.common.truth.Truth.assertWithMessage;
23  import static com.puppycrawl.tools.checkstyle.internal.utils.TestUtil.getExpectedThrowable;
24  import static org.junit.jupiter.api.Assumptions.assumeTrue;
25  
26  import java.io.IOException;
27  import java.net.HttpURLConnection;
28  import java.net.URI;
29  import java.net.URL;
30  import java.util.HashSet;
31  import java.util.Set;
32  
33  import org.junit.jupiter.api.Test;
34  import org.xml.sax.InputSource;
35  
36  import com.puppycrawl.tools.checkstyle.AbstractPathTestSupport;
37  import com.puppycrawl.tools.checkstyle.TreeWalkerFilter;
38  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
39  import com.puppycrawl.tools.checkstyle.api.FilterSet;
40  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
41  
42  /**
43   * Tests SuppressionsLoader.
44   */
45  public class SuppressionsLoaderTest extends AbstractPathTestSupport {
46  
47      @Override
48      public String getPackageLocation() {
49          return "com/puppycrawl/tools/checkstyle/filters/suppressionsloader";
50      }
51  
52      @Test
53      public void testNoSuppressions() throws Exception {
54          final FilterSet fc =
55              SuppressionsLoader.loadSuppressions(getPath("InputSuppressionsLoaderNone.xml"));
56          final FilterSet fc2 = new FilterSet();
57          assertWithMessage("No suppressions should be loaded, but found: %s", fc.getFilters().size())
58              .that(fc.getFilters())
59              .isEqualTo(fc2.getFilters());
60      }
61  
62      @Test
63      public void testLoadFromUrl() throws Exception {
64          final String[] urlCandidates = {
65              "https://raw.githubusercontent.com/checkstyle/checkstyle/master/src/site/resources/"
66                  + "files/suppressions_none.xml",
67              "https://checkstyle.org/files/suppressions_none.xml",
68          };
69          FilterSet actualFilterSet = null;
70  
71          for (String url : urlCandidates) {
72              actualFilterSet = loadFilterSet(url);
73  
74              if (actualFilterSet != null) {
75                  break;
76              }
77          }
78  
79          assumeTrue(actualFilterSet != null, "No Internet connection.");
80          final FilterSet expectedFilterSet = new FilterSet();
81          assertWithMessage("Failed to load from url")
82              .that(actualFilterSet.getFilters())
83              .isEqualTo(expectedFilterSet.getFilters());
84      }
85  
86      @Test
87      public void testLoadFromMalformedUrl() {
88          final CheckstyleException exc =
89                  getExpectedThrowable(CheckstyleException.class,
90                          () -> SuppressionsLoader.loadSuppressions("http"));
91          assertWithMessage("Invalid error message")
92              .that(exc.getMessage())
93              .isEqualTo("Unable to find: http");
94      }
95  
96      @Test
97      public void testLoadFromNonExistentUrl() {
98          final CheckstyleException exc =
99                  getExpectedThrowable(CheckstyleException.class,
100                         () -> SuppressionsLoader.loadSuppressions("/non/existent/file.xml"));
101         assertWithMessage("Invalid error message")
102             .that(exc.getMessage())
103             .isEqualTo("Unable to find: /non/existent/file.xml");
104     }
105 
106     @Test
107     public void testMultipleSuppression() throws Exception {
108         final FilterSet fc =
109             SuppressionsLoader.loadSuppressions(getPath("InputSuppressionsLoaderMultiple.xml"));
110         final FilterSet fc2 = new FilterSet();
111 
112         final SuppressFilterElement se0 =
113                 new SuppressFilterElement("file0", "check0", null, null, null, null);
114         fc2.addFilter(se0);
115         final SuppressFilterElement se1 =
116                 new SuppressFilterElement("file1", "check1", null, null, "1,2-3", null);
117         fc2.addFilter(se1);
118         final SuppressFilterElement se2 =
119                 new SuppressFilterElement("file2", "check2", null, null, null, "1,2-3");
120         fc2.addFilter(se2);
121         final SuppressFilterElement se3 =
122                 new SuppressFilterElement("file3", "check3", null, null, "1,2-3", "1,2-3");
123         fc2.addFilter(se3);
124         final SuppressFilterElement se4 =
125                 new SuppressFilterElement(null, null, "message0", null, null, null);
126         fc2.addFilter(se4);
127         assertWithMessage("Multiple suppressions were loaded incorrectly")
128             .that(fc.getFilters())
129             .isEqualTo(fc2.getFilters());
130     }
131 
132     @Test
133     public void testNoFile() throws IOException {
134         final String fn = getPath("InputSuppressionsLoaderNoFile.xml");
135         final CheckstyleException exc =
136                 getExpectedThrowable(CheckstyleException.class,
137                         () -> SuppressionsLoader.loadSuppressions(fn));
138         final String messageStart = "Unable to parse " + fn;
139         assertWithMessage("Exception message should start with: %s", messageStart)
140                 .that(exc.getMessage())
141                 .startsWith("Unable to parse " + fn);
142         assertWithMessage("Exception message should contain \"files\"")
143                 .that(exc.getMessage())
144                 .contains("\"files\"");
145         assertWithMessage("Exception message should contain \"suppress\"")
146                 .that(exc.getMessage())
147                 .contains("\"suppress\"");
148     }
149 
150     @Test
151     public void testNoCheck() throws IOException {
152         final String fn = getPath("InputSuppressionsLoaderNoCheck.xml");
153         final CheckstyleException exc =
154                 getExpectedThrowable(CheckstyleException.class,
155                         () -> SuppressionsLoader.loadSuppressions(fn));
156         final String messageStart = "Unable to parse " + fn;
157         assertWithMessage("Exception message should start with: %s", messageStart)
158                 .that(exc.getMessage())
159                 .startsWith(messageStart);
160         assertWithMessage("Exception message should contain \"checks\"")
161                 .that(exc.getMessage())
162                 .contains("\"checks\"");
163         assertWithMessage("Exception message should contain \"suppress\"")
164                 .that(exc.getMessage())
165                 .contains("\"suppress\"");
166     }
167 
168     @Test
169     public void testBadInt() throws IOException {
170         final String fn = getPath("InputSuppressionsLoaderBadInt.xml");
171         final CheckstyleException exc =
172                 getExpectedThrowable(CheckstyleException.class,
173                         () -> SuppressionsLoader.loadSuppressions(fn));
174         assertWithMessage(exc.getMessage())
175             .that(exc.getMessage())
176             .startsWith("Number format exception " + fn + " - For input string: \"a\"");
177     }
178 
179     private static FilterSet loadFilterSet(String url) throws Exception {
180         FilterSet filterSet = null;
181 
182         if (isUrlReachable(url)) {
183             int attemptCount = 0;
184             final int attemptLimit = 5;
185 
186             while (attemptCount <= attemptLimit) {
187                 try {
188                     filterSet = SuppressionsLoader.loadSuppressions(url);
189                     break;
190                 }
191                 catch (CheckstyleException exc) {
192                     if (attemptCount < attemptLimit && exc.getMessage()
193                             .contains("Unable to read")) {
194                         attemptCount++;
195                         // wait for bad/disconnection time to pass
196                         Thread.sleep(1000);
197                     }
198                     else {
199                         throw exc;
200                     }
201                 }
202             }
203         }
204         return filterSet;
205     }
206 
207     private static boolean isUrlReachable(String url) {
208         boolean result = true;
209         try {
210             final URL verifiableUrl = URI.create(url).toURL();
211             final HttpURLConnection urlConnect = (HttpURLConnection) verifiableUrl.openConnection();
212             urlConnect.getContent();
213         }
214 
215         catch (IOException ignored) {
216             result = false;
217         }
218         return result;
219     }
220 
221     @Test
222     public void testUnableToFindSuppressions() {
223         final String sourceName = "InputSuppressionsLoaderNone.xml";
224         final ReflectiveOperationException exc =
225                 getExpectedThrowable(ReflectiveOperationException.class,
226                         () -> {
227                             TestUtil.invokeVoidStaticMethod(SuppressionsLoader.class,
228                                     "loadSuppressions",
229                                     new InputSource(sourceName), sourceName);
230                         });
231         assertWithMessage("Invalid exception cause message")
232             .that(exc)
233                 .hasCauseThat()
234                     .hasMessageThat()
235                     .isEqualTo("Unable to find: " + sourceName);
236     }
237 
238     @Test
239     public void testUnableToReadSuppressions() {
240         final String sourceName = "InputSuppressionsLoaderNone.xml";
241         final ReflectiveOperationException exc =
242                 getExpectedThrowable(ReflectiveOperationException.class,
243                         () -> {
244                             TestUtil.invokeVoidStaticMethod(SuppressionsLoader.class,
245                                     "loadSuppressions",
246                                     new InputSource(), sourceName);
247                         });
248         assertWithMessage("Invalid exception cause message")
249             .that(exc)
250                 .hasCauseThat()
251                     .hasMessageThat()
252                     .isEqualTo("Unable to read " + sourceName);
253     }
254 
255     @Test
256     public void testNoCheckNoId() throws IOException {
257         final String fn = getPath("InputSuppressionsLoaderNoCheckAndId.xml");
258         final CheckstyleException exc =
259                 getExpectedThrowable(CheckstyleException.class,
260                         () -> SuppressionsLoader.loadSuppressions(fn));
261         assertWithMessage("Invalid error message")
262             .that(exc.getMessage())
263             .isEqualTo("Unable to parse " + fn
264                     + " - missing checks or id or message attribute");
265     }
266 
267     @Test
268     public void testNoCheckYesId() throws Exception {
269         final String fn = getPath("InputSuppressionsLoaderId.xml");
270         final FilterSet set = SuppressionsLoader.loadSuppressions(fn);
271 
272         assertWithMessage("Invalid number of filters")
273             .that(set.getFilters())
274             .hasSize(1);
275     }
276 
277     @Test
278     public void testInvalidFileFormat() throws IOException {
279         final String fn = getPath("InputSuppressionsLoaderInvalidFile.xml");
280         final CheckstyleException exc =
281                 getExpectedThrowable(CheckstyleException.class,
282                         () -> SuppressionsLoader.loadSuppressions(fn));
283         assertWithMessage("Invalid error message")
284             .that(exc.getMessage())
285             .isEqualTo("Unable to parse " + fn
286                     + " - invalid files or checks or message format");
287     }
288 
289     @Test
290     public void testLoadFromClasspath() throws Exception {
291         final FilterSet fc =
292             SuppressionsLoader.loadSuppressions(getPath("InputSuppressionsLoaderNone.xml"));
293         final FilterSet fc2 = new FilterSet();
294         assertWithMessage("Suppressions were not loaded")
295             .that(fc.getFilters())
296             .isEqualTo(fc2.getFilters());
297     }
298 
299     @Test
300     public void testSettingModuleId() throws Exception {
301         final FilterSet fc =
302                 SuppressionsLoader.loadSuppressions(getPath("InputSuppressionsLoaderWithId.xml"));
303         final SuppressFilterElement suppressElement = (SuppressFilterElement) fc.getFilters()
304                 .toArray()[0];
305 
306         final String id = TestUtil.getInternalState(suppressElement, "moduleId", String.class);
307         assertWithMessage("Id has to be defined")
308             .that(id)
309             .isEqualTo("someId");
310     }
311 
312     @Test
313     public void testXpathSuppressions() throws Exception {
314         final String fn = getPath("InputSuppressionsLoaderXpathCorrect.xml");
315         final Set<TreeWalkerFilter> filterSet = SuppressionsLoader.loadXpathSuppressions(fn);
316 
317         final Set<TreeWalkerFilter> expectedFilterSet = new HashSet<>();
318         final XpathFilterElement xf0 =
319                 new XpathFilterElement("file1", "test", null, "id1", "//CLASS_DEF");
320         expectedFilterSet.add(xf0);
321         final XpathFilterElement xf1 =
322                 new XpathFilterElement(null, null, "message1", null, "//CLASS_DEF");
323         expectedFilterSet.add(xf1);
324         assertWithMessage("Multiple xpath suppressions were loaded incorrectly")
325             .that(filterSet)
326             .isEqualTo(expectedFilterSet);
327     }
328 
329     @Test
330     public void testXpathInvalidFileFormat() throws IOException {
331         final String fn = getPath("InputSuppressionsLoaderXpathInvalidFile.xml");
332         final CheckstyleException exc =
333                 getExpectedThrowable(CheckstyleException.class,
334                         () -> SuppressionsLoader.loadXpathSuppressions(fn));
335         assertWithMessage("Invalid error message")
336             .that(exc.getMessage())
337             .isEqualTo("Unable to parse " + fn
338                     + " - invalid files or checks or message format for suppress-xpath");
339     }
340 
341     @Test
342     public void testXpathNoCheckNoId() throws IOException {
343         final String fn =
344                 getPath("InputSuppressionsLoaderXpathNoCheckAndId.xml");
345         final CheckstyleException exc =
346                 getExpectedThrowable(CheckstyleException.class,
347                         () -> SuppressionsLoader.loadXpathSuppressions(fn));
348         assertWithMessage("Invalid error message")
349             .that(exc.getMessage())
350             .isEqualTo("Unable to parse " + fn
351                     + " - missing checks or id or message attribute for suppress-xpath");
352     }
353 
354     @Test
355     public void testXpathNoCheckYesId() throws Exception {
356         final String fn = getPath("InputSuppressionsLoaderXpathId.xml");
357         final Set<TreeWalkerFilter> filterSet = SuppressionsLoader.loadXpathSuppressions(fn);
358 
359         assertWithMessage("Invalid number of filters")
360             .that(filterSet)
361             .hasSize(1);
362     }
363 
364     @Test
365     public void testXpathSuppressionsExperimentalPuppyCrawl11() throws Exception {
366         final String fn = getPath("InputSuppressionsLoaderXpathExperimentalPuppyCrawl11.xml");
367         final Set<TreeWalkerFilter> filterSet = SuppressionsLoader.loadXpathSuppressions(fn);
368         assertWithMessage("Invalid number of filters")
369             .that(filterSet)
370             .hasSize(1);
371     }
372 
373     @Test
374     public void testXpathSuppressionsExperimentalPuppyCrawl12() throws Exception {
375         final String fn = getPath("InputSuppressionsLoaderXpathExperimentalPuppyCrawl12.xml");
376         final Set<TreeWalkerFilter> filterSet = SuppressionsLoader.loadXpathSuppressions(fn);
377         assertWithMessage("Invalid number of filters")
378             .that(filterSet)
379             .hasSize(1);
380     }
381 
382     @Test
383     public void testXpathSuppressionsExperimentalCheckstyle11() throws Exception {
384         final String fn = getPath("InputSuppressionsLoaderXpathExperimentalCheckstyle11.xml");
385         final Set<TreeWalkerFilter> filterSet = SuppressionsLoader.loadXpathSuppressions(fn);
386         assertWithMessage("Invalid number of filters")
387             .that(filterSet)
388             .hasSize(1);
389     }
390 
391     @Test
392     public void testXpathSuppressionsExperimentalCheckstyle12() throws Exception {
393         final String fn = getPath("InputSuppressionsLoaderXpathExperimentalCheckstyle12.xml");
394         final Set<TreeWalkerFilter> filterSet = SuppressionsLoader.loadXpathSuppressions(fn);
395         assertWithMessage("Invalid number of filters")
396             .that(filterSet)
397             .hasSize(1);
398     }
399 }