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.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 clss 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<?> clss, String fieldName) {
98 return Stream.<Class<?>>iterate(clss, 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, clss.getCanonicalName()));
109 });
110 }
111
112 /**
113 * Retrieves the specified method by its name in the class or its direct super.
114 *
115 * @param clss 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<?> clss,
121 String methodName,
122 int parameters) {
123 final Stream<Method> methods = Stream.<Class<?>>iterate(clss, 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, clss.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 clss 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<?> clss, String fieldName, Class<T> clazz) {
423 try {
424 final Field field = getClassDeclaredField(clss, fieldName);
425 return clazz.cast(field.get(null));
426 }
427 catch (ReflectiveOperationException exc) {
428 final String message = String.format(Locale.ROOT,
429 "Failed to get static field '%s' for class '%s'",
430 fieldName, clss);
431 throw new IllegalStateException(message, exc);
432 }
433 }
434
435 /**
436 * Helper method for casting to collection type Map.
437 *
438 * @param clss the class of the field
439 * @param fieldName the name of the field
440 * @throws RuntimeException if the field can't be read
441 * @noinspection unchecked
442 * @noinspectionreason unchecked - unchecked cast is ok on test code
443 */
444 public static Map<String, String> getInternalStaticStateMap(Class<?> clss, String fieldName) {
445 return getInternalStaticState(clss, fieldName, Map.class);
446 }
447
448 /**
449 * Helper method for casting to collection type Map.
450 *
451 * @param clss the class of the field
452 * @param fieldName the name of the field
453 * @throws RuntimeException if the field can't be read
454 * @noinspection unchecked
455 * @noinspectionreason unchecked - unchecked cast is ok on test code
456 */
457 public static ThreadLocal<List<Object>> getInternalStaticStateThreadLocal(
458 Class<?> clss, String fieldName) {
459 return getInternalStaticState(clss, fieldName, ThreadLocal.class);
460 }
461
462 /**
463 * Writes the value of a field using reflection. This method will traverse the
464 * super class hierarchy until a field with name {@code fieldName} is found.
465 *
466 * @param instance the instance whose field to modify
467 * @param fieldName the name of the field
468 * @param value the new value of the field
469 * @throws RuntimeException if the field can't be changed
470 */
471 public static void setInternalState(Object instance, String fieldName, Object value) {
472 try {
473 final Field field = getClassDeclaredField(instance.getClass(), fieldName);
474 field.set(instance, value);
475 }
476 catch (ReflectiveOperationException exc) {
477 final String message = String.format(Locale.ROOT,
478 "Failed to set field '%s' for instance of class '%s'",
479 fieldName, instance.getClass().getSimpleName());
480 throw new IllegalStateException(message, exc);
481 }
482 }
483
484 /**
485 * Invokes a private method for an instance.
486 *
487 * @param instance the instance whose method to invoke
488 * @param methodToExecute the name of the method to invoke
489 * @param resultClazz used for cast of result
490 * @param arguments the optional arguments
491 * @param <T> the type of the result
492 * @return the method's result
493 * @throws ReflectiveOperationException if the method invocation failed
494 */
495 public static <T> T invokeMethod(Object instance, String methodToExecute,
496 Class<T> resultClazz, Object... arguments)
497 throws ReflectiveOperationException {
498 final Class<?> ownerClass = instance.getClass();
499 final Method method = getClassDeclaredMethod(ownerClass, methodToExecute, arguments.length);
500 return resultClazz.cast(method.invoke(instance, arguments));
501 }
502
503 /**
504 * Helper method to invoke private method for an instance with cast to Object.
505 *
506 * @param instance the instance whose method to invoke
507 * @param methodToExecute the name of the method to invoke
508 * @param arguments the optional arguments
509 * @throws ReflectiveOperationException if the method invocation failed
510 */
511 public static void invokeVoidMethod(Object instance,
512 String methodToExecute, Object... arguments)
513 throws ReflectiveOperationException {
514 invokeMethod(instance, methodToExecute, Object.class, arguments);
515 }
516
517 /**
518 * Helper method to invoke private method for an instance with cast to Set.
519 *
520 * @param instance the instance whose method to invoke
521 * @param methodToExecute the name of the method to invoke
522 * @param arguments the optional arguments
523 * @return the method's result
524 * @throws ReflectiveOperationException if the method invocation failed
525 * @noinspection unchecked
526 * @noinspectionreason unchecked - unchecked cast is ok on test code
527 */
528 public static Set<String> invokeMethodSet(Object instance, String methodToExecute,
529 Object... arguments) throws ReflectiveOperationException {
530 return (Set<String>) invokeMethod(instance, methodToExecute, Set.class, arguments);
531 }
532
533 /**
534 * Invokes a static private method for a class.
535 *
536 * @param ownerClass the class whose static method to invoke
537 * @param methodToExecute the name of the method to invoke
538 * @param resultClass used for cast of result
539 * @param arguments the optional arguments
540 * @param <T> the type of the result
541 * @return the method's result
542 * @throws ReflectiveOperationException if the method invocation failed
543 */
544 public static <T> T invokeStaticMethod(Class<?> ownerClass,
545 String methodToExecute, Class<T> resultClass, Object... arguments)
546 throws ReflectiveOperationException {
547 final Method method = getClassDeclaredMethod(ownerClass, methodToExecute, arguments.length);
548 return resultClass.cast(method.invoke(null, arguments));
549 }
550
551 /**
552 * Helper method to invoke static private method for an instance with cast to Object.
553 *
554 * @param ownerClass the class whose static method to invoke
555 * @param methodToExecute the name of the method to invoke
556 * @param arguments the optional arguments
557 * @throws ReflectiveOperationException if the method invocation failed
558 */
559 public static void invokeVoidStaticMethod(Class<?> ownerClass,
560 String methodToExecute, Object... arguments)
561 throws ReflectiveOperationException {
562 invokeStaticMethod(ownerClass, methodToExecute, Object.class, arguments);
563 }
564
565 /**
566 * Helper method to invoke static private method for an instance with cast to List.
567 *
568 * @param ownerClass the class whose static method to invoke
569 * @param methodToExecute the name of the method to invoke
570 * @param arguments the optional arguments
571 * @return the method's result
572 * @throws ReflectiveOperationException if the method invocation failed
573 * @noinspection unchecked
574 * @noinspectionreason unchecked - unchecked cast is ok on test code
575 */
576 public static List<File> invokeStaticMethodList(Class<?> ownerClass,
577 String methodToExecute, Object... arguments)
578 throws ReflectiveOperationException {
579 return (List<File>) invokeStaticMethod(ownerClass, methodToExecute, List.class, arguments);
580 }
581
582 /**
583 * Instantiates an object of the given class with the given arguments,
584 * even if the constructor is private.
585 *
586 * @param clss The class to instantiate
587 * @param arguments The arguments to pass to the constructor
588 * @param <T> the type of the object to instantiate
589 * @return The instantiated object
590 * @throws ReflectiveOperationException if the constructor invocation failed
591 */
592 @SuppressWarnings("unchecked")
593 public static <T> T instantiate(Class<T> clss, Object... arguments)
594 throws ReflectiveOperationException {
595
596 final Stream<Constructor<T>> ctors =
597 Arrays.stream(clss.getDeclaredConstructors()).map(Constructor.class::cast);
598
599 final Supplier<String> exceptionMessage = () -> {
600 return String.format(Locale.ROOT, "Constructor with %d parameters not found in '%s'",
601 arguments.length, clss.getCanonicalName());
602 };
603
604 final Constructor<T> constructor =
605 getMatchingExecutable(ctors, arguments.length, exceptionMessage);
606 constructor.setAccessible(true);
607
608 return constructor.newInstance(arguments);
609 }
610
611 /**
612 * Returns the inner class type by its name.
613 *
614 * @param declaringClass the class in which the inner class is declared
615 * @param name the unqualified name (simple name) of the inner class
616 * @return the inner class type
617 * @throws ClassNotFoundException if the class not found
618 * @noinspection unchecked
619 * @noinspectionreason unchecked - unchecked cast is ok on test code
620 */
621 public static <T> Class<T> getInnerClassType(Class<?> declaringClass, String name)
622 throws ClassNotFoundException {
623 return (Class<T>) Class.forName(declaringClass.getName() + "$" + name);
624 }
625
626 /**
627 * Executes the provided executable and expects it to throw an exception of the specified type.
628 *
629 * @param expectedType the class of the expected exception type.
630 * @param executable the executable to be executed
631 * @param message the message to be used in case of assertion failure.
632 * @return the expected exception thrown by the executable.
633 */
634 public static <T extends Throwable> T getExpectedThrowable(Class<T> expectedType,
635 Executable executable,
636 String message) {
637 return assertThrows(expectedType, executable, message);
638 }
639
640 /**
641 * Executes the provided executable and expects it to throw an exception of the specified type.
642 *
643 * @param expectedType the class of the expected exception type.
644 * @param executable the executable to be executed
645 * @return the expected exception thrown by the executable.
646 */
647 public static <T extends Throwable> T getExpectedThrowable(Class<T> expectedType,
648 Executable executable) {
649 return assertThrows(expectedType, executable);
650 }
651
652 }