View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.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 }