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