• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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 
17 package com.android.cts.ctsprofiles;
18 
19 import com.android.cts.apicommon.ApiCoverage;
20 import com.android.cts.apicommon.ApiMethod;
21 
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Set;
28 
29 /** Representation of a class included in the CTS package. */
30 public class ClassProfile {
31 
32     public final AnnotationManagement annotationManagement = new AnnotationManagement();
33 
34     private final String mModule;
35 
36     private final String mPackage;
37 
38     private final String mClass;
39 
40     private int mClassType = 0;
41 
42     private ClassProfile mSuperClass = null;
43 
44     // A list of interfaces implemented by this class.
45     private final List<ClassProfile> mInterfaces = new ArrayList<>();
46 
47     // Methods defined in this class.
48     private final Map<String, MethodProfile> mMethods = new HashMap<>();
49 
50     // Test methods defined in this class.
51     private Map<String, MethodProfile> mTestMethods = null;
52 
53     // Methods inherited from apis.
54     private final Map<String, MethodProfile> mInheritedApiMethods = new HashMap<>();
55 
56     // A map of API classes extended/implemented by this class with the API class signature as
57     // the key.
58     private Map<String, ClassProfile> mInheritedApiClasses = null;
59 
60 
61     private static final Set<String> JUNIT4_ANNOTATION_PATTERNS = new HashSet<>(
62             List.of(
63                     "org.junit.*",
64                     "com.android.bedstead.harrier.annotations.meta.RequiresBedsteadJUnit4"
65             ));
66 
67     private static final Set<String> JUNIT3_CLASS_PATTERNS = new HashSet<>(
68             List.of(
69                     "junit.framework.TestCase",
70                     "android.test.AndroidTestCase",
71                     "android.test.InstrumentationTestCase"
72             ));
73 
ClassProfile(String moduleName, String packageName, String className, boolean apiClass)74     public ClassProfile(String moduleName, String packageName, String className, boolean apiClass) {
75         mModule = moduleName;
76         mClass = className;
77         mPackage = packageName;
78         if (apiClass) {
79             mClassType |= ClassType.API.getValue();
80         }
81     }
82 
83     /** Representation of the class type. */
84     public enum ClassType {
85         INTERFACE(1),
86         ABSTRACT(2),
87         JUNIT3(4),
88         JUNIT4(8),
89         ANNOTATION(16),
90         /** A non-test and non-annotation class. */
91         COMMON(32),
92         API(64);
93 
94         private final int mValue;
95 
ClassType(int value)96         ClassType(int value) {
97             mValue = value;
98         }
99 
getValue()100         public int getValue() {
101             return mValue;
102         }
103     }
104 
getClassSignature()105     public String getClassSignature() {
106         return Utils.getClassSignature(mPackage, mClass);
107     }
108 
getClassName()109     public String getClassName() {
110         return mClass;
111     }
112 
getPackageName()113     public String getPackageName() {
114         return mPackage;
115     }
116 
getModuleName()117     public String getModuleName() {
118         return mModule;
119     }
120 
getInterfaces()121     public List<ClassProfile> getInterfaces() {
122         return mInterfaces;
123     }
124 
getMethods()125     public Map<String, MethodProfile> getMethods() {
126         return mMethods;
127     }
128 
getInheritedApiMethods()129     public Map<String, MethodProfile> getInheritedApiMethods() {
130         return mInheritedApiMethods;
131     }
132 
133     /** Creates a class method. */
getOrCreateMethod( String methodName, List<String> params)134     public MethodProfile getOrCreateMethod(
135             String methodName, List<String> params) {
136         String methodSignature = Utils.getMethodSignature(methodName, params);
137         if (!mMethods.containsKey(methodSignature)) {
138             mMethods.put(methodSignature, new MethodProfile(this, methodName, params));
139         }
140         return mMethods.get(methodSignature);
141     }
142 
143     /** Gets API classes extended/implemented by the class. */
getInheritedApiClasses()144     public Map<String, ClassProfile> getInheritedApiClasses() {
145         if (mInheritedApiClasses != null) {
146             return mInheritedApiClasses;
147         }
148         mInheritedApiClasses = new HashMap<>();
149         if (mSuperClass != null) {
150             if (mSuperClass.isApiClass()) {
151                 mInheritedApiClasses.put(mSuperClass.getClassSignature(), mSuperClass);
152             } else {
153                 mInheritedApiClasses.putAll(mSuperClass.getInheritedApiClasses());
154             }
155         }
156         for (ClassProfile interfaceClass : mInterfaces) {
157             if (interfaceClass.isApiClass()) {
158                 mInheritedApiClasses.put(interfaceClass.getClassSignature(), interfaceClass);
159             } else {
160                 mInheritedApiClasses.putAll(interfaceClass.getInheritedApiClasses());
161             }
162         }
163         return mInheritedApiClasses;
164     }
165 
166     /**
167      * Adds a supper method call when the method is inherited from super classes. If the "super"
168      * keyword is not explicitly added, the java bytecode will not show which super class is called.
169      * In this case, find the nearest method along the super class chain and add an additionally
170      * call to that method.
171      */
resolveInheritedMethods(ApiCoverage apiCoverage)172     public void resolveInheritedMethods(ApiCoverage apiCoverage) {
173         for (MethodProfile method : mMethods.values()) {
174             if (method.isDirectMember()) {
175                 continue;
176             }
177             MethodProfile inheritedMethod =
178                     findInheritedMethod(
179                             method.getMethodName(), method.getMethodParams(), apiCoverage);
180             if (inheritedMethod != null) {
181                 method.addMethodCall(inheritedMethod);
182             }
183         }
184     }
185 
186     /**
187      * Filters out methods that are overriding abstract API methods defined in extended API classes
188      * or implemented API interfaces. An additional method link to corresponding abstract API
189      * methods will be recorded to ensure they will be included in the API coverage measurement.
190      */
resolveOverriddenAbstractApiMethods(ApiCoverage apiCoverage)191     public void resolveOverriddenAbstractApiMethods(ApiCoverage apiCoverage) {
192         for (MethodProfile method : mMethods.values()) {
193             if (method.isAbstract() || !method.isDirectMember()) {
194                 continue;
195             }
196             for (ClassProfile inheritedApiClass : getInheritedApiClasses().values()) {
197                 // Skip java.lang.Object, which can make the runtime very long.
198                 if (inheritedApiClass.getClassSignature().startsWith("java.lang.Object")) {
199                     continue;
200                 }
201                 ApiMethod apiMethod =
202                         apiCoverage.getMethod(
203                                 inheritedApiClass.getPackageName(),
204                                 inheritedApiClass.getClassName(),
205                                 method.getMethodName(),
206                                 method.getMethodParams());
207                 if (apiMethod == null || !apiMethod.isAbstractMethod()) {
208                     continue;
209                 }
210                 MethodProfile overriddenApiMethod =
211                         inheritedApiClass.getOrCreateMethod(
212                                 method.getMethodName(), method.getMethodParams());
213                 // The corresponding abstract API method should be regarded as covered.
214                 method.addOverriddenApiMethod(overriddenApiMethod);
215             }
216         }
217     }
218 
219     /**
220      * Records API methods that are inherited by this class.
221      *
222      * <p>This method iterates through all inherited API classes and their methods. For each
223      * non-abstract API method, it attempts to find a corresponding method in the current class. If
224      * the method is inherited from an API, it is added to the {@code mInheritedApiMethods} map.
225      *
226      * @param apiCoverage The {@link ApiCoverage} object containing information about API classes
227      *     and methods.
228      */
resolveInheritedApiMethods(ApiCoverage apiCoverage)229     public void resolveInheritedApiMethods(ApiCoverage apiCoverage) {
230         for (ClassProfile inheritedApiClass : getInheritedApiClasses().values()) {
231             // Skip java.lang.Object, which can make the runtime very long.
232             if (inheritedApiClass.getClassSignature().startsWith("java.lang.Object")) {
233                 continue;
234             }
235             for (ApiMethod apiMethod :
236                     apiCoverage
237                             .getClass(
238                                     inheritedApiClass.getPackageName(),
239                                     inheritedApiClass.getClassName())
240                             .getMethods()) {
241                 if (apiMethod.isAbstractMethod()) {
242                     continue;
243                 }
244                 String methodName = apiMethod.getName();
245                 List<String> methodParams = apiMethod.getParameterTypes();
246                 MethodProfile method = findInheritedMethod(methodName, methodParams, apiCoverage);
247                 if (method != null && method.isApiMethod()) {
248                     mInheritedApiMethods.putIfAbsent(
249                             Utils.getMethodSignatureWithClass(
250                                     getPackageName(), getClassName(), methodName, methodParams),
251                             method);
252                 }
253             }
254         }
255     }
256 
257     /** Adds an interface implemented by the class. */
addInterface(ClassProfile interfaceProfile)258     public void addInterface(ClassProfile interfaceProfile) {
259         mInterfaces.add(interfaceProfile);
260     }
261 
262     /** Adds a class type for the class. */
addClassType(ClassType classType)263     public void addClassType(ClassType classType) {
264         mClassType |= classType.getValue();
265     }
266 
setSuperClass(ClassProfile superClass)267     public void setSuperClass(ClassProfile superClass) {
268         mSuperClass = superClass;
269     }
270 
271     /** Collects all test methods contained in the class. */
getTestMethods()272     public Map<String, MethodProfile> getTestMethods() {
273         if (mTestMethods != null) {
274             return mTestMethods;
275         }
276         mTestMethods = new HashMap<>();
277         mMethods.forEach((methodKey, method) -> {
278             if (method.isTestMethod()) {
279                 mTestMethods.put(methodKey, method);
280             }
281         });
282         // Test methods defined in the super class will also be collected.
283         if (mSuperClass != null) {
284             mSuperClass.getTestMethods().forEach(mTestMethods::putIfAbsent);
285         }
286         return mTestMethods;
287     }
288 
289     /** Returns true if the class is a test class. */
isTestClass()290     public boolean isTestClass() {
291         if (matchAnyTypes(ClassType.ANNOTATION.getValue() | ClassType.API.getValue())) {
292             return false;
293         }
294         if (!isJunit4Class() && !isJunit3Class()) {
295             addClassType(ClassType.COMMON);
296             return false;
297         }
298         return true;
299     }
300 
301     /** Returns true if the class is an API class. */
isApiClass()302     public boolean isApiClass() {
303         return matchAllTypes(ClassType.API.getValue());
304     }
305 
306     /** Returns true if the class is a test class but not an abstract class. */
isNonAbstractTestClass()307     public boolean isNonAbstractTestClass() {
308         return (isTestClass() && !matchAnyTypes(
309                 ClassType.ABSTRACT.getValue() | ClassType.INTERFACE.getValue()));
310     }
311 
312 
313     /** Returns true if it is decided that whether this is a test class or not. */
testClassResolved()314     private boolean testClassResolved() {
315         return matchAnyTypes(
316                 ClassType.JUNIT3.getValue()
317                         | ClassType.JUNIT4.getValue()
318                         | ClassType.COMMON.getValue()
319                         | ClassType.ANNOTATION.getValue()
320                         | ClassType.API.getValue()
321         );
322     }
323 
324     /** Returns true if the class is a Junit4 test class. */
isJunit4Class()325     protected boolean isJunit4Class() {
326         if (testClassResolved()) {
327             return matchAllTypes(ClassType.JUNIT4.getValue());
328         }
329         // Check if the class is marked by a Junit4 runner.
330         for (ClassProfile annotation : annotationManagement.getAnnotations()) {
331             for (String pattern : JUNIT4_ANNOTATION_PATTERNS) {
332                 if (annotation.getClassSignature().matches(pattern)) {
333                     addClassType(ClassType.JUNIT4);
334                     return true;
335                 }
336             }
337         }
338         // Check if any methods are marked by a Junit4 annotation.
339         for (MethodProfile method : mMethods.values()) {
340             if (method.isJunit4Method()) {
341                 addClassType(ClassType.JUNIT4);
342                 return true;
343             }
344         }
345         // Check if the class is extended from a Junit4 class.
346         if (mSuperClass != null && mSuperClass.isJunit4Class()) {
347             addClassType(ClassType.JUNIT4);
348             return true;
349         }
350         return false;
351     }
352 
353     /** Returns true if the class is a Junit3 test class. */
isJunit3Class()354     protected boolean isJunit3Class() {
355         if (testClassResolved()) {
356             return matchAllTypes(ClassType.JUNIT3.getValue());
357         }
358         if (mSuperClass != null) {
359             // Check if the class is extended from a Junit3 base class.
360             for (String pattern : JUNIT3_CLASS_PATTERNS) {
361                 if (mSuperClass.getClassSignature().matches(pattern)) {
362                     addClassType(ClassType.JUNIT3);
363                     return true;
364                 }
365             }
366             // Check if the class is extended from a Junit3 class.
367             if (mSuperClass.isJunit3Class()) {
368                 addClassType(ClassType.JUNIT3);
369                 return true;
370             }
371         }
372         return false;
373     }
374 
375     /**
376      * Finds the inherited method corresponding to the given method signature, searching through the
377      * current class, its superclass, and implemented interfaces.
378      *
379      * @param methodName The name of the method to find.
380      * @param params The list of parameter types for the method.
381      * @param apiCoverage The {@link ApiCoverage} object containing API information.
382      * @return The {@link MethodProfile} of the inherited method, or {@code null} if not found.
383      */
findInheritedMethod( String methodName, List<String> params, ApiCoverage apiCoverage)384     private MethodProfile findInheritedMethod(
385             String methodName, List<String> params, ApiCoverage apiCoverage) {
386         if (isApiClass()) {
387             ApiMethod apiMethod = apiCoverage.getMethod(mPackage, mClass, methodName, params);
388             return apiMethod == null ? null : getOrCreateMethod(methodName, params);
389         }
390         String methodSignature = Utils.getMethodSignature(methodName, params);
391         MethodProfile inheritedMethod = mMethods.get(methodSignature);
392         if (inheritedMethod != null && inheritedMethod.isDirectMember()) {
393             return inheritedMethod;
394         }
395         if (mSuperClass != null) {
396             inheritedMethod = mSuperClass.findInheritedMethod(methodName, params, apiCoverage);
397             if (inheritedMethod != null) {
398                 return inheritedMethod;
399             }
400         }
401         for (ClassProfile interfaceClass : getInterfaces()) {
402             inheritedMethod = interfaceClass.findInheritedMethod(methodName, params, apiCoverage);
403             if (inheritedMethod != null) {
404                 return inheritedMethod;
405             }
406         }
407         return null;
408     }
409 
matchAnyTypes(int typesValue)410     private boolean matchAnyTypes(int typesValue) {
411         return (mClassType & typesValue) != 0;
412     }
413 
matchAllTypes(int typesValue)414     private boolean matchAllTypes(int typesValue) {
415         return (mClassType & typesValue) == typesValue;
416     }
417 }
418