Home | History | Annotate | Download | only in cts
      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;
     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
     76     protected void setUp() throws Exception {
     77         super.setUp();
     78 
     79         assertNotNull(mCtsBuild);
     80     }
     81 
     82     @Override
     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      */
     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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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