xref: /aosp_15_r20/cts/hostsidetests/jvmti/attaching/host/src/android/jvmti/cts/JvmtiAttachingHostTest.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 
15 package android.jvmti.cts;
16 
17 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
18 import com.android.ddmlib.NullOutputReceiver;
19 import com.android.tradefed.build.IBuildInfo;
20 import com.android.tradefed.config.Option;
21 import com.android.tradefed.device.ITestDevice;
22 import com.android.tradefed.log.LogUtil.CLog;
23 import com.android.tradefed.testtype.DeviceTestCase;
24 import com.android.tradefed.testtype.IAbi;
25 import com.android.tradefed.testtype.IAbiReceiver;
26 import com.android.tradefed.testtype.IBuildReceiver;
27 import com.android.tradefed.util.AbiUtils;
28 import com.android.tradefed.util.ArrayUtil;
29 import com.android.tradefed.util.FileUtil;
30 import com.android.tradefed.util.IRunUtil;
31 import com.android.tradefed.util.RunUtil;
32 import com.android.tradefed.util.ZipUtil;
33 
34 import java.io.DataInputStream;
35 import java.io.File;
36 import java.net.ServerSocket;
37 import java.net.Socket;
38 import java.util.concurrent.TimeUnit;
39 import java.util.zip.ZipFile;
40 
41 /**
42  * Specialization of JvmtiHostTest to test attaching on startup.
43  */
44 public class JvmtiAttachingHostTest extends DeviceTestCase implements IBuildReceiver, IAbiReceiver {
45     // inject these options from HostTest directly using --set-option <option name>:<option value>
46     @Option(name = "package-name",
47             description = "The package name of the device test",
48             mandatory = true)
49     private String mTestPackageName = null;
50 
51     @Option(name = "test-file-name",
52             description = "the name of a test zip file to install on device.",
53             mandatory = true)
54     private String mTestApk = null;
55 
56     private CompatibilityBuildHelper mBuildHelper;
57     private IAbi mAbi;
58     private int mCurrentUser;
59 
60     @Override
setBuild(IBuildInfo arg0)61     public void setBuild(IBuildInfo arg0) {
62         mBuildHelper = new CompatibilityBuildHelper(arg0);
63     }
64 
65     @Override
setAbi(IAbi arg0)66     public void setAbi(IAbi arg0) {
67         mAbi = arg0;
68     }
69 
70     private static interface TestRun {
run(ITestDevice device, String pkg, String apk, String abiName)71         public void run(ITestDevice device, String pkg, String apk, String abiName);
72     }
73 
74     private final static String AGENT = "libctsjvmtiattachagent.so";
75 
76     private final static String STARTUP_AGENT_DIR = "code_cache/startup_agents";
77 
78     private static String REMOTE_SOCKET_NAME = "CtsJvmtiAttachingHostTestCases_SOCKET";
79 
80     @Override
setUp()81     protected void setUp() throws Exception {
82         mCurrentUser = getDevice().getCurrentUser();
83     }
84 
testJvmtiAttachDuringBind()85     public void testJvmtiAttachDuringBind() throws Exception {
86         runJvmtiAgentLoadTest((ITestDevice device, String pkg, String apk, String abiName) -> {
87             try {
88                 runAttachTestCmd(device, pkg, "--attach-agent-bind " + AGENT);
89             } catch (Exception e) {
90                 throw new RuntimeException("Failed bind-time attaching", e);
91             }
92         });
93     }
94 
testJvmtiAttachEarly()95     public void testJvmtiAttachEarly() throws Exception {
96         runJvmtiAgentLoadTest((ITestDevice device, String pkg, String apk, String abiName) -> {
97             try {
98                 String pwd = device.executeShellCommand(
99                         "run-as " + pkg + " --user " + mCurrentUser + " pwd");
100                 if (pwd == null) {
101                     throw new RuntimeException("pwd failed");
102                 }
103                 pwd = pwd.trim();
104                 if (pwd.isEmpty()) {
105                     throw new RuntimeException("pwd failed");
106                 }
107 
108                 // Give it a different name, so we do not have "contamination" from
109                 // the test APK.
110                 String libInDataData = AGENT.substring(0, AGENT.length() - ".so".length())
111                         + "2.so";
112                 String agentInDataData =
113                         installLibToDataData(device, pkg, abiName, apk, pwd, AGENT,
114                                 libInDataData);
115                 runAttachTestCmd(device, pkg, "--attach-agent " + agentInDataData);
116             } catch (Exception e) {
117                 throw new RuntimeException("Failed pre-bind attaching", e);
118             }
119         });
120     }
121 
testJvmtiAgentStartupAgents()122     public void testJvmtiAgentStartupAgents() throws Exception {
123         runJvmtiAgentLoadTest((ITestDevice device, String pkg, String apk, String abiName) -> {
124             String startup_dir = null;
125             try {
126                 startup_dir = getPwd(device, pkg) + "/" + STARTUP_AGENT_DIR;
127                 device.executeShellCommand(
128                         "run-as " + pkg + " --user " + mCurrentUser + " mkdir -p " + startup_dir);
129             } catch (Exception e) {
130                 throw new RuntimeException("Failed to install startup-agents");
131             }
132             try {
133                 installLibToDataData(device, pkg, abiName, apk, startup_dir, AGENT, AGENT);
134                 // Run and check attach occurs.
135                 runAttachTestCmd(device, pkg, "");
136                 runAttachTestCmd(device, pkg, "");
137             } catch (Exception e) {
138                 throw new RuntimeException("Failed startup_agents attaching", e);
139             } finally {
140                 try {
141                     // Cleanup the startup-agents directory
142                     device.executeShellCommand(
143                             "run-as " + pkg + " --user " + mCurrentUser + " rm -rf " + startup_dir);
144                 } catch (Exception e) {
145                     throw new RuntimeException("Failed to clean up " + startup_dir, e);
146                 }
147             }
148         });
149     }
150 
testJvmtiAgentAppInternal()151     public void testJvmtiAgentAppInternal() throws Exception {
152         runJvmtiAgentLoadTest((ITestDevice device, String pkg, String apk, String abiName) -> {
153             try {
154                 String setAgentAppCmd = "cmd activity set-agent-app " + pkg + " " + AGENT;
155                 device.executeShellCommand(setAgentAppCmd);
156             } catch (Exception e) {
157                 throw new RuntimeException("Failed running set-agent-app", e);
158             }
159 
160             try {
161                 runAttachTestCmd(device, pkg, "");
162 
163                 // And again.
164                 runAttachTestCmd(device, pkg, "");
165             } catch (Exception e) {
166                 throw new RuntimeException("Failed agent-app attaching", e);
167             }
168         });
169     }
170 
testJvmtiAgentAppExternal()171     public void testJvmtiAgentAppExternal() throws Exception {
172         runJvmtiAgentLoadTest((ITestDevice device, String pkg, String apk, String abiName) -> {
173             try {
174                 String pwd = getPwd(device, pkg);
175                 // Give it a different name, so we do not have "contamination" from
176                 // the test APK.
177                 String libInDataData = AGENT.substring(0, AGENT.length() - ".so".length())
178                         + "2.so";
179                 String agentInDataData =
180                         installLibToDataData(device, pkg, abiName, apk, pwd, AGENT,
181                                 libInDataData);
182 
183                 String setAgentAppCmd = "cmd activity set-agent-app " + pkg + " " + agentInDataData;
184                 device.executeShellCommand(setAgentAppCmd);
185             } catch (Exception e) {
186                 throw new RuntimeException("Failed running set-agent-app", e);
187             }
188 
189             try {
190                 runAttachTestCmd(device, pkg, "");
191 
192                 // And again.
193                 runAttachTestCmd(device, pkg, "");
194             } catch (Exception e) {
195                 throw new RuntimeException("Failed agent-app attaching", e);
196             }
197         });
198     }
199 
getPwd(ITestDevice device, String pkg)200     private String getPwd(ITestDevice device, String pkg) throws Exception {
201         String pwd = device.executeShellCommand(
202                 "run-as " + pkg + " --user " + mCurrentUser + " pwd");
203         if (pwd == null) {
204             throw new RuntimeException("pwd failed");
205         }
206         pwd = pwd.trim();
207         if (pwd.isEmpty()) {
208             throw new RuntimeException("pwd failed");
209         }
210         return pwd;
211     }
212 
runJvmtiAgentLoadTest(TestRun runner)213     private void runJvmtiAgentLoadTest(TestRun runner) throws Exception {
214         final ITestDevice device = getDevice();
215 
216         String testingArch = AbiUtils.getBaseArchForAbi(mAbi.getName());
217         String deviceArch = getDeviceBaseArch(device);
218 
219         //Only bypass if Base Archs are different
220         if (!testingArch.equals(deviceArch)) {
221             CLog.d(
222                     "Bypass as testing Base Arch:"
223                             + testingArch
224                             + " is different from DUT Base Arch:"
225                             + deviceArch);
226             return;
227         }
228 
229         if (mTestApk == null || mTestPackageName == null) {
230             throw new IllegalStateException("Incorrect configuration");
231         }
232 
233         // Wakeup the device if it is on the lockscreen and move it to the home screen.
234         device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
235         device.executeShellCommand("wm dismiss-keyguard");
236         device.executeShellCommand("input keyevent KEYCODE_HOME");
237 
238         runner.run(device, mTestPackageName, mTestApk, mAbi.getName());
239     }
240 
getDeviceBaseArch(ITestDevice device)241     private String getDeviceBaseArch(ITestDevice device) throws Exception {
242         String abi = device.executeShellCommand("getprop ro.product.cpu.abi").replace("\n", "");
243         CLog.d("DUT abi:" + abi);
244         return AbiUtils.getBaseArchForAbi(abi);
245     }
246 
runAttachTestCmd(ITestDevice device, String pkg, String agentParams)247     private static void runAttachTestCmd(ITestDevice device, String pkg, String agentParams)
248             throws Exception {
249         // Get a reverse socket setup
250         try (final ServerSocket ss = new ServerSocket(0)) {
251             device.executeAdbCommand(
252                     "reverse", "localabstract:" + REMOTE_SOCKET_NAME, "tcp:" + ss.getLocalPort());
253             String attachCmd = "cmd activity start -S " + agentParams + " -n " + pkg
254                     + "/android.jvmti.JvmtiActivity";
255 
256             // Don't try to parse the output. We'll get data from the socket or a timeout if it
257             // didn't start. For some reason this command sometimes fails. Retry up to ten times to
258             // make the test less flaky.
259             device.executeShellCommand(attachCmd, NullOutputReceiver.getReceiver(), 10,
260                   TimeUnit.SECONDS, 10);
261             // Wait for startup up to 30 seconds.
262             ss.setSoTimeout(30000);
263             try (Socket s = ss.accept()) {
264                 DataInputStream dis = new DataInputStream(s.getInputStream());
265                 String res = dis.readUTF();
266                 s.shutdownInput();
267                 if (!res.trim().equals("SUCCESS")) {
268                     throw new RuntimeException("Failed test due to remote error: " + res);
269                 }
270             } catch (Exception e) {
271                 throw new RuntimeException("Failed to read output", e);
272             }
273         } finally {
274             device.executeAdbCommand("reverse", "--remove", "localabstract:" + REMOTE_SOCKET_NAME);
275         }
276     }
277 
installLibToDataData(ITestDevice device, String pkg, String abiName, String apk, String dataData, String library, String newLibName)278     private String installLibToDataData(ITestDevice device, String pkg, String abiName,
279             String apk, String dataData, String library, String newLibName) throws Exception {
280         ZipFile zf = null;
281         File tmpFile = null;
282         String libInTmp = null;
283         try {
284             String libInDataData = dataData + "/" + newLibName;
285 
286             File apkFile = mBuildHelper.getTestFile(apk);
287             zf = new ZipFile(apkFile);
288 
289             String libPathInApk = "lib/" + abiName + "/" + library;
290             tmpFile = ZipUtil.extractFileFromZip(zf, libPathInApk);
291 
292             libInTmp = "/data/local/tmp/" + tmpFile.getName();
293             if (!device.pushFile(tmpFile, libInTmp)) {
294                 throw new RuntimeException("Could not push library " + library + " to device");
295             }
296 
297             String runAsCp = device.executeShellCommand(
298                     "run-as " + pkg + " --user " + mCurrentUser +
299                             " cp " + libInTmp + " " + libInDataData);
300             if (runAsCp != null && !runAsCp.trim().isEmpty()) {
301                 throw new RuntimeException(runAsCp.trim());
302             }
303 
304             String runAsChmod = device.executeShellCommand(
305                     "run-as " + pkg + " --user " + mCurrentUser + " chmod a+x " + libInDataData);
306             if (runAsChmod != null && !runAsChmod.trim().isEmpty()) {
307                 throw new RuntimeException(runAsChmod.trim());
308             }
309 
310             return libInDataData;
311         } finally {
312             FileUtil.deleteFile(tmpFile);
313             ZipUtil.closeZip(zf);
314             if (libInTmp != null) {
315                 try {
316                     device.executeShellCommand("rm " + libInTmp);
317                 } catch (Exception e) {
318                     CLog.e("Failed cleaning up library on device");
319                 }
320             }
321         }
322     }
323 }
324