1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle.checks.metrics;
21
22 import static com.google.common.truth.Truth.assertWithMessage;
23 import static com.puppycrawl.tools.checkstyle.checks.metrics.NPathComplexityCheck.MSG_KEY;
24
25 import java.io.File;
26 import java.math.BigInteger;
27 import java.util.Collection;
28 import java.util.Optional;
29 import java.util.SortedSet;
30
31 import org.antlr.v4.runtime.CommonToken;
32 import org.junit.jupiter.api.Test;
33
34 import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
35 import com.puppycrawl.tools.checkstyle.DetailAstImpl;
36 import com.puppycrawl.tools.checkstyle.JavaParser;
37 import com.puppycrawl.tools.checkstyle.api.Context;
38 import com.puppycrawl.tools.checkstyle.api.DetailAST;
39 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
40 import com.puppycrawl.tools.checkstyle.api.Violation;
41 import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
42 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
43
44
45 public class NPathComplexityCheckTest extends AbstractModuleTestSupport {
46
47 @Override
48 protected String getPackageLocation() {
49 return "com/puppycrawl/tools/checkstyle/checks/metrics/npathcomplexity";
50 }
51
52 @Test
53 public void testCalculation() throws Exception {
54 final String[] expected = {
55 "12:5: " + getCheckMessage(MSG_KEY, 2, 0),
56 "17:17: " + getCheckMessage(MSG_KEY, 2, 0),
57 "29:5: " + getCheckMessage(MSG_KEY, 10, 0),
58 "42:5: " + getCheckMessage(MSG_KEY, 3, 0),
59 "52:5: " + getCheckMessage(MSG_KEY, 7, 0),
60 "70:5: " + getCheckMessage(MSG_KEY, 3, 0),
61 "83:5: " + getCheckMessage(MSG_KEY, 3, 0),
62 "95:5: " + getCheckMessage(MSG_KEY, 3, 0),
63 "111:13: " + getCheckMessage(MSG_KEY, 2, 0),
64 "120:5: " + getCheckMessage(MSG_KEY, 48, 0),
65 "130:5: " + getCheckMessage(MSG_KEY, 1, 0),
66 "131:5: " + getCheckMessage(MSG_KEY, 1, 0),
67 "137:17: " + getCheckMessage(MSG_KEY, 3, 0),
68 "151:21: " + getCheckMessage(MSG_KEY, 3, 0),
69 };
70
71 verifyWithInlineConfigParser(
72 getPath("InputNPathComplexityDefault.java"), expected);
73 }
74
75 @Test
76 public void testCalculation2() throws Exception {
77 final String[] expected = {
78 "12:5: " + getCheckMessage(MSG_KEY, 5, 0),
79 "18:5: " + getCheckMessage(MSG_KEY, 5, 0),
80 "25:5: " + getCheckMessage(MSG_KEY, 4, 0),
81 "40:5: " + getCheckMessage(MSG_KEY, 4, 0),
82 "56:5: " + getCheckMessage(MSG_KEY, 6, 0),
83 "72:5: " + getCheckMessage(MSG_KEY, 15, 0),
84 "97:5: " + getCheckMessage(MSG_KEY, 11, 0),
85 "107:5: " + getCheckMessage(MSG_KEY, 8, 0),
86 "120:5: " + getCheckMessage(MSG_KEY, 120, 0),
87 "132:5: " + getCheckMessage(MSG_KEY, 6, 0),
88 "142:5: " + getCheckMessage(MSG_KEY, 21, 0),
89 "155:5: " + getCheckMessage(MSG_KEY, 35, 0),
90 "163:5: " + getCheckMessage(MSG_KEY, 25, 0),
91 "178:5: " + getCheckMessage(MSG_KEY, 2, 0),
92 };
93
94 verifyWithInlineConfigParser(
95 getPath("InputNPathComplexity.java"), expected);
96 }
97
98 @Test
99 public void testCalculation3() throws Exception {
100 final String[] expected = {
101 "11:5: " + getCheckMessage(MSG_KEY, 64, 0),
102 };
103
104 verifyWithInlineConfigParser(
105 getNonCompilablePath("InputNPathComplexityDefault2.java"), expected);
106 }
107
108 @Test
109 public void testIntegerOverflow() throws Exception {
110
111 final long largerThanMaxInt = 3_486_784_401L;
112
113 final String[] expected = {
114 "20:5: " + getCheckMessage(MSG_KEY, largerThanMaxInt, 0),
115 };
116
117 verifyWithInlineConfigParser(
118 getPath("InputNPathComplexityOverflow.java"), expected);
119 }
120
121 @Test
122 @SuppressWarnings("unchecked")
123 public void testStatefulFieldsClearedOnBeginTree1() {
124 final DetailAstImpl ast = new DetailAstImpl();
125 ast.setType(TokenTypes.LITERAL_ELSE);
126
127 final NPathComplexityCheck check = new NPathComplexityCheck();
128 assertWithMessage("Stateful field is not cleared after beginTree")
129 .that(TestUtil.isStatefulFieldClearedDuringBeginTree(check, ast, "rangeValues",
130 rangeValues -> ((Collection<Context>) rangeValues).isEmpty()))
131 .isTrue();
132 assertWithMessage("Stateful field is not cleared after beginTree")
133 .that(TestUtil.isStatefulFieldClearedDuringBeginTree(check, ast, "expressionValues",
134 expressionValues -> ((Collection<Context>) expressionValues).isEmpty()))
135 .isTrue();
136 assertWithMessage("Stateful field is not cleared after beginTree")
137 .that(TestUtil.isStatefulFieldClearedDuringBeginTree(check, ast, "branchVisited",
138 branchVisited -> !(boolean) branchVisited))
139 .isTrue();
140 assertWithMessage("Stateful field is not cleared after beginTree")
141 .that(TestUtil.isStatefulFieldClearedDuringBeginTree(check, ast,
142 "currentRangeValue",
143 currentRangeValue -> currentRangeValue.equals(BigInteger.ZERO)))
144 .isTrue();
145 }
146
147 @Test
148 @SuppressWarnings("unchecked")
149 public void testStatefulFieldsClearedOnBeginTree2() {
150 final DetailAstImpl ast = new DetailAstImpl();
151 ast.setType(TokenTypes.LITERAL_RETURN);
152 ast.setLineNo(5);
153 final DetailAstImpl child = new DetailAstImpl();
154 child.setType(TokenTypes.SEMI);
155 ast.addChild(child);
156
157 final NPathComplexityCheck check = new NPathComplexityCheck();
158 assertWithMessage("Stateful field is not cleared after beginTree")
159 .that(TestUtil.isStatefulFieldClearedDuringBeginTree(check, ast, "afterValues",
160 isAfterValues -> ((Collection<Context>) isAfterValues).isEmpty()))
161 .isTrue();
162 }
163
164 @Test
165 public void testStatefulFieldsClearedOnBeginTree3() throws Exception {
166 final NPathComplexityCheck check = new NPathComplexityCheck();
167 final Optional<DetailAST> question = TestUtil.findTokenInAstByPredicate(
168 JavaParser.parseFile(new File(getPath("InputNPathComplexity.java")),
169 JavaParser.Options.WITHOUT_COMMENTS),
170 ast -> ast.getType() == TokenTypes.QUESTION);
171
172 assertWithMessage("Ast should contain QUESTION")
173 .that(question.isPresent())
174 .isTrue();
175
176 assertWithMessage("State is not cleared on beginTree")
177 .that(TestUtil.isStatefulFieldClearedDuringBeginTree(check, question.orElseThrow(),
178 "processingTokenEnd", processingTokenEnd -> {
179 return TestUtil.<Integer>getInternalState(processingTokenEnd,
180 "endLineNo") == 0
181 && TestUtil.<Integer>getInternalState(processingTokenEnd,
182 "endColumnNo") == 0;
183 }))
184 .isTrue();
185 }
186
187 @Test
188 public void testDefaultConfiguration() throws Exception {
189 final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
190 verifyWithInlineConfigParser(
191 getPath("InputNPathComplexityDefault2.java"), expected);
192 }
193
194 @Test
195 public void testNpathComplexityRecords() throws Exception {
196 final int max = 1;
197
198 final String[] expected = {
199 "15:5: " + getCheckMessage(MSG_KEY, 3, max),
200 "25:9: " + getCheckMessage(MSG_KEY, 2, max),
201 "30:21: " + getCheckMessage(MSG_KEY, 2, max),
202 "44:9: " + getCheckMessage(MSG_KEY, 3, max),
203 };
204
205 verifyWithInlineConfigParser(
206 getNonCompilablePath("InputNPathComplexityRecords.java"), expected);
207 }
208
209 @Test
210 public void testNpathComplexitySwitchExpression() throws Exception {
211
212 final int max = 1;
213
214 final String[] expected = {
215 "12:5: " + getCheckMessage(MSG_KEY, 5, max),
216 "29:5: " + getCheckMessage(MSG_KEY, 5, max),
217 "44:5: " + getCheckMessage(MSG_KEY, 6, max),
218 "60:5: " + getCheckMessage(MSG_KEY, 6, max),
219 };
220
221 verifyWithInlineConfigParser(
222 getNonCompilablePath("InputNPathComplexityCheckSwitchExpression.java"),
223 expected);
224 }
225
226 @Test
227 public void testBranchVisited() throws Exception {
228
229 final String[] expected = {
230 "13:3: " + getCheckMessage(MSG_KEY, 37, 20),
231 };
232
233 verifyWithInlineConfigParser(
234 getPath("InputNPathComplexityCheckBranchVisited.java"),
235 expected);
236 }
237
238 @Test
239 public void testCount() throws Exception {
240
241 final String[] expected = {
242 "11:5: " + getCheckMessage(MSG_KEY, 30, 20),
243 "22:5: " + getCheckMessage(MSG_KEY, 72, 20),
244 "67:5: " + getCheckMessage(MSG_KEY, 23, 20),
245 };
246
247 verifyWithInlineConfigParser(
248 getPath("InputNPathComplexityCheckCount.java"),
249 expected);
250 }
251
252 @Test
253 public void testPatternMatchingForSwitch() throws Exception {
254
255 final String[] expected = {
256 "14:5: " + getCheckMessage(MSG_KEY, 3, 1),
257 "23:5: " + getCheckMessage(MSG_KEY, 3, 1),
258 "32:5: " + getCheckMessage(MSG_KEY, 3, 1),
259 "41:5: " + getCheckMessage(MSG_KEY, 3, 1),
260 "50:5: " + getCheckMessage(MSG_KEY, 5, 1),
261 "59:5: " + getCheckMessage(MSG_KEY, 5, 1),
262 "68:5: " + getCheckMessage(MSG_KEY, 4, 1),
263 "76:5: " + getCheckMessage(MSG_KEY, 4, 1),
264 "86:5: " + getCheckMessage(MSG_KEY, 3, 1),
265 "95:5: " + getCheckMessage(MSG_KEY, 3, 1),
266 };
267
268 verifyWithInlineConfigParser(
269 getNonCompilablePath("InputNPathComplexityPatternMatchingForSwitch.java"),
270 expected);
271
272 }
273
274 @Test
275 public void testWhenExpression() throws Exception {
276
277 final String[] expected = {
278 "14:5: " + getCheckMessage(MSG_KEY, 3, 1),
279 "20:5: " + getCheckMessage(MSG_KEY, 3, 1),
280 "28:5: " + getCheckMessage(MSG_KEY, 3, 1),
281 "36:5: " + getCheckMessage(MSG_KEY, 4, 1),
282 "44:5: " + getCheckMessage(MSG_KEY, 4, 1),
283 "52:5: " + getCheckMessage(MSG_KEY, 5, 1),
284 "60:5: " + getCheckMessage(MSG_KEY, 7, 1),
285 "69:5: " + getCheckMessage(MSG_KEY, 5, 1),
286 "77:5: " + getCheckMessage(MSG_KEY, 5, 1),
287 "85:5: " + getCheckMessage(MSG_KEY, 6, 1),
288 };
289
290 verifyWithInlineConfigParser(
291 getNonCompilablePath("InputNPathComplexityWhenExpression.java"),
292 expected);
293
294 }
295
296 @Test
297 public void testGetAcceptableTokens() {
298 final NPathComplexityCheck npathComplexityCheckObj = new NPathComplexityCheck();
299 final int[] actual = npathComplexityCheckObj.getAcceptableTokens();
300 final int[] expected = {
301 TokenTypes.CTOR_DEF,
302 TokenTypes.METHOD_DEF,
303 TokenTypes.STATIC_INIT,
304 TokenTypes.INSTANCE_INIT,
305 TokenTypes.LITERAL_WHILE,
306 TokenTypes.LITERAL_DO,
307 TokenTypes.LITERAL_FOR,
308 TokenTypes.LITERAL_IF,
309 TokenTypes.LITERAL_ELSE,
310 TokenTypes.LITERAL_SWITCH,
311 TokenTypes.CASE_GROUP,
312 TokenTypes.LITERAL_TRY,
313 TokenTypes.LITERAL_CATCH,
314 TokenTypes.QUESTION,
315 TokenTypes.LITERAL_RETURN,
316 TokenTypes.LITERAL_DEFAULT,
317 TokenTypes.COMPACT_CTOR_DEF,
318 TokenTypes.SWITCH_RULE,
319 TokenTypes.LITERAL_WHEN,
320 };
321 assertWithMessage("Acceptable tokens should not be null")
322 .that(actual)
323 .isNotNull();
324 assertWithMessage("Invalid acceptable tokens")
325 .that(actual)
326 .isEqualTo(expected);
327 }
328
329 @Test
330 public void testGetRequiredTokens() {
331 final NPathComplexityCheck npathComplexityCheckObj = new NPathComplexityCheck();
332 final int[] actual = npathComplexityCheckObj.getRequiredTokens();
333 final int[] expected = {
334 TokenTypes.CTOR_DEF,
335 TokenTypes.METHOD_DEF,
336 TokenTypes.STATIC_INIT,
337 TokenTypes.INSTANCE_INIT,
338 TokenTypes.LITERAL_WHILE,
339 TokenTypes.LITERAL_DO,
340 TokenTypes.LITERAL_FOR,
341 TokenTypes.LITERAL_IF,
342 TokenTypes.LITERAL_ELSE,
343 TokenTypes.LITERAL_SWITCH,
344 TokenTypes.CASE_GROUP,
345 TokenTypes.LITERAL_TRY,
346 TokenTypes.LITERAL_CATCH,
347 TokenTypes.QUESTION,
348 TokenTypes.LITERAL_RETURN,
349 TokenTypes.LITERAL_DEFAULT,
350 TokenTypes.COMPACT_CTOR_DEF,
351 TokenTypes.SWITCH_RULE,
352 TokenTypes.LITERAL_WHEN,
353 };
354 assertWithMessage("Required tokens should not be null")
355 .that(actual)
356 .isNotNull();
357 assertWithMessage("Invalid required tokens")
358 .that(actual)
359 .isEqualTo(expected);
360 }
361
362 @Test
363 public void testDefaultHooks() {
364 final NPathComplexityCheck npathComplexityCheckObj = new NPathComplexityCheck();
365 final DetailAstImpl ast = new DetailAstImpl();
366 ast.initialize(new CommonToken(TokenTypes.INTERFACE_DEF, "interface"));
367
368 npathComplexityCheckObj.visitToken(ast);
369 final SortedSet<Violation> violations1 = npathComplexityCheckObj.getViolations();
370
371 assertWithMessage("No exception violations expected")
372 .that(violations1)
373 .isEmpty();
374
375 npathComplexityCheckObj.leaveToken(ast);
376 final SortedSet<Violation> violations2 = npathComplexityCheckObj.getViolations();
377
378 assertWithMessage("No exception violations expected")
379 .that(violations2)
380 .isEmpty();
381 }
382
383
384
385
386
387
388
389
390
391
392
393
394
395 @Test
396 public void testTokenEndIsAfterSameLineColumn() throws Exception {
397 final NPathComplexityCheck check = new NPathComplexityCheck();
398 final Object tokenEnd = TestUtil.getInternalState(check, "processingTokenEnd");
399 final DetailAstImpl token = new DetailAstImpl();
400 token.setLineNo(0);
401 token.setColumnNo(0);
402
403 assertWithMessage("isAfter must be true for same line/column")
404 .that(TestUtil.<Boolean>invokeMethod(tokenEnd, "isAfter", token))
405 .isTrue();
406 }
407
408 @Test
409 public void testVisitTokenBeforeExpressionRange() {
410
411 final DetailAstImpl astIf = mockAST(TokenTypes.LITERAL_IF, "if", 2, 2);
412 final DetailAstImpl astIfLeftParen = mockAST(TokenTypes.LPAREN, "(", 3, 3);
413 astIf.addChild(astIfLeftParen);
414 final DetailAstImpl astIfTrue =
415 mockAST(TokenTypes.LITERAL_TRUE, "true", 3, 3);
416 astIf.addChild(astIfTrue);
417 final DetailAstImpl astIfRightParen = mockAST(TokenTypes.RPAREN, ")", 4, 4);
418 astIf.addChild(astIfRightParen);
419
420 final DetailAstImpl astTernary = mockAST(TokenTypes.QUESTION, "?", 1, 1);
421 final DetailAstImpl astTernaryTrue =
422 mockAST(TokenTypes.LITERAL_TRUE, "true", 1, 2);
423 astTernary.addChild(astTernaryTrue);
424
425 final NPathComplexityCheck npathComplexityCheckObj = new NPathComplexityCheck();
426 npathComplexityCheckObj.beginTree(null);
427
428
429 npathComplexityCheckObj.visitToken(astIf);
430 final SortedSet<Violation> violations1 = npathComplexityCheckObj.getViolations();
431
432 assertWithMessage("No exception violations expected")
433 .that(violations1)
434 .isEmpty();
435
436
437 npathComplexityCheckObj.visitToken(astTernary);
438 final SortedSet<Violation> violations2 = npathComplexityCheckObj.getViolations();
439
440 assertWithMessage("No exception violations expected")
441 .that(violations2)
442 .isEmpty();
443 }
444
445
446
447
448
449
450
451
452
453
454 private static DetailAstImpl mockAST(final int tokenType, final String tokenText,
455 final int tokenRow, final int tokenColumn) {
456 final CommonToken tokenImportSemi = new CommonToken(tokenType, tokenText);
457 tokenImportSemi.setLine(tokenRow);
458 tokenImportSemi.setCharPositionInLine(tokenColumn);
459 final DetailAstImpl astSemi = new DetailAstImpl();
460 astSemi.initialize(tokenImportSemi);
461 return astSemi;
462 }
463
464 }