1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.hoststubgen.hosthelper;
17 
18 import com.google.common.reflect.ClassPath;
19 import com.google.common.reflect.ClassPath.ClassInfo;
20 
21 import junit.framework.JUnit4TestAdapter;
22 import junit.framework.TestSuite;
23 
24 import java.util.regex.Pattern;
25 
26 /**
27  * A very simple Junit {@link TestSuite} builder that finds all classes that look like test classes.
28  *
29  * We use it to run ravenwood test jars from the command line.
30  */
31 public class HostTestSuite {
32     private static final String CLASS_NAME_REGEX_ENV = "HOST_TEST_CLASS_NAME_REGEX";
33 
34     /**
35      * Called by junit, and return all test-looking classes as a suite.
36      */
suite()37     public static TestSuite suite() {
38         TestSuite suite = new TestSuite();
39 
40         final Pattern classNamePattern;
41         final var filterRegex = System.getenv(CLASS_NAME_REGEX_ENV);
42         if (filterRegex == null) {
43             classNamePattern = Pattern.compile("");
44         } else {
45             classNamePattern = Pattern.compile(filterRegex);
46         }
47         try {
48             // We use guava to list all classes.
49             ClassPath cp = ClassPath.from(HostTestSuite.class.getClassLoader());
50 
51             for (var classInfo : cp.getAllClasses()) {
52                 Class<?> clazz = asTestClass(classInfo);
53                 if (clazz != null) {
54                     if (classNamePattern.matcher(clazz.getSimpleName()).find()) {
55                         System.out.println("Test class found " + clazz.getName());
56                     } else {
57                         System.out.println("Skipping test class (for $"
58                                 + CLASS_NAME_REGEX_ENV + "): " + clazz.getName());
59                     }
60                     suite.addTest(new JUnit4TestAdapter(clazz));
61                 }
62             }
63         } catch (Exception e) {
64             throw new RuntimeException(e);
65         }
66         return suite;
67     }
68 
69     /**
70      * Decide whether a class looks like a test class or not, and if so, return it as a Class
71      * instance.
72      */
asTestClass(ClassInfo classInfo)73     private static Class<?> asTestClass(ClassInfo classInfo) {
74         try {
75             final Class<?> clazz = classInfo.load();
76 
77             // Does it extend junit.framework.TestCase?
78             if (junit.framework.TestCase.class.isAssignableFrom(clazz)) {
79                 // Ignore classes in JUnit itself, or the android(x) test lib.
80                 if (classInfo.getName().startsWith("junit.")
81                         || classInfo.getName().startsWith("org.junit.")
82                         || classInfo.getName().startsWith("android.test.")
83                         || classInfo.getName().startsWith("androidx.test.")) {
84                     return null; // Ignore junit classes.
85                 }
86                 return clazz;
87             }
88             // Does it have any "@Test" method?
89             for (var method : clazz.getMethods()) {
90                 for (var an : method.getAnnotations()) {
91                     if (an.annotationType() == org.junit.Test.class) {
92                         return clazz;
93                     }
94                 }
95             }
96             return null;
97         } catch (java.lang.NoClassDefFoundError e) {
98             // Ignore unloadable classes.
99             return null;
100         }
101     }
102 }
103