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.xts.apimapper.helper; 18 19 import static com.android.xts.apimapper.helper.DeviceMethodCallStats.getPackageClass; 20 21 import android.util.Log; 22 23 import com.google.errorprone.annotations.FormatMethod; 24 import com.google.errorprone.annotations.FormatString; 25 26 import org.junit.rules.TestRule; 27 import org.junit.runner.Description; 28 import org.junit.runners.model.Statement; 29 30 import java.util.HashSet; 31 import java.util.Set; 32 33 /** 34 * A device-side junit rule used by the code generated via the apimapper tool. 35 */ 36 public final class DeviceApiMapperHelperRule implements TestRule { 37 38 public static final String CALL_STATS_PREFIX = "#callstats:"; 39 public static final String CALL_LOG_PREFIX = "#call:"; 40 private static final String TAG = "ApiMapperHelperRule"; 41 42 private final Set<String> mLogs = new HashSet<>(); 43 private final boolean mLogCalls; 44 private final DeviceMethodCallStats mStats = new DeviceMethodCallStats(); 45 46 private final OnBeforeCallListener mListener = 47 (callerClass, callerMethod, opcode, methodOwner, methodName, descriptor, callerEntity) 48 -> { 49 // Print out the method call. 50 logMethodCall( 51 callerClass, 52 callerMethod, 53 opcode, 54 methodOwner, 55 methodName, 56 descriptor, 57 callerEntity 58 ); 59 String className = callerEntity == null 60 ? methodOwner : callerEntity.getClass().getName(); 61 // Record the method call. 62 mStats.onMethodCalled(className, methodName, descriptor); 63 }; 64 DeviceApiMapperHelperRule()65 public DeviceApiMapperHelperRule() { 66 this(false); 67 } 68 DeviceApiMapperHelperRule(boolean logCalls)69 public DeviceApiMapperHelperRule(boolean logCalls) { 70 mLogCalls = logCalls; 71 } 72 73 /** Log the test information before test starts. */ onBeforeTest(String className, String methodName)74 public void onBeforeTest(String className, String methodName) { 75 log("Device Test started: %s#%s", className, methodName); 76 startCollectingApis(); 77 } 78 79 /** Log the test information after test ends. */ onAfterTest(String className, String methodName)80 public void onAfterTest(String className, String methodName) { 81 finishCollectingApis(className, methodName); 82 log("Device Test finished: %s#%s", className, methodName); 83 } 84 85 /** Print out the method call information to the logcat. */ logMethodCall( String callerClass, String callerMethod, int opcode, String methodOwner, String methodName, String descriptor, Object callerEntity )86 public void logMethodCall( 87 String callerClass, 88 String callerMethod, 89 int opcode, 90 String methodOwner, 91 String methodName, 92 String descriptor, 93 Object callerEntity 94 ) { 95 if (!mLogCalls) { 96 return; 97 } 98 String realClass = callerEntity == null ? methodOwner : callerEntity.getClass().getName(); 99 String apiSignature = String.format( 100 "%s:%s:%s", 101 getPackageClass(realClass), 102 methodName, 103 descriptor 104 ); 105 if (mLogs.contains(apiSignature)) { 106 return; 107 } 108 mLogs.add(apiSignature); 109 log("%s %s:%s:%s:%s:%s", CALL_LOG_PREFIX, callerClass, callerMethod, 110 getPackageClass(realClass), methodName, descriptor); 111 } 112 113 @Override apply(Statement base, Description desc)114 public Statement apply(Statement base, Description desc) { 115 return new Statement() { 116 @Override 117 public void evaluate() throws Throwable { 118 if (!desc.isSuite()) { 119 onBeforeTest(desc.getTestClass().getName(), desc.getMethodName()); 120 } 121 try { 122 base.evaluate(); 123 } finally { 124 if (!desc.isSuite()) { 125 onAfterTest(desc.getTestClass().getName(), desc.getMethodName()); 126 } 127 } 128 } 129 }; 130 } 131 132 @FormatMethod 133 private void log(@FormatString String format, Object... args) { 134 Log.i(TAG, String.format(format, args)); 135 } 136 137 private void startCollectingApis() { 138 DeviceMethodCallHook.addOnBeforeCallListener(mListener); 139 mStats.clear(); 140 } 141 142 private void finishCollectingApis(String className, String methodName) { 143 DeviceMethodCallHook.removeOnBeforeCallListener(); 144 mStats.dump(TAG, CALL_STATS_PREFIX, className, methodName); 145 } 146 } 147