1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 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.internal.utils;
21
22 import static org.junit.jupiter.api.Assertions.assertThrows;
23
24 import java.io.File;
25 import java.io.OutputStream;
26 import java.lang.reflect.Constructor;
27 import java.lang.reflect.Field;
28 import java.lang.reflect.Method;
29 import java.lang.reflect.Modifier;
30 import java.util.Arrays;
31 import java.util.Collection;
32 import java.util.List;
33 import java.util.Locale;
34 import java.util.Map;
35 import java.util.Objects;
36 import java.util.Optional;
37 import java.util.Set;
38 import java.util.concurrent.Callable;
39 import java.util.concurrent.FutureTask;
40 import java.util.concurrent.TimeUnit;
41 import java.util.function.Predicate;
42 import java.util.function.Supplier;
43 import java.util.regex.Pattern;
44 import java.util.stream.Stream;
45
46 import org.junit.jupiter.api.function.Executable;
47 import org.mockito.internal.util.Checks;
48
49 import com.puppycrawl.tools.checkstyle.AbstractAutomaticBean;
50 import com.puppycrawl.tools.checkstyle.PackageNamesLoader;
51 import com.puppycrawl.tools.checkstyle.PackageObjectFactory;
52 import com.puppycrawl.tools.checkstyle.TreeWalkerAuditEvent;
53 import com.puppycrawl.tools.checkstyle.TreeWalkerFilter;
54 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
55 import com.puppycrawl.tools.checkstyle.api.AuditListener;
56 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
57 import com.puppycrawl.tools.checkstyle.api.DetailAST;
58 import com.puppycrawl.tools.checkstyle.api.TextBlock;
59
60 public final class TestUtil {
61
62 /**
63 * The stack size used in {@link TestUtil#getResultWithLimitedResources}.
64 * This value should be as small as possible. Some JVM requires this value to be
65 * at least 144k.
66 *
67 * @see <a href="https://www.baeldung.com/jvm-configure-stack-sizes">
68 * Configuring Stack Sizes in the JVM</a>
69 */
70 private static final int MINIMAL_STACK_SIZE = 147456;
71
72 private TestUtil() {
73 }
74
75 /**
76 * Verifies that utils class has private constructor and invokes it to satisfy code coverage.
77 *
78 * @param utilClass class to test for c-tor
79 * @return true if constructor is expected.
80 */
81 public static boolean isUtilsClassHasPrivateConstructor(final Class<?> utilClass)
82 throws ReflectiveOperationException {
83 final Constructor<?> constructor = utilClass.getDeclaredConstructor();
84 final boolean result = Modifier.isPrivate(constructor.getModifiers());
85 constructor.setAccessible(true);
86 constructor.newInstance();
87 return result;
88 }
89
90 /**
91 * Retrieves the specified field by its name in the class or its direct super.
92 *
93 * @param targetClass the class to retrieve the field for
94 * @param fieldName the name of the field to retrieve
95 * @return the class' field if found
96 */
97 private static Field getClassDeclaredField(Class<?> targetClass, String fieldName) {
98 return Stream.<Class<?>>iterate(targetClass, Objects::nonNull, Class::getSuperclass)
99 .flatMap(cls -> Arrays.stream(cls.getDeclaredFields()))
100 .filter(field -> fieldName.equals(field.getName()))
101 .findFirst()
102 .map(field -> {
103 field.setAccessible(true);
104 return field;
105 })
106 .orElseThrow(() -> {
107 return new IllegalStateException(String.format(Locale.ROOT,
108 "Field '%s' not found in '%s'", fieldName, targetClass.getCanonicalName()));
109 });
110 }
111
112 /**
113 * Retrieves the specified method by its name in the class or its direct super.
114 *
115 * @param targetClass the class to retrieve the method for
116 * @param methodName the name of the method to retrieve
117 * @param parameters the expected number of parameters
118 * @return the class' method
119 */
120 private static Method getClassDeclaredMethod(Class<?> targetClass,
121 String methodName,
122 int parameters) {
123 final Stream<Method> methods = Stream.<Class<?>>iterate(targetClass, Class::getSuperclass)
124 .flatMap(cls -> Arrays.stream(cls.getDeclaredMethods()))
125 .filter(method -> {
126 return methodName.equals(method.getName());
127 });
128
129 final Supplier<String> exceptionMessage = () -> {
130 return String.format(Locale.ROOT, "Method '%s' with %d parameters not found in '%s'",
131 methodName, parameters, targetClass.getCanonicalName());
132 };
133
134 return getMatchingExecutable(methods, parameters, exceptionMessage);
135 }
136
137 /**
138 * Retrieves the specified executable from a class.
139 *
140 * @param <T> the type of executable to search
141 * @param execs The stream of executables to search
142 * @param parameters the expected number of parameters
143 * @param exceptionMessage the exception message to use if executable is not found
144 * @return the matching executable
145 */
146 private static <T extends java.lang.reflect.Executable> T getMatchingExecutable(
147 Stream<T> execs, int parameters, Supplier<String> exceptionMessage) {
148 return execs.filter(method -> {
149 return parameters == method.getParameterCount();
150 })
151 .findFirst()
152 .map(method -> {
153 method.setAccessible(true);
154 return method;
155 })
156 .orElseThrow(() -> {
157 return new IllegalStateException(exceptionMessage.get());
158 });
159 }
160
161 /**
162 * Checks if stateful field is cleared during {@link AbstractCheck#beginTree} in check.
163 *
164 * @param check check object which field is to be verified
165 * @param astToVisit ast to pass into check methods
166 * @param fieldName name of the field to be checked
167 * @param isClear function for checking field state
168 * @return {@code true} if state of the field is cleared
169 */
170 public static boolean isStatefulFieldClearedDuringBeginTree(AbstractCheck check,
171 DetailAST astToVisit,
172 String fieldName,
173 Predicate<Object> isClear) {
174 check.beginTree(astToVisit);
175 check.visitToken(astToVisit);
176 check.beginTree(null);
177 return isClear.test(getInternalState(check, fieldName, Object.class));
178 }
179
180 /**
181 * Checks if stateful field is cleared during {@link AbstractAutomaticBean}'s finishLocalSetup.
182 *
183 * @param filter filter object which field is to be verified
184 * @param event event to pass into filter methods
185 * @param fieldName name of the field to be checked
186 * @param isClear function for checking field state
187 * @return {@code true} if state of the field is cleared
188 * @throws Exception if there was an error.
189 */
190 public static boolean isStatefulFieldClearedDuringLocalSetup(
191 TreeWalkerFilter filter, TreeWalkerAuditEvent event,
192 String fieldName, Predicate<Object> isClear) throws Exception {
193 filter.accept(event);
194 invokeVoidMethod(filter, "finishLocalSetup");
195 final Field resultField = getClassDeclaredField(filter.getClass(), fieldName);
196 return isClear.test(resultField.get(filter));
197 }
198
199 /**
200 * Returns the default PackageObjectFactory with the default package names.
201 *
202 * @return the default PackageObjectFactory.
203 */
204 public static PackageObjectFactory getPackageObjectFactory() throws CheckstyleException {
205 final ClassLoader cl = TestUtil.class.getClassLoader();
206 final Set<String> packageNames = PackageNamesLoader.getPackageNames(cl);
207 return new PackageObjectFactory(packageNames, cl);
208 }
209
210 /**
211 * Finds node of specified type among root children, siblings, siblings children
212 * on any deep level.
213 *
214 * @param root DetailAST
215 * @param predicate predicate
216 * @return {@link Optional} of {@link DetailAST} node which matches the predicate.
217 */
218 public static Optional<DetailAST> findTokenInAstByPredicate(DetailAST root,
219 Predicate<DetailAST> predicate) {
220 DetailAST curNode = root;
221 while (!predicate.test(curNode)) {
222 DetailAST toVisit = curNode.getFirstChild();
223 while (curNode != null && toVisit == null) {
224 toVisit = curNode.getNextSibling();
225 if (toVisit == null) {
226 curNode = curNode.getParent();
227 }
228 }
229
230 if (curNode == toVisit || curNode == root.getParent()) {
231 curNode = null;
232 break;
233 }
234
235 curNode = toVisit;
236 }
237 return Optional.ofNullable(curNode);
238 }
239
240 /**
241 * Returns the JDK version as a number that is easy to compare.
242 *
243 * <p>
244 * For JDK "1.8" it will be 8; for JDK "11" it will be 11.
245 * </p>
246 *
247 * @return JDK version as integer
248 */
249 public static int getJdkVersion() {
250 String version = System.getProperty("java.specification.version");
251 if (version.startsWith("1.")) {
252 version = version.substring(2);
253 }
254 return Integer.parseInt(version);
255 }
256
257 /**
258 * Adjusts the expected number of flushes for tests that call {@link OutputStream#close} method.
259 *
260 * <p>
261 * After <a href="https://bugs.openjdk.java.net/browse/JDK-8220477">JDK-8220477</a>
262 * there is one additional flush from {@code sun.nio.cs.StreamEncoder#implClose}.
263 * </p>
264 *
265 * @param flushCount flush count to adjust
266 * @return adjusted flush count
267 */
268 public static int adjustFlushCountForOutputStreamClose(int flushCount) {
269 int result = flushCount;
270 if (getJdkVersion() >= 13) {
271 ++result;
272 }
273 return result;
274 }
275
276 /**
277 * Runs a given task with limited stack size and time duration, then
278 * returns the result. See AbstractModuleTestSupport#verifyWithLimitedResources
279 * for an example of how to use this method when task does not return a result, i.e.
280 * the given method's return type is {@code void}.
281 *
282 * @param callable the task to execute
283 * @param <V> return type of task - {@code Void} if task does not return result
284 * @return result
285 * @throws Exception if getting result fails
286 */
287 public static <V> V getResultWithLimitedResources(Callable<V> callable) throws Exception {
288 final FutureTask<V> futureTask = new FutureTask<>(callable);
289 final Thread thread = new Thread(null, futureTask,
290 "LimitedStackSizeThread", MINIMAL_STACK_SIZE);
291 thread.start();
292 return futureTask.get(10, TimeUnit.SECONDS);
293 }
294
295 /**
296 * Reads the value of a field using reflection. This method will traverse the
297 * super class hierarchy until a field with name {@code fieldName} is found.
298 *
299 * @param instance the instance to read
300 * @param fieldName the name of the field
301 * @throws RuntimeException if the field can't be read
302 */
303 public static <T> T getInternalState(Object instance, String fieldName, Class<T> clazz) {
304 try {
305 final Field field = getClassDeclaredField(instance.getClass(), fieldName);
306 return clazz.cast(field.get(instance));
307 }
308 catch (ReflectiveOperationException exc) {
309 final String message = String.format(Locale.ROOT,
310 "Failed to get field '%s' for instance of class '%s'",
311 fieldName, instance.getClass().getSimpleName());
312 throw new IllegalStateException(message, exc);
313 }
314 }
315
316 /**
317 * Helper method for casting collection type Map.
318 *
319 * @param instance the instance to read
320 * @param fieldName the name of the field
321 * @throws RuntimeException if the field can't be read
322 * @noinspection unchecked
323 * @noinspectionreason unchecked - unchecked cast is ok on test code
324 */
325 public static Map<String, String> getInternalStateMap(Object instance, String fieldName) {
326 return getInternalState(instance, fieldName, Map.class);
327 }
328
329 /**
330 * Helper method for casting collection type Map.
331 *
332 * @param instance the instance to read
333 * @param fieldName the name of the field
334 * @throws RuntimeException if the field can't be read
335 * @noinspection unchecked
336 * @noinspectionreason unchecked - unchecked cast is ok on test code
337 */
338 public static Map<Integer, List<TextBlock>> getInternalStateMapIntegerList(
339 Object instance, String fieldName) {
340 return getInternalState(instance, fieldName, Map.class);
341 }
342
343 /**
344 * Helper method for casting collection type List.
345 *
346 * @param instance the instance to read
347 * @param fieldName the name of the field
348 * @throws RuntimeException if the field can't be read
349 * @noinspection unchecked
350 * @noinspectionreason unchecked - unchecked cast is ok on test code
351 */
352 public static List<AuditListener> getInternalStateListAuditListener(
353 Object instance, String fieldName) {
354 return getInternalState(instance, fieldName, List.class);
355 }
356
357 /**
358 * Helper method for casting collection type List.
359 *
360 * @param instance the instance to read
361 * @param fieldName the name of the field
362 * @throws RuntimeException if the field can't be read
363 * @noinspection unchecked
364 * @noinspectionreason unchecked - unchecked cast is ok on test code
365 */
366 public static List<Pattern> getInternalStateListPattern(
367 Object instance, String fieldName) {
368 return getInternalState(instance, fieldName, List.class);
369 }
370
371 /**
372 * Helper method for casting collection type List.
373 *
374 * @param instance the instance to read
375 * @param fieldName the name of the field
376 * @throws RuntimeException if the field can't be read
377 * @noinspection unchecked
378 * @noinspectionreason unchecked - unchecked cast is ok on test code
379 */
380 public static List<Comparable<Object>> getInternalStateListComparable(
381 Object instance, String fieldName) {
382 return getInternalState(instance, fieldName, List.class);
383 }
384
385 /**
386 * Helper method for casting to Collection.
387 *
388 * @param instance the instance to read
389 * @param fieldName the name of the field
390 * @throws RuntimeException if the field can't be read
391 * @noinspection unchecked
392 * @noinspectionreason unchecked - unchecked cast is ok on test code
393 */
394 public static Collection<Checks> getInternalStateCollectionChecks(
395 Object instance, String fieldName) {
396 return getInternalState(instance, fieldName, Collection.class);
397 }
398
399 /**
400 * Helper method for casting to collection type Set.
401 *
402 * @param instance the instance to read
403 * @param fieldName the name of the field
404 * @throws RuntimeException if the field can't be read
405 * @noinspection unchecked
406 * @noinspectionreason unchecked - unchecked cast is ok on test code
407 */
408 public static Set<TreeWalkerFilter> getInternalStateSetTreeWalkerFilter(
409 Object instance, String fieldName) {
410 return getInternalState(instance, fieldName, Set.class);
411 }
412
413 /**
414 * Reads the value of a static field using reflection. This method will traverse the
415 * super class hierarchy until a field with name {@code fieldName} is found.
416 *
417 * @param targetClass the class of the field
418 * @param fieldName the name of the field
419 * @param clazz the expected type of the field value, used for type-safe casting
420 * @throws RuntimeException if the field can't be read
421 */
422 public static <T> T getInternalStaticState(Class<?> targetClass, String fieldName,
423 Class<T> clazz) {
424 try {
425 final Field field = getClassDeclaredField(targetClass, fieldName);
426 return clazz.cast(field.get(null));
427 }
428 catch (ReflectiveOperationException exc) {
429 final String message = String.format(Locale.ROOT,
430 "Failed to get static field '%s' for class '%s'",
431 fieldName, targetClass);
432 throw new IllegalStateException(message, exc);
433 }
434 }
435
436 /**
437 * Helper method for casting to collection type Map.
438 *
439 * @param targetClass the class of the field
440 * @param fieldName the name of the field
441 * @throws RuntimeException if the field can't be read
442 * @noinspection unchecked
443 * @noinspectionreason unchecked - unchecked cast is ok on test code
444 */
445 public static Map<String, String> getInternalStaticStateMap(Class<?> targetClass,
446 String fieldName) {
447 return getInternalStaticState(targetClass, fieldName, Map.class);
448 }
449
450 /**
451 * Helper method for casting to collection type Map.
452 *
453 * @param targetClass the class of the field
454 * @param fieldName the name of the field
455 * @throws RuntimeException if the field can't be read
456 * @noinspection unchecked
457 * @noinspectionreason unchecked - unchecked cast is ok on test code
458 */
459 public static ThreadLocal<List<Object>> getInternalStaticStateThreadLocal(
460 Class<?> targetClass, String fieldName) {
461 return getInternalStaticState(targetClass, fieldName, ThreadLocal.class);
462 }
463
464 /**
465 * Writes the value of a field using reflection. This method will traverse the
466 * super class hierarchy until a field with name {@code fieldName} is found.
467 *
468 * @param instance the instance whose field to modify
469 * @param fieldName the name of the field
470 * @param value the new value of the field
471 * @throws RuntimeException if the field can't be changed
472 */
473 public static void setInternalState(Object instance, String fieldName, Object value) {
474 try {
475 final Field field = getClassDeclaredField(instance.getClass(), fieldName);
476 field.set(instance, value);
477 }
478 catch (ReflectiveOperationException exc) {
479 final String message = String.format(Locale.ROOT,
480 "Failed to set field '%s' for instance of class '%s'",
481 fieldName, instance.getClass().getSimpleName());
482 throw new IllegalStateException(message, exc);
483 }
484 }
485
486 /**
487 * Invokes a private method for an instance.
488 *
489 * @param instance the instance whose method to invoke
490 * @param methodToExecute the name of the method to invoke
491 * @param resultClazz used for cast of result
492 * @param arguments the optional arguments
493 * @param <T> the type of the result
494 * @return the method's result
495 * @throws ReflectiveOperationException if the method invocation failed
496 */
497 public static <T> T invokeMethod(Object instance, String methodToExecute,
498 Class<T> resultClazz, Object... arguments)
499 throws ReflectiveOperationException {
500 final Class<?> ownerClass = instance.getClass();
501 final Method method = getClassDeclaredMethod(ownerClass, methodToExecute, arguments.length);
502 return resultClazz.cast(method.invoke(instance, arguments));
503 }
504
505 /**
506 * Helper method to invoke private method for an instance with cast to Object.
507 *
508 * @param instance the instance whose method to invoke
509 * @param methodToExecute the name of the method to invoke
510 * @param arguments the optional arguments
511 * @throws ReflectiveOperationException if the method invocation failed
512 */
513 public static void invokeVoidMethod(Object instance,
514 String methodToExecute, Object... arguments)
515 throws ReflectiveOperationException {
516 invokeMethod(instance, methodToExecute, Object.class, arguments);
517 }
518
519 /**
520 * Helper method to invoke private method for an instance with cast to Set.
521 *
522 * @param instance the instance whose method to invoke
523 * @param methodToExecute the name of the method to invoke
524 * @param arguments the optional arguments
525 * @return the method's result
526 * @throws ReflectiveOperationException if the method invocation failed
527 * @noinspection unchecked
528 * @noinspectionreason unchecked - unchecked cast is ok on test code
529 */
530 public static Set<String> invokeMethodSet(Object instance, String methodToExecute,
531 Object... arguments) throws ReflectiveOperationException {
532 return (Set<String>) invokeMethod(instance, methodToExecute, Set.class, arguments);
533 }
534
535 /**
536 * Invokes a static private method for a class.
537 *
538 * @param ownerClass the class whose static method to invoke
539 * @param methodToExecute the name of the method to invoke
540 * @param resultClass used for cast of result
541 * @param arguments the optional arguments
542 * @param <T> the type of the result
543 * @return the method's result
544 * @throws ReflectiveOperationException if the method invocation failed
545 */
546 public static <T> T invokeStaticMethod(Class<?> ownerClass,
547 String methodToExecute, Class<T> resultClass, Object... arguments)
548 throws ReflectiveOperationException {
549 final Method method = getClassDeclaredMethod(ownerClass, methodToExecute, arguments.length);
550 return resultClass.cast(method.invoke(null, arguments));
551 }
552
553 /**
554 * Helper method to invoke static private method for an instance with cast to Object.
555 *
556 * @param ownerClass the class whose static method to invoke
557 * @param methodToExecute the name of the method to invoke
558 * @param arguments the optional arguments
559 * @throws ReflectiveOperationException if the method invocation failed
560 */
561 public static void invokeVoidStaticMethod(Class<?> ownerClass,
562 String methodToExecute, Object... arguments)
563 throws ReflectiveOperationException {
564 invokeStaticMethod(ownerClass, methodToExecute, Object.class, arguments);
565 }
566
567 /**
568 * Helper method to invoke static private method for an instance with cast to List.
569 *
570 * @param ownerClass the class whose static method to invoke
571 * @param methodToExecute the name of the method to invoke
572 * @param arguments the optional arguments
573 * @return the method's result
574 * @throws ReflectiveOperationException if the method invocation failed
575 * @noinspection unchecked
576 * @noinspectionreason unchecked - unchecked cast is ok on test code
577 */
578 public static List<File> invokeStaticMethodList(Class<?> ownerClass,
579 String methodToExecute, Object... arguments)
580 throws ReflectiveOperationException {
581 return (List<File>) invokeStaticMethod(ownerClass, methodToExecute, List.class, arguments);
582 }
583
584 /**
585 * Instantiates an object of the given class with the given arguments,
586 * even if the constructor is private.
587 *
588 * @param targetClass The class to instantiate
589 * @param arguments The arguments to pass to the constructor
590 * @param <T> the type of the object to instantiate
591 * @return The instantiated object
592 * @throws ReflectiveOperationException if the constructor invocation failed
593 */
594 @SuppressWarnings("unchecked")
595 public static <T> T instantiate(Class<T> targetClass, Object... arguments)
596 throws ReflectiveOperationException {
597
598 final Stream<Constructor<T>> ctors =
599 Arrays.stream(targetClass.getDeclaredConstructors()).map(Constructor.class::cast);
600
601 final Supplier<String> exceptionMessage = () -> {
602 return String.format(Locale.ROOT, "Constructor with %d parameters not found in '%s'",
603 arguments.length, targetClass.getCanonicalName());
604 };
605
606 final Constructor<T> constructor =
607 getMatchingExecutable(ctors, arguments.length, exceptionMessage);
608 constructor.setAccessible(true);
609
610 return constructor.newInstance(arguments);
611 }
612
613 /**
614 * Returns the inner class type by its name.
615 *
616 * @param declaringClass the class in which the inner class is declared
617 * @param name the unqualified name (simple name) of the inner class
618 * @return the inner class type
619 * @throws ClassNotFoundException if the class not found
620 * @noinspection unchecked
621 * @noinspectionreason unchecked - unchecked cast is ok on test code
622 */
623 public static <T> Class<T> getInnerClassType(Class<?> declaringClass, String name)
624 throws ClassNotFoundException {
625 return (Class<T>) Class.forName(declaringClass.getName() + "$" + name);
626 }
627
628 /**
629 * Executes the provided executable and expects it to throw an exception of the specified type.
630 *
631 * @param expectedType the class of the expected exception type.
632 * @param executable the executable to be executed
633 * @param message the message to be used in case of assertion failure.
634 * @return the expected exception thrown by the executable.
635 */
636 public static <T extends Throwable> T getExpectedThrowable(Class<T> expectedType,
637 Executable executable,
638 String message) {
639 return assertThrows(expectedType, executable, message);
640 }
641
642 /**
643 * Executes the provided executable and expects it to throw an exception of the specified type.
644 *
645 * @param expectedType the class of the expected exception type.
646 * @param executable the executable to be executed
647 * @return the expected exception thrown by the executable.
648 */
649 public static <T extends Throwable> T getExpectedThrowable(Class<T> expectedType,
650 Executable executable) {
651 return assertThrows(expectedType, executable);
652 }
653
654 }