am instrument gets protobuf

Refactor the am instrument command and add a version that
outputs protobuf in addition to the old one that prints
loosely formatted text.

Change-Id: I34079d8af2b7b6c6c59837d54719806109ba286c
Test: bit tool
This commit is contained in:
Joe Onorato
2016-10-20 11:27:16 -07:00
parent 3a5eb29720
commit f0c719821d
4 changed files with 547 additions and 198 deletions

View File

@@ -3,8 +3,11 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
$(call all-proto-files-under, proto)
LOCAL_MODULE := am
LOCAL_PROTOC_OPTIMIZE_TYPE := stream
include $(BUILD_JAVA_LIBRARY)
include $(CLEAR_VARS)
@@ -13,3 +16,14 @@ LOCAL_SRC_FILES := am
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_MODULE_TAGS := optional
include $(BUILD_PREBUILT)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
$(call all-proto-files-under, proto)
LOCAL_MODULE := libinstrumentation
LOCAL_PROTOC_OPTIMIZE_TYPE := full
LOCAL_EXPORT_C_INCLUDE_DIRS := \
$(call intermediates-dir-for,STATIC_LIBRARIES,libinstrumentation,HOST,,,)/proto/$(LOCAL_PATH)/proto
include $(BUILD_HOST_STATIC_LIBRARY)

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
syntax = "proto2";
package android.am;
option java_package = "com.android.commands.am";
message ResultsBundleEntry {
optional string key = 1;
optional string value_string = 2;
optional sint32 value_int = 3;
optional float value_float = 4;
optional double value_double = 5;
optional sint64 value_long = 6;
optional ResultsBundle value_bundle = 7;
}
message ResultsBundle {
repeated ResultsBundleEntry entries = 1;
}
message TestStatus {
optional sint32 result_code = 3;
optional ResultsBundle results = 4;
}
enum SessionStatusCode {
/**
* The command ran successfully. This does not imply that the tests passed.
*/
SESSION_FINISHED = 0;
/**
* There was an unrecoverable error running the tests.
*/
SESSION_ABORTED = 1;
}
message SessionStatus {
optional SessionStatusCode status_code = 1;
optional string error_text = 2;
optional sint32 result_code = 3;
optional ResultsBundle results = 4;
}
message Session {
repeated TestStatus test_status = 1;
optional SessionStatus session_status = 2;
}

View File

@@ -1,20 +1,18 @@
/*
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.commands.am;
@@ -235,6 +233,7 @@ public class Am extends BaseCommand {
" -e <NAME> <VALUE>: set argument <NAME> to <VALUE>. For test runners a\n" +
" common form is [-e <testrunner_flag> <value>[,<value>...]].\n" +
" -p <FILE>: write profiling data to <FILE>\n" +
" -m: Write output as protobuf (machine readable)\n" +
" -w: wait for instrumentation to finish before returning. Required for\n" +
" test runners.\n" +
" --user <USER_ID> | current: Specify user instrumentation runs in;\n" +
@@ -543,208 +542,43 @@ public class Am extends BaseCommand {
receiver.waitForFinish();
}
private void runInstrument() throws Exception {
String profileFile = null;
boolean wait = false;
boolean rawMode = false;
boolean no_window_animation = false;
int userId = UserHandle.USER_CURRENT;
Bundle args = new Bundle();
String argKey = null, argValue = null;
IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
String abi = null;
public void runInstrument() throws Exception {
Instrument instrument = new Instrument(mAm, mPm);
String opt;
while ((opt=nextOption()) != null) {
if (opt.equals("-p")) {
profileFile = nextArgRequired();
instrument.profileFile = nextArgRequired();
} else if (opt.equals("-w")) {
wait = true;
instrument.wait = true;
} else if (opt.equals("-r")) {
rawMode = true;
instrument.rawMode = true;
} else if (opt.equals("-m")) {
instrument.proto = true;
} else if (opt.equals("-e")) {
argKey = nextArgRequired();
argValue = nextArgRequired();
args.putString(argKey, argValue);
final String argKey = nextArgRequired();
final String argValue = nextArgRequired();
instrument.args.putString(argKey, argValue);
} else if (opt.equals("--no_window_animation")
|| opt.equals("--no-window-animation")) {
no_window_animation = true;
instrument.noWindowAnimation = true;
} else if (opt.equals("--user")) {
userId = parseUserArg(nextArgRequired());
instrument.userId = parseUserArg(nextArgRequired());
} else if (opt.equals("--abi")) {
abi = nextArgRequired();
instrument.abi = nextArgRequired();
} else {
System.err.println("Error: Unknown option: " + opt);
return;
}
}
if (userId == UserHandle.USER_ALL) {
if (instrument.userId == UserHandle.USER_ALL) {
System.err.println("Error: Can't start instrumentation with user 'all'");
return;
}
String cnArg = nextArgRequired();
instrument.componentNameArg = nextArgRequired();
ComponentName cn;
if (cnArg.contains("/")) {
cn = ComponentName.unflattenFromString(cnArg);
if (cn == null) throw new IllegalArgumentException("Bad component name: " + cnArg);
} else {
List<InstrumentationInfo> infos = mPm.queryInstrumentation(null, 0).getList();
final int numInfos = infos == null ? 0: infos.size();
List<ComponentName> cns = new ArrayList<>();
for (int i = 0; i < numInfos; i++) {
InstrumentationInfo info = infos.get(i);
ComponentName c = new ComponentName(info.packageName, info.name);
if (cnArg.equals(info.packageName)) {
cns.add(c);
}
}
if (cns.size() == 0) {
throw new IllegalArgumentException("No instrumentation found for: " + cnArg);
} else if (cns.size() == 1) {
cn = cns.get(0);
} else {
StringBuilder cnsStr = new StringBuilder();
final int numCns = cns.size();
for (int i = 0; i < numCns; i++) {
cnsStr.append(cns.get(i).flattenToString());
cnsStr.append(", ");
}
// Remove last ", "
cnsStr.setLength(cnsStr.length() - 2);
throw new IllegalArgumentException("Found multiple instrumentations: "
+ cnsStr.toString());
}
}
InstrumentationWatcher watcher = null;
UiAutomationConnection connection = null;
if (wait) {
watcher = new InstrumentationWatcher();
watcher.setRawOutput(rawMode);
connection = new UiAutomationConnection();
}
float[] oldAnims = null;
if (no_window_animation) {
oldAnims = wm.getAnimationScales();
wm.setAnimationScale(0, 0.0f);
wm.setAnimationScale(1, 0.0f);
}
if (abi != null) {
final String[] supportedAbis = Build.SUPPORTED_ABIS;
boolean matched = false;
for (String supportedAbi : supportedAbis) {
if (supportedAbi.equals(abi)) {
matched = true;
break;
}
}
if (!matched) {
throw new AndroidException(
"INSTRUMENTATION_FAILED: Unsupported instruction set " + abi);
}
}
if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId, abi)) {
throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString());
}
if (watcher != null) {
if (!watcher.waitForFinish()) {
System.out.println("INSTRUMENTATION_ABORTED: System has crashed.");
}
}
if (oldAnims != null) {
wm.setAnimationScales(oldAnims);
}
}
private class InstrumentationWatcher extends IInstrumentationWatcher.Stub {
private boolean mFinished = false;
private boolean mRawMode = false;
/**
* Set or reset "raw mode". In "raw mode", all bundles are dumped. In "pretty mode",
* if a bundle includes Instrumentation.REPORT_KEY_STREAMRESULT, just print that.
* @param rawMode true for raw mode, false for pretty mode.
*/
public void setRawOutput(boolean rawMode) {
mRawMode = rawMode;
}
@Override
public void instrumentationStatus(ComponentName name, int resultCode, Bundle results) {
synchronized (this) {
// pretty printer mode?
String pretty = null;
if (!mRawMode && results != null) {
pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
}
if (pretty != null) {
System.out.print(pretty);
} else {
if (results != null) {
for (String key : results.keySet()) {
System.out.println(
"INSTRUMENTATION_STATUS: " + key + "=" + results.get(key));
}
}
System.out.println("INSTRUMENTATION_STATUS_CODE: " + resultCode);
}
notifyAll();
}
}
@Override
public void instrumentationFinished(ComponentName name, int resultCode,
Bundle results) {
synchronized (this) {
// pretty printer mode?
String pretty = null;
if (!mRawMode && results != null) {
pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
}
if (pretty != null) {
System.out.println(pretty);
} else {
if (results != null) {
for (String key : results.keySet()) {
System.out.println(
"INSTRUMENTATION_RESULT: " + key + "=" + results.get(key));
}
}
System.out.println("INSTRUMENTATION_CODE: " + resultCode);
}
mFinished = true;
notifyAll();
}
}
public boolean waitForFinish() {
synchronized (this) {
while (!mFinished) {
try {
if (!mAm.asBinder().pingBinder()) {
return false;
}
wait(1000);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
}
return true;
}
instrument.run();
}
}

View File

@@ -0,0 +1,435 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.commands.am;
import android.app.IActivityManager;
import android.app.IInstrumentationWatcher;
import android.app.Instrumentation;
import android.app.UiAutomationConnection;
import android.content.ComponentName;
import android.content.pm.IPackageManager;
import android.content.pm.InstrumentationInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.AndroidException;
import android.util.proto.ProtoOutputStream;
import android.view.IWindowManager;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Runs the am instrument command
*/
public class Instrument {
private final IActivityManager mAm;
private final IPackageManager mPm;
private final IWindowManager mWm;
// Command line arguments
public String profileFile = null;
public boolean wait = false;
public boolean rawMode = false;
public boolean proto = false;
public boolean noWindowAnimation = false;
public String abi = null;
public int userId = UserHandle.USER_CURRENT;
public Bundle args = new Bundle();
// Required
public String componentNameArg;
/**
* Construct the instrument command runner.
*/
public Instrument(IActivityManager am, IPackageManager pm) {
mAm = am;
mPm = pm;
mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
}
/**
* Base class for status reporting.
*
* All the methods on this interface are called within the synchronized block
* of the InstrumentationWatcher, so calls are in order. However, that means
* you must be careful not to do blocking operations because you don't know
* exactly the locking dependencies.
*/
private interface StatusReporter {
/**
* Status update for tests.
*/
public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
Bundle results);
/**
* The tests finished.
*/
public void onInstrumentationFinishedLocked(ComponentName name, int resultCode,
Bundle results);
/**
* @param errorText a description of the error
* @param commandError True if the error is related to the commandline, as opposed
* to a test failing.
*/
public void onError(String errorText, boolean commandError);
}
/**
* Printer for the 'classic' text based status reporting.
*/
private class TextStatusReporter implements StatusReporter {
private boolean mRawMode;
/**
* Human-ish readable output.
*
* @param rawMode In "raw mode" (true), all bundles are dumped.
* In "pretty mode" (false), if a bundle includes
* Instrumentation.REPORT_KEY_STREAMRESULT, just print that.
*/
public TextStatusReporter(boolean rawMode) {
mRawMode = rawMode;
}
@Override
public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
Bundle results) {
// pretty printer mode?
String pretty = null;
if (!mRawMode && results != null) {
pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
}
if (pretty != null) {
System.out.print(pretty);
} else {
if (results != null) {
for (String key : results.keySet()) {
System.out.println(
"INSTRUMENTATION_STATUS: " + key + "=" + results.get(key));
}
}
System.out.println("INSTRUMENTATION_STATUS_CODE: " + resultCode);
}
}
@Override
public void onInstrumentationFinishedLocked(ComponentName name, int resultCode,
Bundle results) {
// pretty printer mode?
String pretty = null;
if (!mRawMode && results != null) {
pretty = results.getString(Instrumentation.REPORT_KEY_STREAMRESULT);
}
if (pretty != null) {
System.out.println(pretty);
} else {
if (results != null) {
for (String key : results.keySet()) {
System.out.println(
"INSTRUMENTATION_RESULT: " + key + "=" + results.get(key));
}
}
System.out.println("INSTRUMENTATION_CODE: " + resultCode);
}
}
@Override
public void onError(String errorText, boolean commandError) {
// The regular BaseCommand error printing will print the commandErrors.
if (!commandError) {
System.out.println(errorText);
}
}
}
/**
* Printer for the protobuf based status reporting.
*/
private class ProtoStatusReporter implements StatusReporter {
@Override
public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
Bundle results) {
final ProtoOutputStream proto = new ProtoOutputStream();
final long token = proto.startRepeatedObject(InstrumentationData.Session.TEST_STATUS);
proto.writeSInt32(InstrumentationData.TestStatus.RESULT_CODE, resultCode);
writeBundle(proto, InstrumentationData.TestStatus.RESULTS, results);
proto.endRepeatedObject(token);
writeProtoToStdout(proto);
}
@Override
public void onInstrumentationFinishedLocked(ComponentName name, int resultCode,
Bundle results) {
final ProtoOutputStream proto = new ProtoOutputStream();
final long token = proto.startObject(InstrumentationData.Session.SESSION_STATUS);
proto.writeEnum(InstrumentationData.SessionStatus.STATUS_CODE,
InstrumentationData.SESSION_FINISHED);
proto.writeSInt32(InstrumentationData.SessionStatus.RESULT_CODE, resultCode);
writeBundle(proto, InstrumentationData.SessionStatus.RESULTS, results);
proto.endObject(token);
writeProtoToStdout(proto);
}
@Override
public void onError(String errorText, boolean commandError) {
final ProtoOutputStream proto = new ProtoOutputStream();
final long token = proto.startObject(InstrumentationData.Session.SESSION_STATUS);
proto.writeEnum(InstrumentationData.SessionStatus.STATUS_CODE,
InstrumentationData.SESSION_ABORTED);
proto.writeString(InstrumentationData.SessionStatus.ERROR_TEXT, errorText);
proto.endObject(token);
writeProtoToStdout(proto);
}
private void writeBundle(ProtoOutputStream proto, long fieldId, Bundle bundle) {
final long bundleToken = proto.startObject(fieldId);
for (final String key: bundle.keySet()) {
final long entryToken = proto.startRepeatedObject(
InstrumentationData.ResultsBundle.ENTRIES);
proto.writeString(InstrumentationData.ResultsBundleEntry.KEY, key);
final Object val = bundle.get(key);
if (val instanceof String) {
proto.writeString(InstrumentationData.ResultsBundleEntry.VALUE_STRING,
(String)val);
} else if (val instanceof Byte) {
proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
((Byte)val).intValue());
} else if (val instanceof Double) {
proto.writeDouble(InstrumentationData.ResultsBundleEntry.VALUE_DOUBLE,
((Double)val).doubleValue());
} else if (val instanceof Float) {
proto.writeFloat(InstrumentationData.ResultsBundleEntry.VALUE_FLOAT,
((Float)val).floatValue());
} else if (val instanceof Integer) {
proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
((Integer)val).intValue());
} else if (val instanceof Long) {
proto.writeSInt64(InstrumentationData.ResultsBundleEntry.VALUE_LONG,
((Long)val).longValue());
} else if (val instanceof Short) {
proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
((Short)val).intValue());
} else if (val instanceof Bundle) {
writeBundle(proto, InstrumentationData.ResultsBundleEntry.VALUE_BUNDLE,
(Bundle)val);
}
proto.endRepeatedObject(entryToken);
}
proto.endObject(bundleToken);
}
private void writeProtoToStdout(ProtoOutputStream proto) {
try {
System.out.write(proto.getBytes());
System.out.flush();
} catch (IOException ex) {
System.err.println("Error writing finished response: ");
ex.printStackTrace(System.err);
}
}
}
/**
* Callbacks from the remote instrumentation instance.
*/
private class InstrumentationWatcher extends IInstrumentationWatcher.Stub {
private final StatusReporter mReporter;
private boolean mFinished = false;
public InstrumentationWatcher(StatusReporter reporter) {
mReporter = reporter;
}
@Override
public void instrumentationStatus(ComponentName name, int resultCode, Bundle results) {
synchronized (this) {
mReporter.onInstrumentationStatusLocked(name, resultCode, results);
notifyAll();
}
}
@Override
public void instrumentationFinished(ComponentName name, int resultCode, Bundle results) {
synchronized (this) {
mReporter.onInstrumentationFinishedLocked(name, resultCode, results);
mFinished = true;
notifyAll();
}
}
public boolean waitForFinish() {
synchronized (this) {
while (!mFinished) {
try {
if (!mAm.asBinder().pingBinder()) {
return false;
}
wait(1000);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
}
return true;
}
}
/**
* Figure out which component they really meant.
*/
private ComponentName parseComponentName(String cnArg) throws Exception {
if (cnArg.contains("/")) {
ComponentName cn = ComponentName.unflattenFromString(cnArg);
if (cn == null) throw new IllegalArgumentException("Bad component name: " + cnArg);
return cn;
} else {
List<InstrumentationInfo> infos = mPm.queryInstrumentation(null, 0).getList();
final int numInfos = infos == null ? 0: infos.size();
ArrayList<ComponentName> cns = new ArrayList<>();
for (int i = 0; i < numInfos; i++) {
InstrumentationInfo info = infos.get(i);
ComponentName c = new ComponentName(info.packageName, info.name);
if (cnArg.equals(info.packageName)) {
cns.add(c);
}
}
if (cns.size() == 0) {
throw new IllegalArgumentException("No instrumentation found for: " + cnArg);
} else if (cns.size() == 1) {
return cns.get(0);
} else {
StringBuilder cnsStr = new StringBuilder();
final int numCns = cns.size();
for (int i = 0; i < numCns; i++) {
cnsStr.append(cns.get(i).flattenToString());
cnsStr.append(", ");
}
// Remove last ", "
cnsStr.setLength(cnsStr.length() - 2);
throw new IllegalArgumentException("Found multiple instrumentations: "
+ cnsStr.toString());
}
}
}
/**
* Run the instrumentation.
*/
public void run() throws Exception {
StatusReporter reporter = null;
float[] oldAnims = null;
try {
// Choose which output we will do.
if (proto) {
reporter = new ProtoStatusReporter();
} else if (wait) {
reporter = new TextStatusReporter(rawMode);
}
// Choose whether we have to wait for the results.
InstrumentationWatcher watcher = null;
UiAutomationConnection connection = null;
if (reporter != null) {
watcher = new InstrumentationWatcher(reporter);
connection = new UiAutomationConnection();
}
// Set the window animation if necessary
if (noWindowAnimation) {
oldAnims = mWm.getAnimationScales();
mWm.setAnimationScale(0, 0.0f);
mWm.setAnimationScale(1, 0.0f);
}
// Figure out which component we are tring to do.
final ComponentName cn = parseComponentName(componentNameArg);
// Choose an ABI if necessary
if (abi != null) {
final String[] supportedAbis = Build.SUPPORTED_ABIS;
boolean matched = false;
for (String supportedAbi : supportedAbis) {
if (supportedAbi.equals(abi)) {
matched = true;
break;
}
}
if (!matched) {
throw new AndroidException(
"INSTRUMENTATION_FAILED: Unsupported instruction set " + abi);
}
}
// Start the instrumentation
if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId,
abi)) {
throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString());
}
// If we have been requested to wait, do so until the instrumentation is finished.
if (watcher != null) {
if (!watcher.waitForFinish()) {
reporter.onError("INSTRUMENTATION_ABORTED: System has crashed.", false);
return;
}
}
} catch (Exception ex) {
// Report failures
if (reporter != null) {
reporter.onError(ex.getMessage(), true);
}
// And re-throw the exception
throw ex;
} finally {
// Clean up
if (oldAnims != null) {
mWm.setAnimationScales(oldAnims);
}
}
}
}