You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
475 lines
11 KiB
475 lines
11 KiB
/*
|
|
* 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 = false;
|
|
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");
|
|
const int classLen = className.length();
|
|
if (classLen > 0) {
|
|
if (classLen > 1 && className[classLen - 1] == '.') {
|
|
cmd.AddArg("-e");
|
|
cmd.AddArg("package");
|
|
|
|
// "am" actually accepts without removing the last ".", but for cleanlines...
|
|
cmd.AddArg(className.substr(0, classLen - 1));
|
|
} else {
|
|
cmd.AddArg("-e");
|
|
cmd.AddArg("class");
|
|
cmd.AddArg(className);
|
|
}
|
|
}
|
|
cmd.AddArg(packageName + "/" + runner);
|
|
|
|
print_command(cmd);
|
|
|
|
int fds[2];
|
|
if (0 != pipe(fds)) {
|
|
return errno;
|
|
}
|
|
|
|
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();
|
|
exec_with_path_search(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;
|
|
}
|
|
|