Make bit able to run gtest native tests.

The output parsing isn't ideal, so these are a bit more spammy
than I'd like, but at least they build, install and run without
the manual glop.

Test: bit incidentd_test
Change-Id: I3c34a4ebbf661f612b4b0f8b4e05cade8669b5a6
diff --git a/tools/bit/main.cpp b/tools/bit/main.cpp
index 860094ae..082ccf3 100644
--- a/tools/bit/main.cpp
+++ b/tools/bit/main.cpp
@@ -33,6 +33,8 @@
 
 using namespace std;
 
+#define NATIVE_TESTS "NATIVE_TESTS"
+
 /**
  * An entry from the command line for something that will be built, installed,
  * and/or tested.
@@ -132,6 +134,32 @@
 {
 }
 
+struct PushedFile
+{
+    TrackedFile file;
+    string dest;
+
+    PushedFile();
+    PushedFile(const PushedFile& that);
+    PushedFile(const string& filename, const string& dest);
+    ~PushedFile() {};
+};
+
+PushedFile::PushedFile()
+{
+}
+
+PushedFile::PushedFile(const PushedFile& that)
+    :file(that.file),
+     dest(that.dest)
+{
+}
+
+PushedFile::PushedFile(const string& f, const string& d)
+    :file(f),
+     dest(d)
+{
+}
 
 /**
  * Record for an test that is going to be launched.
@@ -658,12 +686,14 @@
     }
 
     // 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/";
+    string deviceTargetPath = buildOut + "/target/product/" + buildDevice;
+    string systemPath = deviceTargetPath + "/system/";
+    string dataPath = deviceTargetPath + "/data/";
     bool syncSystem = false;
     bool alwaysSyncSystem = false;
     vector<string> systemFiles;
     vector<InstallApk> installApks;
+    vector<PushedFile> pushedFiles;
     for (size_t i=0; i<targets.size(); i++) {
         Target* target = targets[i];
         if (target->install) {
@@ -687,6 +717,11 @@
                     installApks.push_back(InstallApk(file, !target->build));
                     continue;
                 }
+                // If it's a native test module, push it.
+                if (target->module.HasClass(NATIVE_TESTS) && starts_with(file, dataPath)) {
+                    string installedPath(file.c_str() + deviceTargetPath.length());
+                    pushedFiles.push_back(PushedFile(file, installedPath));
+                }
             }
         }
     }
@@ -701,6 +736,13 @@
             printf("  %s\n", systemFiles[i].c_str());
         }
     }
+    if (pushedFiles.size() > 0){
+        print_info("Files to push:");
+        for (size_t i=0; i<pushedFiles.size(); i++) {
+            printf("  %s\n", pushedFiles[i].file.filename.c_str());
+            printf("    --> %s\n", pushedFiles[i].dest.c_str());
+        }
+    }
     if (installApks.size() > 0){
         print_info("APKs to install:");
         for (size_t i=0; i<installApks.size(); i++) {
@@ -784,6 +826,25 @@
         }
     }
 
+    // Push files
+    if (pushedFiles.size() > 0) {
+        print_status("Pushing files");
+        for (size_t i=0; i<pushedFiles.size(); i++) {
+            const PushedFile& pushed = pushedFiles[i];
+            string dir = dirname(pushed.dest);
+            if (dir.length() == 0 || dir == "/") {
+                // This isn't really a file inside the data directory. Just skip it.
+                continue;
+            }
+            // TODO: if (!apk.file.fileInfo.exists || apk.file.HasChanged())
+            err = run_adb("shell", "mkdir", "-p", dir.c_str(), NULL);
+            check_error(err);
+            err = run_adb("push", pushed.file.filename.c_str(), pushed.dest.c_str());
+            check_error(err);
+            // pushed.installed = true;
+        }
+    }
+
     // Install APKs
     if (installApks.size() > 0) {
         print_status("Installing APKs");
@@ -804,6 +865,74 @@
     // Actions
     //
 
+    // Whether there have been any tests run, so we can print a summary.
+    bool testsRun = false;
+
+    // Run the native tests.
+    // TODO: We don't have a good way of running these and capturing the output of
+    // them live.  It'll take some work.  On the other hand, if they're gtest tests,
+    // the output of gtest is not completely insane like the text output of the
+    // instrumentation tests.  So for now, we'll just live with that.
+    for (size_t i=0; i<targets.size(); i++) {
+        Target* target = targets[i];
+        if (target->test && target->module.HasClass(NATIVE_TESTS)) {
+            // We don't have a clear signal from the build system which of the installed
+            // files is actually the test, so we guess by looking for one with the same
+            // leaf name as the module that is executable.
+            for (size_t j=0; j<target->module.installed.size(); j++) {
+                string filename = target->module.installed[j];
+                if (!starts_with(filename, dataPath)) {
+                    // Native tests go into the data directory.
+                    continue;
+                }
+                if (leafname(filename) != target->module.name) {
+                    // This isn't the test executable.
+                    continue;
+                }
+                if (!is_executable(filename)) {
+                    continue;
+                }
+                string installedPath(filename.c_str() + deviceTargetPath.length());
+                printf("the magic one is: %s\n", filename.c_str());
+                printf("  and it's installed at: %s\n", installedPath.c_str());
+
+                // Convert bit-style actions to gtest test filter arguments
+                if (target->actions.size() > 0) {
+                    testsRun = true;
+                    target->testActionCount++;
+                    bool runAll = false;
+                    string filterArg("--gtest_filter=");
+                    for (size_t k=0; k<target->actions.size(); k++) {
+                        string actionString = target->actions[k];
+                        if (actionString == "*") {
+                            runAll = true;
+                        } else {
+                            filterArg += actionString;
+                            if (k != target->actions.size()-1) {
+                                // We would otherwise have to worry about this condition
+                                // being true, and appending an extra ':', but we know that
+                                // if the extra action is "*", then we'll just run all and
+                                // won't use filterArg anyway, so just keep this condition
+                                // simple.
+                                filterArg += ':';
+                            }
+                        }
+                    }
+                    if (runAll) {
+                        err = run_adb("shell", installedPath.c_str(), NULL);
+                    } else {
+                        err = run_adb("shell", installedPath.c_str(), filterArg.c_str(), NULL);
+                    }
+                    if (err == 0) {
+                        target->testPassCount++;
+                    } else {
+                        target->testFailCount++;
+                    }
+                }
+            }
+        }
+    }
+
     // Inspect the apks, and figure out what is an activity and what needs a test runner
     bool printedInspecting = false;
     vector<TestAction> testActions;
@@ -872,6 +1001,7 @@
     TestResults testResults;
     if (testActions.size() > 0) {
         print_status("Running tests");
+        testsRun = true;
         for (size_t i=0; i<testActions.size(); i++) {
             TestAction& action = testActions[i];
             testResults.SetCurrentAction(&action);
@@ -969,7 +1099,7 @@
 
     // Tests
     bool hasErrors = false;
-    if (testActions.size() > 0) {
+    if (testsRun) {
         printf("%sRan tests:%s\n", g_escapeBold, g_escapeEndColor);
         size_t maxNameLength = 0;
         for (size_t i=0; i<targets.size(); i++) {
diff --git a/tools/bit/make.cpp b/tools/bit/make.cpp
index 6270913..df64a80 100644
--- a/tools/bit/make.cpp
+++ b/tools/bit/make.cpp
@@ -51,6 +51,18 @@
     return filename + "/.bit_cache";
 }
 
+bool
+Module::HasClass(const string& cl)
+{
+    for (vector<string>::const_iterator c = classes.begin(); c != classes.end(); c++) {
+        if (*c == cl) {
+            return true;
+        }
+    }
+    return false;
+}
+
+
 BuildVars::BuildVars(const string& outDir, const string& buildProduct,
         const string& buildVariant, const string& buildType)
     :m_filename(),
diff --git a/tools/bit/make.h b/tools/bit/make.h
index db0b69f..785912a 100644
--- a/tools/bit/make.h
+++ b/tools/bit/make.h
@@ -29,6 +29,8 @@
     vector<string> classes;
     vector<string> paths;
     vector<string> installed;
+
+    bool HasClass(const string& cl);
 };
 
 /**
diff --git a/tools/bit/util.cpp b/tools/bit/util.cpp
index a502a9d..63399d6 100644
--- a/tools/bit/util.cpp
+++ b/tools/bit/util.cpp
@@ -254,4 +254,42 @@
     return result;
 }
 
+bool
+is_executable(const string& filename)
+{
+    int err;
+    struct stat st;
 
+    err = stat(filename.c_str(), &st);
+    if (err != 0) {
+        return false;
+    }
+
+    return (st.st_mode & S_IXUSR) != 0;
+}
+
+string
+dirname(const string& filename)
+{
+    size_t slash = filename.rfind('/');
+    if (slash == string::npos) {
+        return "";
+    } else if (slash == 0) {
+        return "/";
+    } else {
+        return string(filename, 0, slash);
+    }
+}
+
+string
+leafname(const string& filename)
+{
+    size_t slash = filename.rfind('/');
+    if (slash == string::npos) {
+        return filename;
+    } else if (slash == filename.length() - 1) {
+        return "";
+    } else {
+        return string(filename, slash + 1);
+    }
+}
diff --git a/tools/bit/util.h b/tools/bit/util.h
index 718f147..7ccdab1 100644
--- a/tools/bit/util.h
+++ b/tools/bit/util.h
@@ -79,5 +79,10 @@
 
 string read_file(const string& filename);
 
+bool is_executable(const string& filename);
+
+string dirname(const string& filename);
+string leafname(const string& filename);
+
 #endif // UTIL_H