Home | History | Annotate | Download | only in am
      1 /*
      2  * Copyright (C) 2007 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.commands.am;
     18 
     19 import static android.app.ActivityManager.INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS;
     20 import static android.app.ActivityManager.INSTR_FLAG_MOUNT_EXTERNAL_STORAGE_FULL;
     21 
     22 import android.app.IActivityManager;
     23 import android.app.IInstrumentationWatcher;
     24 import android.app.Instrumentation;
     25 import android.app.UiAutomationConnection;
     26 import android.content.ComponentName;
     27 import android.content.pm.IPackageManager;
     28 import android.content.pm.InstrumentationInfo;
     29 import android.os.Build;
     30 import android.os.Bundle;
     31 import android.os.Environment;
     32 import android.os.ServiceManager;
     33 import android.os.UserHandle;
     34 import android.util.AndroidException;
     35 import android.util.proto.ProtoOutputStream;
     36 import android.view.IWindowManager;
     37 
     38 import java.io.File;
     39 import java.io.FileOutputStream;
     40 import java.io.IOException;
     41 import java.io.InputStreamReader;
     42 import java.io.OutputStream;
     43 import java.text.SimpleDateFormat;
     44 import java.util.ArrayList;
     45 import java.util.Collection;
     46 import java.util.Collections;
     47 import java.util.Date;
     48 import java.util.List;
     49 import java.util.Locale;
     50 
     51 
     52 /**
     53  * Runs the am instrument command
     54  *
     55  * Test Result Code:
     56  * 1 - Test running
     57  * 0 - Test passed
     58  * -2 - assertion failure
     59  * -1 - other exceptions
     60  *
     61  * Session Result Code:
     62  * -1: Success
     63  * other: Failure
     64  */
     65 public class Instrument {
     66     private static final String TAG = "am";
     67 
     68     public static final String DEFAULT_LOG_DIR = "instrument-logs";
     69 
     70     private static final int STATUS_TEST_PASSED = 0;
     71     private static final int STATUS_TEST_STARTED = 1;
     72     private static final int STATUS_TEST_FAILED_ASSERTION = -1;
     73     private static final int STATUS_TEST_FAILED_OTHER = -2;
     74 
     75     private final IActivityManager mAm;
     76     private final IPackageManager mPm;
     77     private final IWindowManager mWm;
     78 
     79     // Command line arguments
     80     public String profileFile = null;
     81     public boolean wait = false;
     82     public boolean rawMode = false;
     83     boolean protoStd = false;  // write proto to stdout
     84     boolean protoFile = false;  // write proto to a file
     85     String logPath = null;
     86     public boolean noWindowAnimation = false;
     87     public boolean disableHiddenApiChecks = false;
     88     public boolean disableIsolatedStorage = false;
     89     public String abi = null;
     90     public int userId = UserHandle.USER_CURRENT;
     91     public Bundle args = new Bundle();
     92     // Required
     93     public String componentNameArg;
     94 
     95     /**
     96      * Construct the instrument command runner.
     97      */
     98     public Instrument(IActivityManager am, IPackageManager pm) {
     99         mAm = am;
    100         mPm = pm;
    101         mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
    102     }
    103 
    104     /**
    105      * Base class for status reporting.
    106      *
    107      * All the methods on this interface are called within the synchronized block
    108      * of the InstrumentationWatcher, so calls are in order.  However, that means
    109      * you must be careful not to do blocking operations because you don't know
    110      * exactly the locking dependencies.
    111      */
    112     private interface StatusReporter {
    113         /**
    114          * Status update for tests.
    115          */
    116         public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
    117                 Bundle results);
    118 
    119         /**
    120          * The tests finished.
    121          */
    122         public void onInstrumentationFinishedLocked(ComponentName name, int resultCode,
    123                 Bundle results);
    124 
    125         /**
    126          * @param errorText a description of the error
    127          * @param commandError True if the error is related to the commandline, as opposed
    128          *      to a test failing.
    129          */
    130         public void onError(String errorText, boolean commandError);
    131     }
    132 
    133     private static Collection<String> sorted(Collection<String> list) {
    134         final ArrayList<String> copy = new ArrayList<>(list);
    135         Collections.sort(copy);
    136         return copy;
    137     }
    138 
    139     /**
    140      * Printer for the 'classic' text based status reporting.
    141      */
    142     private class TextStatusReporter implements StatusReporter {
    143         private boolean mRawMode;
    144 
    145         /**
    146          * Human-ish readable output.
    147          *
    148          * @param rawMode   In "raw mode" (true), all bundles are dumped.
    149          *                  In "pretty mode" (false), if a bundle includes
    150          *                  Instrumentation.REPORT_KEY_STREAMRESULT, just print that.
    151          */
    152         public TextStatusReporter(boolean rawMode) {
    153             mRawMode = rawMode;
    154         }
    155 
    156         @Override
    157         public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
    158                 Bundle results) {
    159             // pretty printer mode?
    160             String pretty = null;
    161             if (!mRawMode && results != null) {
    162                 pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
    163             }
    164             if (pretty != null) {
    165                 System.out.print(pretty);
    166             } else {
    167                 if (results != null) {
    168                     for (String key : sorted(results.keySet())) {
    169                         System.out.println(
    170                                 "INSTRUMENTATION_STATUS: " + key + "=" + results.get(key));
    171                     }
    172                 }
    173                 System.out.println("INSTRUMENTATION_STATUS_CODE: " + resultCode);
    174             }
    175         }
    176 
    177         @Override
    178         public void onInstrumentationFinishedLocked(ComponentName name, int resultCode,
    179                 Bundle results) {
    180             // pretty printer mode?
    181             String pretty = null;
    182             if (!mRawMode && results != null) {
    183                 pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
    184             }
    185             if (pretty != null) {
    186                 System.out.println(pretty);
    187             } else {
    188                 if (results != null) {
    189                     for (String key : sorted(results.keySet())) {
    190                         System.out.println(
    191                                 "INSTRUMENTATION_RESULT: " + key + "=" + results.get(key));
    192                     }
    193                 }
    194                 System.out.println("INSTRUMENTATION_CODE: " + resultCode);
    195             }
    196         }
    197 
    198         @Override
    199         public void onError(String errorText, boolean commandError) {
    200             if (mRawMode) {
    201                 System.out.println("onError: commandError=" + commandError + " message="
    202                         + errorText);
    203             }
    204             // The regular BaseCommand error printing will print the commandErrors.
    205             if (!commandError) {
    206                 System.out.println(errorText);
    207             }
    208         }
    209     }
    210 
    211     /**
    212      * Printer for the protobuf based status reporting.
    213      */
    214     private class ProtoStatusReporter implements StatusReporter {
    215 
    216         private File mLog;
    217 
    218         private long mTestStartMs;
    219 
    220         ProtoStatusReporter() {
    221             if (protoFile) {
    222                 if (logPath == null) {
    223                     File logDir = new File(Environment.getLegacyExternalStorageDirectory(),
    224                             DEFAULT_LOG_DIR);
    225                     if (!logDir.exists() && !logDir.mkdirs()) {
    226                         System.err.format("Unable to create log directory: %s\n",
    227                                 logDir.getAbsolutePath());
    228                         protoFile = false;
    229                         return;
    230                     }
    231                     SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd-hhmmss-SSS", Locale.US);
    232                     String fileName = String.format("log-%s.instrumentation_data_proto",
    233                             format.format(new Date()));
    234                     mLog = new File(logDir, fileName);
    235                 } else {
    236                     mLog = new File(Environment.getLegacyExternalStorageDirectory(), logPath);
    237                     File logDir = mLog.getParentFile();
    238                     if (!logDir.exists() && !logDir.mkdirs()) {
    239                         System.err.format("Unable to create log directory: %s\n",
    240                                 logDir.getAbsolutePath());
    241                         protoFile = false;
    242                         return;
    243                     }
    244                 }
    245                 if (mLog.exists()) mLog.delete();
    246             }
    247         }
    248 
    249         @Override
    250         public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
    251                 Bundle results) {
    252             final ProtoOutputStream proto = new ProtoOutputStream();
    253 
    254             final long testStatusToken = proto.start(InstrumentationData.Session.TEST_STATUS);
    255 
    256             proto.write(InstrumentationData.TestStatus.RESULT_CODE, resultCode);
    257             writeBundle(proto, InstrumentationData.TestStatus.RESULTS, results);
    258 
    259             if (resultCode == STATUS_TEST_STARTED) {
    260                 // Logcat -T takes wall clock time (!?)
    261                 mTestStartMs = System.currentTimeMillis();
    262             } else {
    263                 if (mTestStartMs > 0) {
    264                     proto.write(InstrumentationData.TestStatus.LOGCAT, readLogcat(mTestStartMs));
    265                 }
    266                 mTestStartMs = 0;
    267             }
    268 
    269             proto.end(testStatusToken);
    270 
    271             outputProto(proto);
    272         }
    273 
    274         @Override
    275         public void onInstrumentationFinishedLocked(ComponentName name, int resultCode,
    276                 Bundle results) {
    277             final ProtoOutputStream proto = new ProtoOutputStream();
    278 
    279             final long sessionStatusToken = proto.start(InstrumentationData.Session.SESSION_STATUS);
    280             proto.write(InstrumentationData.SessionStatus.STATUS_CODE,
    281                     InstrumentationData.SESSION_FINISHED);
    282             proto.write(InstrumentationData.SessionStatus.RESULT_CODE, resultCode);
    283             writeBundle(proto, InstrumentationData.SessionStatus.RESULTS, results);
    284             proto.end(sessionStatusToken);
    285 
    286             outputProto(proto);
    287         }
    288 
    289         @Override
    290         public void onError(String errorText, boolean commandError) {
    291             final ProtoOutputStream proto = new ProtoOutputStream();
    292 
    293             final long sessionStatusToken = proto.start(InstrumentationData.Session.SESSION_STATUS);
    294             proto.write(InstrumentationData.SessionStatus.STATUS_CODE,
    295                     InstrumentationData.SESSION_ABORTED);
    296             proto.write(InstrumentationData.SessionStatus.ERROR_TEXT, errorText);
    297             proto.end(sessionStatusToken);
    298 
    299             outputProto(proto);
    300         }
    301 
    302         private void writeBundle(ProtoOutputStream proto, long fieldId, Bundle bundle) {
    303             final long bundleToken = proto.start(fieldId);
    304 
    305             for (final String key: sorted(bundle.keySet())) {
    306                 final long entryToken = proto.startRepeatedObject(
    307                         InstrumentationData.ResultsBundle.ENTRIES);
    308 
    309                 proto.write(InstrumentationData.ResultsBundleEntry.KEY, key);
    310 
    311                 final Object val = bundle.get(key);
    312                 if (val instanceof String) {
    313                     proto.write(InstrumentationData.ResultsBundleEntry.VALUE_STRING,
    314                             (String)val);
    315                 } else if (val instanceof Byte) {
    316                     proto.write(InstrumentationData.ResultsBundleEntry.VALUE_INT,
    317                             ((Byte)val).intValue());
    318                 } else if (val instanceof Double) {
    319                     proto.write(InstrumentationData.ResultsBundleEntry.VALUE_DOUBLE, (double)val);
    320                 } else if (val instanceof Float) {
    321                     proto.write(InstrumentationData.ResultsBundleEntry.VALUE_FLOAT, (float)val);
    322                 } else if (val instanceof Integer) {
    323                     proto.write(InstrumentationData.ResultsBundleEntry.VALUE_INT, (int)val);
    324                 } else if (val instanceof Long) {
    325                     proto.write(InstrumentationData.ResultsBundleEntry.VALUE_LONG, (long)val);
    326                 } else if (val instanceof Short) {
    327                     proto.write(InstrumentationData.ResultsBundleEntry.VALUE_INT, (short)val);
    328                 } else if (val instanceof Bundle) {
    329                     writeBundle(proto, InstrumentationData.ResultsBundleEntry.VALUE_BUNDLE,
    330                             (Bundle)val);
    331                 } else if (val instanceof byte[]) {
    332                     proto.write(InstrumentationData.ResultsBundleEntry.VALUE_BYTES, (byte[])val);
    333                 }
    334 
    335                 proto.end(entryToken);
    336             }
    337 
    338             proto.end(bundleToken);
    339         }
    340 
    341         private void outputProto(ProtoOutputStream proto) {
    342             byte[] out = proto.getBytes();
    343             if (protoStd) {
    344                 try {
    345                     System.out.write(out);
    346                     System.out.flush();
    347                 } catch (IOException ex) {
    348                     System.err.println("Error writing finished response: ");
    349                     ex.printStackTrace(System.err);
    350                 }
    351             }
    352             if (protoFile) {
    353                 try (OutputStream os = new FileOutputStream(mLog, true)) {
    354                     os.write(proto.getBytes());
    355                     os.flush();
    356                 } catch (IOException ex) {
    357                     System.err.format("Cannot write to %s:\n", mLog.getAbsolutePath());
    358                     ex.printStackTrace();
    359                 }
    360             }
    361         }
    362     }
    363 
    364 
    365     /**
    366      * Callbacks from the remote instrumentation instance.
    367      */
    368     private class InstrumentationWatcher extends IInstrumentationWatcher.Stub {
    369         private final StatusReporter mReporter;
    370 
    371         private boolean mFinished = false;
    372 
    373         public InstrumentationWatcher(StatusReporter reporter) {
    374             mReporter = reporter;
    375         }
    376 
    377         @Override
    378         public void instrumentationStatus(ComponentName name, int resultCode, Bundle results) {
    379             synchronized (this) {
    380                 mReporter.onInstrumentationStatusLocked(name, resultCode, results);
    381                 notifyAll();
    382             }
    383         }
    384 
    385         @Override
    386         public void instrumentationFinished(ComponentName name, int resultCode, Bundle results) {
    387             synchronized (this) {
    388                 mReporter.onInstrumentationFinishedLocked(name, resultCode, results);
    389                 mFinished = true;
    390                 notifyAll();
    391             }
    392         }
    393 
    394         public boolean waitForFinish() {
    395             synchronized (this) {
    396                 while (!mFinished) {
    397                     try {
    398                         if (!mAm.asBinder().pingBinder()) {
    399                             return false;
    400                         }
    401                         wait(1000);
    402                     } catch (InterruptedException e) {
    403                         throw new IllegalStateException(e);
    404                     }
    405                 }
    406             }
    407             return true;
    408         }
    409     }
    410 
    411     /**
    412      * Figure out which component they really meant.
    413      */
    414     private ComponentName parseComponentName(String cnArg) throws Exception {
    415         if (cnArg.contains("/")) {
    416             ComponentName cn = ComponentName.unflattenFromString(cnArg);
    417             if (cn == null) throw new IllegalArgumentException("Bad component name: " + cnArg);
    418             return cn;
    419         } else {
    420             List<InstrumentationInfo> infos = mPm.queryInstrumentation(null, 0).getList();
    421 
    422             final int numInfos = infos == null ? 0: infos.size();
    423             ArrayList<ComponentName> cns = new ArrayList<>();
    424             for (int i = 0; i < numInfos; i++) {
    425                 InstrumentationInfo info = infos.get(i);
    426 
    427                 ComponentName c = new ComponentName(info.packageName, info.name);
    428                 if (cnArg.equals(info.packageName)) {
    429                     cns.add(c);
    430                 }
    431             }
    432 
    433             if (cns.size() == 0) {
    434                 throw new IllegalArgumentException("No instrumentation found for: " + cnArg);
    435             } else if (cns.size() == 1) {
    436                 return cns.get(0);
    437             } else {
    438                 StringBuilder cnsStr = new StringBuilder();
    439                 final int numCns = cns.size();
    440                 for (int i = 0; i < numCns; i++) {
    441                     cnsStr.append(cns.get(i).flattenToString());
    442                     cnsStr.append(", ");
    443                 }
    444 
    445                 // Remove last ", "
    446                 cnsStr.setLength(cnsStr.length() - 2);
    447 
    448                 throw new IllegalArgumentException("Found multiple instrumentations: "
    449                         + cnsStr.toString());
    450             }
    451         }
    452     }
    453 
    454     /**
    455      * Run the instrumentation.
    456      */
    457     public void run() throws Exception {
    458         StatusReporter reporter = null;
    459         float[] oldAnims = null;
    460 
    461         try {
    462             // Choose which output we will do.
    463             if (protoFile || protoStd) {
    464                 reporter = new ProtoStatusReporter();
    465             } else if (wait) {
    466                 reporter = new TextStatusReporter(rawMode);
    467             }
    468 
    469             // Choose whether we have to wait for the results.
    470             InstrumentationWatcher watcher = null;
    471             UiAutomationConnection connection = null;
    472             if (reporter != null) {
    473                 watcher = new InstrumentationWatcher(reporter);
    474                 connection = new UiAutomationConnection();
    475             }
    476 
    477             // Set the window animation if necessary
    478             if (noWindowAnimation) {
    479                 oldAnims = mWm.getAnimationScales();
    480                 mWm.setAnimationScale(0, 0.0f);
    481                 mWm.setAnimationScale(1, 0.0f);
    482                 mWm.setAnimationScale(2, 0.0f);
    483             }
    484 
    485             // Figure out which component we are trying to do.
    486             final ComponentName cn = parseComponentName(componentNameArg);
    487 
    488             // Choose an ABI if necessary
    489             if (abi != null) {
    490                 final String[] supportedAbis = Build.SUPPORTED_ABIS;
    491                 boolean matched = false;
    492                 for (String supportedAbi : supportedAbis) {
    493                     if (supportedAbi.equals(abi)) {
    494                         matched = true;
    495                         break;
    496                     }
    497                 }
    498                 if (!matched) {
    499                     throw new AndroidException(
    500                             "INSTRUMENTATION_FAILED: Unsupported instruction set " + abi);
    501                 }
    502             }
    503 
    504             // Start the instrumentation
    505             int flags = 0;
    506             if (disableHiddenApiChecks) {
    507                 flags |= INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS;
    508             }
    509             if (disableIsolatedStorage) {
    510                 flags |= INSTR_FLAG_MOUNT_EXTERNAL_STORAGE_FULL;
    511             }
    512             if (!mAm.startInstrumentation(cn, profileFile, flags, args, watcher, connection, userId,
    513                         abi)) {
    514                 throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString());
    515             }
    516 
    517             // If we have been requested to wait, do so until the instrumentation is finished.
    518             if (watcher != null) {
    519                 if (!watcher.waitForFinish()) {
    520                     reporter.onError("INSTRUMENTATION_ABORTED: System has crashed.", false);
    521                     return;
    522                 }
    523             }
    524         } catch (Exception ex) {
    525             // Report failures
    526             if (reporter != null) {
    527                 reporter.onError(ex.getMessage(), true);
    528             }
    529 
    530             // And re-throw the exception
    531             throw ex;
    532         } finally {
    533             // Clean up
    534             if (oldAnims != null) {
    535                 mWm.setAnimationScales(oldAnims);
    536             }
    537         }
    538     }
    539 
    540     private static String readLogcat(long startTimeMs) {
    541         try {
    542             // Figure out the timestamp arg for logcat.
    543             final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    544             final String timestamp = format.format(new Date(startTimeMs));
    545 
    546             // Start the process
    547             final Process process = new ProcessBuilder()
    548                     .command("logcat", "-d", "-v threadtime,uid", "-T", timestamp)
    549                     .start();
    550 
    551             // Nothing to write. Don't let the command accidentally block.
    552             process.getOutputStream().close();
    553 
    554             // Read the output
    555             final StringBuilder str = new StringBuilder();
    556             final InputStreamReader reader = new InputStreamReader(process.getInputStream());
    557             char[] buffer = new char[4096];
    558             int amt;
    559             while ((amt = reader.read(buffer, 0, buffer.length)) >= 0) {
    560                 if (amt > 0) {
    561                     str.append(buffer, 0, amt);
    562                 }
    563             }
    564 
    565             try {
    566                 process.waitFor();
    567             } catch (InterruptedException ex) {
    568                 // We already have the text, drop the exception.
    569             }
    570 
    571             return str.toString();
    572 
    573         } catch (IOException ex) {
    574             return "Error reading logcat command:\n" + ex.toString();
    575         }
    576     }
    577 }
    578 
    579