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