View Javadoc
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 }