1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.atrace.cts;
18 
19 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
20 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
21 import com.android.tradefed.build.IBuildInfo;
22 import com.android.tradefed.device.DeviceNotAvailableException;
23 import com.android.tradefed.log.LogUtil;
24 import com.android.tradefed.result.CollectingTestListener;
25 import com.android.tradefed.result.TestDescription;
26 import com.android.tradefed.result.TestResult;
27 import com.android.tradefed.result.TestRunResult;
28 import com.android.tradefed.testtype.DeviceTestCase;
29 import com.android.tradefed.testtype.IBuildReceiver;
30 
31 import trebuchet.io.BufferProducer;
32 import trebuchet.io.DataSlice;
33 import trebuchet.model.Model;
34 import trebuchet.task.ImportTask;
35 import trebuchet.util.PrintlnImportFeedback;
36 
37 import java.io.FileNotFoundException;
38 import java.nio.charset.StandardCharsets;
39 import java.util.Map;
40 import java.util.concurrent.TimeUnit;
41 
42 public class AtraceHostTestBase extends DeviceTestCase implements IBuildReceiver {
43     private static final String TEST_RUNNER = "androidx.test.runner.AndroidJUnitRunner";
44     private static final String TEST_APK = "CtsAtraceTestApp.apk";
45     // TODO: Make private
46     protected static final String TEST_PKG = "com.android.cts.atracetestapp";
47     private static final String TEST_CLASS = "com.android.cts.atracetestapp.AtraceDeviceTests";
48 
49     private static final String START_TRACE_CMD = "atrace --async_start -a \\* -c -b 16000";
50     private static final String START_TRACE_NO_APP_CMD = "atrace --async_start -c -b 16000";
51     private static final String STOP_TRACE_CMD = "atrace --async_stop";
52 
53     private IBuildInfo mCtsBuild;
54     private boolean mIsInstalled = false;
55 
56     /**
57      * {@inheritDoc}
58      */
59     @Override
setBuild(IBuildInfo buildInfo)60     public void setBuild(IBuildInfo buildInfo) {
61         mCtsBuild = buildInfo;
62     }
63 
64     /**
65      * Install a device side test package.
66      *
67      * @param appFileName Apk file name, such as "CtsNetStatsApp.apk".
68      * @param grantPermissions whether to give runtime permissions.
69      */
installPackage(String appFileName, boolean grantPermissions)70     private void installPackage(String appFileName, boolean grantPermissions)
71             throws FileNotFoundException, DeviceNotAvailableException {
72         LogUtil.CLog.d("Installing app " + appFileName);
73         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
74         final String result = getDevice().installPackage(
75                 buildHelper.getTestFile(appFileName), true, grantPermissions);
76         assertNull("Failed to install " + appFileName + ": " + result, result);
77     }
78 
requireApk()79     private final void requireApk() {
80         if (mIsInstalled) return;
81         try {
82             System.out.println("Installing APK");
83             installPackage(TEST_APK, true);
84             mIsInstalled = true;
85         } catch (FileNotFoundException | DeviceNotAvailableException e) {
86             e.printStackTrace();
87             throw new RuntimeException(e);
88         }
89     }
90 
turnScreenOn()91     protected final void turnScreenOn() {
92         shell("input keyevent KEYCODE_WAKEUP");
93         shell("wm dismiss-keyguard");
94     }
95 
96     @Override
run(junit.framework.TestResult result)97     public void run(junit.framework.TestResult result) {
98         try {
99             super.run(result);
100         } finally {
101             try {
102                 // We don't have the equivalent of @AfterClass, but this basically does that
103                 System.out.println("Uninstalling APK");
104                 getDevice().uninstallPackage(TEST_PKG);
105                 mIsInstalled = false;
106             } catch (DeviceNotAvailableException e) {}
107         }
108     }
109 
110     @Override
setUp()111     protected void setUp() throws Exception {
112         try {
113             shell("atrace --async_stop");
114         } finally {
115             super.setUp();
116         }
117     }
118 
119     @Override
tearDown()120     protected void tearDown() throws Exception {
121         try {
122             shell("atrace --async_stop");
123         } finally {
124             super.tearDown();
125         }
126     }
127 
128     /**
129      * Run a device side test.
130      *
131      * @param pkgName Test package name, such as "com.android.server.cts.netstats".
132      * @param testClassName Test class name; either a fully qualified name, or "." + a class name.
133      * @param testMethodName Test method name.
134      * @throws DeviceNotAvailableException
135      */
runDeviceTests(String pkgName, String testClassName, String testMethodName)136     private PidTidPair runDeviceTests(String pkgName,
137             String testClassName,  String testMethodName)
138             throws DeviceNotAvailableException {
139         if (testClassName != null && testClassName.startsWith(".")) {
140             testClassName = pkgName + testClassName;
141         }
142 
143         RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
144                 pkgName, TEST_RUNNER, getDevice().getIDevice());
145         testRunner.setMaxTimeout(60, TimeUnit.SECONDS);
146         if (testClassName != null && testMethodName != null) {
147             testRunner.setMethodName(testClassName, testMethodName);
148         } else if (testClassName != null) {
149             testRunner.setClassName(testClassName);
150         }
151 
152         CollectingTestListener listener = new CollectingTestListener();
153         assertTrue(getDevice().runInstrumentationTests(testRunner, listener));
154 
155         final TestRunResult result = listener.getCurrentRunResults();
156         if (result.isRunFailure()) {
157             throw new AssertionError("Failed to successfully run device tests for "
158                     + result.getName() + ": " + result.getRunFailureMessage());
159         }
160         if (result.getNumTests() == 0) {
161             throw new AssertionError("No tests were run on the device");
162         }
163 
164         if (result.hasFailedTests()) {
165             // build a meaningful error message
166             StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
167             for (Map.Entry<TestDescription, TestResult> resultEntry :
168                     result.getTestResults().entrySet()) {
169                 if (!resultEntry.getValue().getStatus().equals(
170                         com.android.ddmlib.testrunner.TestResult.TestStatus.PASSED)) {
171                     errorBuilder.append(resultEntry.getKey().toString());
172                     errorBuilder.append(":\n");
173                     errorBuilder.append(resultEntry.getValue().getStackTrace());
174                 }
175             }
176             throw new AssertionError(errorBuilder.toString());
177         }
178         return new PidTidPair(result);
179     }
180 
runAppTest(String testname)181     private final PidTidPair runAppTest(String testname) {
182         requireApk();
183         try {
184             return runDeviceTests(TEST_PKG, TEST_CLASS, testname);
185         } catch (DeviceNotAvailableException e) {
186             throw new RuntimeException(e);
187         }
188     }
189 
shell(String command, String... args)190     protected final String shell(String command, String... args) {
191         if (args != null && args.length > 0) {
192             command += " " + String.join(" ", args);
193         }
194         try {
195             return getDevice().executeShellCommand(command);
196         } catch (DeviceNotAvailableException ex) {
197             throw new RuntimeException(ex);
198         }
199     }
200 
201     private static class StringAdapter implements BufferProducer {
202         private byte[] data;
203         private boolean hasRead = false;
204 
StringAdapter(String str)205         StringAdapter(String str) {
206             this.data = str.getBytes(StandardCharsets.UTF_8);
207         }
208 
209         @Override
next()210         public DataSlice next() {
211             if (!hasRead) {
212                 hasRead = true;
213                 return new DataSlice(data);
214             }
215             return null;
216         }
217 
218         @Override
close()219         public void close() {
220             hasRead = true;
221         }
222     }
223 
parse(String traceOutput)224     private Model parse(String traceOutput) {
225         ImportTask importTask = new ImportTask(new PrintlnImportFeedback());
226         return importTask.importTrace(new StringAdapter(traceOutput));
227     }
228 
runSingleAppTest(AtraceDeviceTestList test)229     protected final PidTidPair runSingleAppTest(AtraceDeviceTestList test) {
230         return runAppTest(test.toString());
231     }
232 
traceSingleTest(AtraceDeviceTestList test, boolean withAppTracing, String... categories)233     protected final TraceResult traceSingleTest(AtraceDeviceTestList test, boolean withAppTracing,
234             String... categories) {
235         requireApk();
236         shell(withAppTracing ? START_TRACE_CMD : START_TRACE_NO_APP_CMD, categories);
237         PidTidPair pidTid = runSingleAppTest(test);
238         String traceOutput = shell("atrace --async_stop", categories);
239         assertNotNull("unable to capture atrace output", traceOutput);
240         return new TraceResult(pidTid, parse(traceOutput));
241     }
242 
traceSingleTest(AtraceDeviceTestList test, String... categories)243     protected final TraceResult traceSingleTest(AtraceDeviceTestList test, String... categories) {
244         return traceSingleTest(test, true, categories);
245     }
246 }
247