Add a new build, install, test development (bit) tool
This is a cleaned up version of a utility that I've had sitting around
for a while.
The bit tool can run an android platform build, then sync or install
the outputs of that, and then run instrumentation tests. It's better
than what we usually do:
make && adb shell stop && adb sync system && adb shell start
Here's the help text:
usage: bit OPTIONS PATTERN
Build, sync and test android code.
The -b -i and -t options allow you to specify which phases
you want to run. If none of those options are given, then
all phases are run. If any of these options are provided
then only the listed phases are run.
OPTIONS
-b Run a build
-i Install the targets
-t Run the tests
PATTERN
One or more targets to build, install and test. The target
names are the names that appear in the LOCAL_MODULE or
LOCAL_PACKAGE_NAME variables in Android.mk or Android.bp files.
Building and installing
-----------------------
The modules specified will be built and then installed. If the
files are on the system partition, they will be synced and the
attached device rebooted. If they are APKs that aren't on the
system partition they are installed with adb install.
For example:
bit framework
Builds framework.jar, syncs the system partition and reboots.
bit SystemUI
Builds SystemUI.apk, syncs the system partition and reboots.
bit CtsProtoTestCases
Builds this CTS apk, adb installs it, but does not run any
tests.
Running Unit Tests
------------------
To run a unit test, list the test class names and optionally the
test method after the module.
For example:
bit CtsProtoTestCases:*
Builds this CTS apk, adb installs it, and runs all the tests
contained in that apk.
bit framework CtsProtoTestCases:*
Builds the framework and the apk, syncs and reboots, then
adb installs CtsProtoTestCases.apk, and runs all tests
contained in that apk.
bit CtsProtoTestCases:.ProtoOutputStreamBoolTest
bit CtsProtoTestCases:android.util.proto.cts.ProtoOutputStreamBoolTest
Builds and installs CtsProtoTestCases.apk, and runs all the
tests in the ProtoOutputStreamBoolTest class.
bit CtsProtoTestCases:.ProtoOutputStreamBoolTest\#testWrite
Builds and installs CtsProtoTestCases.apk, and runs the testWrite
test method on that class.
bit CtsProtoTestCases:.ProtoOutputStreamBoolTest\#testWrite,.ProtoOutputStreamBoolTest\#testRepeated
Builds and installs CtsProtoTestCases.apk, and runs the testWrite
and testRepeated test methods on that class.
Launching an Activity
---------------------
To launch an activity, specify the activity class name after
the module name.
For example:
bit StatusBarTest:NotificationBuilderTest
bit StatusBarTest:.NotificationBuilderTest
bit StatusBarTest:com.android.statusbartest.NotificationBuilderTest
Builds and installs StatusBarTest.apk, launches the
com.android.statusbartest/.NotificationBuilderTest activity.
Change-Id: I9cff7a23852fa1a67369e7807f7ae9f6e45d6131
Test: none
2016-10-19 17:03:06 -07:00
|
|
|
/*
|
|
|
|
* 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();
|
2016-10-21 15:18:18 -07:00
|
|
|
exec_with_path_search(prog, argv, env);
|
Add a new build, install, test development (bit) tool
This is a cleaned up version of a utility that I've had sitting around
for a while.
The bit tool can run an android platform build, then sync or install
the outputs of that, and then run instrumentation tests. It's better
than what we usually do:
make && adb shell stop && adb sync system && adb shell start
Here's the help text:
usage: bit OPTIONS PATTERN
Build, sync and test android code.
The -b -i and -t options allow you to specify which phases
you want to run. If none of those options are given, then
all phases are run. If any of these options are provided
then only the listed phases are run.
OPTIONS
-b Run a build
-i Install the targets
-t Run the tests
PATTERN
One or more targets to build, install and test. The target
names are the names that appear in the LOCAL_MODULE or
LOCAL_PACKAGE_NAME variables in Android.mk or Android.bp files.
Building and installing
-----------------------
The modules specified will be built and then installed. If the
files are on the system partition, they will be synced and the
attached device rebooted. If they are APKs that aren't on the
system partition they are installed with adb install.
For example:
bit framework
Builds framework.jar, syncs the system partition and reboots.
bit SystemUI
Builds SystemUI.apk, syncs the system partition and reboots.
bit CtsProtoTestCases
Builds this CTS apk, adb installs it, but does not run any
tests.
Running Unit Tests
------------------
To run a unit test, list the test class names and optionally the
test method after the module.
For example:
bit CtsProtoTestCases:*
Builds this CTS apk, adb installs it, and runs all the tests
contained in that apk.
bit framework CtsProtoTestCases:*
Builds the framework and the apk, syncs and reboots, then
adb installs CtsProtoTestCases.apk, and runs all tests
contained in that apk.
bit CtsProtoTestCases:.ProtoOutputStreamBoolTest
bit CtsProtoTestCases:android.util.proto.cts.ProtoOutputStreamBoolTest
Builds and installs CtsProtoTestCases.apk, and runs all the
tests in the ProtoOutputStreamBoolTest class.
bit CtsProtoTestCases:.ProtoOutputStreamBoolTest\#testWrite
Builds and installs CtsProtoTestCases.apk, and runs the testWrite
test method on that class.
bit CtsProtoTestCases:.ProtoOutputStreamBoolTest\#testWrite,.ProtoOutputStreamBoolTest\#testRepeated
Builds and installs CtsProtoTestCases.apk, and runs the testWrite
and testRepeated test methods on that class.
Launching an Activity
---------------------
To launch an activity, specify the activity class name after
the module name.
For example:
bit StatusBarTest:NotificationBuilderTest
bit StatusBarTest:.NotificationBuilderTest
bit StatusBarTest:com.android.statusbartest.NotificationBuilderTest
Builds and installs StatusBarTest.apk, launches the
com.android.statusbartest/.NotificationBuilderTest activity.
Change-Id: I9cff7a23852fa1a67369e7807f7ae9f6e45d6131
Test: none
2016-10-19 17:03:06 -07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|