1 /*
2  * Copyright (C) 2016 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 com.android.server.cts;
18 
19 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
20 import com.android.ddmlib.IShellOutputReceiver;
21 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
22 import com.android.ddmlib.testrunner.TestResult.TestStatus;
23 import com.android.tradefed.build.IBuildInfo;
24 import com.android.tradefed.device.CollectingByteOutputReceiver;
25 import com.android.tradefed.device.CollectingOutputReceiver;
26 import com.android.tradefed.device.DeviceNotAvailableException;
27 import com.android.tradefed.log.LogUtil.CLog;
28 import com.android.tradefed.result.CollectingTestListener;
29 import com.android.tradefed.result.TestDescription;
30 import com.android.tradefed.result.TestResult;
31 import com.android.tradefed.result.TestRunResult;
32 import com.android.tradefed.testtype.DeviceTestCase;
33 import com.android.tradefed.testtype.IBuildReceiver;
34 
35 import com.google.common.base.Charsets;
36 import com.google.protobuf.InvalidProtocolBufferException;
37 import com.google.protobuf.MessageLite;
38 import com.google.protobuf.Parser;
39 
40 import java.io.FileNotFoundException;
41 import java.util.Map;
42 import java.util.concurrent.TimeUnit;
43 import java.util.concurrent.atomic.AtomicBoolean;
44 import java.util.regex.Matcher;
45 import java.util.regex.Pattern;
46 
47 import javax.annotation.Nonnull;
48 import javax.annotation.Nullable;
49 
50 public class ProtoDumpTestCase extends DeviceTestCase implements IBuildReceiver {
51     protected static final int PRIVACY_AUTO = 0;
52     protected static final int PRIVACY_EXPLICIT = 1;
53     protected static final int PRIVACY_LOCAL = 2;
54     /** No privacy filtering has been done. All fields should be present. */
55     protected static final int PRIVACY_NONE = 3;
privacyToString(int privacy)56     protected static String privacyToString(int privacy) {
57         switch (privacy) {
58             case PRIVACY_AUTO:
59                 return "AUTO";
60             case PRIVACY_EXPLICIT:
61                 return "EXPLICIT";
62             case PRIVACY_LOCAL:
63                 return "LOCAL";
64             case PRIVACY_NONE:
65                 return "NONE";
66             default:
67                 return "UNKNOWN";
68         }
69     }
70 
71     protected IBuildInfo mCtsBuild;
72 
73     private static final String TEST_RUNNER = "androidx.test.runner.AndroidJUnitRunner";
74 
75     @Override
setUp()76     protected void setUp() throws Exception {
77         super.setUp();
78 
79         assertNotNull(mCtsBuild);
80     }
81 
82     @Override
setBuild(IBuildInfo buildInfo)83     public void setBuild(IBuildInfo buildInfo) {
84         mCtsBuild = buildInfo;
85     }
86 
87     /**
88      * Call onto the device with an adb shell command and get the results of
89      * that as a proto of the given type.
90      *
91      * @param parser A protobuf parser object. e.g. MyProto.parser()
92      * @param command The adb shell command to run. e.g. "dumpsys fingerprint --proto"
93      *
94      * @throws DeviceNotAvailableException If there was a problem communicating with
95      *      the test device.
96      * @throws InvalidProtocolBufferException If there was an error parsing
97      *      the proto. Note that a 0 length buffer is not necessarily an error.
98      */
getDump(Parser<T> parser, String command)99     public <T extends MessageLite> T getDump(Parser<T> parser, String command) throws Exception {
100         final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
101         getDevice().executeShellCommand(command, receiver);
102         return parser.parseFrom(receiver.getOutput());
103     }
104 
105     /**
106      * Install a device side test package.
107      *
108      * @param appFileName Apk file name, such as "CtsNetStatsApp.apk".
109      * @param grantPermissions whether to give runtime permissions.
110      */
installPackage(String appFileName, boolean grantPermissions)111     protected void installPackage(String appFileName, boolean grantPermissions)
112             throws FileNotFoundException, DeviceNotAvailableException {
113         CLog.d("Installing app " + appFileName);
114         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
115         final String result = getDevice().installPackage(
116                 buildHelper.getTestFile(appFileName), true, grantPermissions);
117         assertNull("Failed to install " + appFileName + ": " + result, result);
118     }
119 
120     /**
121      * Run a device side test.
122      *
123      * @param pkgName Test package name, such as "com.android.server.cts.netstats".
124      * @param testClassName Test class name; either a fully qualified name, or "." + a class name.
125      * @param testMethodName Test method name.
126      * @throws DeviceNotAvailableException
127      */
runDeviceTests(@onnull String pkgName, @Nullable String testClassName, @Nullable String testMethodName)128     protected void runDeviceTests(@Nonnull String pkgName,
129             @Nullable String testClassName, @Nullable String testMethodName)
130             throws DeviceNotAvailableException {
131         if (testClassName != null && testClassName.startsWith(".")) {
132             testClassName = pkgName + testClassName;
133         }
134 
135         RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
136                 pkgName, TEST_RUNNER, getDevice().getIDevice());
137         if (testClassName != null && testMethodName != null) {
138             testRunner.setMethodName(testClassName, testMethodName);
139         } else if (testClassName != null) {
140             testRunner.setClassName(testClassName);
141         }
142 
143         CollectingTestListener listener = new CollectingTestListener();
144         assertTrue(getDevice().runInstrumentationTests(testRunner, listener));
145 
146         final TestRunResult result = listener.getCurrentRunResults();
147         if (result.isRunFailure()) {
148             throw new AssertionError("Failed to successfully run device tests for "
149                     + result.getName() + ": " + result.getRunFailureMessage());
150         }
151         if (result.getNumTests() == 0) {
152             throw new AssertionError("No tests were run on the device");
153         }
154 
155         if (result.hasFailedTests()) {
156             // build a meaningful error message
157             StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
158             for (Map.Entry<TestDescription, TestResult> resultEntry :
159                     result.getTestResults().entrySet()) {
160                 if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
161                     errorBuilder.append(resultEntry.getKey().toString());
162                     errorBuilder.append(":\n");
163                     errorBuilder.append(resultEntry.getValue().getStackTrace());
164                 }
165             }
166             throw new AssertionError(errorBuilder.toString());
167         }
168     }
169 
170     /**
171      * Execute the given command, and returns the output.
172      */
execCommandAndGet(String command)173     protected String execCommandAndGet(String command) throws Exception {
174         final CollectingOutputReceiver receiver = new CollectingOutputReceiver();
175         getDevice().executeShellCommand(command, receiver);
176         return receiver.getOutput();
177     }
178 
179     /**
180      * Execute the given command, and find the given pattern with given flags and return the
181      * resulting {@link Matcher}.
182      */
execCommandAndFind(String command, String pattern, int patternFlags)183     protected Matcher execCommandAndFind(String command, String pattern, int patternFlags)
184             throws Exception {
185         final String output = execCommandAndGet(command);
186         final Matcher matcher = Pattern.compile(pattern, patternFlags).matcher(output);
187         assertTrue("Pattern '" + pattern + "' didn't match. Output=\n" + output, matcher.find());
188         return matcher;
189     }
190 
191     /**
192      * Execute the given command, and find the given pattern and return the resulting
193      * {@link Matcher}.
194      */
execCommandAndFind(String command, String pattern)195     protected Matcher execCommandAndFind(String command, String pattern) throws Exception {
196         return execCommandAndFind(command, pattern, 0);
197     }
198 
199     /**
200      * Execute the given command, find the given pattern, and return the first captured group
201      * as a String.
202      */
execCommandAndGetFirstGroup(String command, String pattern)203     protected String execCommandAndGetFirstGroup(String command, String pattern) throws Exception {
204         final Matcher matcher = execCommandAndFind(command, pattern);
205         assertTrue("No group found for pattern '" + pattern + "'", matcher.groupCount() > 0);
206         return matcher.group(1);
207     }
208 
209     /**
210      * Runs logcat and waits (for a maximumum of maxTimeMs) until the desired text is displayed with
211      * the given tag.
212      * Logcat is not cleared, so make sure that text is unique (won't get false hits from old data).
213      * Note that, in practice, the actual max wait time seems to be about 10s longer than maxTimeMs.
214      * Returns true means the desired log line is found.
215      */
checkLogcatForText(String logcatTag, String text, int maxTimeMs)216     protected boolean checkLogcatForText(String logcatTag, String text, int maxTimeMs) {
217         IShellOutputReceiver receiver = new IShellOutputReceiver() {
218             private final StringBuilder mOutputBuffer = new StringBuilder();
219             private final AtomicBoolean mIsCanceled = new AtomicBoolean(false);
220 
221             @Override
222             public void addOutput(byte[] data, int offset, int length) {
223                 if (!isCancelled()) {
224                     synchronized (mOutputBuffer) {
225                         String s = new String(data, offset, length, Charsets.UTF_8);
226                         mOutputBuffer.append(s);
227                         if (checkBufferForText()) {
228                             mIsCanceled.set(true);
229                         }
230                     }
231                 }
232             }
233 
234             private boolean checkBufferForText() {
235                 if (mOutputBuffer.indexOf(text) > -1) {
236                     return true;
237                 } else {
238                     // delete all old data (except the last few chars) since they don't contain text
239                     // (presumably large chunks of data will be added at a time, so this is
240                     // sufficiently efficient.)
241                     int newStart = mOutputBuffer.length() - text.length();
242                     if (newStart > 0) {
243                         mOutputBuffer.delete(0, newStart);
244                     }
245                     return false;
246                 }
247             }
248 
249             @Override
250             public boolean isCancelled() {
251                 return mIsCanceled.get();
252             }
253 
254             @Override
255             public void flush() {
256             }
257         };
258 
259         try {
260             // Wait for at most maxTimeMs for logcat to display the desired text.
261             getDevice().executeShellCommand(String.format("logcat -s %s -e '%s'", logcatTag, text),
262                     receiver, maxTimeMs, TimeUnit.MILLISECONDS, 0);
263             return receiver.isCancelled();
264         } catch (com.android.tradefed.device.DeviceNotAvailableException e) {
265             System.err.println(e);
266         }
267         return false;
268     }
269 }
270