• 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_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      */
Instrument(IActivityManager am, IPackageManager pm)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          */
onInstrumentationStatusLocked(ComponentName name, int resultCode, Bundle results)116         public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
117                 Bundle results);
118 
119         /**
120          * The tests finished.
121          */
onInstrumentationFinishedLocked(ComponentName name, int resultCode, Bundle results)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          */
onError(String errorText, boolean commandError)130         public void onError(String errorText, boolean commandError);
131     }
132 
sorted(Collection<String> list)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          */
TextStatusReporter(boolean rawMode)152         public TextStatusReporter(boolean rawMode) {
153             mRawMode = rawMode;
154         }
155 
156         @Override
onInstrumentationStatusLocked(ComponentName name, int resultCode, Bundle results)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
onInstrumentationFinishedLocked(ComponentName name, int resultCode, Bundle results)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
onError(String errorText, boolean commandError)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 
ProtoStatusReporter()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
onInstrumentationStatusLocked(ComponentName name, int resultCode, Bundle results)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
onInstrumentationFinishedLocked(ComponentName name, int resultCode, Bundle results)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
onError(String errorText, boolean commandError)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 
writeBundle(ProtoOutputStream proto, long fieldId, Bundle bundle)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 
outputProto(ProtoOutputStream proto)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 
InstrumentationWatcher(StatusReporter reporter)373         public InstrumentationWatcher(StatusReporter reporter) {
374             mReporter = reporter;
375         }
376 
377         @Override
instrumentationStatus(ComponentName name, int resultCode, Bundle results)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
instrumentationFinished(ComponentName name, int resultCode, Bundle results)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 
waitForFinish()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      */
parseComponentName(String cnArg)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      */
run()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 
readLogcat(long startTimeMs)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