diff options
| author | 2016-10-21 17:55:42 +0000 | |
|---|---|---|
| committer | 2016-10-21 17:55:46 +0000 | |
| commit | 2f99e60d71684d0f0296ce252a0683dc02cd93bd (patch) | |
| tree | bb0621fe13b3387f11f339aa2edb88e54599ca6a | |
| parent | 10842783e1955c4a0aa2e640cd4531b17a0b4b1d (diff) | |
| parent | 0578cbc6c527bc09a38a0fcd8b9642c25c8ea023 (diff) | |
Merge changes from topic 'am_instrument'
* changes:
Add a new build, install, test development (bit) tool
am instrument gets protobuf
Fix bad type codes in streaming proto compiler
| -rw-r--r-- | cmds/am/Android.mk | 16 | ||||
| -rw-r--r-- | cmds/am/proto/instrumentation_data.proto | 66 | ||||
| -rw-r--r-- | cmds/am/src/com/android/commands/am/Am.java | 228 | ||||
| -rw-r--r-- | cmds/am/src/com/android/commands/am/Instrument.java | 435 | ||||
| -rw-r--r-- | tools/bit/Android.mk | 43 | ||||
| -rw-r--r-- | tools/bit/aapt.cpp | 270 | ||||
| -rw-r--r-- | tools/bit/aapt.h | 39 | ||||
| -rw-r--r-- | tools/bit/adb.cpp | 463 | ||||
| -rw-r--r-- | tools/bit/adb.h | 47 | ||||
| -rw-r--r-- | tools/bit/command.cpp | 183 | ||||
| -rw-r--r-- | tools/bit/command.h | 58 | ||||
| -rw-r--r-- | tools/bit/main.cpp | 981 | ||||
| -rw-r--r-- | tools/bit/make.cpp | 210 | ||||
| -rw-r--r-- | tools/bit/make.h | 50 | ||||
| -rw-r--r-- | tools/bit/print.cpp | 155 | ||||
| -rw-r--r-- | tools/bit/print.h | 39 | ||||
| -rw-r--r-- | tools/bit/util.cpp | 254 | ||||
| -rw-r--r-- | tools/bit/util.h | 83 | ||||
| -rw-r--r-- | tools/streaming_proto/main.cpp | 17 |
19 files changed, 3439 insertions, 198 deletions
diff --git a/cmds/am/Android.mk b/cmds/am/Android.mk index f8350dce7f50..5586dd4e5b18 100644 --- a/cmds/am/Android.mk +++ b/cmds/am/Android.mk @@ -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) + diff --git a/cmds/am/proto/instrumentation_data.proto b/cmds/am/proto/instrumentation_data.proto new file mode 100644 index 000000000000..12a18a2a035f --- /dev/null +++ b/cmds/am/proto/instrumentation_data.proto @@ -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; +} + + diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index e197bfcb15f6..91a45496a5b5 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -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(); - - 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(); - } - } + instrument.componentNameArg = nextArgRequired(); - @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(); } } diff --git a/cmds/am/src/com/android/commands/am/Instrument.java b/cmds/am/src/com/android/commands/am/Instrument.java new file mode 100644 index 000000000000..8eefd256a69d --- /dev/null +++ b/cmds/am/src/com/android/commands/am/Instrument.java @@ -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); + } + } + } +} + diff --git a/tools/bit/Android.mk b/tools/bit/Android.mk new file mode 100644 index 000000000000..7f691a07b06a --- /dev/null +++ b/tools/bit/Android.mk @@ -0,0 +1,43 @@ +# +# Copyright (C) 2015 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. +# +LOCAL_PATH:= $(call my-dir) + +# ========================================================== +# Build the host executable: protoc-gen-javastream +# ========================================================== +include $(CLEAR_VARS) + +LOCAL_MODULE := bit + +LOCAL_SRC_FILES := \ + aapt.cpp \ + adb.cpp \ + command.cpp \ + main.cpp \ + make.cpp \ + print.cpp \ + util.cpp + +LOCAL_STATIC_LIBRARIES := \ + libexpat \ + libinstrumentation \ + libjsoncpp + +LOCAL_SHARED_LIBRARIES := \ + libprotobuf-cpp-full + +include $(BUILD_HOST_EXECUTABLE) + diff --git a/tools/bit/aapt.cpp b/tools/bit/aapt.cpp new file mode 100644 index 000000000000..961b47cdfecd --- /dev/null +++ b/tools/bit/aapt.cpp @@ -0,0 +1,270 @@ +/* + * 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. + */ + +#include "aapt.h" + +#include "command.h" +#include "print.h" +#include "util.h" + +#include <regex> + +const regex NS_REGEX("( *)N: ([^=]+)=(.*)"); +const regex ELEMENT_REGEX("( *)E: ([^ ]+) \\(line=(\\d+)\\)"); +const regex ATTR_REGEX("( *)A: ([^\\(=]+)[^=]*=\"([^\"]+)\".*"); + +const string ANDROID_NS("http://schemas.android.com/apk/res/android"); + +bool +Apk::HasActivity(const string& className) +{ + string fullClassName = full_class_name(package, className); + const size_t N = activities.size(); + for (size_t i=0; i<N; i++) { + if (activities[i] == fullClassName) { + return true; + } + } + return false; +} + +struct Attribute { + string ns; + string name; + string value; +}; + +struct Element { + Element* parent; + string ns; + string name; + int lineno; + vector<Attribute> attributes; + vector<Element*> children; + + /** + * Indentation in the xmltree dump. Might not be equal to the distance + * from the root because namespace rows (scopes) have their own indentation. + */ + int depth; + + Element(); + ~Element(); + + string GetAttr(const string& ns, const string& name) const; + void FindElements(const string& ns, const string& name, vector<Element*>* result, bool recurse); + +}; + +Element::Element() +{ +} + +Element::~Element() +{ + const size_t N = children.size(); + for (size_t i=0; i<N; i++) { + delete children[i]; + } +} + +string +Element::GetAttr(const string& ns, const string& name) const +{ + const size_t N = attributes.size(); + for (size_t i=0; i<N; i++) { + const Attribute& attr = attributes[i]; + if (attr.ns == ns && attr.name == name) { + return attr.value; + } + } + return string(); +} + +void +Element::FindElements(const string& ns, const string& name, vector<Element*>* result, bool recurse) +{ + const size_t N = children.size(); + for (size_t i=0; i<N; i++) { + Element* child = children[i]; + if (child->ns == ns && child->name == name) { + result->push_back(child); + } + if (recurse) { + child->FindElements(ns, name, result, recurse); + } + } +} + +struct Scope { + Scope* parent; + int depth; + map<string,string> namespaces; + + Scope(Scope* parent, int depth); +}; + +Scope::Scope(Scope* p, int d) + :parent(p), + depth(d) +{ + if (p != NULL) { + namespaces = p->namespaces; + } +} + + +string +full_class_name(const string& packageName, const string& className) +{ + if (className.length() == 0) { + return ""; + } + if (className[0] == '.') { + return packageName + className; + } + if (className.find('.') == string::npos) { + return packageName + "." + className; + } + return className; +} + +string +pretty_component_name(const string& packageName, const string& className) +{ + if (starts_with(packageName, className)) { + size_t pn = packageName.length(); + size_t cn = className.length(); + if (cn > pn && className[pn] == '.') { + return packageName + "/" + string(className, pn, string::npos); + } + } + return packageName + "/" + className; +} + +int +inspect_apk(Apk* apk, const string& filename) +{ + // Load the manifest xml + Command cmd("aapt"); + cmd.AddArg("dump"); + cmd.AddArg("xmltree"); + cmd.AddArg(filename); + cmd.AddArg("AndroidManifest.xml"); + + int err; + + string output = get_command_output(cmd, &err, false); + check_error(err); + + // Parse the manifest xml + Scope* scope = new Scope(NULL, -1); + Element* root = NULL; + Element* current = NULL; + vector<string> lines; + split_lines(&lines, output); + for (size_t i=0; i<lines.size(); i++) { + const string& line = lines[i]; + smatch match; + if (regex_match(line, match, NS_REGEX)) { + int depth = match[1].length() / 2; + while (depth < scope->depth) { + Scope* tmp = scope; + scope = scope->parent; + delete tmp; + } + scope = new Scope(scope, depth); + scope->namespaces[match[2]] = match[3]; + } else if (regex_match(line, match, ELEMENT_REGEX)) { + Element* element = new Element(); + + string str = match[2]; + size_t colon = str.find(':'); + if (colon == string::npos) { + element->name = str; + } else { + element->ns = scope->namespaces[string(str, 0, colon)]; + element->name.assign(str, colon+1, string::npos); + } + element->lineno = atoi(match[3].str().c_str()); + element->depth = match[1].length() / 2; + + if (root == NULL) { + current = element; + root = element; + } else { + while (element->depth <= current->depth && current->parent != NULL) { + current = current->parent; + } + element->parent = current; + current->children.push_back(element); + current = element; + } + } else if (regex_match(line, match, ATTR_REGEX)) { + if (current != NULL) { + Attribute attr; + string str = match[2]; + size_t colon = str.find(':'); + if (colon == string::npos) { + attr.name = str; + } else { + attr.ns = scope->namespaces[string(str, 0, colon)]; + attr.name.assign(str, colon+1, string::npos); + } + attr.value = match[3]; + current->attributes.push_back(attr); + } + } + } + while (scope != NULL) { + Scope* tmp = scope; + scope = scope->parent; + delete tmp; + } + + // Package name + apk->package = root->GetAttr("", "package"); + if (apk->package.size() == 0) { + print_error("%s:%d: Manifest root element doesn't contain a package attribute", + filename.c_str(), root->lineno); + delete root; + return 1; + } + + // Instrumentation runner + vector<Element*> instrumentation; + root->FindElements("", "instrumentation", &instrumentation, true); + if (instrumentation.size() > 0) { + // TODO: How could we deal with multiple instrumentation tags? + // We'll just pick the first one. + apk->runner = instrumentation[0]->GetAttr(ANDROID_NS, "name"); + } + + // Activities + vector<Element*> activities; + root->FindElements("", "activity", &activities, true); + for (size_t i=0; i<activities.size(); i++) { + string name = activities[i]->GetAttr(ANDROID_NS, "name"); + if (name.size() == 0) { + continue; + } + apk->activities.push_back(full_class_name(apk->package, name)); + } + + delete root; + return 0; +} + diff --git a/tools/bit/aapt.h b/tools/bit/aapt.h new file mode 100644 index 000000000000..6aeb03f18744 --- /dev/null +++ b/tools/bit/aapt.h @@ -0,0 +1,39 @@ +/* + * 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. + */ + +#ifndef AAPT_H +#define AAPT_H + +#include <string> +#include <vector> + +using namespace std; + +struct Apk +{ + string package; + string runner; + vector<string> activities; + + bool HasActivity(const string& className); +}; + +string full_class_name(const string& packageName, const string& className); +string pretty_component_name(const string& packageName, const string& className); + +int inspect_apk(Apk* apk, const string& filename); + +#endif // AAPT_H diff --git a/tools/bit/adb.cpp b/tools/bit/adb.cpp new file mode 100644 index 000000000000..eb96dae2189c --- /dev/null +++ b/tools/bit/adb.cpp @@ -0,0 +1,463 @@ +/* + * 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. + */ + +#include "adb.h" + +#include "command.h" +#include "print.h" +#include "util.h" + +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <limits.h> + +#include <iostream> +#include <istream> +#include <streambuf> + +using namespace std; + +struct Buffer: public streambuf +{ + Buffer(char* begin, size_t size); +}; + +Buffer::Buffer(char* begin, size_t size) +{ + this->setg(begin, begin, begin + size); +} + +int +run_adb(const char* first, ...) +{ + Command cmd("adb"); + + if (first == NULL) { + return 0; + } + + cmd.AddArg(first); + + va_list args; + va_start(args, first); + while (true) { + const char* arg = va_arg(args, char*); + if (arg == NULL) { + break; + } + cmd.AddArg(arg); + } + va_end(args); + + return run_command(cmd); +} + +string +get_system_property(const string& name, int* err) +{ + Command cmd("adb"); + cmd.AddArg("shell"); + cmd.AddArg("getprop"); + cmd.AddArg(name); + + return trim(get_command_output(cmd, err, false)); +} + + +static uint64_t +read_varint(int fd, int* err, bool* done) +{ + uint32_t bits = 0; + uint64_t result = 0; + while (true) { + uint8_t byte; + ssize_t amt = read(fd, &byte, 1); + if (amt == 0) { + *done = true; + return result; + } else if (amt < 0) { + return *err = errno; + } + result |= uint64_t(byte & 0x7F) << bits; + if ((byte & 0x80) == 0) { + return result; + } + bits += 7; + if (bits > 64) { + *err = -1; + return 0; + } + } +} + +static char* +read_sized_buffer(int fd, int* err, size_t* resultSize) +{ + bool done = false; + uint64_t size = read_varint(fd, err, &done); + if (*err != 0 || done) { + return NULL; + } + if (size == 0) { + *resultSize = 0; + return NULL; + } + // 10 MB seems like a reasonable limit. + if (size > 10*1024*1024) { + print_error("result buffer too large: %llu", size); + return NULL; + } + char* buf = (char*)malloc(size); + if (buf == NULL) { + print_error("Can't allocate a buffer of size for test results: %llu", size); + return NULL; + } + int pos = 0; + while (size - pos > 0) { + ssize_t amt = read(fd, buf+pos, size-pos); + if (amt == 0) { + // early end of pipe + print_error("Early end of pipe."); + *err = -1; + free(buf); + return NULL; + } else if (amt < 0) { + // error + *err = errno; + free(buf); + return NULL; + } + pos += amt; + } + *resultSize = (size_t)size; + return buf; +} + +static int +read_sized_proto(int fd, Message* message) +{ + int err = 0; + size_t size; + char* buf = read_sized_buffer(fd, &err, &size); + if (err != 0) { + if (buf != NULL) { + free(buf); + } + return err; + } else if (size == 0) { + if (buf != NULL) { + free(buf); + } + return 0; + } else if (buf == NULL) { + return -1; + } + Buffer buffer(buf, size); + istream in(&buffer); + + err = message->ParseFromIstream(&in) ? 0 : -1; + + free(buf); + return err; +} + +static int +skip_bytes(int fd, ssize_t size, char* scratch, int scratchSize) +{ + while (size > 0) { + ssize_t amt = size < scratchSize ? size : scratchSize; + fprintf(stderr, "skipping %lu/%ld bytes\n", size, amt); + amt = read(fd, scratch, amt); + if (amt == 0) { + // early end of pipe + print_error("Early end of pipe."); + return -1; + } else if (amt < 0) { + // error + return errno; + } + size -= amt; + } + return 0; +} + +static int +skip_unknown_field(int fd, uint64_t tag, char* scratch, int scratchSize) { + bool done; + int err; + uint64_t size; + switch (tag & 0x7) { + case 0: // varint + read_varint(fd, &err, &done); + if (err != 0) { + return err; + } else if (done) { + return -1; + } else { + return 0; + } + case 1: + return skip_bytes(fd, 8, scratch, scratchSize); + case 2: + size = read_varint(fd, &err, &done); + if (err != 0) { + return err; + } else if (done) { + return -1; + } + if (size > INT_MAX) { + // we'll be here a long time but this keeps it from overflowing + return -1; + } + return skip_bytes(fd, (ssize_t)size, scratch, scratchSize); + case 5: + return skip_bytes(fd, 4, scratch, scratchSize); + default: + print_error("bad wire type for tag 0x%lx\n", tag); + return -1; + } +} + +static int +read_instrumentation_results(int fd, char* scratch, int scratchSize, + InstrumentationCallbacks* callbacks) +{ + bool done = false; + int err = 0; + string result; + while (true) { + uint64_t tag = read_varint(fd, &err, &done); + if (done) { + // Done reading input (this is the only place that a stream end isn't an error). + return 0; + } else if (err != 0) { + return err; + } else if (tag == 0xa) { // test_status + TestStatus status; + err = read_sized_proto(fd, &status); + if (err != 0) { + return err; + } + callbacks->OnTestStatus(status); + } else if (tag == 0x12) { // session_status + SessionStatus status; + err = read_sized_proto(fd, &status); + if (err != 0) { + return err; + } + callbacks->OnSessionStatus(status); + } else { + err = skip_unknown_field(fd, tag, scratch, scratchSize); + if (err != 0) { + return err; + } + } + } + return 0; +} + +int +run_instrumentation_test(const string& packageName, const string& runner, const string& className, + InstrumentationCallbacks* callbacks) +{ + Command cmd("adb"); + cmd.AddArg("shell"); + cmd.AddArg("am"); + cmd.AddArg("instrument"); + cmd.AddArg("-w"); + cmd.AddArg("-m"); + if (className.length() > 0) { + cmd.AddArg("-e"); + cmd.AddArg("class"); + cmd.AddArg(className); + } + cmd.AddArg(packageName + "/" + runner); + + print_command(cmd); + + int fds[2]; + pipe(fds); + + pid_t pid = fork(); + + if (pid == -1) { + // fork error + return errno; + } else if (pid == 0) { + // child + while ((dup2(fds[1], STDOUT_FILENO) == -1) && (errno == EINTR)) {} + close(fds[1]); + close(fds[0]); + const char* prog = cmd.GetProg(); + char* const* argv = cmd.GetArgv(); + char* const* env = cmd.GetEnv(); + execvpe(prog, argv, env); + print_error("Unable to run command: %s", prog); + exit(1); + } else { + // parent + close(fds[1]); + string result; + const int size = 16*1024; + char* buf = (char*)malloc(size); + int err = read_instrumentation_results(fds[0], buf, size, callbacks); + free(buf); + int status; + waitpid(pid, &status, 0); + if (err != 0) { + return err; + } + if (WIFEXITED(status)) { + return WEXITSTATUS(status); + } else { + return -1; + } + } +} + +/** + * Get the second to last bundle in the args list. Stores the last name found + * in last. If the path is not found or if the args list is empty, returns NULL. + */ +static const ResultsBundleEntry * +find_penultimate_entry(const ResultsBundle& bundle, va_list args) +{ + const ResultsBundle* b = &bundle; + const char* arg = va_arg(args, char*); + while (arg) { + string last = arg; + arg = va_arg(args, char*); + bool found = false; + for (int i=0; i<b->entries_size(); i++) { + const ResultsBundleEntry& e = b->entries(i); + if (e.key() == last) { + if (arg == NULL) { + return &e; + } else if (e.has_value_bundle()) { + b = &e.value_bundle(); + found = true; + } + } + } + if (!found) { + return NULL; + } + if (arg == NULL) { + return NULL; + } + } + return NULL; +} + +string +get_bundle_string(const ResultsBundle& bundle, bool* found, ...) +{ + va_list args; + va_start(args, found); + const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args); + va_end(args); + if (entry == NULL) { + *found = false; + return string(); + } + if (entry->has_value_string()) { + *found = true; + return entry->value_string(); + } + *found = false; + return string(); +} + +int32_t +get_bundle_int(const ResultsBundle& bundle, bool* found, ...) +{ + va_list args; + va_start(args, found); + const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args); + va_end(args); + if (entry == NULL) { + *found = false; + return 0; + } + if (entry->has_value_int()) { + *found = true; + return entry->value_int(); + } + *found = false; + return 0; +} + +float +get_bundle_float(const ResultsBundle& bundle, bool* found, ...) +{ + va_list args; + va_start(args, found); + const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args); + va_end(args); + if (entry == NULL) { + *found = false; + return 0; + } + if (entry->has_value_float()) { + *found = true; + return entry->value_float(); + } + *found = false; + return 0; +} + +double +get_bundle_double(const ResultsBundle& bundle, bool* found, ...) +{ + va_list args; + va_start(args, found); + const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args); + va_end(args); + if (entry == NULL) { + *found = false; + return 0; + } + if (entry->has_value_double()) { + *found = true; + return entry->value_double(); + } + *found = false; + return 0; +} + +int64_t +get_bundle_long(const ResultsBundle& bundle, bool* found, ...) +{ + va_list args; + va_start(args, found); + const ResultsBundleEntry* entry = find_penultimate_entry(bundle, args); + va_end(args); + if (entry == NULL) { + *found = false; + return 0; + } + if (entry->has_value_long()) { + *found = true; + return entry->value_long(); + } + *found = false; + return 0; +} + diff --git a/tools/bit/adb.h b/tools/bit/adb.h new file mode 100644 index 000000000000..dca80c853b45 --- /dev/null +++ b/tools/bit/adb.h @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#ifndef ADB_H +#define ADB_H + +#include "instrumentation_data.pb.h" + +#include <string> + +using namespace android::am; +using namespace google::protobuf; +using namespace std; + +class InstrumentationCallbacks { +public: + virtual void OnTestStatus(TestStatus& status) = 0; + virtual void OnSessionStatus(SessionStatus& status) = 0; +}; + +int run_adb(const char* first, ...); + +string get_system_property(const string& name, int* err); + +int run_instrumentation_test(const string& packageName, const string& runner, + const string& className, InstrumentationCallbacks* callbacks); + +string get_bundle_string(const ResultsBundle& bundle, bool* found, ...); +int32_t get_bundle_int(const ResultsBundle& bundle, bool* found, ...); +float get_bundle_float(const ResultsBundle& bundle, bool* found, ...); +double get_bundle_double(const ResultsBundle& bundle, bool* found, ...); +int64_t get_bundle_long(const ResultsBundle& bundle, bool* found, ...); + +#endif // ADB_H diff --git a/tools/bit/command.cpp b/tools/bit/command.cpp new file mode 100644 index 000000000000..c5c12b4fad72 --- /dev/null +++ b/tools/bit/command.cpp @@ -0,0 +1,183 @@ +/* + * 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. + */ + +#include "command.h" + +#include "print.h" +#include "util.h" + +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> + +Command::Command(const string& prog) + :prog(prog) +{ +} + +Command::~Command() +{ +} + +void +Command::AddArg(const string& arg) +{ + args.push_back(arg); +} + +void +Command::AddEnv(const string& name, const string& value) +{ + env[name] = value; +} + +const char* +Command::GetProg() const +{ + return prog.c_str(); +} + +char *const * +Command::GetArgv() const +{ + const int N = args.size(); + char** result = (char**)malloc(sizeof(char*)*(N+2)); + result[0] = strdup(prog.c_str()); + for (int i=0; i<N; i++) { + result[i+1] = strdup(args[i].c_str()); + } + result[N+1] = 0; + return result; +} + +char *const * +Command::GetEnv() const +{ + map<string,string> copy; + for (const char** p=(const char**)environ; *p != NULL; p++) { + char* name = strdup(*p); + char* value = strchr(name, '='); + *value = '\0'; + value++; + copy[name] = value; + free(name); + } + for (map<string,string>::const_iterator it=env.begin(); it!=env.end(); it++) { + copy[it->first] = it->second; + } + char** result = (char**)malloc(sizeof(char*)*(copy.size()+1)); + char** row = result; + for (map<string,string>::const_iterator it=copy.begin(); it!=copy.end(); it++) { + *row = (char*)malloc(it->first.size() + it->second.size() + 2); + strcpy(*row, it->first.c_str()); + strcat(*row, "="); + strcat(*row, it->second.c_str()); + row++; + } + *row = NULL; + return result; +} + +string +get_command_output(const Command& command, int* err, bool quiet) +{ + if (!quiet) { + print_command(command); + } + + int fds[2]; + pipe(fds); + + pid_t pid = fork(); + + if (pid == -1) { + // fork error + *err = errno; + return string(); + } else if (pid == 0) { + // child + while ((dup2(fds[1], STDOUT_FILENO) == -1) && (errno == EINTR)) {} + close(fds[1]); + close(fds[0]); + const char* prog = command.GetProg(); + char* const* argv = command.GetArgv(); + char* const* env = command.GetEnv(); + execvpe(prog, argv, env); + if (!quiet) { + print_error("Unable to run command: %s", prog); + } + exit(1); + } else { + // parent + close(fds[1]); + string result; + const int size = 16*1024; + char* buf = (char*)malloc(size); + while (true) { + ssize_t amt = read(fds[0], buf, size); + if (amt <= 0) { + break; + } else if (amt > 0) { + result.append(buf, amt); + } + } + free(buf); + int status; + waitpid(pid, &status, 0); + if (WIFEXITED(status)) { + *err = WEXITSTATUS(status); + return result; + } else { + *err = -1; + return string(); + } + } +} + + +int +run_command(const Command& command) +{ + print_command(command); + + pid_t pid = fork(); + + if (pid == -1) { + // fork error + return errno; + } else if (pid == 0) { + // child + const char* prog = command.GetProg(); + char* const* argv = command.GetArgv(); + char* const* env = command.GetEnv(); + execvpe(prog, argv, env); + print_error("Unable to run command: %s", prog); + exit(1); + } else { + // parent + int status; + waitpid(pid, &status, 0); + if (WIFEXITED(status)) { + return WEXITSTATUS(status); + } else { + return -1; + } + } +} + diff --git a/tools/bit/command.h b/tools/bit/command.h new file mode 100644 index 000000000000..eb0b88f8d1ac --- /dev/null +++ b/tools/bit/command.h @@ -0,0 +1,58 @@ +/* + * 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. + */ + +#ifndef COMMAND_H +#define COMMAND_H + +#include <map> +#include <string> +#include <vector> + +using namespace std; + +struct Command +{ + Command(const string& prog); + ~Command(); + + void AddArg(const string& arg); + void AddEnv(const string& name, const string& value); + + const char* GetProg() const; + char* const* GetArgv() const; + char* const* GetEnv() const; + + string GetCommandline() const; + + string prog; + vector<string> args; + map<string,string> env; +}; + +/** + * Run the command and collect stdout. + * Returns the exit code. + */ +string get_command_output(const Command& command, int* err, bool quiet=false); + +/** + * Run the command. + * Returns the exit code. + */ +int run_command(const Command& command); + +#endif // COMMAND_H + diff --git a/tools/bit/main.cpp b/tools/bit/main.cpp new file mode 100644 index 000000000000..04836adf2288 --- /dev/null +++ b/tools/bit/main.cpp @@ -0,0 +1,981 @@ +/* + * 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. + */ + +#include "aapt.h" +#include "adb.h" +#include "make.h" +#include "print.h" +#include "util.h" + +#include <sstream> +#include <string> +#include <vector> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <google/protobuf/stubs/common.h> + +using namespace std; + +/** + * An entry from the command line for something that will be built, installed, + * and/or tested. + */ +struct Target { + bool build; + bool install; + bool test; + string pattern; + string name; + vector<string> actions; + Module module; + + int testActionCount; + + int testPassCount; + int testFailCount; + bool actionsWithNoTests; + + Target(bool b, bool i, bool t, const string& p); +}; + +Target::Target(bool b, bool i, bool t, const string& p) + :build(b), + install(i), + test(t), + pattern(p), + testActionCount(0), + testPassCount(0), + testFailCount(0), + actionsWithNoTests(false) +{ +} + +/** + * Command line options. + */ +struct Options { + // For help + bool runHelp; + + // For tab completion + bool runTab; + string tabPattern; + + // For build/install/test + bool reboot; + vector<Target*> targets; + + Options(); + ~Options(); +}; + +Options::Options() + :runHelp(false), + runTab(false), + reboot(false), + targets() +{ +} + +Options::~Options() +{ +} + +struct InstallApk +{ + TrackedFile file; + bool alwaysInstall; + bool installed; + + InstallApk(); + InstallApk(const InstallApk& that); + InstallApk(const string& filename, bool always); + ~InstallApk() {}; +}; + +InstallApk::InstallApk() +{ +} + +InstallApk::InstallApk(const InstallApk& that) + :file(that.file), + alwaysInstall(that.alwaysInstall), + installed(that.installed) +{ +} + +InstallApk::InstallApk(const string& filename, bool always) + :file(filename), + alwaysInstall(always), + installed(false) +{ +} + + +/** + * Record for an test that is going to be launched. + */ +struct TestAction { + TestAction(); + + // The package name from the apk + string packageName; + + // The test runner class + string runner; + + // The test class, or none if all tests should be run + string className; + + // The original target that requested this action + Target* target; + + // The number of tests that passed + int passCount; + + // The number of tests that failed + int failCount; +}; + +TestAction::TestAction() + :passCount(0), + failCount(0) +{ +} + +/** + * Record for an activity that is going to be launched. + */ +struct ActivityAction { + // The package name from the apk + string packageName; + + // The test class, or none if all tests should be run + string className; +}; + +/** + * Callback class for the am instrument command. + */ +class TestResults: public InstrumentationCallbacks +{ +public: + virtual void OnTestStatus(TestStatus& status); + virtual void OnSessionStatus(SessionStatus& status); + + /** + * Set the TestAction that the tests are for. + * It will be updated with statistics as the tests run. + */ + void SetCurrentAction(TestAction* action); + +private: + TestAction* m_currentAction; +}; + +void +TestResults::OnTestStatus(TestStatus& status) +{ + bool found; +// printf("OnTestStatus\n"); +// status.PrintDebugString(); + int32_t resultCode = status.has_results() ? status.result_code() : 0; + + if (!status.has_results()) { + return; + } + const ResultsBundle &results = status.results(); + + int32_t currentTestNum = get_bundle_int(results, &found, "current", NULL); + if (!found) { + currentTestNum = -1; + } + + int32_t testCount = get_bundle_int(results, &found, "numtests", NULL); + if (!found) { + testCount = -1; + } + + string className = get_bundle_string(results, &found, "class", NULL); + if (!found) { + return; + } + + string testName = get_bundle_string(results, &found, "test", NULL); + if (!found) { + return; + } + + if (resultCode == 0) { + // test passed + m_currentAction->passCount++; + m_currentAction->target->testPassCount++; + } else if (resultCode == 1) { + // test starting + ostringstream line; + line << "Running"; + if (currentTestNum > 0) { + line << ": " << currentTestNum; + if (testCount > 0) { + line << " of " << testCount; + } + } + line << ": " << m_currentAction->target->name << ':' << className << "\\#" << testName; + print_one_line("%s", line.str().c_str()); + } else if (resultCode == -2) { + // test failed + m_currentAction->failCount++; + m_currentAction->target->testFailCount++; + printf("%s\n%sFailed: %s:%s\\#%s%s\n", g_escapeClearLine, g_escapeRedBold, + m_currentAction->target->name.c_str(), className.c_str(), + testName.c_str(), g_escapeEndColor); + + string stack = get_bundle_string(results, &found, "stack", NULL); + if (found) { + printf("%s\n", stack.c_str()); + } + } +} + +void +TestResults::OnSessionStatus(SessionStatus& /*status*/) +{ + //status.PrintDebugString(); +} + +void +TestResults::SetCurrentAction(TestAction* action) +{ + m_currentAction = action; +} + +/** + * Prints the usage statement / help text. + */ +static void +print_usage(FILE* out) { + fprintf(out, "usage: bit OPTIONS PATTERN\n"); + fprintf(out, "\n"); + fprintf(out, " Build, sync and test android code.\n"); + fprintf(out, "\n"); + fprintf(out, " The -b -i and -t options allow you to specify which phases\n"); + fprintf(out, " you want to run. If none of those options are given, then\n"); + fprintf(out, " all phases are run. If any of these options are provided\n"); + fprintf(out, " then only the listed phases are run.\n"); + fprintf(out, "\n"); + fprintf(out, " OPTIONS\n"); + fprintf(out, " -b Run a build\n"); + fprintf(out, " -i Install the targets\n"); + fprintf(out, " -t Run the tests\n"); + fprintf(out, "\n"); + fprintf(out, " -r If the runtime needs to be restarted, do a full reboot\n"); + fprintf(out, " instead\n"); + fprintf(out, "\n"); + fprintf(out, " PATTERN\n"); + fprintf(out, " One or more targets to build, install and test. The target\n"); + fprintf(out, " names are the names that appear in the LOCAL_MODULE or\n"); + fprintf(out, " LOCAL_PACKAGE_NAME variables in Android.mk or Android.bp files.\n"); + fprintf(out, "\n"); + fprintf(out, " Building and installing\n"); + fprintf(out, " -----------------------\n"); + fprintf(out, " The modules specified will be built and then installed. If the\n"); + fprintf(out, " files are on the system partition, they will be synced and the\n"); + fprintf(out, " attached device rebooted. If they are APKs that aren't on the\n"); + fprintf(out, " system partition they are installed with adb install.\n"); + fprintf(out, "\n"); + fprintf(out, " For example:\n"); + fprintf(out, " bit framework\n"); + fprintf(out, " Builds framework.jar, syncs the system partition and reboots.\n"); + fprintf(out, "\n"); + fprintf(out, " bit SystemUI\n"); + fprintf(out, " Builds SystemUI.apk, syncs the system partition and reboots.\n"); + fprintf(out, "\n"); + fprintf(out, " bit CtsProtoTestCases\n"); + fprintf(out, " Builds this CTS apk, adb installs it, but does not run any\n"); + fprintf(out, " tests.\n"); + fprintf(out, "\n"); + fprintf(out, " Running Unit Tests\n"); + fprintf(out, " ------------------\n"); + fprintf(out, " To run a unit test, list the test class names and optionally the\n"); + fprintf(out, " test method after the module.\n"); + fprintf(out, "\n"); + fprintf(out, " For example:\n"); + fprintf(out, " bit CtsProtoTestCases:*\n"); + fprintf(out, " Builds this CTS apk, adb installs it, and runs all the tests\n"); + fprintf(out, " contained in that apk.\n"); + fprintf(out, "\n"); + fprintf(out, " bit framework CtsProtoTestCases:*\n"); + fprintf(out, " Builds the framework and the apk, syncs and reboots, then\n"); + fprintf(out, " adb installs CtsProtoTestCases.apk, and runs all tests \n"); + fprintf(out, " contained in that apk.\n"); + fprintf(out, "\n"); + fprintf(out, " bit CtsProtoTestCases:.ProtoOutputStreamBoolTest\n"); + fprintf(out, " bit CtsProtoTestCases:android.util.proto.cts.ProtoOutputStreamBoolTest\n"); + fprintf(out, " Builds and installs CtsProtoTestCases.apk, and runs all the\n"); + fprintf(out, " tests in the ProtoOutputStreamBoolTest class.\n"); + fprintf(out, "\n"); + fprintf(out, " bit CtsProtoTestCases:.ProtoOutputStreamBoolTest\\#testWrite\n"); + fprintf(out, " Builds and installs CtsProtoTestCases.apk, and runs the testWrite\n"); + fprintf(out, " test method on that class.\n"); + fprintf(out, "\n"); + fprintf(out, " bit CtsProtoTestCases:.ProtoOutputStreamBoolTest\\#testWrite,.ProtoOutputStreamBoolTest\\#testRepeated\n"); + fprintf(out, " Builds and installs CtsProtoTestCases.apk, and runs the testWrite\n"); + fprintf(out, " and testRepeated test methods on that class.\n"); + fprintf(out, "\n"); + fprintf(out, " Launching an Activity\n"); + fprintf(out, " ---------------------\n"); + fprintf(out, " To launch an activity, specify the activity class name after\n"); + fprintf(out, " the module name.\n"); + fprintf(out, "\n"); + fprintf(out, " For example:\n"); + fprintf(out, " bit StatusBarTest:NotificationBuilderTest\n"); + fprintf(out, " bit StatusBarTest:.NotificationBuilderTest\n"); + fprintf(out, " bit StatusBarTest:com.android.statusbartest.NotificationBuilderTest\n"); + fprintf(out, " Builds and installs StatusBarTest.apk, launches the\n"); + fprintf(out, " com.android.statusbartest/.NotificationBuilderTest activity.\n"); + fprintf(out, "\n"); + fprintf(out, "\n"); + fprintf(out, "usage: bit --tab ...\n"); + fprintf(out, "\n"); + fprintf(out, " Lists the targets in a format for tab completion. To get tab\n"); + fprintf(out, " completion, add this to your bash environment:\n"); + fprintf(out, "\n"); + fprintf(out, " complete -C \"bit --tab\" bit\n"); + fprintf(out, "\n"); + fprintf(out, " Sourcing android's build/envsetup.sh will do this for you\n"); + fprintf(out, " automatically.\n"); + fprintf(out, "\n"); + fprintf(out, "\n"); + fprintf(out, "usage: bit --help\n"); + fprintf(out, "usage: bit -h\n"); + fprintf(out, "\n"); + fprintf(out, " Print this help message\n"); + fprintf(out, "\n"); +} + + +/** + * Sets the appropriate flag* variables. If there is a problem with the + * commandline arguments, prints the help message and exits with an error. + */ +static void +parse_args(Options* options, int argc, const char** argv) +{ + // Help + if (argc == 2 && (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0)) { + options->runHelp = true; + return; + } + + // Tab + if (argc >= 4 && strcmp(argv[1], "--tab") == 0) { + options->runTab = true; + options->tabPattern = argv[3]; + return; + } + + // Normal usage + bool anyPhases = false; + bool gotPattern = false; + bool flagBuild = false; + bool flagInstall = false; + bool flagTest = false; + for (int i=1; i < argc; i++) { + string arg(argv[i]); + if (arg[0] == '-') { + for (size_t j=1; j<arg.size(); j++) { + switch (arg[j]) { + case '-': + break; + case 'b': + if (gotPattern) { + gotPattern = false; + flagInstall = false; + flagTest = false; + } + flagBuild = true; + anyPhases = true; + break; + case 'i': + if (gotPattern) { + gotPattern = false; + flagBuild = false; + flagTest = false; + } + flagInstall = true; + anyPhases = true; + break; + case 't': + if (gotPattern) { + gotPattern = false; + flagBuild = false; + flagInstall = false; + } + flagTest = true; + anyPhases = true; + break; + case 'r': + options->reboot = true; + break; + default: + fprintf(stderr, "Unrecognized option '%c'\n", arg[j]); + print_usage(stderr); + exit(1); + break; + } + } + } else { + Target* target = new Target(flagBuild || !anyPhases, flagInstall || !anyPhases, + flagTest || !anyPhases, arg); + size_t colonPos = arg.find(':'); + if (colonPos == 0) { + fprintf(stderr, "Test / activity supplied without a module to build: %s\n", + arg.c_str()); + print_usage(stderr); + exit(1); + } else if (colonPos == string::npos) { + target->name = arg; + } else { + target->name.assign(arg, 0, colonPos); + size_t beginPos = colonPos+1; + size_t commaPos; + while (true) { + commaPos = arg.find(',', beginPos); + if (commaPos == string::npos) { + if (beginPos != arg.size()) { + target->actions.push_back(string(arg, beginPos, commaPos)); + } + break; + } else { + if (commaPos != beginPos) { + target->actions.push_back(string(arg, beginPos, commaPos-beginPos)); + } + beginPos = commaPos+1; + } + } + } + options->targets.push_back(target); + gotPattern = true; + } + } + // If no pattern was supplied, give an error + if (options->targets.size() == 0) { + fprintf(stderr, "No PATTERN supplied.\n\n"); + print_usage(stderr); + exit(1); + } +} + +/** + * Get an environment variable. + * Exits with an error if it is unset or the empty string. + */ +static string +get_required_env(const char* name, bool quiet) +{ + const char* value = getenv(name); + if (value == NULL || value[0] == '\0') { + if (!quiet) { + fprintf(stderr, "%s not set. Did you source build/envsetup.sh," + " run lunch and do a build?\n", name); + } + exit(1); + } + return string(value); +} + +/** + * Get the out directory. + * + * This duplicates the logic in build/make/core/envsetup.mk (which hasn't changed since 2011) + * so that we don't have to wait for get_build_var make invocation. + */ +string +get_out_dir() +{ + const char* out_dir = getenv("OUT_DIR"); + if (out_dir == NULL || out_dir[0] == '\0') { + const char* common_base = getenv("OUT_DIR_COMMON_BASE"); + if (common_base == NULL || common_base[0] == '\0') { + // We don't prefix with buildTop because we cd there and it + // makes all the filenames long when being pretty printed. + return "out"; + } else { + char* pwd = get_current_dir_name(); + const char* slash = strrchr(pwd, '/'); + if (slash == NULL) { + slash = ""; + } + string result(common_base); + result += slash; + free(pwd); + return result; + } + } + return string(out_dir); +} + +/** + * Check that a system property on the device matches the expected value. + * Exits with an error if they don't. + */ +static void +check_device_property(const string& property, const string& expected) +{ + int err; + string deviceValue = get_system_property(property, &err); + check_error(err); + if (deviceValue != expected) { + print_error("There is a mismatch between the build you just did and the device you"); + print_error("are trying to sync it to in the %s system property", property.c_str()); + print_error(" build: %s", expected.c_str()); + print_error(" device: %s", deviceValue.c_str()); + exit(1); + } +} + +/** + * Run the build, install, and test actions. + */ +void +run_phases(vector<Target*> targets, bool reboot) +{ + int err = 0; + + // + // Initialization + // + + print_status("Initializing"); + + const string buildTop = get_required_env("ANDROID_BUILD_TOP", false); + const string buildProduct = get_required_env("TARGET_PRODUCT", false); + const string buildVariant = get_required_env("TARGET_BUILD_VARIANT", false); + const string buildType = get_required_env("TARGET_BUILD_TYPE", false); + const string buildDevice = get_build_var(buildTop, "TARGET_DEVICE", false); + const string buildId = get_build_var(buildTop, "BUILD_ID", false); + const string buildOut = get_out_dir(); + + // TODO: print_command("cd", buildTop.c_str()); + chdir(buildTop.c_str()); + + // Get the modules for the targets + map<string,Module> modules; + read_modules(buildOut, buildDevice, &modules, false); + for (size_t i=0; i<targets.size(); i++) { + Target* target = targets[i]; + map<string,Module>::iterator mod = modules.find(target->name); + if (mod != modules.end()) { + target->module = mod->second; + } else { + print_error("Error: Could not find module: %s", target->name.c_str()); + err = 1; + } + } + if (err != 0) { + exit(1); + } + + // Choose the goals + vector<string> goals; + for (size_t i=0; i<targets.size(); i++) { + Target* target = targets[i]; + if (target->build) { + goals.push_back(target->name); + } + } + + // Figure out whether we need to sync the system and which apks to install + string systemPath = buildOut + "/target/product/" + buildDevice + "/system/"; + string dataPath = buildOut + "/target/product/" + buildDevice + "/data/"; + bool syncSystem = false; + bool alwaysSyncSystem = false; + vector<InstallApk> installApks; + for (size_t i=0; i<targets.size(); i++) { + Target* target = targets[i]; + if (target->install) { + for (size_t j=0; j<target->module.installed.size(); j++) { + const string& file = target->module.installed[j]; + // System partition + if (starts_with(file, systemPath)) { + syncSystem = true; + if (!target->build) { + // If a system partition target didn't get built then + // it won't change we will always need to do adb sync + alwaysSyncSystem = true; + } + continue; + } + // Apk in the data partition + if (starts_with(file, dataPath) && ends_with(file, ".apk")) { + // Always install it if we didn't build it because otherwise + // it will never have changed. + installApks.push_back(InstallApk(file, !target->build)); + continue; + } + } + } + } + map<string,FileInfo> systemFilesBefore; + if (syncSystem && !alwaysSyncSystem) { + get_directory_contents(systemPath, &systemFilesBefore); + } + + // + // Build + // + + // Run the build + if (goals.size() > 0) { + print_status("Building"); + err = build_goals(goals); + check_error(err); + } + + // + // Install + // + + // Sync the system partition and reboot + bool skipSync = false; + if (syncSystem) { + print_status("Syncing /system"); + + if (!alwaysSyncSystem) { + // If nothing changed and we weren't forced to sync, skip the reboot for speed. + map<string,FileInfo> systemFilesAfter; + get_directory_contents(systemPath, &systemFilesAfter); + skipSync = !directory_contents_differ(systemFilesBefore, systemFilesAfter); + } + if (skipSync) { + printf("Skipping sync because no files changed.\n"); + } else { + // Do some sanity checks + check_device_property("ro.build.product", buildProduct); + check_device_property("ro.build.type", buildVariant); + check_device_property("ro.build.id", buildId); + + // Stop & Sync + err = run_adb("shell", "stop", NULL); + check_error(err); + err = run_adb("remount", NULL); + check_error(err); + err = run_adb("sync", "system", NULL); + check_error(err); + + if (reboot) { + print_status("Rebooting"); + + err = run_adb("reboot", NULL); + check_error(err); + err = run_adb("wait-for-device", NULL); + check_error(err); + } else { + print_status("Restarting the runtime"); + + err = run_adb("shell", "setprop", "sys.boot_completed", "0", NULL); + check_error(err); + err = run_adb("shell", "start", NULL); + check_error(err); + } + + while (true) { + string completed = get_system_property("sys.boot_completed", &err); + check_error(err); + if (completed == "1") { + break; + } + sleep(2); + } + sleep(1); + err = run_adb("shell", "wm", "dismiss-keyguard", NULL); + check_error(err); + } + } + + // Install APKs + if (installApks.size() > 0) { + print_status("Installing APKs"); + for (size_t i=0; i<installApks.size(); i++) { + InstallApk& apk = installApks[i]; + if (!apk.file.fileInfo.exists || apk.file.HasChanged()) { + // It didn't exist before or it changed, so int needs install + err = run_adb("install", "-r", apk.file.filename.c_str(), NULL); + check_error(err); + apk.installed = true; + } else { + printf("APK didn't change. Skipping install of %s\n", apk.file.filename.c_str()); + } + } + } + + // + // Actions + // + + // Inspect the apks, and figure out what is an activity and what needs a test runner + bool printedInspecting = false; + vector<TestAction> testActions; + vector<ActivityAction> activityActions; + for (size_t i=0; i<targets.size(); i++) { + Target* target = targets[i]; + if (target->test) { + for (size_t j=0; j<target->module.installed.size(); j++) { + string filename = target->module.installed[j]; + + if (!ends_with(filename, ".apk")) { + continue; + } + + if (!printedInspecting) { + printedInspecting = true; + print_status("Inspecting APKs"); + } + + Apk apk; + err = inspect_apk(&apk, filename); + check_error(err); + + for (size_t k=0; k<target->actions.size(); k++) { + string actionString = target->actions[k]; + if (actionString == "*") { + if (apk.runner.length() == 0) { + print_error("Error: Test requested for apk that doesn't" + " have an <instrumentation> tag: %s\n", + target->module.name.c_str()); + exit(1); + } + TestAction action; + action.packageName = apk.package; + action.runner = apk.runner; + action.target = target; + testActions.push_back(action); + target->testActionCount++; + } else if (apk.HasActivity(actionString)) { + ActivityAction action; + action.packageName = apk.package; + action.className = full_class_name(apk.package, actionString); + activityActions.push_back(action); + } else { + if (apk.runner.length() == 0) { + print_error("Error: Test requested for apk that doesn't" + " have an <instrumentation> tag: %s\n", + target->module.name.c_str()); + exit(1); + } + TestAction action; + action.packageName = apk.package; + action.runner = apk.runner; + action.className = full_class_name(apk.package, actionString); + action.target = target; + testActions.push_back(action); + target->testActionCount++; + } + } + } + } + } + + // Run the instrumentation tests + TestResults testResults; + if (testActions.size() > 0) { + print_status("Running tests"); + for (size_t i=0; i<testActions.size(); i++) { + TestAction& action = testActions[i]; + testResults.SetCurrentAction(&action); + err = run_instrumentation_test(action.packageName, action.runner, action.className, + &testResults); + check_error(err); + if (action.passCount == 0 && action.failCount == 0) { + action.target->actionsWithNoTests = true; + } + int total = action.passCount + action.failCount; + printf("%sRan %d test%s for %s. ", g_escapeClearLine, + total, total > 1 ? "s" : "", action.target->name.c_str()); + if (action.passCount == 0 && action.failCount == 0) { + printf("%s%d passed, %d failed%s\n", g_escapeYellowBold, action.passCount, + action.failCount, g_escapeEndColor); + } else if (action.failCount > 0) { + printf("%d passed, %s%d failed%s\n", action.passCount, g_escapeRedBold, + action.failCount, g_escapeEndColor); + } else { + printf("%s%d passed%s, %d failed\n", g_escapeGreenBold, action.passCount, + g_escapeEndColor, action.failCount); + } + } + } + + // Launch the activity + if (activityActions.size() > 0) { + print_status("Starting activity"); + + if (activityActions.size() > 1) { + print_warning("Multiple activities specified. Will only start the first one:"); + for (size_t i=0; i<activityActions.size(); i++) { + ActivityAction& action = activityActions[i]; + print_warning(" %s", + pretty_component_name(action.packageName, action.className).c_str()); + } + } + + const ActivityAction& action = activityActions[0]; + string componentName = action.packageName + "/" + action.className; + err = run_adb("shell", "am", "start", componentName.c_str(), NULL); + check_error(err); + } + + // + // Print summary + // + + printf("\n%s--------------------------------------------%s\n", g_escapeBold, g_escapeEndColor); + + // Build + if (goals.size() > 0) { + printf("%sBuilt:%s\n", g_escapeBold, g_escapeEndColor); + for (size_t i=0; i<goals.size(); i++) { + printf(" %s\n", goals[i].c_str()); + } + } + + // Install + if (syncSystem) { + if (skipSync) { + printf("%sSkipped syncing /system partition%s\n", g_escapeBold, g_escapeEndColor); + } else { + printf("%sSynced /system partition%s\n", g_escapeBold, g_escapeEndColor); + } + } + if (installApks.size() > 0) { + bool printedTitle = false; + for (size_t i=0; i<installApks.size(); i++) { + const InstallApk& apk = installApks[i]; + if (apk.installed) { + if (!printedTitle) { + printf("%sInstalled:%s\n", g_escapeBold, g_escapeEndColor); + printedTitle = true; + } + printf(" %s\n", apk.file.filename.c_str()); + } + } + printedTitle = false; + for (size_t i=0; i<installApks.size(); i++) { + const InstallApk& apk = installApks[i]; + if (!apk.installed) { + if (!printedTitle) { + printf("%sSkipped install:%s\n", g_escapeBold, g_escapeEndColor); + printedTitle = true; + } + printf(" %s\n", apk.file.filename.c_str()); + } + } + } + + // Tests + if (testActions.size() > 0) { + printf("%sRan tests:%s\n", g_escapeBold, g_escapeEndColor); + size_t maxNameLength = 0; + for (size_t i=0; i<targets.size(); i++) { + Target* target = targets[i]; + if (target->test) { + size_t len = target->name.length(); + if (len > maxNameLength) { + maxNameLength = len; + } + } + } + string padding(maxNameLength, ' '); + for (size_t i=0; i<targets.size(); i++) { + Target* target = targets[i]; + if (target->testActionCount > 0) { + printf(" %s%s", target->name.c_str(), padding.c_str() + target->name.length()); + if (target->actionsWithNoTests) { + printf(" %s%d passed, %d failed%s\n", g_escapeYellowBold, + target->testPassCount, target->testFailCount, g_escapeEndColor); + } else if (target->testFailCount > 0) { + printf(" %d passed, %s%d failed%s\n", target->testPassCount, + g_escapeRedBold, target->testFailCount, g_escapeEndColor); + } else { + printf(" %s%d passed%s, %d failed\n", g_escapeGreenBold, + target->testPassCount, g_escapeEndColor, target->testFailCount); + } + } + } + } + if (activityActions.size() > 1) { + printf("%sStarted Activity:%s\n", g_escapeBold, g_escapeEndColor); + const ActivityAction& action = activityActions[0]; + printf(" %s\n", pretty_component_name(action.packageName, action.className).c_str()); + } + + printf("%s--------------------------------------------%s\n", g_escapeBold, g_escapeEndColor); +} + +/** + * Implement tab completion of the target names from the all modules file. + */ +void +run_tab_completion(const string& word) +{ + const string buildTop = get_required_env("ANDROID_BUILD_TOP", true); + const string buildProduct = get_required_env("TARGET_PRODUCT", false); + const string buildOut = get_out_dir(); + + chdir(buildTop.c_str()); + + string buildDevice = sniff_device_name(buildOut, buildProduct); + + map<string,Module> modules; + read_modules(buildOut, buildDevice, &modules, true); + + for (map<string,Module>::const_iterator it = modules.begin(); it != modules.end(); it++) { + if (starts_with(it->first, word)) { + printf("%s\n", it->first.c_str()); + } + } +} + +/** + * Main entry point. + */ +int +main(int argc, const char** argv) +{ + GOOGLE_PROTOBUF_VERIFY_VERSION; + init_print(); + + Options options; + parse_args(&options, argc, argv); + + if (options.runHelp) { + // Help + print_usage(stdout); + exit(0); + } else if (options.runTab) { + run_tab_completion(options.tabPattern); + exit(0); + } else { + // Normal run + run_phases(options.targets, options.reboot); + } + + return 0; +} + diff --git a/tools/bit/make.cpp b/tools/bit/make.cpp new file mode 100644 index 000000000000..60b5687bb313 --- /dev/null +++ b/tools/bit/make.cpp @@ -0,0 +1,210 @@ +/* + * 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. + */ + +#include "make.h" + +#include "command.h" +#include "print.h" +#include "util.h" + +#include <json/reader.h> +#include <json/value.h> + +#include <fstream> +#include <string> +#include <map> + +#include <sys/types.h> +#include <dirent.h> +#include <string.h> + +using namespace std; + +map<string,string> g_buildVars; + +string +get_build_var(const string& buildTop, const string& name, bool quiet) +{ + int err; + + map<string,string>::iterator it = g_buildVars.find(name); + if (it == g_buildVars.end()) { + Command cmd("make"); + cmd.AddArg("--no-print-directory"); + cmd.AddArg("-C"); + cmd.AddArg(buildTop); + cmd.AddArg("-f"); + cmd.AddArg("build/core/config.mk"); + cmd.AddArg(string("dumpvar-") + name); + cmd.AddEnv("CALLED_FROM_SETUP", "true"); + cmd.AddEnv("BUILD_SYSTEM", "build/core"); + + string output = trim(get_command_output(cmd, &err, quiet)); + if (err == 0) { + g_buildVars[name] = output; + return output; + } else { + return string(); + } + } else { + return it->second; + } +} + +string +sniff_device_name(const string& buildOut, const string& product) +{ + string match("ro.build.product=" + product); + + string base(buildOut + "/target/product"); + DIR* dir = opendir(base.c_str()); + if (dir == NULL) { + return string(); + } + + dirent* entry; + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] == '.') { + continue; + } + if (entry->d_type == DT_DIR) { + string filename(base + "/" + entry->d_name + "/system/build.prop"); + vector<string> lines; + split_lines(&lines, read_file(filename)); + for (size_t i=0; i<lines.size(); i++) { + if (lines[i] == match) { + return entry->d_name; + } + } + } + } + + closedir(dir); + return string(); +} + +void +json_error(const string& filename, const char* error, bool quiet) +{ + if (!quiet) { + print_error("Unable to parse module info file (%s): %s", error, filename.c_str()); + print_error("Have you done a full build?"); + } + exit(1); +} + +static void +get_values(const Json::Value& json, const string& name, vector<string>* result) +{ + Json::Value nullValue; + + const Json::Value& value = json.get(name, nullValue); + if (!value.isArray()) { + return; + } + + const int N = value.size(); + for (int i=0; i<N; i++) { + const Json::Value& child = value[i]; + if (child.isString()) { + result->push_back(child.asString()); + } + } +} + +void +read_modules(const string& buildOut, const string& device, map<string,Module>* result, bool quiet) +{ + string filename(string(buildOut + "/target/product/") + device + "/module-info.json"); + std::ifstream stream(filename, std::ifstream::binary); + + if (stream.fail()) { + if (!quiet) { + print_error("Unable to open module info file: %s", filename.c_str()); + print_error("Have you done a full build?"); + } + exit(1); + } + + Json::Value json; + Json::Reader reader; + if (!reader.parse(stream, json)) { + json_error(filename, "can't parse json format", quiet); + return; + } + + if (!json.isObject()) { + json_error(filename, "root element not an object", quiet); + return; + } + + vector<string> names = json.getMemberNames(); + const int N = names.size(); + for (int i=0; i<N; i++) { + const string& name = names[i]; + + const Json::Value& value = json[name]; + if (!value.isObject()) { + continue; + } + + Module module; + + module.name = name; + get_values(value, "class", &module.classes); + get_values(value, "path", &module.paths); + get_values(value, "installed", &module.installed); + + // Only keep classes we can handle + for (ssize_t i = module.classes.size() - 1; i >= 0; i--) { + string cl = module.classes[i]; + if (!(cl == "JAVA_LIBRARIES" || cl == "EXECUTABLES" || cl == "SHARED_LIBRARIES" + || cl == "APPS")) { + module.classes.erase(module.classes.begin() + i); + } + } + if (module.classes.size() == 0) { + continue; + } + + // Only target modules (not host) + for (ssize_t i = module.installed.size() - 1; i >= 0; i--) { + string fn = module.installed[i]; + if (!starts_with(fn, buildOut + "/target/")) { + module.installed.erase(module.installed.begin() + i); + } + } + if (module.installed.size() == 0) { + continue; + } + + (*result)[name] = module; + } +} + +int +build_goals(const vector<string>& goals) +{ + Command cmd("make"); + cmd.AddArg("-f"); + cmd.AddArg("build/core/main.mk"); + for (size_t i=0; i<goals.size(); i++) { + cmd.AddArg(goals[i]); + } + + return run_command(cmd); +} + diff --git a/tools/bit/make.h b/tools/bit/make.h new file mode 100644 index 000000000000..bb83c6e14226 --- /dev/null +++ b/tools/bit/make.h @@ -0,0 +1,50 @@ +/* + * 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. + */ + +#ifndef MAKE_H +#define MAKE_H + +#include <map> +#include <string> +#include <vector> + +using namespace std; + +struct Module +{ + string name; + vector<string> classes; + vector<string> paths; + vector<string> installed; +}; + +string get_build_var(const string& buildTop, const string& name, bool quiet); + +/** + * Poke around in the out directory and try to find a device name that matches + * our product. This is faster than running get_build_var and good enough for + * tab completion. + * + * Returns the empty string if we can't find one. + */ +string sniff_device_name(const string& buildOut, const string& product); + +void read_modules(const string& buildOut, const string& buildDevice, + map<string,Module>* modules, bool quiet); + +int build_goals(const vector<string>& goals); + +#endif // MAKE_H diff --git a/tools/bit/print.cpp b/tools/bit/print.cpp new file mode 100644 index 000000000000..790e0b4b227e --- /dev/null +++ b/tools/bit/print.cpp @@ -0,0 +1,155 @@ +/* + * 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. + */ + +#include "print.h" + +#include <sys/ioctl.h> +#include <stdio.h> +#include <unistd.h> + +#include "util.h" + +bool g_stdoutIsTty; +char const* g_escapeBold; +char const* g_escapeRedBold; +char const* g_escapeGreenBold; +char const* g_escapeYellowBold; +char const* g_escapeUnderline; +char const* g_escapeEndColor; +char const* g_escapeClearLine; + +void +init_print() +{ + if (isatty(fileno(stdout))) { + g_stdoutIsTty = true; + g_escapeBold = "\033[1m"; + g_escapeRedBold = "\033[91m\033[1m"; + g_escapeGreenBold = "\033[92m\033[1m"; + g_escapeYellowBold = "\033[93m\033[1m"; + g_escapeUnderline = "\033[4m"; + g_escapeEndColor = "\033[0m"; + g_escapeClearLine = "\033[K"; + } else { + g_stdoutIsTty = false; + g_escapeBold = ""; + g_escapeRedBold = ""; + g_escapeGreenBold = ""; + g_escapeYellowBold = ""; + g_escapeUnderline = ""; + g_escapeEndColor = ""; + g_escapeClearLine = ""; + } +} + +void +print_status(const char* format, ...) +{ + printf("\n%s%s", g_escapeBold, g_escapeUnderline); + + va_list args; + va_start(args, format); + vfprintf(stdout, format, args); + va_end(args); + + printf("%s\n", g_escapeEndColor); +} + +void +print_command(const Command& command) +{ + fputs(g_escapeBold, stdout); + for (map<string,string>::const_iterator it=command.env.begin(); it!=command.env.end(); it++) { + fputs(it->first.c_str(), stdout); + fputc('=', stdout); + fputs(escape_for_commandline(it->second.c_str()).c_str(), stdout); + putc(' ', stdout); + } + fputs(command.prog.c_str(), stdout); + for (vector<string>::const_iterator it=command.args.begin(); it!=command.args.end(); it++) { + putc(' ', stdout); + fputs(escape_for_commandline(it->c_str()).c_str(), stdout); + } + fputs(g_escapeEndColor, stdout); + fputc('\n', stdout); +} + +void +print_error(const char* format, ...) +{ + fputs(g_escapeRedBold, stderr); + + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + + fputs(g_escapeEndColor, stderr); + fputc('\n', stderr); +} + +void +print_warning(const char* format, ...) +{ + fputs(g_escapeYellowBold, stderr); + + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + + fputs(g_escapeEndColor, stderr); + fputc('\n', stderr); +} + +void +print_one_line(const char* format, ...) +{ + if (g_stdoutIsTty) { + struct winsize ws; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws); + int size = ws.ws_col + 1; + char* buf = (char*)malloc(size); + + va_list args; + va_start(args, format); + vsnprintf(buf, size, format, args); + va_end(args); + + printf("%s%s\r", buf, g_escapeClearLine); + free(buf); + + fflush(stdout); + } else { + va_list args; + va_start(args, format); + vfprintf(stdout, format, args); + va_end(args); + printf("\n"); + } +} + +void +check_error(int err) +{ + if (err != 0) { + fputc('\n', stderr); + print_error("Stopping due to errors."); + exit(1); + } +} + + diff --git a/tools/bit/print.h b/tools/bit/print.h new file mode 100644 index 000000000000..b6c3e9aa27fa --- /dev/null +++ b/tools/bit/print.h @@ -0,0 +1,39 @@ +/* + * 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. + */ + +#ifndef PRINT_H +#define PRINT_H + +#include "command.h" + +extern bool g_stdoutIsTty; +extern char const* g_escapeBold; +extern char const* g_escapeRedBold; +extern char const* g_escapeGreenBold; +extern char const* g_escapeYellowBold; +extern char const* g_escapeUnderline; +extern char const* g_escapeEndColor; +extern char const* g_escapeClearLine; + +void init_print(); +void print_status(const char* format, ...); +void print_command(const Command& command); +void print_error(const char* format, ...); +void print_warning(const char* format, ...); +void print_one_line(const char* format, ...); +void check_error(int err); + +#endif // PRINT_H diff --git a/tools/bit/util.cpp b/tools/bit/util.cpp new file mode 100644 index 000000000000..fc93bcb8c935 --- /dev/null +++ b/tools/bit/util.cpp @@ -0,0 +1,254 @@ +/* + * 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. + */ + +#include "util.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <string.h> +#include <unistd.h> + + +FileInfo::FileInfo() +{ + memset(this, 0, sizeof(FileInfo)); +} + +FileInfo::FileInfo(const FileInfo& that) +{ + memcpy(this, &that, sizeof(FileInfo)); +} + +FileInfo::FileInfo(const string& filename) +{ + struct stat st; + int err = stat(filename.c_str(), &st); + if (err != 0) { + memset(this, 0, sizeof(FileInfo)); + } else { + exists = true; + mtime = st.st_mtime; + ctime = st.st_ctime; + size = st.st_size; + } +} + +bool +FileInfo::operator==(const FileInfo& that) const +{ + return exists == that.exists + && mtime == that.mtime + && ctime == that.ctime + && size == that.size; +} + +bool +FileInfo::operator!=(const FileInfo& that) const +{ + return exists != that.exists + || mtime != that.mtime + || ctime != that.ctime + || size != that.size; +} + +FileInfo::~FileInfo() +{ +} + +TrackedFile::TrackedFile() + :filename(), + fileInfo() +{ +} + +TrackedFile::TrackedFile(const TrackedFile& that) +{ + filename = that.filename; + fileInfo = that.fileInfo; +} + +TrackedFile::TrackedFile(const string& file) + :filename(file), + fileInfo(file) +{ +} + +TrackedFile::~TrackedFile() +{ +} + +bool +TrackedFile::HasChanged() const +{ + FileInfo updated(filename); + return !updated.exists || fileInfo != updated; +} + +void +get_directory_contents(const string& name, map<string,FileInfo>* results) +{ + int err; + DIR* dir = opendir(name.c_str()); + if (dir == NULL) { + return; + } + + dirent* entry; + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } + if (entry->d_type == DT_DIR) { + string subdir = name + "/" + entry->d_name; + get_directory_contents(subdir, results); + } else if (entry->d_type == DT_LNK || entry->d_type == DT_REG) { + string filename(name + "/" + entry->d_name); + (*results)[filename] = FileInfo(filename); + } + } + + closedir(dir); +} + +bool +directory_contents_differ(const map<string,FileInfo>& before, const map<string,FileInfo>& after) +{ + if (before.size() != after.size()) { + return true; + } + map<string,FileInfo>::const_iterator b = before.begin(); + map<string,FileInfo>::const_iterator a = after.begin(); + while (b != before.end() && a != after.end()) { + if (b->first != a->first) { + return true; + } + if (a->second != b->second) { + return true; + } + a++; + b++; + } + return false; +} + +string +escape_quotes(const char* str) +{ + string result; + while (*str) { + if (*str == '"') { + result += '\\'; + result += '"'; + } else { + result += *str; + } + } + return result; +} + +string +escape_for_commandline(const char* str) +{ + if (strchr(str, '"') != NULL || strchr(str, ' ') != NULL + || strchr(str, '\t') != NULL) { + return escape_quotes(str); + } else { + return str; + } +} + +static bool +spacechr(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; +} + +string +trim(const string& str) +{ + const ssize_t N = (ssize_t)str.size(); + ssize_t begin = 0; + while (begin < N && spacechr(str[begin])) { + begin++; + } + ssize_t end = N - 1; + while (end >= begin && spacechr(str[end])) { + end--; + } + return string(str, begin, end-begin+1); +} + +bool +starts_with(const string& str, const string& prefix) +{ + return str.compare(0, prefix.length(), prefix) == 0; +} + +bool +ends_with(const string& str, const string& suffix) +{ + if (str.length() < suffix.length()) { + return false; + } else { + return str.compare(str.length()-suffix.length(), suffix.length(), suffix) == 0; + } +} + +void +split_lines(vector<string>* result, const string& str) +{ + const int N = str.length(); + int begin = 0; + int end = 0; + for (; end < N; end++) { + const char c = str[end]; + if (c == '\r' || c == '\n') { + if (begin != end) { + result->push_back(string(str, begin, end-begin)); + } + begin = end+1; + } + } + if (begin != end) { + result->push_back(string(str, begin, end-begin)); + } +} + +string +read_file(const string& filename) +{ + FILE* file = fopen(filename.c_str(), "r"); + if (file == NULL) { + return string(); + } + + fseek(file, 0, SEEK_END); + int size = ftell(file); + fseek(file, 0, SEEK_SET); + + char* buf = (char*)malloc(size); + fread(buf, 1, size, file); + + string result(buf, size); + + free(buf); + fclose(file); + + return result; +} + + diff --git a/tools/bit/util.h b/tools/bit/util.h new file mode 100644 index 000000000000..718f1474a969 --- /dev/null +++ b/tools/bit/util.h @@ -0,0 +1,83 @@ +/* + * 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. + */ + +#ifndef UTIL_H +#define UTIL_H + +#include <map> +#include <string> +#include <vector> + +using namespace std; + +struct FileInfo +{ + bool exists; + time_t mtime; + time_t ctime; + off_t size; + + FileInfo(); + FileInfo(const FileInfo& that); + explicit FileInfo(const string& filename); + ~FileInfo(); + + bool operator==(const FileInfo& that) const; + bool operator!=(const FileInfo& that) const; +}; + + +/** + * Record for a file that we are watching + */ +struct TrackedFile { + string filename; + FileInfo fileInfo; + + TrackedFile(); + TrackedFile(const TrackedFile& that); + explicit TrackedFile(const string& filename); + ~TrackedFile(); + + // Returns if the file has changed. If it doesn't currently exist, returns true. + bool HasChanged() const; +}; + +/** + * Get FileInfo structures recursively for all the files and symlinks in a directory. + * Does not traverse symlinks, but it does record them. + */ +void get_directory_contents(const string& dir, map<string,FileInfo>* results); + +bool directory_contents_differ(const map<string,FileInfo>& before, + const map<string,FileInfo>& after); + +string escape_quotes(const char* str); + +string escape_for_commandline(const char* str); + +string trim(const string& trim); + +bool starts_with(const string& str, const string& prefix); + +bool ends_with(const string& str, const string& suffix); + +void split_lines(vector<string>* result, const string& str); + +string read_file(const string& filename); + +#endif // UTIL_H + diff --git a/tools/streaming_proto/main.cpp b/tools/streaming_proto/main.cpp index d2862137b216..5435728a3d4b 100644 --- a/tools/streaming_proto/main.cpp +++ b/tools/streaming_proto/main.cpp @@ -191,38 +191,55 @@ get_field_id(const FieldDescriptorProto& field) switch (field.type()) { case FieldDescriptorProto::TYPE_DOUBLE: result |= FIELD_TYPE_DOUBLE; + break; case FieldDescriptorProto::TYPE_FLOAT: result |= FIELD_TYPE_FLOAT; + break; case FieldDescriptorProto::TYPE_INT64: result |= FIELD_TYPE_INT64; + break; case FieldDescriptorProto::TYPE_UINT64: result |= FIELD_TYPE_UINT64; + break; case FieldDescriptorProto::TYPE_INT32: result |= FIELD_TYPE_INT32; + break; case FieldDescriptorProto::TYPE_FIXED64: result |= FIELD_TYPE_FIXED64; + break; case FieldDescriptorProto::TYPE_FIXED32: result |= FIELD_TYPE_FIXED32; + break; case FieldDescriptorProto::TYPE_BOOL: result |= FIELD_TYPE_BOOL; + break; case FieldDescriptorProto::TYPE_STRING: result |= FIELD_TYPE_STRING; + break; case FieldDescriptorProto::TYPE_MESSAGE: result |= FIELD_TYPE_OBJECT; + break; case FieldDescriptorProto::TYPE_BYTES: result |= FIELD_TYPE_BYTES; + break; case FieldDescriptorProto::TYPE_UINT32: result |= FIELD_TYPE_UINT32; + break; case FieldDescriptorProto::TYPE_ENUM: result |= FIELD_TYPE_ENUM; + break; case FieldDescriptorProto::TYPE_SFIXED32: result |= FIELD_TYPE_SFIXED32; + break; case FieldDescriptorProto::TYPE_SFIXED64: result |= FIELD_TYPE_SFIXED64; + break; case FieldDescriptorProto::TYPE_SINT32: result |= FIELD_TYPE_SINT32; + break; case FieldDescriptorProto::TYPE_SINT64: result |= FIELD_TYPE_SINT64; + break; default: ; } |