Add the ability to run each test in isolation.

This adds a new gtest main that will run each test in its own forked
process. This can avoid problems where some tests do not clean up after
themselves, or a test modifies global state that can change the behavior
of another test.

This is only supported on device and linux.

Test: Passes extensive unit tests.
Change-Id: Iab29bf60b9f5cd9c1af67e85f502ada7efe24b18
diff --git a/Options.cpp b/Options.cpp
new file mode 100644
index 0000000..4b203af
--- /dev/null
+++ b/Options.cpp
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2017 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 <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <cctype>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <android-base/parseint.h>
+#include <gtest/gtest.h>
+
+#include "Options.h"
+
+namespace android {
+namespace gtest_extras {
+
+// The total time each test can run before timing out and being killed.
+constexpr uint64_t kDefaultDeadlineThresholdMs = 90000;
+
+// The total time each test can run before a warning is issued.
+constexpr uint64_t kDefaultSlowThresholdMs = 2000;
+
+const std::unordered_map<std::string, Options::ArgInfo> Options::kArgs = {
+    {"deadline_threshold_ms", {FLAG_TAKES_VALUE, &Options::SetThreshold}},
+    {"slow_threshold_ms", {FLAG_TAKES_VALUE, &Options::SetThreshold}},
+    {"gtest_format", {FLAG_NONE, &Options::SetBool}},
+    {"gtest_list_tests", {FLAG_NONE, &Options::SetBool}},
+    {"gtest_filter", {FLAG_ENVIRONMENT_VARIABLE | FLAG_TAKES_VALUE, &Options::SetString}},
+    {
+        "gtest_repeat",
+        {FLAG_ENVIRONMENT_VARIABLE | FLAG_TAKES_VALUE, &Options::SetIterations},
+    },
+    {"gtest_output", {FLAG_ENVIRONMENT_VARIABLE | FLAG_TAKES_VALUE, &Options::SetXmlFile}},
+    {"gtest_print_time", {FLAG_ENVIRONMENT_VARIABLE | FLAG_TAKES_VALUE, &Options::SetPrintTime}},
+    {
+        "gtest_also_run_disabled_tests",
+        {FLAG_ENVIRONMENT_VARIABLE | FLAG_CHILD, &Options::SetBool},
+    },
+    {"gtest_color",
+     {FLAG_ENVIRONMENT_VARIABLE | FLAG_TAKES_VALUE | FLAG_CHILD, &Options::SetString}},
+    {"gtest_death_test_style",
+     {FLAG_ENVIRONMENT_VARIABLE | FLAG_TAKES_VALUE | FLAG_CHILD, nullptr}},
+    {"gtest_break_on_failure", {FLAG_ENVIRONMENT_VARIABLE | FLAG_INCOMPATIBLE, nullptr}},
+    {"gtest_catch_exceptions", {FLAG_ENVIRONMENT_VARIABLE | FLAG_INCOMPATIBLE, nullptr}},
+    {"gtest_random_seed", {FLAG_ENVIRONMENT_VARIABLE | FLAG_INCOMPATIBLE, nullptr}},
+    {"gtest_shuffle", {FLAG_ENVIRONMENT_VARIABLE | FLAG_INCOMPATIBLE, nullptr}},
+    {"gtest_stream_result_to", {FLAG_ENVIRONMENT_VARIABLE | FLAG_INCOMPATIBLE, nullptr}},
+    {"gtest_throw_on_failure", {FLAG_ENVIRONMENT_VARIABLE | FLAG_INCOMPATIBLE, nullptr}},
+};
+
+static void PrintError(const std::string& arg, std::string msg, bool from_env) {
+  if (from_env) {
+    std::string variable(arg);
+    std::transform(variable.begin(), variable.end(), variable.begin(),
+                   [](char c) { return std::toupper(c); });
+    printf("env[%s] %s\n", variable.c_str(), msg.c_str());
+  } else if (arg[0] == '-') {
+    printf("%s %s\n", arg.c_str(), msg.c_str());
+  } else {
+    printf("--%s %s\n", arg.c_str(), msg.c_str());
+  }
+}
+
+template <typename IntType>
+static bool GetNumeric(const char* arg, const char* value, IntType* numeric_value, bool from_env) {
+  if (!android::base::ParseInt<IntType>(value, numeric_value)) {
+    if (errno == ERANGE) {
+      PrintError(arg, std::string("value overflows (") + value + ")", from_env);
+    } else {
+      PrintError(arg, std::string("value is not formatted as a numeric value (") + value + ")",
+                 from_env);
+    }
+    return false;
+  }
+  return true;
+}
+
+bool Options::SetPrintTime(const std::string&, const std::string& value, bool) {
+  if (strtol(value.c_str(), nullptr, 10) == 0) {
+    bools_.find("gtest_print_time")->second = false;
+  }
+  return true;
+}
+
+bool Options::SetThreshold(const std::string& arg, const std::string& value, bool from_env) {
+  uint64_t* numeric = &thresholds_.find(arg)->second;
+  if (!GetNumeric<uint64_t>(arg.c_str(), value.c_str(), numeric, from_env)) {
+    return false;
+  }
+  if (*numeric == 0) {
+    PrintError(arg, "requires a number greater than zero.", from_env);
+    return false;
+  }
+  return true;
+}
+
+bool Options::SetBool(const std::string& arg, const std::string&, bool) {
+  bools_.find(arg)->second = true;
+  return true;
+}
+
+bool Options::SetIterations(const std::string& arg, const std::string& value, bool from_env) {
+  if (!GetNumeric<int>(arg.c_str(), value.c_str(), &num_iterations_, from_env)) {
+    return false;
+  }
+  return true;
+}
+
+bool Options::SetString(const std::string& arg, const std::string& value, bool) {
+  strings_.find(arg)->second = value;
+  return true;
+}
+
+bool Options::SetXmlFile(const std::string& arg, const std::string& value, bool from_env) {
+  if (value.substr(0, 4) != "xml:") {
+    PrintError(arg, "only supports an xml output file.", from_env);
+    return false;
+  }
+  std::string xml_file(value.substr(4));
+  if (xml_file.empty()) {
+    PrintError(arg, "requires a file name after xml:", from_env);
+    return false;
+  }
+  // Need an absolute file.
+  if (xml_file[0] != '/') {
+    char* cwd = getcwd(nullptr, 0);
+    if (cwd == nullptr) {
+      PrintError(arg,
+                 std::string("cannot get absolute pathname, getcwd() is failing: ") +
+                     strerror(errno) + '\n',
+                 from_env);
+      return false;
+    }
+    xml_file = std::string(cwd) + '/' + xml_file;
+    free(cwd);
+  }
+
+  // If the output file is a directory, add the name of a file.
+  if (xml_file.back() == '/') {
+    xml_file += "test_details.xml";
+  }
+  strings_.find("xml_file")->second = xml_file;
+  return true;
+}
+
+bool Options::HandleArg(const std::string& arg, const std::string& value, const ArgInfo& info,
+                        bool from_env) {
+  if (info.flags & FLAG_INCOMPATIBLE) {
+    PrintError(arg, "is not compatible with isolation runs.", from_env);
+    return false;
+  }
+
+  if (info.flags & FLAG_TAKES_VALUE) {
+    if (value.empty()) {
+      PrintError(arg, "requires an argument.", from_env);
+      return false;
+    }
+
+    if (info.func != nullptr && !(this->*(info.func))(arg, value, from_env)) {
+      return false;
+    }
+  } else if (!value.empty()) {
+    PrintError(arg, "does not take an argument.", from_env);
+    return false;
+  } else if (info.func != nullptr) {
+    return (this->*(info.func))(arg, value, from_env);
+  }
+  return true;
+}
+
+bool Options::Process(const std::vector<const char*>& args, std::vector<const char*>* child_args) {
+  // Initialize the variables.
+  job_count_ = static_cast<size_t>(sysconf(_SC_NPROCESSORS_ONLN));
+  num_iterations_ = ::testing::GTEST_FLAG(repeat);
+  thresholds_.clear();
+  thresholds_["deadline_threshold_ms"] = kDefaultDeadlineThresholdMs;
+  thresholds_["slow_threshold_ms"] = kDefaultSlowThresholdMs;
+  strings_.clear();
+  strings_["gtest_color"] = ::testing::GTEST_FLAG(color);
+  strings_["xml_file"] = ::testing::GTEST_FLAG(output);
+  strings_["gtest_filter"] = "";
+  bools_.clear();
+  bools_["gtest_print_time"] = ::testing::GTEST_FLAG(print_time);
+  bools_["gtest_format"] = false;
+  bools_["gtest_also_run_disabled_tests"] = ::testing::GTEST_FLAG(also_run_disabled_tests);
+  bools_["gtest_list_tests"] = false;
+
+  child_args->clear();
+
+  // Loop through all of the possible environment variables.
+  for (const auto& entry : kArgs) {
+    if (entry.second.flags & FLAG_ENVIRONMENT_VARIABLE) {
+      std::string variable(entry.first);
+      std::transform(variable.begin(), variable.end(), variable.begin(),
+                     [](char c) { return std::toupper(c); });
+      char* env = getenv(variable.c_str());
+      if (env == nullptr) {
+        continue;
+      }
+      std::string value(env);
+      if (!HandleArg(entry.first, value, entry.second, true)) {
+        return false;
+      }
+    }
+  }
+
+  child_args->push_back(args[0]);
+
+  // Assumes the first value is not an argument, so skip it.
+  for (size_t i = 1; i < args.size(); i++) {
+    // Special handle of -j or -jXX.
+    if (strncmp(args[i], "-j", 2) == 0) {
+      const char* value = &args[i][2];
+      if (*value == '\0') {
+        // Get the next argument.
+        if (i == args.size() - 1) {
+          printf("-j requires an argument.\n");
+          return false;
+        }
+        i++;
+        value = args[i];
+      }
+      if (!GetNumeric<size_t>("-j", value, &job_count_, false)) {
+        return false;
+      }
+    } else if (strncmp("--", args[i], 2) == 0) {
+      // See if this is a name=value argument.
+      std::string name;
+      std::string value;
+      const char* equal = strchr(args[i], '=');
+      if (equal != nullptr) {
+        name = std::string(&args[i][2], static_cast<size_t>(equal - args[i]) - 2);
+        value = equal + 1;
+      } else {
+        name = args[i] + 2;
+      }
+      auto entry = kArgs.find(name);
+      if (entry == kArgs.end()) {
+        printf("Unknown argument: %s\n", args[i]);
+        return false;
+      }
+
+      if (entry->second.flags & FLAG_CHILD) {
+        child_args->push_back(args[i]);
+      }
+
+      if (!HandleArg(name, value, entry->second)) {
+        return false;
+      }
+    } else if (args[i][0] == '-') {
+      printf("Unknown argument: %s\n", args[i]);
+      return false;
+    } else {
+      printf("Unexpected argument '%s'\n", args[i]);
+      return false;
+    }
+  }
+  return true;
+}
+
+}  // namespace gtest_extras
+}  // namespace android