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.assertFalse;
24  import static org.junit.Assert.assertTrue;
25  import static org.junit.Assert.fail;
26  
27  import java.io.File;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.net.HttpURLConnection;
31  import java.net.URL;
32  
33  import org.junit.Rule;
34  import org.junit.Test;
35  import org.junit.rules.TemporaryFolder;
36  
37  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
38  import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
39  import com.puppycrawl.tools.checkstyle.api.AuditEvent;
40  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
41  import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
42  import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
43  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
44  
45  public class SuppressionFilterTest extends AbstractModuleTestSupport {
46  
47      @Rule
48      public final TemporaryFolder temporaryFolder = new TemporaryFolder();
49  
50      @Override
51      protected String getPackageLocation() {
52          return "com/puppycrawl/tools/checkstyle/filters/suppressionfilter";
53      }
54  
55      @Test
56      public void testAccept() throws Exception {
57          final String fileName = getPath("InputSuppressionFilterNone.xml");
58          final boolean optional = false;
59          final SuppressionFilter filter = createSuppressionFilter(fileName, optional);
60  
61          final AuditEvent ev = new AuditEvent(this, "ATest.java", null);
62  
63          assertTrue("Audit event should be excepted when there are no suppressions",
64              filter.accept(ev));
65      }
66  
67      @Test
68      public void testAcceptFalse() throws Exception {
69          final String fileName = getPath("InputSuppressionFilterSuppress.xml");
70          final boolean optional = false;
71          final SuppressionFilter filter = createSuppressionFilter(fileName, optional);
72  
73          final LocalizedMessage message = new LocalizedMessage(1, 1, null, "msg", null,
74                  SeverityLevel.ERROR, null, getClass(), null);
75          final AuditEvent ev = new AuditEvent(this, "ATest.java", message);
76  
77          assertFalse("Audit event should be rejected when there is a matching suppression",
78              filter.accept(ev));
79      }
80  
81      @Test
82      public void testAcceptOnNullFile() throws CheckstyleException {
83          final String fileName = null;
84          final boolean optional = false;
85          final SuppressionFilter filter = createSuppressionFilter(fileName, optional);
86  
87          final AuditEvent ev = new AuditEvent(this, "AnyJava.java", null);
88          assertTrue("Audit event on null file should be excepted, but was not", filter.accept(ev));
89      }
90  
91      @Test
92      public void testNonExistentSuppressionFileWithFalseOptional() {
93          final String fileName = "non_existent_suppression_file.xml";
94          try {
95              final boolean optional = false;
96              createSuppressionFilter(fileName, optional);
97              fail("Exception is expected");
98          }
99          catch (CheckstyleException ex) {
100             assertEquals("Invalid error message",
101                 "Unable to find: " + fileName, ex.getMessage());
102         }
103     }
104 
105     @Test
106     public void testExistingInvalidSuppressionFileWithTrueOptional() throws IOException {
107         final String fileName = getPath("InputSuppressionFilterInvalidFile.xml");
108         try {
109             final boolean optional = true;
110             createSuppressionFilter(fileName, optional);
111             fail("Exception is expected");
112         }
113         catch (CheckstyleException ex) {
114             assertEquals("Invalid error message",
115                 "Unable to parse " + fileName + " - invalid files or checks or message format",
116                 ex.getMessage());
117         }
118     }
119 
120     @Test
121     public void testExistingSuppressionFileWithTrueOptional() throws Exception {
122         final String fileName = getPath("InputSuppressionFilterNone.xml");
123         final boolean optional = true;
124         final SuppressionFilter filter = createSuppressionFilter(fileName, optional);
125 
126         final AuditEvent ev = new AuditEvent(this, "AnyFile.java", null);
127 
128         assertTrue("Suppression file with true optional was not accepted",
129             filter.accept(ev));
130     }
131 
132     @Test
133     public void testNonExistentSuppressionFileWithTrueOptional() throws Exception {
134         final String fileName = "non_existent_suppression_file.xml";
135         final boolean optional = true;
136         final SuppressionFilter filter = createSuppressionFilter(fileName, optional);
137 
138         final AuditEvent ev = new AuditEvent(this, "AnyFile.java", null);
139 
140         assertTrue("Should except event when suppression file does not exist",
141             filter.accept(ev));
142     }
143 
144     @Test
145     public void testNonExistentSuppressionUrlWithTrueOptional() throws Exception {
146         final String fileName =
147                 "https://checkstyle.org/non_existent_suppression.xml";
148         final boolean optional = true;
149         final SuppressionFilter filter = createSuppressionFilter(fileName, optional);
150 
151         final AuditEvent ev = new AuditEvent(this, "AnyFile.java", null);
152 
153         assertTrue("Should except event when suppression file url does not exist",
154             filter.accept(ev));
155     }
156 
157     @Test
158     public void testLocalFileExternalResourceContentDoesNotChange() throws Exception {
159         final DefaultConfiguration filterConfig = createModuleConfig(SuppressionFilter.class);
160         filterConfig.addAttribute("file", getPath("InputSuppressionFilterNone.xml"));
161 
162         final DefaultConfiguration checkerConfig = createRootConfig(filterConfig);
163         final File cacheFile = temporaryFolder.newFile();
164         checkerConfig.addAttribute("cacheFile", cacheFile.getPath());
165 
166         final String filePath = temporaryFolder.newFile("file.java").getPath();
167         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
168 
169         verify(checkerConfig, filePath, expected);
170         // One more time to use cache.
171         verify(checkerConfig, filePath, expected);
172     }
173 
174     @Test
175     public void testRemoteFileExternalResourceContentDoesNotChange() throws Exception {
176         final String[] urlCandidates = {
177             "https://checkstyle.org/files/suppressions_none.xml",
178             "https://raw.githubusercontent.com/checkstyle/checkstyle/master/src/site/resources/"
179                 + "files/suppressions_none.xml",
180         };
181 
182         String urlForTest = null;
183         for (String url : urlCandidates) {
184             if (isConnectionAvailableAndStable(url)) {
185                 urlForTest = url;
186                 break;
187             }
188         }
189 
190         // Run the test only if connection is available and url is reachable.
191         // We must use an if statement over junit's rule or assume because
192         // powermock disrupts the assume exception and turns into a failure
193         // instead of a skip when it doesn't pass
194         if (urlForTest != null) {
195             final DefaultConfiguration firstFilterConfig =
196                 createModuleConfig(SuppressionFilter.class);
197             // -@cs[CheckstyleTestMakeup] need to test dynamic property
198             firstFilterConfig.addAttribute("file", urlForTest);
199 
200             final DefaultConfiguration firstCheckerConfig = createRootConfig(firstFilterConfig);
201             final File cacheFile = temporaryFolder.newFile();
202             firstCheckerConfig.addAttribute("cacheFile", cacheFile.getPath());
203 
204             final String pathToEmptyFile = temporaryFolder.newFile("file.java").getPath();
205             final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
206 
207             verify(firstCheckerConfig, pathToEmptyFile, expected);
208 
209             // One more time to use cache.
210             final DefaultConfiguration secondFilterConfig =
211                 createModuleConfig(SuppressionFilter.class);
212             // -@cs[CheckstyleTestMakeup] need to test dynamic property
213             secondFilterConfig.addAttribute("file", urlForTest);
214 
215             final DefaultConfiguration secondCheckerConfig = createRootConfig(secondFilterConfig);
216             secondCheckerConfig.addAttribute("cacheFile", cacheFile.getPath());
217 
218             verify(secondCheckerConfig, pathToEmptyFile, expected);
219         }
220     }
221 
222     private static boolean isConnectionAvailableAndStable(String url) throws Exception {
223         boolean available = false;
224 
225         if (isUrlReachable(url)) {
226             final int attemptLimit = 5;
227             int attemptCount = 0;
228 
229             while (attemptCount <= attemptLimit) {
230                 try (InputStream stream = new URL(url).openStream()) {
231                     // Attempt to read a byte in order to check whether file content is available
232                     available = stream.read() != -1;
233                     break;
234                 }
235                 catch (IOException ex) {
236                     // for some reason Travis CI failed some times (unstable) on reading the file
237                     if (attemptCount < attemptLimit && ex.getMessage().contains("Unable to read")) {
238                         attemptCount++;
239                         available = false;
240                         // wait for bad / disconnection time to pass
241                         Thread.sleep(1000);
242                     }
243                     else {
244                         throw ex;
245                     }
246                 }
247             }
248         }
249         return available;
250     }
251 
252     private static boolean isUrlReachable(String url) {
253         boolean result = true;
254         try {
255             final URL verifiableUrl = new URL(url);
256             final HttpURLConnection urlConnect = (HttpURLConnection) verifiableUrl.openConnection();
257             urlConnect.getContent();
258         }
259         catch (IOException ignored) {
260             result = false;
261         }
262         return result;
263     }
264 
265     private static SuppressionFilter createSuppressionFilter(String fileName, boolean optional)
266             throws CheckstyleException {
267         final SuppressionFilter suppressionFilter = new SuppressionFilter();
268         suppressionFilter.setFile(fileName);
269         suppressionFilter.setOptional(optional);
270         suppressionFilter.finishLocalSetup();
271         return suppressionFilter;
272     }
273 
274 }