blob: fa7d3d4031d41a702703ff292f895480b693f47c [file] [log] [blame]
/*
* 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");
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;
}