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.checks;
21  
22  import static org.junit.Assert.assertArrayEquals;
23  import static org.junit.Assert.assertEquals;
24  import static org.junit.Assert.assertFalse;
25  import static org.junit.Assert.assertTrue;
26  import static org.junit.Assert.fail;
27  
28  import java.io.File;
29  import java.lang.reflect.Constructor;
30  import java.lang.reflect.InvocationTargetException;
31  import java.lang.reflect.Method;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Optional;
35  
36  import org.junit.After;
37  import org.junit.Test;
38  import org.powermock.reflect.Whitebox;
39  
40  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
41  import com.puppycrawl.tools.checkstyle.Checker;
42  import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
43  import com.puppycrawl.tools.checkstyle.JavaParser;
44  import com.puppycrawl.tools.checkstyle.TreeWalker;
45  import com.puppycrawl.tools.checkstyle.api.AuditEvent;
46  import com.puppycrawl.tools.checkstyle.api.Configuration;
47  import com.puppycrawl.tools.checkstyle.api.DetailAST;
48  import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
49  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
50  import com.puppycrawl.tools.checkstyle.checks.naming.MemberNameCheck;
51  import com.puppycrawl.tools.checkstyle.checks.whitespace.AbstractParenPadCheck;
52  import com.puppycrawl.tools.checkstyle.checks.whitespace.TypecastParenPadCheck;
53  import com.puppycrawl.tools.checkstyle.filters.SuppressWarningsFilter;
54  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
55  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
56  
57  public class SuppressWarningsHolderTest extends AbstractModuleTestSupport {
58  
59      @Override
60      protected String getPackageLocation() {
61          return "com/puppycrawl/tools/checkstyle/checks/suppresswarningsholder";
62      }
63  
64      @After
65      public void cleanUp() {
66          // clear cache that may have been set by tests
67  
68          new SuppressWarningsHolder().beginTree(null);
69  
70          final Map<String, String> map = Whitebox.getInternalState(SuppressWarningsHolder.class,
71                  "CHECK_ALIAS_MAP");
72          map.clear();
73      }
74  
75      @Test
76      public void testGet() {
77          final SuppressWarningsHolder checkObj = new SuppressWarningsHolder();
78          final int[] expected = {TokenTypes.ANNOTATION};
79          assertArrayEquals("Required token array differs from expected",
80                  expected, checkObj.getRequiredTokens());
81          assertArrayEquals("Required token array differs from expected",
82                  expected, checkObj.getAcceptableTokens());
83      }
84  
85      @Test
86      public void testOnComplexAnnotations() throws Exception {
87          final Configuration checkConfig = createModuleConfig(SuppressWarningsHolder.class);
88  
89          final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
90  
91          verify(checkConfig, getPath("InputSuppressWarningsHolder.java"), expected);
92      }
93  
94      @Test
95      public void testOnComplexAnnotationsNonConstant() throws Exception {
96          final Configuration checkConfig = createModuleConfig(SuppressWarningsHolder.class);
97  
98          final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
99  
100         verify(checkConfig,
101                 getNonCompilablePath("InputSuppressWarningsHolderNonConstant.java"), expected);
102     }
103 
104     @Test
105     public void testCustomAnnotation() throws Exception {
106         final Configuration checkConfig = createModuleConfig(SuppressWarningsHolder.class);
107 
108         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
109 
110         verify(checkConfig, getPath("InputSuppressWarningsHolder5.java"), expected);
111     }
112 
113     @Test
114     public void testAll() throws Exception {
115         final Configuration checkConfig = createModuleConfig(SuppressWarningsHolder.class);
116         final DefaultConfiguration treeWalker = createModuleConfig(TreeWalker.class);
117         final Configuration filter = createModuleConfig(SuppressWarningsFilter.class);
118         final DefaultConfiguration violationCheck = createModuleConfig(TypecastParenPadCheck.class);
119         violationCheck.addAttribute("option", "space");
120 
121         treeWalker.addChild(checkConfig);
122         treeWalker.addChild(violationCheck);
123 
124         final DefaultConfiguration root = createRootConfig(treeWalker);
125         root.addChild(filter);
126 
127         final String[] expected = {
128             "8:72: "
129                     + getCheckMessage(TypecastParenPadCheck.class,
130                             AbstractParenPadCheck.MSG_WS_NOT_PRECEDED, ")"),
131         };
132 
133         verify(root, getPath("InputSuppressWarningsHolder6.java"), expected);
134     }
135 
136     @Test
137     public void testGetDefaultAlias() {
138         assertEquals("Default alias differs from expected",
139                 "somename", SuppressWarningsHolder.getDefaultAlias("SomeName"));
140         assertEquals("Default alias differs from expected",
141                 "somename", SuppressWarningsHolder.getDefaultAlias("SomeNameCheck"));
142     }
143 
144     @Test
145     public void testSetAliasListEmpty() {
146         final SuppressWarningsHolder holder = new SuppressWarningsHolder();
147         holder.setAliasList("");
148         assertEquals("Empty alias list should not be set", "",
149             SuppressWarningsHolder.getAlias(""));
150     }
151 
152     @Test
153     public void testSetAliasListCorrect() {
154         final SuppressWarningsHolder holder = new SuppressWarningsHolder();
155         holder.setAliasList("alias=value");
156         assertEquals("Alias differs from expected",
157                 "value", SuppressWarningsHolder.getAlias("alias"));
158     }
159 
160     @Test
161     public void testSetAliasListWrong() {
162         final SuppressWarningsHolder holder = new SuppressWarningsHolder();
163 
164         try {
165             holder.setAliasList("=SomeAlias");
166             fail("Exception expected");
167         }
168         catch (IllegalArgumentException ex) {
169             assertEquals("Error message is unexpected",
170                     "'=' expected in alias list item: =SomeAlias", ex.getMessage());
171         }
172     }
173 
174     @Test
175     public void testIsSuppressed() throws Exception {
176         populateHolder("MockEntry", 100, 100, 350, 350);
177         final AuditEvent event = createAuditEvent("check", 100, 10);
178 
179         assertFalse("Event is not suppressed", SuppressWarningsHolder.isSuppressed(event));
180     }
181 
182     @Test
183     public void testIsSuppressedByName() throws Exception {
184         populateHolder("check", 100, 100, 350, 350);
185         final SuppressWarningsHolder holder = new SuppressWarningsHolder();
186         final AuditEvent event = createAuditEvent("id", 110, 10);
187         holder.setAliasList(MemberNameCheck.class.getName() + "=check");
188 
189         assertTrue("Event is not suppressed", SuppressWarningsHolder.isSuppressed(event));
190     }
191 
192     @Test
193     public void testIsSuppressedByModuleId() throws Exception {
194         populateHolder("check", 100, 100, 350, 350);
195         final AuditEvent event = createAuditEvent("check", 350, 350);
196 
197         assertTrue("Event is not suppressed", SuppressWarningsHolder.isSuppressed(event));
198     }
199 
200     @Test
201     public void testIsSuppressedAfterEventEnd() throws Exception {
202         populateHolder("check", 100, 100, 350, 350);
203         final AuditEvent event = createAuditEvent("check", 350, 352);
204 
205         assertFalse("Event is not suppressed", SuppressWarningsHolder.isSuppressed(event));
206     }
207 
208     @Test
209     public void testIsSuppressedAfterEventEnd2() throws Exception {
210         populateHolder("check", 100, 100, 350, 350);
211         final AuditEvent event = createAuditEvent("check", 400, 10);
212 
213         assertFalse("Event is not suppressed", SuppressWarningsHolder.isSuppressed(event));
214     }
215 
216     @Test
217     public void testIsSuppressedAfterEventStart() throws Exception {
218         populateHolder("check", 100, 100, 350, 350);
219         final AuditEvent event = createAuditEvent("check", 100, 100);
220 
221         assertTrue("Event is not suppressed", SuppressWarningsHolder.isSuppressed(event));
222     }
223 
224     @Test
225     public void testIsSuppressedWithAllArgument() throws Exception {
226         populateHolder("all", 100, 100, 350, 350);
227 
228         final Checker source = new Checker();
229         final LocalizedMessage firstMessageForTest =
230             new LocalizedMessage(100, 10, null, null, null, "id", MemberNameCheck.class, "msg");
231         final AuditEvent firstEventForTest =
232             new AuditEvent(source, "fileName", firstMessageForTest);
233         assertFalse("Event is suppressed",
234                 SuppressWarningsHolder.isSuppressed(firstEventForTest));
235 
236         final LocalizedMessage secondMessageForTest =
237             new LocalizedMessage(100, 150, null, null, null, "id", MemberNameCheck.class, "msg");
238         final AuditEvent secondEventForTest =
239             new AuditEvent(source, "fileName", secondMessageForTest);
240         assertTrue("Event is not suppressed",
241                 SuppressWarningsHolder.isSuppressed(secondEventForTest));
242 
243         final LocalizedMessage thirdMessageForTest =
244             new LocalizedMessage(200, 1, null, null, null, "id", MemberNameCheck.class, "msg");
245         final AuditEvent thirdEventForTest =
246             new AuditEvent(source, "fileName", thirdMessageForTest);
247         assertTrue("Event is not suppressed",
248                 SuppressWarningsHolder.isSuppressed(thirdEventForTest));
249     }
250 
251     @Test
252     public void testAnnotationInTry() throws Exception {
253         final Configuration checkConfig = createModuleConfig(SuppressWarningsHolder.class);
254 
255         final String[] expected = {
256             "11: " + getCheckMessage(SuppressWarningsHolder.MSG_KEY),
257         };
258 
259         verify(checkConfig, getPath("InputSuppressWarningsHolder2.java"), expected);
260     }
261 
262     @Test
263     public void testEmptyAnnotation() throws Exception {
264         final Configuration checkConfig = createModuleConfig(SuppressWarningsHolder.class);
265 
266         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
267 
268         verify(checkConfig, getPath("InputSuppressWarningsHolder3.java"), expected);
269     }
270 
271     @Test
272     public void testGetAllAnnotationValuesWrongArg() throws ReflectiveOperationException {
273         final SuppressWarningsHolder holder = new SuppressWarningsHolder();
274         final Method getAllAnnotationValues = holder.getClass()
275                 .getDeclaredMethod("getAllAnnotationValues", DetailAST.class);
276         getAllAnnotationValues.setAccessible(true);
277 
278         final DetailAST methodDef = new DetailAST();
279         methodDef.setType(TokenTypes.METHOD_DEF);
280         methodDef.setText("Method Def");
281         methodDef.setLineNo(0);
282         methodDef.setColumnNo(0);
283 
284         final DetailAST lparen = new DetailAST();
285         lparen.setType(TokenTypes.LPAREN);
286 
287         final DetailAST parent = new DetailAST();
288         parent.addChild(lparen);
289         parent.addChild(methodDef);
290 
291         try {
292             getAllAnnotationValues.invoke(holder, parent);
293             fail("Exception expected");
294         }
295         catch (InvocationTargetException ex) {
296             assertTrue("Error type is unexpected",
297                     ex.getCause() instanceof IllegalArgumentException);
298             assertEquals("Error message is unexpected",
299                     "Unexpected AST: Method Def[0x0]", ex.getCause().getMessage());
300         }
301     }
302 
303     @Test
304     public void testGetAnnotationValuesWrongArg() throws ReflectiveOperationException {
305         final SuppressWarningsHolder holder = new SuppressWarningsHolder();
306         final Method getAllAnnotationValues = holder.getClass()
307                 .getDeclaredMethod("getAnnotationValues", DetailAST.class);
308         getAllAnnotationValues.setAccessible(true);
309 
310         final DetailAST methodDef = new DetailAST();
311         methodDef.setType(TokenTypes.METHOD_DEF);
312         methodDef.setText("Method Def");
313         methodDef.setLineNo(0);
314         methodDef.setColumnNo(0);
315 
316         try {
317             getAllAnnotationValues.invoke(holder, methodDef);
318             fail("Exception expected");
319         }
320         catch (InvocationTargetException ex) {
321             assertTrue("Error type is unexpected",
322                     ex.getCause() instanceof IllegalArgumentException);
323             assertEquals("Error message is unexpected",
324                     "Expression or annotation array"
325                     + " initializer AST expected: Method Def[0x0]", ex.getCause().getMessage());
326         }
327     }
328 
329     @Test
330     public void testGetAnnotationTargetWrongArg() throws ReflectiveOperationException {
331         final SuppressWarningsHolder holder = new SuppressWarningsHolder();
332         final Method getAnnotationTarget = holder.getClass()
333                 .getDeclaredMethod("getAnnotationTarget", DetailAST.class);
334         getAnnotationTarget.setAccessible(true);
335 
336         final DetailAST methodDef = new DetailAST();
337         methodDef.setType(TokenTypes.METHOD_DEF);
338         methodDef.setText("Method Def");
339 
340         final DetailAST parent = new DetailAST();
341         parent.setType(TokenTypes.ASSIGN);
342         parent.setText("Parent ast");
343         parent.addChild(methodDef);
344         parent.setLineNo(0);
345         parent.setColumnNo(0);
346 
347         try {
348             getAnnotationTarget.invoke(holder, methodDef);
349             fail("Exception expected");
350         }
351         catch (InvocationTargetException ex) {
352             assertTrue("Error type is unexpected",
353                     ex.getCause() instanceof IllegalArgumentException);
354             assertEquals("Error message is unexpected",
355                     "Unexpected container AST: Parent ast[0x0]", ex.getCause().getMessage());
356         }
357     }
358 
359     @Test
360     public void testAstWithoutChildren() {
361         final SuppressWarningsHolder holder = new SuppressWarningsHolder();
362         final DetailAST methodDef = new DetailAST();
363         methodDef.setType(TokenTypes.METHOD_DEF);
364 
365         try {
366             holder.visitToken(methodDef);
367             fail("Exception expected");
368         }
369         catch (IllegalArgumentException ex) {
370             assertEquals("Error message is unexpected",
371                     "Identifier AST expected, but get null.", ex.getMessage());
372         }
373     }
374 
375     @Test
376     public void testAnnotationWithFullName() throws Exception {
377         final Configuration checkConfig = createModuleConfig(SuppressWarningsHolder.class);
378 
379         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
380 
381         verify(checkConfig, getPath("InputSuppressWarningsHolder4.java"), expected);
382     }
383 
384     @Test
385     @SuppressWarnings("unchecked")
386     public void testClearState() throws Exception {
387         final SuppressWarningsHolder check = new SuppressWarningsHolder();
388 
389         final Optional<DetailAST> annotationDef = TestUtil.findTokenInAstByPredicate(
390                 JavaParser.parseFile(
391                     new File(getPath("InputSuppressWarningsHolder.java")),
392                     JavaParser.Options.WITHOUT_COMMENTS),
393             ast -> ast.getType() == TokenTypes.ANNOTATION);
394 
395         assertTrue("Ast should contain ANNOTATION", annotationDef.isPresent());
396         assertTrue("State is not cleared on beginTree",
397             TestUtil.isStatefulFieldClearedDuringBeginTree(check, annotationDef.get(),
398                 "ENTRIES",
399                 entries -> ((ThreadLocal<List<Object>>) entries).get().isEmpty()));
400     }
401 
402     private static void populateHolder(String checkName, int firstLine,
403                                                          int firstColumn, int lastLine,
404                                                          int lastColumn) throws Exception {
405         final Class<?> entry = Class
406                 .forName("com.puppycrawl.tools.checkstyle.checks.SuppressWarningsHolder$Entry");
407         final Constructor<?> entryConstr = entry.getDeclaredConstructor(String.class, int.class,
408                 int.class, int.class, int.class);
409         entryConstr.setAccessible(true);
410 
411         final Object entryInstance = entryConstr.newInstance(checkName, firstLine,
412                 firstColumn, lastLine, lastColumn);
413 
414         final ThreadLocal<List<Object>> entries = Whitebox
415                 .getInternalState(SuppressWarningsHolder.class, "ENTRIES");
416         entries.get().add(entryInstance);
417     }
418 
419     private static AuditEvent createAuditEvent(String moduleId, int line, int column) {
420         final Checker source = new Checker();
421         final LocalizedMessage message = new LocalizedMessage(line, column, null, null, null,
422                 moduleId, MemberNameCheck.class, "message");
423         return new AuditEvent(source, "filename", message);
424     }
425 
426 }