View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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.Locale;
31  import java.util.Optional;
32  import java.util.Set;
33  import java.util.concurrent.Callable;
34  import java.util.concurrent.FutureTask;
35  import java.util.concurrent.TimeUnit;
36  import java.util.function.Predicate;
37  import java.util.stream.Stream;
38  
39  import org.junit.jupiter.api.function.Executable;
40  
41  import com.puppycrawl.tools.checkstyle.AbstractAutomaticBean;
42  import com.puppycrawl.tools.checkstyle.PackageNamesLoader;
43  import com.puppycrawl.tools.checkstyle.PackageObjectFactory;
44  import com.puppycrawl.tools.checkstyle.TreeWalkerAuditEvent;
45  import com.puppycrawl.tools.checkstyle.TreeWalkerFilter;
46  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
47  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
48  import com.puppycrawl.tools.checkstyle.api.DetailAST;
49  
50  public final class TestUtil {
51  
52      /**
53       * The stack size used in {@link TestUtil#getResultWithLimitedResources}.
54       * This value should be as small as possible. Some JVM requires this value to be
55       * at least 144k.
56       *
57       * @see <a href="https://www.baeldung.com/jvm-configure-stack-sizes">
58       *      Configuring Stack Sizes in the JVM</a>
59       */
60      private static final int MINIMAL_STACK_SIZE = 147456;
61  
62      private TestUtil() {
63      }
64  
65      /**
66       * Verifies that utils class has private constructor and invokes it to satisfy code coverage.
67       *
68       * @param utilClass class to test for c-tor
69       * @return true if constructor is expected.
70       */
71      public static boolean isUtilsClassHasPrivateConstructor(final Class<?> utilClass)
72              throws ReflectiveOperationException {
73          final Constructor<?> constructor = utilClass.getDeclaredConstructor();
74          final boolean result = Modifier.isPrivate(constructor.getModifiers());
75          constructor.setAccessible(true);
76          constructor.newInstance();
77          return result;
78      }
79  
80      /**
81       * Retrieves the specified field by its name in the class or its direct super.
82       *
83       * @param clss the class to retrieve the field for
84       * @param fieldName the name of the field to retrieve
85       * @return the class' field if found
86       */
87      public static Field getClassDeclaredField(Class<?> clss, String fieldName) {
88          return Stream.<Class<?>>iterate(clss, Class::getSuperclass)
89              .flatMap(cls -> Arrays.stream(cls.getDeclaredFields()))
90              .filter(field -> fieldName.equals(field.getName()))
91              .findFirst()
92              .map(field -> {
93                  field.setAccessible(true);
94                  return field;
95              })
96              .orElseThrow(() -> {
97                  return new IllegalStateException(String.format(Locale.ROOT,
98                          "Field '%s' not found in '%s'", fieldName, clss.getCanonicalName()));
99              });
100     }
101 
102     /**
103      * Retrieves the specified method by its name in the class or its direct super.
104      *
105      * @param clss the class to retrieve the method for
106      * @param methodName the name of the method to retrieve
107      * @param parameters the expected number of parameters
108      * @return the class' method
109      */
110     public static Method getClassDeclaredMethod(Class<?> clss, String methodName, int parameters) {
111         return Stream.<Class<?>>iterate(clss, Class::getSuperclass)
112             .flatMap(cls -> Arrays.stream(cls.getDeclaredMethods()))
113             .filter(method -> {
114                 return methodName.equals(method.getName())
115                     && parameters == method.getParameterCount();
116             })
117             .findFirst()
118             .map(method -> {
119                 method.setAccessible(true);
120                 return method;
121             })
122             .orElseThrow(() -> {
123                 return new IllegalStateException(String.format(Locale.ROOT,
124                         "Method '%s' with %d parameters not found in '%s'",
125                         methodName, parameters, clss.getCanonicalName()));
126             });
127     }
128 
129     /**
130      * Checks if stateful field is cleared during {@link AbstractCheck#beginTree} in check.
131      *
132      * @param check      check object which field is to be verified
133      * @param astToVisit ast to pass into check methods
134      * @param fieldName  name of the field to be checked
135      * @param isClear    function for checking field state
136      * @return {@code true} if state of the field is cleared
137      */
138     public static boolean isStatefulFieldClearedDuringBeginTree(AbstractCheck check,
139                                                                 DetailAST astToVisit,
140                                                                 String fieldName,
141                                                                 Predicate<Object> isClear) {
142         check.beginTree(astToVisit);
143         check.visitToken(astToVisit);
144         check.beginTree(null);
145         return isClear.test(getInternalState(check, fieldName));
146     }
147 
148     /**
149      * Checks if stateful field is cleared during {@link AbstractAutomaticBean}'s finishLocalSetup.
150      *
151      * @param filter filter object which field is to be verified
152      * @param event event to pass into filter methods
153      * @param fieldName name of the field to be checked
154      * @param isClear function for checking field state
155      * @return {@code true} if state of the field is cleared
156      * @throws Exception if there was an error.
157      */
158     public static boolean isStatefulFieldClearedDuringLocalSetup(
159             TreeWalkerFilter filter, TreeWalkerAuditEvent event,
160             String fieldName, Predicate<Object> isClear) throws Exception {
161         filter.accept(event);
162         invokeMethod(filter, "finishLocalSetup");
163         final Field resultField = getClassDeclaredField(filter.getClass(), fieldName);
164         return isClear.test(resultField.get(filter));
165     }
166 
167     /**
168      * Returns the default PackageObjectFactory with the default package names.
169      *
170      * @return the default PackageObjectFactory.
171      */
172     public static PackageObjectFactory getPackageObjectFactory() throws CheckstyleException {
173         final ClassLoader cl = TestUtil.class.getClassLoader();
174         final Set<String> packageNames = PackageNamesLoader.getPackageNames(cl);
175         return new PackageObjectFactory(packageNames, cl);
176     }
177 
178     /**
179      * Finds node of specified type among root children, siblings, siblings children
180      * on any deep level.
181      *
182      * @param root      DetailAST
183      * @param predicate predicate
184      * @return {@link Optional} of {@link DetailAST} node which matches the predicate.
185      */
186     public static Optional<DetailAST> findTokenInAstByPredicate(DetailAST root,
187                                                                 Predicate<DetailAST> predicate) {
188         DetailAST curNode = root;
189         while (!predicate.test(curNode)) {
190             DetailAST toVisit = curNode.getFirstChild();
191             while (curNode != null && toVisit == null) {
192                 toVisit = curNode.getNextSibling();
193                 if (toVisit == null) {
194                     curNode = curNode.getParent();
195                 }
196             }
197 
198             if (curNode == toVisit || curNode == root.getParent()) {
199                 curNode = null;
200                 break;
201             }
202 
203             curNode = toVisit;
204         }
205         return Optional.ofNullable(curNode);
206     }
207 
208     /**
209      * <p>
210      * Returns the JDK version as a number that is easy to compare.
211      * </p>
212      * <p>
213      * For JDK "1.8" it will be 8; for JDK "11" it will be 11.
214      * </p>
215      *
216      * @return JDK version as integer
217      */
218     public static int getJdkVersion() {
219         String version = System.getProperty("java.specification.version");
220         if (version.startsWith("1.")) {
221             version = version.substring(2);
222         }
223         return Integer.parseInt(version);
224     }
225 
226     /**
227      * <p>
228      * Adjusts the expected number of flushes for tests that call {@link OutputStream#close} method.
229      * </p>
230      * <p>
231      * After <a href="https://bugs.openjdk.java.net/browse/JDK-8220477">JDK-8220477</a>
232      * there is one additional flush from {@code sun.nio.cs.StreamEncoder#implClose}.
233      * </p>
234      *
235      * @param flushCount flush count to adjust
236      * @return adjusted flush count
237      */
238     public static int adjustFlushCountForOutputStreamClose(int flushCount) {
239         int result = flushCount;
240         if (getJdkVersion() >= 13) {
241             ++result;
242         }
243         return result;
244     }
245 
246     /**
247      * Runs a given task with limited stack size and time duration, then
248      * returns the result. See AbstractModuleTestSupport#verifyWithLimitedResources
249      * for an example of how to use this method when task does not return a result, i.e.
250      * the given method's return type is {@code void}.
251      *
252      * @param callable the task to execute
253      * @param <V> return type of task - {@code Void} if task does not return result
254      * @return result
255      * @throws Exception if getting result fails
256      */
257     public static <V> V getResultWithLimitedResources(Callable<V> callable) throws Exception {
258         final FutureTask<V> futureTask = new FutureTask<>(callable);
259         final Thread thread = new Thread(null, futureTask,
260                 "LimitedStackSizeThread", MINIMAL_STACK_SIZE);
261         thread.start();
262         return futureTask.get(10, TimeUnit.SECONDS);
263     }
264 
265     /**
266      * Reads the value of a field using reflection. This method will traverse the
267      * super class hierarchy until a field with name {@code fieldName} is found.
268      *
269      * @param instance the instance to read
270      * @param fieldName the name of the field
271      * @throws RuntimeException if the field  can't be read
272      * @noinspection unchecked
273      * @noinspectionreason unchecked - unchecked cast is ok on test code
274      */
275     public static <T> T getInternalState(Object instance, String fieldName) {
276         try {
277             final Field field = getClassDeclaredField(instance.getClass(), fieldName);
278             return (T) field.get(instance);
279         }
280         catch (ReflectiveOperationException ex) {
281             final String message = String.format(Locale.ROOT,
282                     "Failed to get field '%s' for instance of class '%s'",
283                     fieldName, instance.getClass().getSimpleName());
284             throw new IllegalStateException(message, ex);
285         }
286     }
287 
288     /**
289      * Reads the value of a static field using reflection. This method will traverse the
290      * super class hierarchy until a field with name {@code fieldName} is found.
291      *
292      * @param clss the class of the field
293      * @param fieldName the name of the field
294      * @throws RuntimeException if the field  can't be read
295      * @noinspection unchecked
296      * @noinspectionreason unchecked - unchecked cast is ok on test code
297      */
298     public static <T> T getInternalStaticState(Class<?> clss, String fieldName) {
299         try {
300             final Field field = getClassDeclaredField(clss, fieldName);
301             return (T) field.get(null);
302         }
303         catch (ReflectiveOperationException ex) {
304             final String message = String.format(Locale.ROOT,
305                     "Failed to get static field '%s' for class '%s'",
306                     fieldName, clss);
307             throw new IllegalStateException(message, ex);
308         }
309     }
310 
311     /**
312      * Writes the value of a field using reflection. This method will traverse the
313      * super class hierarchy until a field with name {@code fieldName} is found.
314      *
315      * @param instance the instance whose field to modify
316      * @param fieldName the name of the field
317      * @param value the new value of the field
318      * @throws RuntimeException if the field  can't be changed
319      */
320     public static void setInternalState(Object instance, String fieldName, Object value) {
321         try {
322             final Field field = getClassDeclaredField(instance.getClass(), fieldName);
323             field.set(instance, value);
324         }
325         catch (ReflectiveOperationException ex) {
326             final String message = String.format(Locale.ROOT,
327                     "Failed to set field '%s' for instance of class '%s'",
328                     fieldName, instance.getClass().getSimpleName());
329             throw new IllegalStateException(message, ex);
330         }
331     }
332 
333     /**
334      * Invokes a private method for an instance.
335      *
336      * @param instance the instance whose method to invoke
337      * @param methodToExecute the name of the method to invoke
338      * @param arguments the optional arguments
339      * @param <T> the type of the result
340      * @return the method's result
341      * @throws ReflectiveOperationException if the method invocation failed
342      * @noinspection unchecked
343      * @noinspectionreason unchecked - unchecked cast is ok on test code
344      */
345     public static <T> T invokeMethod(Object instance,
346             String methodToExecute, Object... arguments) throws ReflectiveOperationException {
347         final Class<?> clss = instance.getClass();
348         final Method method = getClassDeclaredMethod(clss, methodToExecute, arguments.length);
349         return (T) method.invoke(instance, arguments);
350     }
351 
352     /**
353      * Invokes a static private method for a class.
354      *
355      * @param clss the class whose static method to invoke
356      * @param methodToExecute the name of the method to invoke
357      * @param arguments the optional arguments
358      * @param <T> the type of the result
359      * @return the method's result
360      * @throws ReflectiveOperationException if the method invocation failed
361      * @noinspection unchecked
362      * @noinspectionreason unchecked - unchecked cast is ok on test code
363      */
364     public static <T> T invokeStaticMethod(Class<?> clss,
365             String methodToExecute, Object... arguments) throws ReflectiveOperationException {
366         final Method method = getClassDeclaredMethod(clss, methodToExecute, arguments.length);
367         return (T) method.invoke(null, arguments);
368     }
369 
370     /**
371      * Returns the inner class type by its name.
372      *
373      * @param declaringClass the class in which the inner class is declared
374      * @param name the unqualified name (simple name) of the inner class
375      * @return the inner class type
376      * @throws ClassNotFoundException if the class not found
377      * @noinspection unchecked
378      * @noinspectionreason unchecked - unchecked cast is ok on test code
379      */
380     public static <T> Class<T> getInnerClassType(Class<?> declaringClass, String name)
381             throws ClassNotFoundException {
382         return (Class<T>) Class.forName(declaringClass.getName() + "$" + name);
383     }
384 
385     /**
386      * Executes the provided executable and expects it to throw an exception of the specified type.
387      *
388      * @param expectedType the class of the expected exception type.
389      * @param executable the executable to be executed
390      * @param message the message to be used in case of assertion failure.
391      * @return the expected exception thrown by the executable.
392      */
393     public static <T extends Throwable> T getExpectedThrowable(Class<T> expectedType,
394                                                                Executable executable,
395                                                                String message) {
396         return assertThrows(expectedType, executable, message);
397     }
398 
399     /**
400      *  Executes the provided executable and expects it to throw an exception of the specified type.
401      *
402      * @param expectedType the class of the expected exception type.
403      * @param executable the executable to be executed
404      * @return the expected exception thrown by the executable.
405      */
406     public static <T extends Throwable> T getExpectedThrowable(Class<T> expectedType,
407                                                                Executable executable) {
408         return assertThrows(expectedType, executable);
409     }
410 
411 }