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.apimap; 18 19 import com.android.cts.apicommon.ApiClass; 20 import com.android.cts.apicommon.ApiCoverage; 21 import com.android.cts.apicommon.ApiPackage; 22 import com.android.cts.ctsprofiles.ClassProfile; 23 import com.android.cts.ctsprofiles.MethodProfile; 24 import com.android.cts.ctsprofiles.ModuleProfile; 25 26 import java.util.HashMap; 27 import java.util.HashSet; 28 import java.util.List; 29 import java.util.Map; 30 import java.util.Set; 31 import java.util.Stack; 32 33 34 /** A class for collecting APIs covered by a CTS module. */ 35 public class CallGraphManager { 36 37 // Cache API calls for each CTS method. 38 private final Map<String, CoveredApiCache> mCoveredApiCaches = new HashMap<>(); 39 40 private final ModuleProfile mModule; 41 42 /** Cache the covered API list for a CTS method. */ 43 static class CoveredApiCache { 44 45 private final Map<String, MethodProfile> mApiConstructors = new HashMap<>(); 46 47 private final Map<String, MethodProfile> mApiMethods = new HashMap<>(); 48 mergeApis(CoveredApiCache apis)49 public void mergeApis(CoveredApiCache apis) { 50 addMethods(apis.getApiMethods()); 51 addConstructors(apis.getApiConstructors()); 52 } 53 addMethods(Map<String, MethodProfile> methods)54 public void addMethods(Map<String, MethodProfile> methods) { 55 mApiMethods.putAll(methods); 56 } 57 addConstructors(Map<String, MethodProfile> constructors)58 public void addConstructors(Map<String, MethodProfile> constructors) { 59 mApiConstructors.putAll(constructors); 60 } 61 getApiConstructors()62 public Map<String, MethodProfile> getApiConstructors() { 63 return mApiConstructors; 64 } 65 getApiMethods()66 public Map<String, MethodProfile> getApiMethods() { 67 return mApiMethods; 68 } 69 } 70 CallGraphManager(ModuleProfile moduleProfile)71 public CallGraphManager(ModuleProfile moduleProfile) { 72 mModule = moduleProfile; 73 } 74 getModule()75 public ModuleProfile getModule() { 76 return mModule; 77 } 78 79 /** 80 * Maps detected APIs to CTS test methods and marks them as covered by this CTS module. 81 */ resolveCoveredApis(ApiCoverage apiCoverage)82 public void resolveCoveredApis(ApiCoverage apiCoverage) { 83 for (ClassProfile classProfile : mModule.getClasses()) { 84 if (!classProfile.isNonAbstractTestClass()) { 85 continue; 86 } 87 for (MethodProfile methodProfile : classProfile.getTestMethods().values()) { 88 TarJan tarjan = new TarJan(methodProfile, mCoveredApiCaches.keySet()); 89 Stack<Integer> stack = new Stack<>(); 90 Set<Integer> visitedComponents = new HashSet<>(); 91 String methodSignature = methodProfile.getMethodSignatureWithClass(); 92 stack.add(tarjan.getComponentID(methodSignature)); 93 visitedComponents.add(tarjan.getComponentID(methodSignature)); 94 // Do recursive search for API calls. 95 resolveMethodCoveredApis(stack, visitedComponents, tarjan); 96 markCoveredApisWithCaller(methodSignature, apiCoverage); 97 } 98 } 99 markCoveredApisWithoutCaller(apiCoverage); 100 } 101 102 /** Collects covered APIs for a test method via memorized search. */ resolveMethodCoveredApis( Stack<Integer> stack, Set<Integer> visitedComponents, TarJan tarjan)103 private CoveredApiCache resolveMethodCoveredApis( 104 Stack<Integer> stack, 105 Set<Integer> visitedComponents, 106 TarJan tarjan) { 107 List<MethodProfile> methods = tarjan.getComponent(stack.peek()); 108 String methodSignature = methods.get(0).getMethodSignatureWithClass(); 109 CoveredApiCache coveredApis = mCoveredApiCaches.get(methodSignature); 110 if (coveredApis != null) { 111 return coveredApis; 112 } 113 coveredApis = new CoveredApiCache(); 114 for (MethodProfile method: methods) { 115 coveredApis.addMethods(method.getApiMethodCalls()); 116 coveredApis.addConstructors(method.getApiConstructorCalls()); 117 } 118 for (MethodProfile method: methods) { 119 for (MethodProfile methodCall : method.getCommonMethodCalls().values()) { 120 String methodCallSignature = methodCall.getMethodSignatureWithClass(); 121 int componentID = tarjan.getComponentID(methodCallSignature); 122 if (visitedComponents.contains(componentID)) { 123 continue; 124 } 125 visitedComponents.add(componentID); 126 stack.add(componentID); 127 CoveredApiCache apis = resolveMethodCoveredApis(stack, visitedComponents, tarjan); 128 coveredApis.mergeApis(apis); 129 stack.pop(); 130 } 131 } 132 for (MethodProfile method: methods) { 133 mCoveredApiCaches.put(method.getMethodSignatureWithClass(), coveredApis); 134 } 135 return coveredApis; 136 } 137 138 /** Searches for the API class based on the given package name and class name. */ getApiClass( String packageName, String className, ApiCoverage apiCoverage)139 private ApiClass getApiClass( 140 String packageName, String className, ApiCoverage apiCoverage) { 141 ApiPackage apiPackage = apiCoverage.getPackage(packageName); 142 if (apiPackage != null) { 143 return apiPackage.getClass(className); 144 } 145 return null; 146 } 147 148 /** Marks that APIs are covered by this CTS module. */ markCoveredApisWithoutCaller(ApiCoverage apiCoverage)149 private void markCoveredApisWithoutCaller(ApiCoverage apiCoverage) { 150 for (ClassProfile classProfile: mModule.getClasses()) { 151 if (!classProfile.isApiClass()) { 152 continue; 153 } 154 ApiClass apiClass = getApiClass( 155 classProfile.getPackageName(), 156 classProfile.getClassName(), 157 apiCoverage 158 ); 159 if (apiClass == null) { 160 continue; 161 } 162 for (MethodProfile methodProfile: classProfile.getMethods().values()) { 163 if (methodProfile.getMethodName().equals("<init>")) { 164 apiClass.markConstructorCovered( 165 methodProfile.getMethodParams(), 166 mModule.getModuleName() 167 ); 168 } else { 169 apiClass.markMethodCovered( 170 methodProfile.getMethodName(), 171 methodProfile.getMethodParams(), 172 mModule.getModuleName() 173 ); 174 } 175 } 176 } 177 } 178 179 /** Marks that APIs are called by the given CTS test method. */ markCoveredApisWithCaller(String methodSignature, ApiCoverage apiCoverage)180 private void markCoveredApisWithCaller(String methodSignature, ApiCoverage apiCoverage) { 181 CoveredApiCache apiCache = mCoveredApiCaches.get(methodSignature); 182 if (apiCache == null) { 183 return; 184 } 185 for (MethodProfile apiConstructor : apiCache.getApiConstructors().values()) { 186 ApiClass apiClass = getApiClass( 187 apiConstructor.getPackageName(), 188 apiConstructor.getClassName(), 189 apiCoverage 190 ); 191 if (apiClass != null) { 192 apiClass.markConstructorCoveredTest( 193 apiConstructor.getMethodParams(), 194 String.format("[%s] %s", mModule.getModuleName(), methodSignature) 195 ); 196 } 197 } 198 for (MethodProfile apiMethod : apiCache.getApiMethods().values()) { 199 ApiClass apiClass = getApiClass( 200 apiMethod.getPackageName(), apiMethod.getClassName(), apiCoverage); 201 if (apiClass != null) { 202 apiClass.markMethodCoveredTest( 203 apiMethod.getMethodName(), 204 apiMethod.getMethodParams(), 205 String.format("[%s] %s", mModule.getModuleName(), methodSignature) 206 ); 207 } 208 } 209 } 210 } 211