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