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.concurrent.TimeUnit; 42 import java.util.concurrent.atomic.AtomicBoolean; 43 import java.util.Map; 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 = "android.support.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 } 271