bfc9e71e0d
am: 98fc198f1f Change-Id: Id17adee4b02cc499261f88c3faed15d889f5b6c3
1052 lines
34 KiB
C++
1052 lines
34 KiB
C++
/*
|
|
* 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;
|
|
int unknownFailureCount; // unknown failure == "Process crashed", etc.
|
|
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),
|
|
unknownFailureCount(0),
|
|
actionsWithNoTests(false)
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Command line options.
|
|
*/
|
|
struct Options {
|
|
// For help
|
|
bool runHelp;
|
|
|
|
// For tab completion
|
|
bool runTab;
|
|
string tabPattern;
|
|
|
|
// For build/install/test
|
|
bool noRestart;
|
|
bool reboot;
|
|
vector<Target*> targets;
|
|
|
|
Options();
|
|
~Options();
|
|
};
|
|
|
|
Options::Options()
|
|
:runHelp(false),
|
|
runTab(false),
|
|
noRestart(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);
|
|
|
|
bool IsSuccess();
|
|
|
|
string GetErrorMessage();
|
|
|
|
private:
|
|
TestAction* m_currentAction;
|
|
SessionStatus m_sessionStatus;
|
|
};
|
|
|
|
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 == -1) || (resultCode == -2)) {
|
|
// test failed
|
|
// Note -2 means an assertion failure, and -1 means other exceptions. We just treat them
|
|
// all as "failures".
|
|
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();
|
|
m_sessionStatus = status;
|
|
if (m_currentAction && !IsSuccess()) {
|
|
m_currentAction->target->unknownFailureCount++;
|
|
}
|
|
}
|
|
|
|
void
|
|
TestResults::SetCurrentAction(TestAction* action)
|
|
{
|
|
m_currentAction = action;
|
|
}
|
|
|
|
bool
|
|
TestResults::IsSuccess()
|
|
{
|
|
return m_sessionStatus.result_code() == -1; // Activity.RESULT_OK.
|
|
}
|
|
|
|
string
|
|
TestResults::GetErrorMessage()
|
|
{
|
|
bool found;
|
|
string shortMsg = get_bundle_string(m_sessionStatus.results(), &found, "shortMsg", NULL);
|
|
if (!found) {
|
|
return IsSuccess() ? "" : "Unknown failure";
|
|
}
|
|
return shortMsg;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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, " -n Don't reboot or restart\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, " bit CtsProtoTestCases:android.util.proto.cts.\n");
|
|
fprintf(out, " Builds and installs CtsProtoTestCases.apk, and runs the tests in the java package\n");
|
|
fprintf(out, " \"android.util.proto.cts\".\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 'n':
|
|
options->noRestart = 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);
|
|
delete target;
|
|
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[PATH_MAX];
|
|
if (getcwd(pwd, PATH_MAX) == NULL) {
|
|
fprintf(stderr, "Your pwd is too long.\n");
|
|
exit(1);
|
|
}
|
|
const char* slash = strrchr(pwd, '/');
|
|
if (slash == NULL) {
|
|
slash = "";
|
|
}
|
|
string result(common_base);
|
|
result += slash;
|
|
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);
|
|
}
|
|
}
|
|
|
|
static void
|
|
chdir_or_exit(const char *path) {
|
|
// TODO: print_command("cd", path);
|
|
if (0 != chdir(path)) {
|
|
print_error("Error: Could not chdir: %s", path);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Run the build, install, and test actions.
|
|
*/
|
|
bool
|
|
run_phases(vector<Target*> targets, const Options& options)
|
|
{
|
|
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);
|
|
|
|
chdir_or_exit(buildTop.c_str());
|
|
|
|
const string buildDevice = get_build_var("TARGET_DEVICE", false);
|
|
const string buildId = get_build_var("BUILD_ID", false);
|
|
const string buildOut = get_out_dir();
|
|
|
|
// 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
|
|
if (!options.noRestart) {
|
|
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 (!options.noRestart) {
|
|
if (options.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", "-g", 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);
|
|
}
|
|
if (!testResults.IsSuccess()) {
|
|
printf("\n%sTest didn't finish successfully: %s%s\n", g_escapeRedBold,
|
|
testResults.GetErrorMessage().c_str(), g_escapeEndColor);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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
|
|
bool hasErrors = false;
|
|
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->unknownFailureCount > 0) {
|
|
printf(" %sUnknown failure, see above message.%s\n",
|
|
g_escapeRedBold, g_escapeEndColor);
|
|
hasErrors = true;
|
|
} else if (target->actionsWithNoTests) {
|
|
printf(" %s%d passed, %d failed%s\n", g_escapeYellowBold,
|
|
target->testPassCount, target->testFailCount, g_escapeEndColor);
|
|
hasErrors = true;
|
|
} else if (target->testFailCount > 0) {
|
|
printf(" %d passed, %s%d failed%s\n", target->testPassCount,
|
|
g_escapeRedBold, target->testFailCount, g_escapeEndColor);
|
|
hasErrors = true;
|
|
} 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);
|
|
return !hasErrors;
|
|
}
|
|
|
|
/**
|
|
* 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_or_exit(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
|
|
exit(run_phases(options.targets, options) ? 0 : 1);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|