summaryrefslogtreecommitdiff
path: root/tools/bit/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/bit/main.cpp')
-rw-r--r--tools/bit/main.cpp981
1 files changed, 981 insertions, 0 deletions
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;
+}
+