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 com.puppycrawl.tools.checkstyle.checks.naming.AbstractNameCheck.MSG_INVALID_PATTERN;
24  import static com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck.MSG_KEY;
25  
26  import java.io.File;
27  import java.io.FileNotFoundException;
28  import java.io.IOException;
29  import java.nio.file.Files;
30  import java.nio.file.StandardCopyOption;
31  import java.util.List;
32  
33  import org.junit.jupiter.api.Test;
34  import org.junit.jupiter.api.io.TempDir;
35  
36  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
37  import com.puppycrawl.tools.checkstyle.api.AuditEvent;
38  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
39  import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
40  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
41  import com.puppycrawl.tools.checkstyle.api.Violation;
42  import com.puppycrawl.tools.checkstyle.checks.coding.MagicNumberCheck;
43  import com.puppycrawl.tools.checkstyle.checks.naming.ConstantNameCheck;
44  import com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineCheck;
45  import com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck;
46  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
47  
48  public class SuppressWithNearbyTextFilterTest extends AbstractModuleTestSupport {
49  
50      private static final String REGEXP_SINGLELINE_CHECK_FORMAT = "this should not appear";
51  
52      @TempDir
53      public File temporaryFolder;
54  
55      @Override
56      protected String getPackageLocation() {
57          return "com/puppycrawl/tools/checkstyle/filters/suppresswithnearbytextfilter";
58      }
59  
60      @Test
61      public void testDefaultConfig() throws Exception {
62          final int expectedLineLength = 90;
63          final String pattern = "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$";
64  
65          final String[] violationMessages = {
66              "29: " + getLineLengthCheckMessage(expectedLineLength, 94),
67              "31: " + getLineLengthCheckMessage(expectedLineLength, 97),
68              "41: " + getLineLengthCheckMessage(expectedLineLength, 94),
69              "44:22: " + getCheckMessage(ConstantNameCheck.class,
70                      MSG_INVALID_PATTERN, "badConstant", pattern),
71              "47:22: " + getCheckMessage(ConstantNameCheck.class,
72                      MSG_INVALID_PATTERN, "badConstant1", pattern),
73          };
74  
75          final String[] suppressedMessages = {
76              "31: " + getLineLengthCheckMessage(expectedLineLength, 97),
77              "41: " + getLineLengthCheckMessage(expectedLineLength, 94),
78              "47:22: " + getCheckMessage(ConstantNameCheck.class,
79                      MSG_INVALID_PATTERN, "badConstant1", pattern),
80          };
81  
82          verifyFilterWithInlineConfigParser(
83              getPath("InputSuppressWithNearbyTextFilterDefaultConfig.java"),
84              violationMessages, removeSuppressed(violationMessages, suppressedMessages)
85          );
86      }
87  
88      @Test
89      public void testNearbyTextPattern() throws Exception {
90          final int expectedLineLength = 90;
91  
92          final String[] violationMessages = {
93              "15: " + getRegexpSinglelineCheckMessage(),
94              "28: " + getLineLengthCheckMessage(expectedLineLength, 94),
95              "33: " + getLineLengthCheckMessage(expectedLineLength, 93),
96              "33: " + getRegexpSinglelineCheckMessage(),
97              "39: " + getLineLengthCheckMessage(expectedLineLength, 93),
98              "44: " + getRegexpSinglelineCheckMessage(),
99              "49: " + getLineLengthCheckMessage(expectedLineLength, 95),
100             "54: " + getRegexpSinglelineCheckMessage(),
101             "58: " + getLineLengthCheckMessage(expectedLineLength, 97),
102         };
103 
104         final String[] suppressedMessages = {
105             "33: " + getLineLengthCheckMessage(expectedLineLength, 93),
106             "33: " + getRegexpSinglelineCheckMessage(),
107             "39: " + getLineLengthCheckMessage(expectedLineLength, 93),
108             "49: " + getLineLengthCheckMessage(expectedLineLength, 95),
109             "54: " + getRegexpSinglelineCheckMessage(),
110             "58: " + getLineLengthCheckMessage(expectedLineLength, 97),
111         };
112 
113         verifyFilterWithInlineConfigParser(
114             getPath("InputSuppressWithNearbyTextFilterNearbyTextPattern.css.txt"),
115             violationMessages, removeSuppressed(violationMessages, suppressedMessages)
116         );
117     }
118 
119     @Test
120     public void testCheckPattern() throws Exception {
121         final int expectedLineLength = 80;
122 
123         final String[] violationMessages = {
124             "15: " + getRegexpSinglelineCheckMessage(),
125             "28: " + getLineLengthCheckMessage(expectedLineLength, 89),
126             "29: " + getRegexpSinglelineCheckMessage(),
127             "35: " + getLineLengthCheckMessage(expectedLineLength, 87),
128         };
129 
130         final String[] suppressedMessages = {
131             "28: " + getLineLengthCheckMessage(expectedLineLength, 89),
132             "35: " + getLineLengthCheckMessage(expectedLineLength, 87),
133         };
134 
135         verifyFilterWithInlineConfigParser(
136             getPath("InputSuppressWithNearbyTextFilterCheckPattern.bash.txt"),
137             violationMessages, removeSuppressed(violationMessages, suppressedMessages)
138         );
139     }
140 
141     @Test
142     public void testMessagePattern() throws Exception {
143         final int expectedLineLength = 90;
144 
145         final String[] violationMessages = {
146             "15: " + getRegexpSinglelineCheckMessage(),
147             "33: " + getRegexpSinglelineCheckMessage(),
148             "38: " + getLineLengthCheckMessage(expectedLineLength, 98),
149             "42: " + getLineLengthCheckMessage(expectedLineLength, 96),
150         };
151 
152         final String[] suppressedMessages = {
153             "38: " + getLineLengthCheckMessage(expectedLineLength, 98),
154             "42: " + getLineLengthCheckMessage(expectedLineLength, 96),
155         };
156 
157         verifyFilterWithInlineConfigParser(
158             getPath("InputSuppressWithNearbyTextFilterMessagePattern.xml.txt"),
159             violationMessages, removeSuppressed(violationMessages, suppressedMessages)
160         );
161     }
162 
163     @Test
164     public void testIdPattern() throws Exception {
165         final int expectedLineLength = 80;
166 
167         final String[] violationMessages = {
168             "16: " + getRegexpSinglelineCheckMessage(),
169             "29: " + getRegexpSinglelineCheckMessage(),
170             "34: " + getLineLengthCheckMessage(expectedLineLength, 83),
171             "38: " + getLineLengthCheckMessage(expectedLineLength, 84),
172         };
173 
174         final String[] suppressedMessages = {
175             "34: " + getLineLengthCheckMessage(expectedLineLength, 83),
176             "38: " + getLineLengthCheckMessage(expectedLineLength, 84),
177         };
178 
179         verifyFilterWithInlineConfigParser(
180             getPath("InputSuppressWithNearbyTextFilterIdPattern.html.txt"),
181             violationMessages, removeSuppressed(violationMessages, suppressedMessages)
182         );
183     }
184 
185     @Test
186     public void testLineRangePositive3() throws Exception {
187         final int expectedLineLength = 92;
188 
189         final String[] violationMessages = {
190             "15: " + getRegexpSinglelineCheckMessage(),
191             "27: " + getLineLengthCheckMessage(expectedLineLength, 98),
192             "28: " + getLineLengthCheckMessage(expectedLineLength, 98),
193             "29: " + getLineLengthCheckMessage(expectedLineLength, 98),
194             "30: " + getLineLengthCheckMessage(expectedLineLength, 93),
195             "33: " + getRegexpSinglelineCheckMessage(),
196             "34: " + getRegexpSinglelineCheckMessage(),
197             "35: " + getRegexpSinglelineCheckMessage(),
198             "36: " + getRegexpSinglelineCheckMessage(),
199         };
200 
201         final String[] suppressedMessages = {
202             "27: " + getLineLengthCheckMessage(expectedLineLength, 98),
203             "28: " + getLineLengthCheckMessage(expectedLineLength, 98),
204             "29: " + getLineLengthCheckMessage(expectedLineLength, 98),
205             "33: " + getRegexpSinglelineCheckMessage(),
206             "34: " + getRegexpSinglelineCheckMessage(),
207             "35: " + getRegexpSinglelineCheckMessage(),
208             "36: " + getRegexpSinglelineCheckMessage(),
209         };
210 
211         verifyFilterWithInlineConfigParser(
212             getPath("InputSuppressWithNearbyTextFilterLineRangePositive3.sql.txt"),
213             violationMessages, removeSuppressed(violationMessages, suppressedMessages)
214         );
215     }
216 
217     @Test
218     public void testLineRangeNegative2() throws Exception {
219         final int expectedLineLength = 91;
220 
221         final String[] violationMessages = {
222             "15: " + getRegexpSinglelineCheckMessage(),
223             "27: " + getLineLengthCheckMessage(expectedLineLength, 96),
224             "28: " + getLineLengthCheckMessage(expectedLineLength, 94),
225             "29: " + getLineLengthCheckMessage(expectedLineLength, 94),
226             "30: " + getLineLengthCheckMessage(expectedLineLength, 98),
227             "33: " + getRegexpSinglelineCheckMessage(),
228             "34: " + getRegexpSinglelineCheckMessage(),
229             "35: " + getRegexpSinglelineCheckMessage(),
230             "36: " + getRegexpSinglelineCheckMessage(),
231         };
232 
233         final String[] suppressedMessages = {
234             "28: " + getLineLengthCheckMessage(expectedLineLength, 94),
235             "29: " + getLineLengthCheckMessage(expectedLineLength, 94),
236             "30: " + getLineLengthCheckMessage(expectedLineLength, 98),
237             "34: " + getRegexpSinglelineCheckMessage(),
238             "35: " + getRegexpSinglelineCheckMessage(),
239             "36: " + getRegexpSinglelineCheckMessage(),
240         };
241 
242         verifyFilterWithInlineConfigParser(
243             getPath("InputSuppressWithNearbyTextFilterLineRangeNegative2.txt"),
244             violationMessages, removeSuppressed(violationMessages, suppressedMessages)
245         );
246     }
247 
248     @Test
249     public void testVariableCheckPatternAndLineRange() throws Exception {
250         final int expectedLineLength = 85;
251 
252         final String[] violationMessages = {
253             "19: " + getLineLengthCheckMessage(expectedLineLength, 89),
254             "20: " + getLineLengthCheckMessage(expectedLineLength, 89),
255             "21: " + getLineLengthCheckMessage(expectedLineLength, 89),
256             "22: " + getLineLengthCheckMessage(expectedLineLength, 87),
257             "24: " + getLineLengthCheckMessage(expectedLineLength, 87),
258             "25: " + getLineLengthCheckMessage(expectedLineLength, 89),
259             "26: " + getLineLengthCheckMessage(expectedLineLength, 89),
260         };
261 
262         final String[] suppressedMessages = {
263             "19: " + getLineLengthCheckMessage(expectedLineLength, 89),
264             "20: " + getLineLengthCheckMessage(expectedLineLength, 89),
265             "21: " + getLineLengthCheckMessage(expectedLineLength, 89),
266             "25: " + getLineLengthCheckMessage(expectedLineLength, 89),
267             "26: " + getLineLengthCheckMessage(expectedLineLength, 89),
268         };
269 
270         verifyFilterWithInlineConfigParser(
271             getPath("InputSuppressWithNearbyTextFilter"
272                     + "VariableNearbyTextPatternAndLineRange.xml.txt"),
273             violationMessages, removeSuppressed(violationMessages, suppressedMessages)
274         );
275     }
276 
277     @Test
278     public void testNearbyTextPatternAny() throws Exception {
279         final int expectedLineLength = 76;
280 
281         final String[] violationMessages = {
282             "18: " + getLineLengthCheckMessage(expectedLineLength, 80),
283         };
284 
285         final String[] suppressedMessages = {
286             "18: " + getLineLengthCheckMessage(expectedLineLength, 80),
287         };
288 
289         verifyFilterWithInlineConfigParser(
290             getPath("InputSuppressWithNearbyTextFilterNearbyTextPatternAny.txt"),
291             violationMessages, removeSuppressed(violationMessages, suppressedMessages)
292         );
293     }
294 
295     @Test
296     public void testNearbyTextPatternCompactVariableCheckPattern() throws Exception {
297         final String[] violationMessages = {
298             "26:13: " + getCheckMessage(MagicNumberCheck.class, MagicNumberCheck.MSG_KEY, "42"),
299             "27:13: " + getCheckMessage(MagicNumberCheck.class, MagicNumberCheck.MSG_KEY, "43"),
300         };
301 
302         final String[] suppressedMessages = {
303             "26:13: " + getCheckMessage(MagicNumberCheck.class, MagicNumberCheck.MSG_KEY, "42"),
304         };
305 
306         verifyFilterWithInlineConfigParser(
307                 getPath("InputSuppressWithNearbyTextFilterNearbyTextPattern"
308                         + "CompactVariableCheckPattern.java"),
309                 violationMessages, removeSuppressed(violationMessages, suppressedMessages)
310         );
311     }
312 
313     @Test
314     public void testNearbyTextPatternUrlLineLengthSuppression() throws Exception {
315         final int expectedLineLength = 90;
316 
317         final String[] violationMessages = {
318             "32: " + getLineLengthCheckMessage(expectedLineLength, 98),
319             "39: " + getLineLengthCheckMessage(expectedLineLength, 97),
320         };
321 
322         final String[] suppressedMessages = {
323             "32: " + getLineLengthCheckMessage(expectedLineLength, 98),
324             "39: " + getLineLengthCheckMessage(expectedLineLength, 97),
325         };
326 
327         verifyFilterWithInlineConfigParser(
328             getPath("InputSuppressWithNearbyTextFilterNearbyTextPatternUrlLineLengthSuppression"
329                     + ".java"),
330             violationMessages, removeSuppressed(violationMessages, suppressedMessages)
331         );
332     }
333 
334     @Test
335     public void testInvalidCheckPattern() throws Exception {
336         final String[] violationAndSuppressedMessages = {
337             "18: " + getLineLengthCheckMessage(80, 93),
338         };
339 
340         try {
341             verifyFilterWithInlineConfigParser(
342                 getPath("InputSuppressWithNearbyTextFilterInvalidCheckPattern.txt"),
343                 violationAndSuppressedMessages
344             );
345             assertWithMessage("CheckstyleException is expected").fail();
346         }
347         catch (CheckstyleException ex) {
348             final IllegalArgumentException cause = (IllegalArgumentException) ex.getCause();
349             assertWithMessage("Invalid exception message")
350                 .that(cause)
351                 .hasMessageThat()
352                 .isEqualTo("unable to parse expanded comment a![b");
353         }
354     }
355 
356     @Test
357     public void testInvalidIdPattern() throws Exception {
358         final String[] violationAndSuppressedMessages = {
359             "18: " + getLineLengthCheckMessage(80, 93),
360         };
361 
362         try {
363             verifyFilterWithInlineConfigParser(
364                 getPath("InputSuppressWithNearbyTextFilterInvalidIdPattern.txt"),
365                 violationAndSuppressedMessages
366             );
367             assertWithMessage("CheckstyleException is expected").fail();
368         }
369         catch (CheckstyleException ex) {
370             final IllegalArgumentException cause = (IllegalArgumentException) ex.getCause();
371             assertWithMessage("Invalid exception message")
372                 .that(cause)
373                 .hasMessageThat()
374                 .isEqualTo("unable to parse expanded comment a![b");
375         }
376     }
377 
378     @Test
379     public void testInvalidMessagePattern() throws Exception {
380         final String[] violationAndSuppressedMessages = {
381             "18: " + getLineLengthCheckMessage(80, 93),
382         };
383 
384         try {
385             verifyFilterWithInlineConfigParser(
386                 getPath("InputSuppressWithNearbyTextFilterInvalidMessagePattern.txt"),
387                 violationAndSuppressedMessages
388             );
389             assertWithMessage("CheckstyleException is expected").fail();
390         }
391         catch (CheckstyleException ex) {
392             final IllegalArgumentException cause = (IllegalArgumentException) ex.getCause();
393             assertWithMessage("Invalid exception message")
394                 .that(cause)
395                 .hasMessageThat()
396                 .isEqualTo("unable to parse expanded comment a![b");
397         }
398     }
399 
400     @Test
401     public void testInvalidLineRange() throws Exception {
402         final String[] violationAndSuppressedMessages = {
403             "18: " + getLineLengthCheckMessage(80, 93),
404         };
405 
406         try {
407             verifyFilterWithInlineConfigParser(
408                 getPath("InputSuppressWithNearbyTextFilterInvalidLineRange.txt"),
409                 violationAndSuppressedMessages
410             );
411             assertWithMessage("CheckstyleException is expected").fail();
412         }
413         catch (CheckstyleException ex) {
414             assertWithMessage("Invalid exception message")
415                 .that(ex)
416                 .hasCauseThat()
417                 .hasMessageThat()
418                 .isEqualTo("unable to parse line range"
419                         + " from 'SUPPRESS CHECKSTYLE LineLengthCheck' using a!b");
420         }
421     }
422 
423     /**
424      * Calls the filter twice and removes input file in between to ensure that
425      * the cached file is not read twice.
426      * We cannot use {@link AbstractModuleTestSupport#verifyFilterWithInlineConfigParser}
427      * because to kill pitest survival we need to remove target file between
428      * filter execution to accept violation
429      */
430     @Test
431     public void testCachingExecution() throws Exception {
432         final SuppressWithNearbyTextFilter suppressFilter = new SuppressWithNearbyTextFilter();
433         final String inputPath =
434                 getPath("InputSuppressWithNearbyTextFilterDefaultConfig.java");
435         final File tempFile = new File(temporaryFolder,
436                 "InputSuppressWithNearbyTextFilterDefaultConfig.java");
437         Files.copy(new File(inputPath).toPath(), tempFile.toPath(),
438                 StandardCopyOption.REPLACE_EXISTING);
439 
440         final AuditEvent auditEvent1 = new AuditEvent(
441                 tempFile.getPath(), tempFile.getPath(),
442                 new Violation(1, null, null, null, null,
443                         Object.class, null)
444         );
445         suppressFilter.accept(auditEvent1);
446         final boolean deleted = tempFile.delete();
447         assertWithMessage("Temporary file should be deleted.")
448                 .that(deleted).isTrue();
449         final AuditEvent auditEvent2 = new AuditEvent(
450                 tempFile.getPath(), tempFile.getPath(),
451                 new Violation(2, null, null, null, null,
452                         Object.class, null)
453         );
454         suppressFilter.accept(auditEvent2);
455 
456         assertWithMessage("Cache should handle missing file.")
457                 .that(tempFile.exists()).isFalse();
458     }
459 
460     /**
461      * Calls the filter with an audit event without a violation and asserts that the filter
462      * accepts the event. Our goal is 100% test coverage and it is not possible to create
463      * null violation with {@link AbstractModuleTestSupport#verifyFilterWithInlineConfigParser}.
464      */
465     @Test
466     public void testAcceptNullViolation() {
467         final SuppressWithNearbyTextFilter filter = new SuppressWithNearbyTextFilter();
468         final AuditEvent auditEvent = new AuditEvent(this);
469         assertWithMessage("Filter should accept audit event")
470                 .that(filter.accept(auditEvent))
471                 .isTrue();
472         assertWithMessage("File name should not be null")
473             .that(auditEvent.getFileName())
474             .isNull();
475     }
476 
477     /**
478      * Calls the filter with an audit event for a non-existing file and asserts that the filter
479      * throws an exception. Our goal is 100% test coverage and we cannot use
480      * {@link AbstractModuleTestSupport#verifyFilterWithInlineConfigParser} because
481      * {@code InlineConfigParser} does not allow to specify a non-existing file.
482      */
483     @Test
484     public void testThrowsIllegalStateExceptionWhenFileNotFound() {
485         final Violation message = new Violation(1, 1, 1, TokenTypes.CLASS_DEF,
486             "messages.properties", "key", null, SeverityLevel.ERROR, null, getClass(), null);
487         final String fileName = "nonexisting_file";
488         final AuditEvent auditEvent = new AuditEvent(this, fileName, message);
489         final SuppressWithNearbyTextFilter filter = new SuppressWithNearbyTextFilter();
490 
491         try {
492             filter.accept(auditEvent);
493             assertWithMessage(IllegalStateException.class.getSimpleName() + " is expected").fail();
494         }
495         catch (IllegalStateException ex) {
496             assertWithMessage("Invalid exception message")
497                 .that(ex.getMessage())
498                 .isEqualTo("Cannot read source file: " + fileName);
499 
500             final Throwable cause = ex.getCause();
501             assertWithMessage("Exception cause has invalid type")
502                     .that(cause)
503                     .isInstanceOf(FileNotFoundException.class);
504             assertWithMessage("Invalid exception message")
505                 .that(cause)
506                 .hasMessageThat()
507                 .isEqualTo(fileName + " (No such file or directory)");
508         }
509     }
510 
511     /**
512      * Calls the filter with an audit event for a directory and asserts that the filter accepts the
513      * event. Some violations can be on a directory instead of a file. Our goal is 100% test
514      * coverage and we cannot use
515      * {@link AbstractModuleTestSupport#verifyFilterWithInlineConfigParser} because
516      * it is impossible to specify a directory with input file configuration.
517      *
518      * @throws IOException if an error occurs while formatting the path to the input file.
519      */
520     @Test
521     public void testFilterWithDirectory() throws IOException {
522         final SuppressWithNearbyTextFilter filter = new SuppressWithNearbyTextFilter();
523         final AuditEvent event = new AuditEvent(this, getPath(""), new Violation(1, 1,
524                 "bundle", "key", null, SeverityLevel.ERROR, "moduleId", getClass(),
525                 "customMessage"));
526 
527         assertWithMessage("filter should accept directory")
528                 .that(filter.accept(event))
529                 .isTrue();
530     }
531 
532     /**
533      * Calls the filter on two consecutive real input files and asserts that the 'suppressions'
534      * internal field is cleared after each run.
535      * Our goal is 100% test coverage and we need access to the implementation details
536      * so {@link AbstractModuleTestSupport#verifyFilterWithInlineConfigParser} is not used.
537      *
538      * @throws IOException if an error occurs while formatting the path to the input file.
539      */
540     @Test
541     public void testSuppressionsAreClearedEachRun() throws IOException {
542         final SuppressWithNearbyTextFilter filter = new SuppressWithNearbyTextFilter();
543 
544         final List<?> suppressions1 = getSuppressionsAfterExecution(filter,
545                 getPath("InputSuppressWithNearbyTextFilterDefaultConfig.java"));
546         assertWithMessage("Invalid suppressions size")
547                 .that(suppressions1)
548                 .hasSize(3);
549 
550         final List<?> suppressions2 = getSuppressionsAfterExecution(filter,
551                 getPath("InputSuppressWithNearbyTextFilterOneLineText.txt"));
552         assertWithMessage("Invalid suppressions size")
553                 .that(suppressions2)
554                 .isEmpty();
555     }
556 
557     /**
558      * Calls the filter on two consecutive real input files and asserts that the
559      * 'cachedFileAbsolutePath' internal field has changed after the first run.
560      * Our goal is 100% test coverage and we need access to the implementation details
561      * so {@link AbstractModuleTestSupport#verifyFilterWithInlineConfigParser} is not used.
562      *
563      * @throws IOException if an error occurs while formatting the path to the input file.
564      */
565     @Test
566     public void testCachedFileAbsolutePathHasChangedEachRun() throws IOException {
567         final SuppressWithNearbyTextFilter filter = new SuppressWithNearbyTextFilter();
568 
569         final String cachedFileAbsolutePath1 = getCachedFileAbsolutePathAfterExecution(filter,
570                 getPath("InputSuppressWithNearbyTextFilterDefaultConfig.java"));
571         final String cachedFileAbsolutePath2 = getCachedFileAbsolutePathAfterExecution(filter,
572                 getPath("InputSuppressWithNearbyTextFilterOneLineText.txt"));
573 
574         assertWithMessage("cachedFileAbsolutePath has not changed")
575                 .that(cachedFileAbsolutePath1)
576                 .isNotEqualTo(cachedFileAbsolutePath2);
577     }
578 
579     /**
580      * Calls the filter with a real input file and returns a list of identified suppressions.
581      *
582      * @return {@code Suppression} list.
583      */
584     private static List<?> getSuppressionsAfterExecution(SuppressWithNearbyTextFilter filter,
585                                                          String filename) {
586         final AuditEvent dummyEvent = buildDummyAuditEvent(filename);
587         filter.accept(dummyEvent);
588         return TestUtil.getInternalState(filter, "suppressions");
589     }
590 
591     /**
592      * Calls the filter with a real input file and returns the
593      * state of the 'cachedFileAbsolutePath' field.
594      *
595      * @return {@code cachedFileAbsolutePath} value.
596      */
597     private static String getCachedFileAbsolutePathAfterExecution(SuppressWithNearbyTextFilter
598                                                                            filter,
599                                                                   String filename) {
600         final AuditEvent dummyEvent = buildDummyAuditEvent(filename);
601         filter.accept(dummyEvent);
602         return TestUtil.getInternalState(filter, "cachedFileAbsolutePath");
603     }
604 
605     /**
606      * Builds a dummy audit event with a violation for the given file name.
607      *
608      * @param filename input file name.
609      * @return dummy {@code AuditEvent} instance.
610      */
611     private static AuditEvent buildDummyAuditEvent(String filename) {
612         final Violation violation = new Violation(1, null, null,
613                 null, null, Object.class, null);
614         return new AuditEvent("", filename, violation);
615     }
616 
617     private static String getLineLengthCheckMessage(int expectedLength, int actualLength) {
618         return getCheckMessage(LineLengthCheck.class, MSG_KEY, expectedLength, actualLength);
619     }
620 
621     private static String getRegexpSinglelineCheckMessage() {
622         final String msgRegexExceeded = "regexp.exceeded";
623         return getCheckMessage(RegexpSinglelineCheck.class,
624                 msgRegexExceeded, REGEXP_SINGLELINE_CHECK_FORMAT);
625     }
626 }