[run_test_component] Allow callers to specify a static realm label.

run_test_component runs each test in a dedicated hermatic realm,
with storage scoped to the lifetime of the test component, and with
a randomly chosen realm name.

Chromium test suites have input and output file parameters, so are
not compatible with transient randomly-named storage paths.

Callers can have tests run with a predictable realm name, and with
the realm's storage left intact after the test has completed, via
a new --realm-label parameter. This is a temporary measure and
will be replaced with more robust primitives for test I/O.

Bug: 44265, chromium:1038786
Change-Id: I515f99c97d58bfe96415f79c686a48029ef8e9a8
diff --git a/garnet/bin/run_test_component/main.cc b/garnet/bin/run_test_component/main.cc
index cce491f..2b5545e 100644
--- a/garnet/bin/run_test_component/main.cc
+++ b/garnet/bin/run_test_component/main.cc
@@ -38,8 +38,7 @@
 
 void PrintUsage() {
   fprintf(stderr, R"(
-Usage: run_test_component <test_url> [arguments...]
-       run_test_component <test_matcher> [arguments...]
+Usage: run_test_component [--static-realm-label=<label>] <test_url>|<test_matcher> [arguments...]
 
        *test_url* takes the form of component manifest URL which uniquely
        identifies a test component. Example:
@@ -53,6 +52,13 @@
        example:
         run_test_component run_test_component_unit
           will match fuchsia-pkg://fuchsia.com/run_test_component_unittests#meta/run_test_component_unittests.cmx and run it.
+
+       By default each test component will be run in an environment with
+       transient storage and a randomly-generated identifier, ensuring that
+       the tests have no persisted side-effects. If --static-realm-label is
+       specified then the test will run in a persisted realm with that label,
+       allowing files to be provide to, or retrieve from, the test, e.g. for
+       diagnostic purposes.
 )");
 }
 
@@ -229,10 +235,18 @@
       test_env_services->AllowParentService(service);
     }
 
-    uint32_t rand;
-    zx_cprng_draw(&rand, sizeof(rand));
-    std::string env_label = fxl::StringPrintf("%s%08x", kEnvPrefix, rand);
-    fuchsia::sys::EnvironmentOptions env_opt{.delete_storage_on_death = true};
+    // By default run tests in a realm with a random name and transient storage.
+    // Callers may specify a static realm label through which to exchange files
+    // with the test component.
+    std::string env_label = std::move(parse_result.realm_label);
+    fuchsia::sys::EnvironmentOptions env_opt;
+    if (env_label.empty()) {
+      uint32_t rand;
+      zx_cprng_draw(&rand, sizeof(rand));
+      env_label = fxl::StringPrintf("%s%08x", kEnvPrefix, rand);
+      env_opt.delete_storage_on_death = true;
+    }
+
     enclosing_env = sys::testing::EnclosingEnvironment::Create(
         std::move(env_label), parent_env, std::move(test_env_services), std::move(env_opt));
     launcher = enclosing_env->launcher_ptr();
diff --git a/garnet/bin/run_test_component/run_test_component.cc b/garnet/bin/run_test_component/run_test_component.cc
index ce982d2..45df9dd 100644
--- a/garnet/bin/run_test_component/run_test_component.cc
+++ b/garnet/bin/run_test_component/run_test_component.cc
@@ -23,16 +23,33 @@
 static constexpr char kComponentIndexerUrl[] =
     "fuchsia-pkg://fuchsia.com/component_index#meta/component_index.cmx";
 
+static constexpr char kLabelArgPrefix[] = "--static-realm-label=";
+
 ParseArgsResult ParseArgs(const std::shared_ptr<sys::ServiceDirectory>& services, int argc,
                           const char** argv) {
   ParseArgsResult result;
   result.error = false;
-  if (argc < 2) {
-    result.error = true;
-    result.error_msg = "Pass atleast one argument";
-    return result;
+  int url_or_matcher_argi = 1;
+
+  std::string url;
+  while (true) {
+    if (argc < url_or_matcher_argi+1) {
+      result.error = true;
+      result.error_msg = "Missing test URL, or matcher argument";
+      return result;
+    }
+
+    std::string argument = argv[url_or_matcher_argi];
+    const size_t kLabelArgPrefixLength = strlen(kLabelArgPrefix);
+    if (argument.substr(0, kLabelArgPrefixLength) == kLabelArgPrefix) {
+      result.realm_label = argument.substr(kLabelArgPrefixLength);
+      url_or_matcher_argi++;
+      continue;
+    }
+
+    url = argument;
+    break;
   }
-  std::string url = argv[1];
 
   if (!component::FuchsiaPkgUrl::IsFuchsiaPkgScheme(url)) {
     fuchsia::sys::LaunchInfo index_launch_info;
@@ -50,7 +67,7 @@
     ComponentIndexSyncPtr index;
     index_provider->Connect(index.NewRequest());
 
-    std::string test_name = argv[1];
+    std::string test_name = url;
     ComponentIndex_FuzzySearch_Result fuzzy_search_result;
     zx_status_t status = index->FuzzySearch(test_name, &fuzzy_search_result);
     if (status != ZX_OK) {
@@ -90,7 +107,7 @@
 
   result.launch_info.url = url;
   result.launch_info.arguments.emplace();
-  for (int i = 2; i < argc; i++) {
+  for (int i = url_or_matcher_argi+1; i < argc; i++) {
     result.launch_info.arguments->push_back(argv[i]);
   }
   return result;
diff --git a/garnet/bin/run_test_component/run_test_component.h b/garnet/bin/run_test_component/run_test_component.h
index d65307d..b33b7f4 100644
--- a/garnet/bin/run_test_component/run_test_component.h
+++ b/garnet/bin/run_test_component/run_test_component.h
@@ -17,6 +17,7 @@
   std::string error_msg;
   fuchsia::sys::LaunchInfo launch_info;
   std::vector<std::string> matching_urls;
+  std::string realm_label;
 };
 
 // Parses args.
diff --git a/garnet/bin/run_test_component/run_test_component_unittest.cc b/garnet/bin/run_test_component/run_test_component_unittest.cc
index 64bc2a2..7f48b02 100644
--- a/garnet/bin/run_test_component/run_test_component_unittest.cc
+++ b/garnet/bin/run_test_component/run_test_component_unittest.cc
@@ -30,6 +30,7 @@
     EXPECT_EQ(component_url, result.launch_info.url);
     EXPECT_EQ(0u, result.launch_info.arguments->size());
     EXPECT_EQ(0u, result.matching_urls.size());
+    EXPECT_EQ("", result.realm_label);
   }
 
   {
@@ -41,6 +42,25 @@
     EXPECT_EQ(2u, result.launch_info.arguments->size());
     EXPECT_EQ(argv[2], result.launch_info.arguments->at(0));
     EXPECT_EQ(argv[3], result.launch_info.arguments->at(1));
+    EXPECT_EQ("", result.realm_label);
+  }
+
+  {
+    const char* argv[] = {kBinName, "--static-realm-label=kittens", component_url, "myarg1", "myarg2"};
+    auto result = ParseArgs(env_services, 5, argv);
+    EXPECT_FALSE(result.error);
+    EXPECT_EQ(component_url, result.launch_info.url);
+    ASSERT_TRUE(result.launch_info.arguments.has_value());
+    EXPECT_EQ(2u, result.launch_info.arguments->size());
+    EXPECT_EQ(argv[3], result.launch_info.arguments->at(0));
+    EXPECT_EQ(argv[4], result.launch_info.arguments->at(1));
+    EXPECT_EQ("kittens", result.realm_label);
+  }
+
+  {
+    const char* argv[] = {kBinName, "--unknown-argument=gives_error", component_url, "myarg1", "myarg2"};
+    auto result = ParseArgs(env_services, 5, argv);
+    EXPECT_TRUE(result.error);
   }
 
   {
@@ -62,6 +82,7 @@
     EXPECT_FALSE(result.error);
     EXPECT_EQ(expected_urls.size(), result.matching_urls.size());
     EXPECT_THAT(result.matching_urls, ::testing::UnorderedElementsAreArray(expected_urls));
+    EXPECT_EQ("", result.realm_label);
   }
 
   {
@@ -75,6 +96,7 @@
     ASSERT_EQ(1u, result.matching_urls.size());
     EXPECT_EQ(result.matching_urls[0], expected_url);
     EXPECT_EQ(expected_url, result.launch_info.url);
+    EXPECT_EQ("", result.realm_label);
   }
 }