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