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.testrunner.RemoteAndroidTestRunner; 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.result.ITestLifeCycleReceiver; 24 import com.android.tradefed.result.TestDescription; 25 import com.android.tradefed.testtype.DeviceTestCase; 26 import com.android.tradefed.testtype.IAbi; 27 import com.android.tradefed.testtype.IAbiReceiver; 28 import com.android.tradefed.testtype.IBuildReceiver; 29 import com.android.tradefed.util.AbiUtils; 30 import com.android.tradefed.util.FileUtil; 31 import com.android.tradefed.util.ZipUtil; 32 33 import java.io.File; 34 import java.util.LinkedList; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.concurrent.TimeUnit; 38 import java.util.zip.ZipFile; 39 40 /** 41 * Test a JVMTI device test. 42 * 43 * Reads the configuration (APK and package name) out of the embedded config.properties file. Runs 44 * the agent (expected to be packaged with the APK) into the app's /data/data directory, starts a 45 * test run and attaches the agent. 46 */ 47 public class JvmtiHostTest extends DeviceTestCase implements IBuildReceiver, IAbiReceiver { 48 private static final String RUNNER = "androidx.test.runner.AndroidJUnitRunner"; 49 // inject these options from HostTest directly using --set-option <option name>:<option value> 50 @Option(name = "package-name", 51 description = "The package name of the device test", 52 mandatory = true) 53 private String mTestPackageName = null; 54 55 @Option(name = "test-file-name", 56 description = "the name of a test zip file to install on device.", 57 mandatory = true) 58 private String mTestApk = null; 59 60 @Option(name = "hidden-api-checks", 61 description = "If we should enable hidden api checks. Default 'true'. Set to 'false' " + 62 "to disable hiddenapi.", 63 mandatory = false) 64 private String mHiddenApiChecksEnabled = null; 65 66 private CompatibilityBuildHelper mBuildHelper; 67 private IAbi mAbi; 68 private int mCurrentUser; 69 70 @Override setBuild(IBuildInfo arg0)71 public void setBuild(IBuildInfo arg0) { 72 mBuildHelper = new CompatibilityBuildHelper(arg0); 73 } 74 75 @Override setAbi(IAbi arg0)76 public void setAbi(IAbi arg0) { 77 mAbi = arg0; 78 } 79 80 // Constant returned to indicate get-current-user failed. See comment at/near 81 // https://cs.android.com/android/_/android/platform/tools/tradefederation/+/android11-release:device_build_interfaces/com/android/tradefed/device/ITestDevice.java;l=780 82 private static final int GET_USER_FAILURE = -10000; 83 84 // Try getting current user and throw an exception immediately if we fail. 85 @Override setUp()86 protected void setUp() throws Exception { 87 mCurrentUser = getDevice().getCurrentUser(); 88 if (mCurrentUser == GET_USER_FAILURE) { 89 throw new RuntimeException("am get-current-user failed!"); 90 } 91 } 92 testJvmti()93 public void testJvmti() throws Exception { 94 final ITestDevice device = getDevice(); 95 96 String testingArch = AbiUtils.getBaseArchForAbi(mAbi.getName()); 97 String deviceArch = getDeviceBaseArch(device); 98 99 //Only bypass if Base Archs are different 100 if (!testingArch.equals(deviceArch)) { 101 CLog.d( 102 "Bypass as testing Base Arch:" 103 + testingArch 104 + " is different from DUT Base Arch:" 105 + deviceArch); 106 return; 107 } 108 109 if (mTestApk == null || mTestPackageName == null) { 110 throw new IllegalStateException("Incorrect configuration"); 111 } 112 113 if (null != mHiddenApiChecksEnabled && 114 !"false".equals(mHiddenApiChecksEnabled) && 115 !"true".equals(mHiddenApiChecksEnabled)) { 116 throw new IllegalStateException( 117 "option hidden-api-checks must be 'true' or 'false' if present."); 118 } 119 boolean disable_hidden_api = 120 mHiddenApiChecksEnabled != null && "false".equals(mHiddenApiChecksEnabled); 121 String old_hiddenapi_setting = null; 122 if (disable_hidden_api) { 123 old_hiddenapi_setting = device.getSetting("global", "hidden_api_policy"); 124 device.setSetting("global", "hidden_api_policy", "1"); 125 } 126 127 try { 128 RemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(mTestPackageName, RUNNER, 129 device.getIDevice()); 130 // set a max deadline limit to avoid hanging forever 131 runner.setMaxTimeToOutputResponse(5, TimeUnit.MINUTES); 132 133 AttachAgent aa = new AttachAgent(device, mTestPackageName, mTestApk); 134 aa.prepare(); 135 TestResults tr = new TestResults(aa); 136 137 device.runInstrumentationTests(runner, tr); 138 139 assertTrue(tr.getErrors(), tr.hasStarted()); 140 assertFalse(tr.getErrors(), tr.hasFailed()); 141 } finally { 142 if (disable_hidden_api) { 143 device.setSetting("global", "hidden_api_policy", old_hiddenapi_setting); 144 } 145 } 146 } 147 getDeviceBaseArch(ITestDevice device)148 private String getDeviceBaseArch(ITestDevice device) throws Exception { 149 String abi = device.executeShellCommand("getprop ro.product.cpu.abi").replace("\n", ""); 150 CLog.d("DUT abi:" + abi); 151 return AbiUtils.getBaseArchForAbi(abi); 152 } 153 154 private class AttachAgent implements Runnable { 155 private ITestDevice mDevice; 156 private String mPkg; 157 private String mApk; 158 159 private String mAgentInDataData; 160 AttachAgent(ITestDevice device, String pkg, String apk)161 public AttachAgent(ITestDevice device, String pkg, String apk) { 162 this.mDevice = device; 163 this.mPkg = pkg; 164 this.mApk = apk; 165 } 166 prepare()167 public void prepare() { 168 try { 169 String pwd = mDevice.executeShellCommand( 170 "run-as " + mPkg + " --user " + mCurrentUser + " pwd"); 171 if (pwd == null) { 172 throw new RuntimeException("pwd failed"); 173 } 174 pwd = pwd.trim(); 175 if (pwd.isEmpty()) { 176 throw new RuntimeException("pwd failed"); 177 } 178 179 mAgentInDataData = installLibToDataData(pwd, "libctsjvmtiagent.so"); 180 } catch (Exception e) { 181 throw new RuntimeException("Failed installing", e); 182 } 183 } 184 185 @Override run()186 public void run() { 187 try { 188 if (mAgentInDataData == null) { 189 throw new IllegalStateException("prepare() has not been called"); 190 } 191 String attachCmd = "cmd activity attach-agent " + mPkg + " " + mAgentInDataData; 192 String attachReply = mDevice.executeShellCommand(attachCmd); 193 // Don't try to parse the output. The test will time out anyways if this didn't 194 // work. 195 if (attachReply != null && !attachReply.trim().isEmpty()) { 196 CLog.e(attachReply); 197 } 198 } catch (Exception e) { 199 throw new RuntimeException("Failed attaching", e); 200 } 201 } 202 installLibToDataData(String dataData, String library)203 private String installLibToDataData(String dataData, String library) throws Exception { 204 ZipFile zf = null; 205 File tmpFile = null; 206 String libInTmp = null; 207 try { 208 String libInDataData = dataData + "/" + library; 209 210 File apkFile = mBuildHelper.getTestFile(mApk); 211 zf = new ZipFile(apkFile); 212 213 String libPathInApk = "lib/" + mAbi.getName() + "/" + library; 214 tmpFile = ZipUtil.extractFileFromZip(zf, libPathInApk); 215 216 libInTmp = "/data/local/tmp/" + tmpFile.getName(); 217 if (!mDevice.pushFile(tmpFile, libInTmp)) { 218 throw new RuntimeException("Could not push library " + library + " to device"); 219 } 220 221 String runAsCp = mDevice.executeShellCommand( 222 "run-as " + mPkg + " --user " + mCurrentUser + 223 " cp " + libInTmp + " " + libInDataData); 224 if (runAsCp != null && !runAsCp.trim().isEmpty()) { 225 throw new RuntimeException(runAsCp.trim()); 226 } 227 228 String runAsChmod = mDevice.executeShellCommand( 229 "run-as " + mPkg + " --user " + mCurrentUser + 230 " chmod a+x " + libInDataData); 231 if (runAsChmod != null && !runAsChmod.trim().isEmpty()) { 232 throw new RuntimeException(runAsChmod.trim()); 233 } 234 235 return libInDataData; 236 } finally { 237 FileUtil.deleteFile(tmpFile); 238 ZipUtil.closeZip(zf); 239 if (libInTmp != null) { 240 try { 241 mDevice.executeShellCommand("rm " + libInTmp); 242 } catch (Exception e) { 243 CLog.e("Failed cleaning up library on device"); 244 } 245 } 246 } 247 } 248 } 249 250 private static class TestResults implements ITestLifeCycleReceiver { 251 private boolean mFailed = false; 252 private boolean mStarted = false; 253 private final Runnable mOnStart; 254 private List<String> mErrors = new LinkedList<>(); 255 TestResults(Runnable onStart)256 public TestResults(Runnable onStart) { 257 this.mOnStart = onStart; 258 } 259 hasFailed()260 public boolean hasFailed() { 261 return mFailed; 262 } 263 hasStarted()264 public boolean hasStarted() { 265 return mStarted; 266 } 267 getErrors()268 public String getErrors() { 269 if (mErrors.isEmpty()) { 270 return ""; 271 } 272 return mErrors.toString(); 273 } 274 275 @Override testAssumptionFailure(TestDescription arg0, String arg1)276 public void testAssumptionFailure(TestDescription arg0, String arg1) { 277 mFailed = true; 278 mErrors.add(arg0.toString() + " " + arg1); 279 } 280 281 @Override testEnded(TestDescription arg0, Map<String, String> arg1)282 public void testEnded(TestDescription arg0, Map<String, String> arg1) {} 283 284 @Override testFailed(TestDescription arg0, String arg1)285 public void testFailed(TestDescription arg0, String arg1) { 286 mFailed = true; 287 mErrors.add(arg0.toString() + " " + arg1); 288 } 289 290 @Override testIgnored(TestDescription arg0)291 public void testIgnored(TestDescription arg0) {} 292 293 @Override testRunEnded(long arg0, Map<String, String> arg1)294 public void testRunEnded(long arg0, Map<String, String> arg1) {} 295 296 @Override testRunFailed(String arg0)297 public void testRunFailed(String arg0) { 298 mFailed = true; 299 mErrors.add(arg0); 300 } 301 302 @Override testRunStarted(String arg0, int arg1)303 public void testRunStarted(String arg0, int arg1) { 304 if (mOnStart != null) { 305 mOnStart.run(); 306 } 307 } 308 309 @Override testRunStopped(long arg0)310 public void testRunStopped(long arg0) {} 311 312 @Override testStarted(TestDescription arg0)313 public void testStarted(TestDescription arg0) { 314 mStarted = true; 315 } 316 } 317 } 318