| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
| * in compliance with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software distributed under the License |
| * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
| * or implied. See the License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| |
| package android.jvmti.cts; |
| |
| import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; |
| import com.android.ddmlib.NullOutputReceiver; |
| import com.android.tradefed.build.IBuildInfo; |
| import com.android.tradefed.config.Option; |
| import com.android.tradefed.device.ITestDevice; |
| import com.android.tradefed.log.LogUtil.CLog; |
| import com.android.tradefed.testtype.DeviceTestCase; |
| import com.android.tradefed.testtype.IAbi; |
| import com.android.tradefed.testtype.IAbiReceiver; |
| import com.android.tradefed.testtype.IBuildReceiver; |
| import com.android.tradefed.util.AbiUtils; |
| import com.android.tradefed.util.ArrayUtil; |
| import com.android.tradefed.util.FileUtil; |
| import com.android.tradefed.util.IRunUtil; |
| import com.android.tradefed.util.RunUtil; |
| import com.android.tradefed.util.ZipUtil; |
| |
| import java.io.DataInputStream; |
| import java.io.File; |
| import java.net.ServerSocket; |
| import java.net.Socket; |
| import java.util.concurrent.TimeUnit; |
| import java.util.zip.ZipFile; |
| |
| /** |
| * Specialization of JvmtiHostTest to test attaching on startup. |
| */ |
| public class JvmtiAttachingHostTest extends DeviceTestCase implements IBuildReceiver, IAbiReceiver { |
| // inject these options from HostTest directly using --set-option <option name>:<option value> |
| @Option(name = "package-name", |
| description = "The package name of the device test", |
| mandatory = true) |
| private String mTestPackageName = null; |
| |
| @Option(name = "test-file-name", |
| description = "the name of a test zip file to install on device.", |
| mandatory = true) |
| private String mTestApk = null; |
| |
| private CompatibilityBuildHelper mBuildHelper; |
| private IAbi mAbi; |
| private int mCurrentUser; |
| |
| @Override |
| public void setBuild(IBuildInfo arg0) { |
| mBuildHelper = new CompatibilityBuildHelper(arg0); |
| } |
| |
| @Override |
| public void setAbi(IAbi arg0) { |
| mAbi = arg0; |
| } |
| |
| private static interface TestRun { |
| public void run(ITestDevice device, String pkg, String apk, String abiName); |
| } |
| |
| private final static String AGENT = "libctsjvmtiattachagent.so"; |
| |
| private final static String STARTUP_AGENT_DIR = "code_cache/startup_agents"; |
| |
| private static String REMOTE_SOCKET_NAME = "CtsJvmtiAttachingHostTestCases_SOCKET"; |
| |
| @Override |
| protected void setUp() throws Exception { |
| mCurrentUser = getDevice().getCurrentUser(); |
| } |
| |
| public void testJvmtiAttachDuringBind() throws Exception { |
| runJvmtiAgentLoadTest((ITestDevice device, String pkg, String apk, String abiName) -> { |
| try { |
| runAttachTestCmd(device, pkg, "--attach-agent-bind " + AGENT); |
| } catch (Exception e) { |
| throw new RuntimeException("Failed bind-time attaching", e); |
| } |
| }); |
| } |
| |
| public void testJvmtiAttachEarly() throws Exception { |
| runJvmtiAgentLoadTest((ITestDevice device, String pkg, String apk, String abiName) -> { |
| try { |
| String pwd = device.executeShellCommand( |
| "run-as " + pkg + " --user " + mCurrentUser + " pwd"); |
| if (pwd == null) { |
| throw new RuntimeException("pwd failed"); |
| } |
| pwd = pwd.trim(); |
| if (pwd.isEmpty()) { |
| throw new RuntimeException("pwd failed"); |
| } |
| |
| // Give it a different name, so we do not have "contamination" from |
| // the test APK. |
| String libInDataData = AGENT.substring(0, AGENT.length() - ".so".length()) |
| + "2.so"; |
| String agentInDataData = |
| installLibToDataData(device, pkg, abiName, apk, pwd, AGENT, |
| libInDataData); |
| runAttachTestCmd(device, pkg, "--attach-agent " + agentInDataData); |
| } catch (Exception e) { |
| throw new RuntimeException("Failed pre-bind attaching", e); |
| } |
| }); |
| } |
| |
| public void testJvmtiAgentStartupAgents() throws Exception { |
| runJvmtiAgentLoadTest((ITestDevice device, String pkg, String apk, String abiName) -> { |
| String startup_dir = null; |
| try { |
| startup_dir = getPwd(device, pkg) + "/" + STARTUP_AGENT_DIR; |
| device.executeShellCommand( |
| "run-as " + pkg + " --user " + mCurrentUser + " mkdir -p " + startup_dir); |
| } catch (Exception e) { |
| throw new RuntimeException("Failed to install startup-agents"); |
| } |
| try { |
| installLibToDataData(device, pkg, abiName, apk, startup_dir, AGENT, AGENT); |
| // Run and check attach occurs. |
| runAttachTestCmd(device, pkg, ""); |
| runAttachTestCmd(device, pkg, ""); |
| } catch (Exception e) { |
| throw new RuntimeException("Failed startup_agents attaching", e); |
| } finally { |
| try { |
| // Cleanup the startup-agents directory |
| device.executeShellCommand( |
| "run-as " + pkg + " --user " + mCurrentUser + " rm -rf " + startup_dir); |
| } catch (Exception e) { |
| throw new RuntimeException("Failed to clean up " + startup_dir, e); |
| } |
| } |
| }); |
| } |
| |
| public void testJvmtiAgentAppInternal() throws Exception { |
| runJvmtiAgentLoadTest((ITestDevice device, String pkg, String apk, String abiName) -> { |
| try { |
| String setAgentAppCmd = "cmd activity set-agent-app " + pkg + " " + AGENT; |
| device.executeShellCommand(setAgentAppCmd); |
| } catch (Exception e) { |
| throw new RuntimeException("Failed running set-agent-app", e); |
| } |
| |
| try { |
| runAttachTestCmd(device, pkg, ""); |
| |
| // And again. |
| runAttachTestCmd(device, pkg, ""); |
| } catch (Exception e) { |
| throw new RuntimeException("Failed agent-app attaching", e); |
| } |
| }); |
| } |
| |
| public void testJvmtiAgentAppExternal() throws Exception { |
| runJvmtiAgentLoadTest((ITestDevice device, String pkg, String apk, String abiName) -> { |
| try { |
| String pwd = getPwd(device, pkg); |
| // Give it a different name, so we do not have "contamination" from |
| // the test APK. |
| String libInDataData = AGENT.substring(0, AGENT.length() - ".so".length()) |
| + "2.so"; |
| String agentInDataData = |
| installLibToDataData(device, pkg, abiName, apk, pwd, AGENT, |
| libInDataData); |
| |
| String setAgentAppCmd = "cmd activity set-agent-app " + pkg + " " + agentInDataData; |
| device.executeShellCommand(setAgentAppCmd); |
| } catch (Exception e) { |
| throw new RuntimeException("Failed running set-agent-app", e); |
| } |
| |
| try { |
| runAttachTestCmd(device, pkg, ""); |
| |
| // And again. |
| runAttachTestCmd(device, pkg, ""); |
| } catch (Exception e) { |
| throw new RuntimeException("Failed agent-app attaching", e); |
| } |
| }); |
| } |
| |
| private String getPwd(ITestDevice device, String pkg) throws Exception { |
| String pwd = device.executeShellCommand( |
| "run-as " + pkg + " --user " + mCurrentUser + " pwd"); |
| if (pwd == null) { |
| throw new RuntimeException("pwd failed"); |
| } |
| pwd = pwd.trim(); |
| if (pwd.isEmpty()) { |
| throw new RuntimeException("pwd failed"); |
| } |
| return pwd; |
| } |
| |
| private void runJvmtiAgentLoadTest(TestRun runner) throws Exception { |
| final ITestDevice device = getDevice(); |
| |
| String testingArch = AbiUtils.getBaseArchForAbi(mAbi.getName()); |
| String deviceArch = getDeviceBaseArch(device); |
| |
| //Only bypass if Base Archs are different |
| if (!testingArch.equals(deviceArch)) { |
| CLog.d( |
| "Bypass as testing Base Arch:" |
| + testingArch |
| + " is different from DUT Base Arch:" |
| + deviceArch); |
| return; |
| } |
| |
| if (mTestApk == null || mTestPackageName == null) { |
| throw new IllegalStateException("Incorrect configuration"); |
| } |
| |
| // Wakeup the device if it is on the lockscreen and move it to the home screen. |
| device.executeShellCommand("input keyevent KEYCODE_WAKEUP"); |
| device.executeShellCommand("wm dismiss-keyguard"); |
| device.executeShellCommand("input keyevent KEYCODE_HOME"); |
| |
| runner.run(device, mTestPackageName, mTestApk, mAbi.getName()); |
| } |
| |
| private String getDeviceBaseArch(ITestDevice device) throws Exception { |
| String abi = device.executeShellCommand("getprop ro.product.cpu.abi").replace("\n", ""); |
| CLog.d("DUT abi:" + abi); |
| return AbiUtils.getBaseArchForAbi(abi); |
| } |
| |
| private static void runAttachTestCmd(ITestDevice device, String pkg, String agentParams) |
| throws Exception { |
| // Get a reverse socket setup |
| try (final ServerSocket ss = new ServerSocket(0)) { |
| device.executeAdbCommand( |
| "reverse", "localabstract:" + REMOTE_SOCKET_NAME, "tcp:" + ss.getLocalPort()); |
| String attachCmd = "cmd activity start -S " + agentParams + " -n " + pkg |
| + "/android.jvmti.JvmtiActivity"; |
| |
| // Don't try to parse the output. We'll get data from the socket or a timeout if it |
| // didn't start. For some reason this command sometimes fails. Retry up to ten times to |
| // make the test less flaky. |
| device.executeShellCommand(attachCmd, NullOutputReceiver.getReceiver(), 10, |
| TimeUnit.SECONDS, 10); |
| // Wait for startup up to 30 seconds. |
| ss.setSoTimeout(30000); |
| try (Socket s = ss.accept()) { |
| DataInputStream dis = new DataInputStream(s.getInputStream()); |
| String res = dis.readUTF(); |
| s.shutdownInput(); |
| if (!res.trim().equals("SUCCESS")) { |
| throw new RuntimeException("Failed test due to remote error: " + res); |
| } |
| } catch (Exception e) { |
| throw new RuntimeException("Failed to read output", e); |
| } |
| } finally { |
| device.executeAdbCommand("reverse", "--remove", "localabstract:" + REMOTE_SOCKET_NAME); |
| } |
| } |
| |
| private String installLibToDataData(ITestDevice device, String pkg, String abiName, |
| String apk, String dataData, String library, String newLibName) throws Exception { |
| ZipFile zf = null; |
| File tmpFile = null; |
| String libInTmp = null; |
| try { |
| String libInDataData = dataData + "/" + newLibName; |
| |
| File apkFile = mBuildHelper.getTestFile(apk); |
| zf = new ZipFile(apkFile); |
| |
| String libPathInApk = "lib/" + abiName + "/" + library; |
| tmpFile = ZipUtil.extractFileFromZip(zf, libPathInApk); |
| |
| libInTmp = "/data/local/tmp/" + tmpFile.getName(); |
| if (!device.pushFile(tmpFile, libInTmp)) { |
| throw new RuntimeException("Could not push library " + library + " to device"); |
| } |
| |
| String runAsCp = device.executeShellCommand( |
| "run-as " + pkg + " --user " + mCurrentUser + |
| " cp " + libInTmp + " " + libInDataData); |
| if (runAsCp != null && !runAsCp.trim().isEmpty()) { |
| throw new RuntimeException(runAsCp.trim()); |
| } |
| |
| String runAsChmod = device.executeShellCommand( |
| "run-as " + pkg + " --user " + mCurrentUser + " chmod a+x " + libInDataData); |
| if (runAsChmod != null && !runAsChmod.trim().isEmpty()) { |
| throw new RuntimeException(runAsChmod.trim()); |
| } |
| |
| return libInDataData; |
| } finally { |
| FileUtil.deleteFile(tmpFile); |
| ZipUtil.closeZip(zf); |
| if (libInTmp != null) { |
| try { |
| device.executeShellCommand("rm " + libInTmp); |
| } catch (Exception e) { |
| CLog.e("Failed cleaning up library on device"); |
| } |
| } |
| } |
| } |
| } |