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     @Override
setUp()81     protected void setUp() throws Exception {
82         mCurrentUser = getDevice().getCurrentUser();
83     }
84 
testJvmti()85     public void testJvmti() throws Exception {
86         final ITestDevice device = getDevice();
87 
88         String testingArch = AbiUtils.getBaseArchForAbi(mAbi.getName());
89         String deviceArch = getDeviceBaseArch(device);
90 
91         //Only bypass if Base Archs are different
92         if (!testingArch.equals(deviceArch)) {
93             CLog.d(
94                     "Bypass as testing Base Arch:"
95                             + testingArch
96                             + " is different from DUT Base Arch:"
97                             + deviceArch);
98             return;
99         }
100 
101         if (mTestApk == null || mTestPackageName == null) {
102             throw new IllegalStateException("Incorrect configuration");
103         }
104 
105         if (null != mHiddenApiChecksEnabled &&
106             !"false".equals(mHiddenApiChecksEnabled) &&
107             !"true".equals(mHiddenApiChecksEnabled)) {
108           throw new IllegalStateException(
109               "option hidden-api-checks must be 'true' or 'false' if present.");
110         }
111         boolean disable_hidden_api =
112             mHiddenApiChecksEnabled != null && "false".equals(mHiddenApiChecksEnabled);
113         String old_hiddenapi_setting = null;
114         if (disable_hidden_api) {
115             old_hiddenapi_setting = device.getSetting("global", "hidden_api_policy");
116             device.setSetting("global", "hidden_api_policy", "1");
117         }
118 
119         try {
120             RemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(mTestPackageName, RUNNER,
121                     device.getIDevice());
122             // set a max deadline limit to avoid hanging forever
123             runner.setMaxTimeToOutputResponse(5, TimeUnit.MINUTES);
124 
125             AttachAgent aa = new AttachAgent(device, mTestPackageName, mTestApk);
126             aa.prepare();
127             TestResults tr = new TestResults(aa);
128 
129             device.runInstrumentationTests(runner, tr);
130 
131             assertTrue(tr.getErrors(), tr.hasStarted());
132             assertFalse(tr.getErrors(), tr.hasFailed());
133         } finally {
134             if (disable_hidden_api) {
135                 device.setSetting("global", "hidden_api_policy", old_hiddenapi_setting);
136             }
137         }
138     }
139 
getDeviceBaseArch(ITestDevice device)140     private String getDeviceBaseArch(ITestDevice device) throws Exception {
141         String abi = device.executeShellCommand("getprop ro.product.cpu.abi").replace("\n", "");
142         CLog.d("DUT abi:" + abi);
143         return AbiUtils.getBaseArchForAbi(abi);
144     }
145 
146     private class AttachAgent implements Runnable {
147         private ITestDevice mDevice;
148         private String mPkg;
149         private String mApk;
150 
151         private String mAgentInDataData;
152 
AttachAgent(ITestDevice device, String pkg, String apk)153         public AttachAgent(ITestDevice device, String pkg, String apk) {
154             this.mDevice = device;
155             this.mPkg = pkg;
156             this.mApk = apk;
157         }
158 
prepare()159         public void prepare() {
160             try {
161                 String pwd = mDevice.executeShellCommand(
162                         "run-as " + mPkg + " --user " + mCurrentUser + " pwd");
163                 if (pwd == null) {
164                     throw new RuntimeException("pwd failed");
165                 }
166                 pwd = pwd.trim();
167                 if (pwd.isEmpty()) {
168                     throw new RuntimeException("pwd failed");
169                 }
170 
171                 mAgentInDataData = installLibToDataData(pwd, "libctsjvmtiagent.so");
172             } catch (Exception e) {
173                 throw new RuntimeException("Failed installing", e);
174             }
175         }
176 
177         @Override
run()178         public void run() {
179             try {
180                 if (mAgentInDataData == null) {
181                     throw new IllegalStateException("prepare() has not been called");
182                 }
183                 String attachCmd = "cmd activity attach-agent " + mPkg + " " + mAgentInDataData;
184                 String attachReply = mDevice.executeShellCommand(attachCmd);
185                 // Don't try to parse the output. The test will time out anyways if this didn't
186                 // work.
187                 if (attachReply != null && !attachReply.trim().isEmpty()) {
188                     CLog.e(attachReply);
189                 }
190             } catch (Exception e) {
191                 throw new RuntimeException("Failed attaching", e);
192             }
193         }
194 
installLibToDataData(String dataData, String library)195         private String installLibToDataData(String dataData, String library) throws Exception {
196             ZipFile zf = null;
197             File tmpFile = null;
198             String libInTmp = null;
199             try {
200                 String libInDataData = dataData + "/" + library;
201 
202                 File apkFile = mBuildHelper.getTestFile(mApk);
203                 zf = new ZipFile(apkFile);
204 
205                 String libPathInApk = "lib/" + mAbi.getName() + "/" + library;
206                 tmpFile = ZipUtil.extractFileFromZip(zf, libPathInApk);
207 
208                 libInTmp = "/data/local/tmp/" + tmpFile.getName();
209                 if (!mDevice.pushFile(tmpFile, libInTmp)) {
210                     throw new RuntimeException("Could not push library " + library + " to device");
211                 }
212 
213                 String runAsCp = mDevice.executeShellCommand(
214                         "run-as " + mPkg + " --user " + mCurrentUser +
215                                 " cp " + libInTmp + " " + libInDataData);
216                 if (runAsCp != null && !runAsCp.trim().isEmpty()) {
217                     throw new RuntimeException(runAsCp.trim());
218                 }
219 
220                 String runAsChmod = mDevice.executeShellCommand(
221                         "run-as " + mPkg + " --user " + mCurrentUser +
222                                 " chmod a+x " + libInDataData);
223                 if (runAsChmod != null && !runAsChmod.trim().isEmpty()) {
224                     throw new RuntimeException(runAsChmod.trim());
225                 }
226 
227                 return libInDataData;
228             } finally {
229                 FileUtil.deleteFile(tmpFile);
230                 ZipUtil.closeZip(zf);
231                 if (libInTmp != null) {
232                     try {
233                         mDevice.executeShellCommand("rm " + libInTmp);
234                     } catch (Exception e) {
235                         CLog.e("Failed cleaning up library on device");
236                     }
237                 }
238             }
239         }
240     }
241 
242     private static class TestResults implements ITestLifeCycleReceiver {
243         private boolean mFailed = false;
244         private boolean mStarted = false;
245         private final Runnable mOnStart;
246         private List<String> mErrors = new LinkedList<>();
247 
TestResults(Runnable onStart)248         public TestResults(Runnable onStart) {
249             this.mOnStart = onStart;
250         }
251 
hasFailed()252         public boolean hasFailed() {
253             return mFailed;
254         }
255 
hasStarted()256         public boolean hasStarted() {
257             return mStarted;
258         }
259 
getErrors()260         public String getErrors() {
261             if (mErrors.isEmpty()) {
262                 return "";
263             }
264             return mErrors.toString();
265         }
266 
267         @Override
testAssumptionFailure(TestDescription arg0, String arg1)268         public void testAssumptionFailure(TestDescription arg0, String arg1) {
269             mFailed = true;
270             mErrors.add(arg0.toString() + " " + arg1);
271         }
272 
273         @Override
testEnded(TestDescription arg0, Map<String, String> arg1)274         public void testEnded(TestDescription arg0, Map<String, String> arg1) {}
275 
276         @Override
testFailed(TestDescription arg0, String arg1)277         public void testFailed(TestDescription arg0, String arg1) {
278             mFailed = true;
279             mErrors.add(arg0.toString() + " " + arg1);
280         }
281 
282         @Override
testIgnored(TestDescription arg0)283         public void testIgnored(TestDescription arg0) {}
284 
285         @Override
testRunEnded(long arg0, Map<String, String> arg1)286         public void testRunEnded(long arg0, Map<String, String> arg1) {}
287 
288         @Override
testRunFailed(String arg0)289         public void testRunFailed(String arg0) {
290             mFailed = true;
291             mErrors.add(arg0);
292         }
293 
294         @Override
testRunStarted(String arg0, int arg1)295         public void testRunStarted(String arg0, int arg1) {
296             if (mOnStart != null) {
297                 mOnStart.run();
298             }
299         }
300 
301         @Override
testRunStopped(long arg0)302         public void testRunStopped(long arg0) {}
303 
304         @Override
testStarted(TestDescription arg0)305         public void testStarted(TestDescription arg0) {
306             mStarted = true;
307         }
308     }
309 }
310