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