Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0578cbc6 authored by Joe Onorato's avatar Joe Onorato
Browse files

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
parent f0c71982
Loading
Loading
Loading
Loading

tools/bit/Android.mk

0 → 100644
+43 −0
Original line number Diff line number Diff line
#
# Copyright (C) 2015 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.
#
LOCAL_PATH:= $(call my-dir)

# ==========================================================
# Build the host executable: protoc-gen-javastream
# ==========================================================
include $(CLEAR_VARS)

LOCAL_MODULE := bit

LOCAL_SRC_FILES := \
    aapt.cpp \
    adb.cpp \
    command.cpp \
    main.cpp \
    make.cpp \
    print.cpp \
    util.cpp

LOCAL_STATIC_LIBRARIES := \
    libexpat \
    libinstrumentation \
    libjsoncpp

LOCAL_SHARED_LIBRARIES := \
    libprotobuf-cpp-full

include $(BUILD_HOST_EXECUTABLE)

tools/bit/aapt.cpp

0 → 100644
+270 −0
Original line number Diff line number Diff line
/*
 * 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 "command.h"
#include "print.h"
#include "util.h"

#include <regex>

const regex NS_REGEX("( *)N: ([^=]+)=(.*)");
const regex ELEMENT_REGEX("( *)E: ([^ ]+) \\(line=(\\d+)\\)");
const regex ATTR_REGEX("( *)A: ([^\\(=]+)[^=]*=\"([^\"]+)\".*");

const string ANDROID_NS("http://schemas.android.com/apk/res/android");

bool
Apk::HasActivity(const string& className)
{
    string fullClassName = full_class_name(package, className);
    const size_t N = activities.size();
    for (size_t i=0; i<N; i++) {
        if (activities[i] == fullClassName) {
            return true;
        }
    }
    return false;
}

struct Attribute {
    string ns;
    string name;
    string value;
};

struct Element {
    Element* parent;
    string ns;
    string name;
    int lineno;
    vector<Attribute> attributes;
    vector<Element*> children;

    /**
     * Indentation in the xmltree dump. Might not be equal to the distance
     * from the root because namespace rows (scopes) have their own indentation.
     */
    int depth;

    Element();
    ~Element();

    string GetAttr(const string& ns, const string& name) const;
    void FindElements(const string& ns, const string& name, vector<Element*>* result, bool recurse);
    
};

Element::Element()
{
}

Element::~Element()
{
    const size_t N = children.size();
    for (size_t i=0; i<N; i++) {
        delete children[i];
    }
}

string
Element::GetAttr(const string& ns, const string& name) const
{
    const size_t N = attributes.size();
    for (size_t i=0; i<N; i++) {
        const Attribute& attr = attributes[i];
        if (attr.ns == ns && attr.name == name) {
            return attr.value;
        }
    }
    return string();
}

void
Element::FindElements(const string& ns, const string& name, vector<Element*>* result, bool recurse)
{
    const size_t N = children.size();
    for (size_t i=0; i<N; i++) {
        Element* child = children[i];
        if (child->ns == ns && child->name == name) {
            result->push_back(child);
        }
        if (recurse) {
            child->FindElements(ns, name, result, recurse);
        }
    }
}

struct Scope {
    Scope* parent;
    int depth;
    map<string,string> namespaces;

    Scope(Scope* parent, int depth);
};

Scope::Scope(Scope* p, int d)
    :parent(p),
     depth(d)
{
     if (p != NULL) {
         namespaces = p->namespaces;
     }
}


string
full_class_name(const string& packageName, const string& className)
{
    if (className.length() == 0) {
        return "";
    }
    if (className[0] == '.') {
        return packageName + className;
    }
    if (className.find('.') == string::npos) {
        return packageName + "." + className;
    }
    return className;
}

string
pretty_component_name(const string& packageName, const string& className)
{
    if (starts_with(packageName, className)) {
        size_t pn = packageName.length();
        size_t cn = className.length();
        if (cn > pn && className[pn] == '.') {
            return packageName + "/" + string(className, pn, string::npos);
        }
    }
    return packageName + "/" + className;
}

int
inspect_apk(Apk* apk, const string& filename)
{
    // Load the manifest xml
    Command cmd("aapt");
    cmd.AddArg("dump");
    cmd.AddArg("xmltree");
    cmd.AddArg(filename);
    cmd.AddArg("AndroidManifest.xml");

    int err;

    string output = get_command_output(cmd, &err, false);
    check_error(err);

    // Parse the manifest xml
    Scope* scope = new Scope(NULL, -1);
    Element* root = NULL;
    Element* current = NULL;
    vector<string> lines;
    split_lines(&lines, output);
    for (size_t i=0; i<lines.size(); i++) {
        const string& line = lines[i];
        smatch match;
        if (regex_match(line, match, NS_REGEX)) {
            int depth = match[1].length() / 2;
            while (depth < scope->depth) {
                Scope* tmp = scope;
                scope = scope->parent;
                delete tmp;
            }
            scope = new Scope(scope, depth);
            scope->namespaces[match[2]] = match[3];
        } else if (regex_match(line, match, ELEMENT_REGEX)) {
            Element* element = new Element();

            string str = match[2];
            size_t colon = str.find(':');
            if (colon == string::npos) {
                element->name = str;
            } else {
                element->ns = scope->namespaces[string(str, 0, colon)];
                element->name.assign(str, colon+1, string::npos);
            }
            element->lineno = atoi(match[3].str().c_str());
            element->depth = match[1].length() / 2;

            if (root == NULL) {
                current = element;
                root = element;
            } else {
                while (element->depth <= current->depth && current->parent != NULL) {
                    current = current->parent;
                }
                element->parent = current;
                current->children.push_back(element);
                current = element;
            }
        } else if (regex_match(line, match, ATTR_REGEX)) {
            if (current != NULL) {
                Attribute attr;
                string str = match[2];
                size_t colon = str.find(':');
                if (colon == string::npos) {
                    attr.name = str;
                } else {
                    attr.ns = scope->namespaces[string(str, 0, colon)];
                    attr.name.assign(str, colon+1, string::npos);
                }
                attr.value = match[3];
                current->attributes.push_back(attr);
            }
        }
    }
    while (scope != NULL) {
        Scope* tmp = scope;
        scope = scope->parent;
        delete tmp;
    }

    // Package name
    apk->package = root->GetAttr("", "package");
    if (apk->package.size() == 0) {
        print_error("%s:%d: Manifest root element doesn't contain a package attribute",
                filename.c_str(), root->lineno);
        delete root;
        return 1;
    }

    // Instrumentation runner
    vector<Element*> instrumentation;
    root->FindElements("", "instrumentation", &instrumentation, true);
    if (instrumentation.size() > 0) {
        // TODO: How could we deal with multiple instrumentation tags?
        // We'll just pick the first one.
        apk->runner = instrumentation[0]->GetAttr(ANDROID_NS, "name");
    }

    // Activities
    vector<Element*> activities;
    root->FindElements("", "activity", &activities, true);
    for (size_t i=0; i<activities.size(); i++) {
        string name = activities[i]->GetAttr(ANDROID_NS, "name");
        if (name.size() == 0) {
            continue;
        }
        apk->activities.push_back(full_class_name(apk->package, name));
    }

    delete root;
    return 0;
}

tools/bit/aapt.h

0 → 100644
+39 −0
Original line number Diff line number Diff line
/*
 * 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.
 */

#ifndef AAPT_H
#define AAPT_H

#include <string>
#include <vector>

using namespace std;

struct Apk
{
    string package;
    string runner;
    vector<string> activities;

    bool HasActivity(const string& className);
};

string full_class_name(const string& packageName, const string& className);
string pretty_component_name(const string& packageName, const string& className);

int inspect_apk(Apk* apk, const string& filename);

#endif // AAPT_H

tools/bit/adb.cpp

0 → 100644
+463 −0
Original line number Diff line number Diff line
/*
 * 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();
        execvpe(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;
}

tools/bit/adb.h

0 → 100644
+47 −0
Original line number Diff line number Diff line
/*
 * 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.
 */

#ifndef ADB_H
#define ADB_H

#include "instrumentation_data.pb.h"

#include <string>

using namespace android::am;
using namespace google::protobuf;
using namespace std;

class InstrumentationCallbacks {
public:
    virtual void OnTestStatus(TestStatus& status) = 0;
    virtual void OnSessionStatus(SessionStatus& status) = 0;
};

int run_adb(const char* first, ...);

string get_system_property(const string& name, int* err);

int run_instrumentation_test(const string& packageName, const string& runner,
        const string& className, InstrumentationCallbacks* callbacks);

string get_bundle_string(const ResultsBundle& bundle, bool* found, ...);
int32_t get_bundle_int(const ResultsBundle& bundle, bool* found, ...);
float get_bundle_float(const ResultsBundle& bundle, bool* found, ...);
double get_bundle_double(const ResultsBundle& bundle, bool* found, ...);
int64_t get_bundle_long(const ResultsBundle& bundle, bool* found, ...);

#endif // ADB_H
Loading