• 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.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