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