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.checks.javadoc;
21  
22  import static com.google.common.truth.Truth.assertWithMessage;
23  import static com.puppycrawl.tools.checkstyle.checks.javadoc.WriteTagCheck.MSG_MISSING_TAG;
24  import static com.puppycrawl.tools.checkstyle.checks.javadoc.WriteTagCheck.MSG_TAG_FORMAT;
25  import static com.puppycrawl.tools.checkstyle.checks.javadoc.WriteTagCheck.MSG_WRITE_TAG;
26  
27  import java.io.ByteArrayInputStream;
28  import java.io.ByteArrayOutputStream;
29  import java.io.File;
30  import java.io.InputStreamReader;
31  import java.io.LineNumberReader;
32  import java.nio.charset.StandardCharsets;
33  import java.util.ArrayList;
34  import java.util.Collections;
35  import java.util.HashSet;
36  import java.util.List;
37  import java.util.Locale;
38  import java.util.regex.Matcher;
39  import java.util.regex.Pattern;
40  
41  import org.junit.jupiter.api.Test;
42  
43  import com.puppycrawl.tools.checkstyle.AbstractAutomaticBean;
44  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
45  import com.puppycrawl.tools.checkstyle.Checker;
46  import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
47  import com.puppycrawl.tools.checkstyle.DefaultLogger;
48  import com.puppycrawl.tools.checkstyle.PackageObjectFactory;
49  import com.puppycrawl.tools.checkstyle.TreeWalker;
50  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
51  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
52  import de.thetaphi.forbiddenapis.SuppressForbidden;
53  
54  /**
55   * Unit test for WriteTagCheck.
56   */
57  public class WriteTagCheckTest extends AbstractModuleTestSupport {
58  
59      @Override
60      protected String getPackageLocation() {
61          return "com/puppycrawl/tools/checkstyle/checks/javadoc/writetag";
62      }
63  
64      @Test
65      public void testDefaultSettings() throws Exception {
66          final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
67          verifyWithInlineConfigParser(getPath("InputWriteTagDefault.java"), expected);
68      }
69  
70      @Test
71      public void testTag() throws Exception {
72          final String[] expected = {
73              "15: " + getCheckMessage(MSG_WRITE_TAG, "@author", "Daniel Grenner"),
74          };
75          verifyWithInlineConfigParserTwice(getPath("InputWriteTag.java"), expected);
76      }
77  
78      @Test
79      public void testMissingFormat() throws Exception {
80          final String[] expected = {
81              "15: " + getCheckMessage(MSG_WRITE_TAG, "@author", "Daniel Grenner"),
82          };
83          verifyWithInlineConfigParserTwice(
84                  getPath("InputWriteTagMissingFormat.java"), expected);
85      }
86  
87      @Test
88      public void testTagIncomplete() throws Exception {
89          final String[] expected = {
90              "16: " + getCheckMessage(MSG_WRITE_TAG, "@incomplete",
91                  "This class needs more code..."),
92          };
93          verifyWithInlineConfigParserTwice(
94                  getPath("InputWriteTagIncomplete.java"), expected);
95      }
96  
97      @Test
98      public void testDoubleTag() throws Exception {
99          final String[] expected = {
100             "18: " + getCheckMessage(MSG_WRITE_TAG, "@doubletag", "first text"),
101             "19: " + getCheckMessage(MSG_WRITE_TAG, "@doubletag", "second text"),
102         };
103         verifyWithInlineConfigParserTwice(
104                 getPath("InputWriteTagDoubleTag.java"), expected);
105     }
106 
107     @Test
108     public void testEmptyTag() throws Exception {
109         final String[] expected = {
110             "19: " + getCheckMessage(MSG_WRITE_TAG, "@emptytag", ""),
111         };
112         verifyWithInlineConfigParserTwice(
113                 getPath("InputWriteTagEmptyTag.java"), expected);
114     }
115 
116     @Test
117     public void testMissingTag() throws Exception {
118         final String[] expected = {
119             "20: " + getCheckMessage(MSG_MISSING_TAG, "@missingtag"),
120         };
121         verifyWithInlineConfigParserTwice(
122                 getPath("InputWriteTagMissingTag.java"), expected);
123     }
124 
125     @Test
126     public void testInterface() throws Exception {
127         final String[] expected = {
128             "15: " + getCheckMessage(MSG_WRITE_TAG, "@author", "Daniel Grenner"),
129         };
130         verifyWithInlineConfigParserTwice(
131                 getPath("InputWriteTagInterface.java"), expected
132         );
133     }
134 
135     @Test
136     public void testBlockComment() throws Exception {
137         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
138         verifyWithInlineConfigParserTwice(
139                 getPath("InputWriteTagBlockComment.java"), expected
140         );
141     }
142 
143     @Test
144     public void testMethod() throws Exception {
145         final String[] expected = {
146             "24: " + getCheckMessage(MSG_WRITE_TAG, "@todo",
147                     "Add a constructor comment"),
148             "36: " + getCheckMessage(MSG_WRITE_TAG, "@todo", "Add a comment"),
149         };
150         verifyWithInlineConfigParserTwice(
151                 getPath("InputWriteTagMethod.java"), expected);
152     }
153 
154     @Test
155     public void testSeverity() throws Exception {
156         final String[] expected = {
157             "16: " + getCheckMessage(MSG_WRITE_TAG, "@author", "Daniel Grenner"),
158         };
159         verifyWithInlineConfigParserTwice(
160                 getPath("InputWriteTagSeverity.java"), expected);
161     }
162 
163     /**
164      * Reason for low level testing:
165      * There is no direct way to fetch severity level directly.
166      * This is an exceptional case in which the logs are fetched indirectly using default
167      * logger listener in order to check for the severity level being reset by logging twice.
168      * First log should be the tag's severity level then it should reset the severity level back to
169      * error which is then checked upon using the log's severity level.
170      * This test needs to use a forbidden api {@code ByteArrayOutputStream#toString()}
171      * to get the logs as a string from the output stream
172      */
173     @Test
174     @SuppressForbidden
175     public void testResetSeverityLevel() throws Exception {
176 
177         final Checker checker = new Checker();
178 
179         final TreeWalker treeWalker = new TreeWalker();
180         final PackageObjectFactory factory = new PackageObjectFactory(
181                 new HashSet<>(), Thread.currentThread().getContextClassLoader());
182 
183         treeWalker.setModuleFactory(factory);
184         treeWalker.finishLocalSetup();
185 
186         final DefaultConfiguration writeTagConfig = createModuleConfig(WriteTagCheck.class);
187         writeTagConfig.addProperty("tag", "@author");
188         writeTagConfig.addProperty("tagFormat", "Mohanad");
189         writeTagConfig.addProperty("tagSeverity", "warning");
190 
191         treeWalker.setupChild(writeTagConfig);
192 
193         checker.addFileSetCheck(treeWalker);
194 
195         final ByteArrayOutputStream out = TestUtil.getInternalState(this, "stream");
196         final DefaultLogger logger = new DefaultLogger(out,
197                 AbstractAutomaticBean.OutputStreamOptions.CLOSE);
198         checker.addListener(logger);
199 
200         execute(checker, getPath("InputWriteTagResetSeverity.java"));
201 
202         final String output = out.toString();
203 
204         // logs severity levels are between square brackets []
205         final Pattern severityPattern = Pattern.compile("\\[(ERROR|WARN|INFO|IGNORE)]");
206 
207         final Matcher matcher = severityPattern.matcher(output);
208 
209         // First log is just the normal tag one
210         final boolean firstMatchFound = matcher.find();
211         assertWithMessage("Severity level should be wrapped in a square bracket []")
212                 .that(firstMatchFound)
213                 .isTrue();
214 
215         final String tagExpectedSeverityLevel = "warn";
216         final String firstSeverityLevel = matcher.group(1).toLowerCase(Locale.ENGLISH);
217 
218         assertWithMessage("First log should have an error severity level")
219                 .that(firstSeverityLevel)
220                 .isEqualTo(tagExpectedSeverityLevel);
221 
222         // Now we check for the second log which should log error  if
223         // the previous log did not have an issue while resetting the original severity level
224         final boolean secondMatchFound = matcher.find();
225         assertWithMessage("Severity level should be wrapped in a square bracket []")
226                 .that(secondMatchFound)
227                 .isTrue();
228 
229         final String expectedSeverityLevelAfterReset = "error";
230 
231         final String secondSeverityLevel = matcher.group(1).toLowerCase(Locale.ENGLISH);
232 
233         assertWithMessage("Second violation's severity level"
234                 + " should have been reset back to default (error)")
235                 .that(secondSeverityLevel)
236                 .isEqualTo(expectedSeverityLevelAfterReset);
237 
238     }
239 
240     @Test
241     public void testIgnoreMissing() throws Exception {
242         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
243         verifyWithInlineConfigParserTwice(getPath("InputWriteTagIgnore.java"), expected);
244     }
245 
246     @Test
247     public void testRegularEx() throws Exception {
248         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
249         verifyWithInlineConfigParserTwice(
250                 getPath("InputWriteTagRegularExpression.java"), expected);
251     }
252 
253     @Test
254     public void testRegularExError() throws Exception {
255         final String[] expected = {
256             "15: " + getCheckMessage(MSG_TAG_FORMAT, "@author", "ABC"),
257         };
258         verifyWithInlineConfigParserTwice(
259                 getPath("InputWriteTagExpressionError.java"), expected);
260     }
261 
262     @Test
263     public void testEnumsAndAnnotations() throws Exception {
264         final String[] expected = {
265             "16: " + getCheckMessage(MSG_WRITE_TAG, "@incomplete",
266                     "This enum needs more code..."),
267             "21: " + getCheckMessage(MSG_WRITE_TAG, "@incomplete",
268                     "This enum constant needs more code..."),
269             "28: " + getCheckMessage(MSG_WRITE_TAG, "@incomplete",
270                     "This annotation needs more code..."),
271             "33: " + getCheckMessage(MSG_WRITE_TAG, "@incomplete",
272                     "This annotation field needs more code..."),
273         };
274         verifyWithInlineConfigParserTwice(
275                 getPath("InputWriteTagEnumsAndAnnotations.java"), expected);
276     }
277 
278     @Test
279     public void testNoJavadocs() throws Exception {
280         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
281 
282         verifyWithInlineConfigParserTwice(getPath("InputWriteTagNoJavadoc.java"), expected);
283     }
284 
285     @Test
286     public void testWriteTagRecordsAndCompactCtors() throws Exception {
287         final String[] expected = {
288             "19: " + getCheckMessage(MSG_TAG_FORMAT, "@incomplete", "\\S"),
289             "26: " + getCheckMessage(MSG_WRITE_TAG, "@incomplete",
290                     "Failed to recognize 'record' introduced in Java 14."),
291             "37: " + getCheckMessage(MSG_WRITE_TAG, "@incomplete",
292                     "Failed to recognize 'record' introduced in Java 14."),
293             "48: " + getCheckMessage(MSG_WRITE_TAG, "@incomplete",
294                     "Failed to recognize 'record' introduced in Java 14."),
295             "62: " + getCheckMessage(MSG_WRITE_TAG, "@incomplete",
296                     "Failed to recognize 'record' introduced in Java 14."),
297         };
298         verifyWithInlineConfigParserTwice(
299             getNonCompilablePath("InputWriteTagRecordsAndCompactCtors.java"), expected);
300     }
301 
302     @Override
303     protected void verify(Checker checker,
304                           File[] processedFiles,
305                           String messageFileName,
306                           String... expected)
307             throws Exception {
308         getStream().flush();
309         final List<File> theFiles = new ArrayList<>();
310         Collections.addAll(theFiles, processedFiles);
311         final int errs = checker.process(theFiles);
312 
313         // process each of the lines
314         try (ByteArrayInputStream localStream =
315                 new ByteArrayInputStream(getStream().toByteArray());
316             LineNumberReader lnr = new LineNumberReader(
317                 new InputStreamReader(localStream, StandardCharsets.UTF_8))) {
318             for (int i = 0; i < expected.length; i++) {
319                 final String expectedResult = messageFileName + ":" + expected[i];
320                 final String actual = lnr.readLine();
321                 assertWithMessage("error message " + i)
322                         .that(actual)
323                         .isEqualTo(expectedResult);
324             }
325 
326             assertWithMessage("unexpected output: " + lnr.readLine())
327                     .that(errs)
328                     .isAtMost(expected.length);
329         }
330         checker.destroy();
331     }
332 
333 }