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