[iquery] Fix goldens test to work with ASAN.

Our loading of iquery and sh from the iquery goldens test is not
compatible with ASAN, which requires all binaries to be packaged
hermetically with the test package.

While iquery can be included this way, sh cannot because it is a system
uapp in Zircon.

This change fixes the problem by making the following changes:
- Package iquery with the goldens test.
- Add an option to iquery to first change directories to a specific
location (one of the uses of sh).
- Change the golden test to perform the ID substitution necessary for
the outputs to match the golden file (the other use of sh, which piped
output through sed).
- Change all golden file input strings to just provide arguments to
iquery, not any additional shell commands.

CF-586: #done

Change-Id: I209318e9ceed4afb6714b1f03a91447b4cd16a19
diff --git a/garnet/bin/iquery/main.cc b/garnet/bin/iquery/main.cc
index 478dbb0..a12019f 100644
--- a/garnet/bin/iquery/main.cc
+++ b/garnet/bin/iquery/main.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include <iostream>
+#include <unistd.h>
 
 #include <lib/fxl/command_line.h>
 #include <lib/fxl/log_settings_command_line.h>
@@ -21,6 +22,12 @@
     return 1;
   }
 
+  if (!options.chdir.empty()) {
+    if (chdir(options.chdir.c_str()) != 0) {
+      FXL_LOG(ERROR) << "Failed to chdir to " << options.chdir;
+    }
+  }
+
   if (command_line.HasOption("help") || options.paths.size() == 0) {
     options.Usage(command_line.argv0());
     return 0;
diff --git a/garnet/bin/iquery/options.cc b/garnet/bin/iquery/options.cc
index 8ced437..28466d8 100644
--- a/garnet/bin/iquery/options.cc
+++ b/garnet/bin/iquery/options.cc
@@ -20,7 +20,7 @@
 
 std::set<std::string> kKnownOptions = {
     "cat", "absolute_paths", "find",    "format", "full_paths", "help",
-    "ls",  "recursive",      "verbose", "quiet",  "log-file",
+    "ls",  "recursive",      "verbose", "quiet",  "log-file",   "dir"
 };
 
 // Validate whether the option is within the defined ones.
@@ -65,6 +65,8 @@
       return;
   }
 
+  command_line.GetOptionValue("dir", &chdir);
+
   if (command_line.HasOption("cat") && !SetMode(command_line, Mode::CAT))
     return;
   else if (command_line.HasOption("find") && !SetMode(command_line, Mode::FIND))
@@ -103,11 +105,14 @@
 void Options::Usage(const std::string& argv0) {
   std::cout << fxl::Substitute(
       R"txt(Usage: $0 (--cat|--find|--ls) [--recursive]
-      [--format=<FORMAT>] [(--full_paths|--absolute_paths)]
+      [--format=<FORMAT>] [(--full_paths|--absolute_paths)] [--dir=<PATH>]
       PATH [...PATH]
 
   Utility for querying exposed object directories.
 
+  Global options:
+  --dir:     Change directory to the given PATH before executing commands.
+
   Mode options:
   --cat:  [DEFAULT] Print the data for the object(s) given by each PATH.
           Specifying --recursive will also output the children for that object.
@@ -119,6 +124,7 @@
   --recursive: Whether iquery should continue inside an object. See each mode's
                description to see how it modifies their behaviors.
 
+  Formatting:
   --format: What formatter to use for output. Available options are:
     - text: [DEFAULT] Simple text output meant for manual inspection.
     - json: JSON format meant for machine consumption.
diff --git a/garnet/bin/iquery/options.h b/garnet/bin/iquery/options.h
index 7ce3a084..be57988 100644
--- a/garnet/bin/iquery/options.h
+++ b/garnet/bin/iquery/options.h
@@ -32,20 +32,34 @@
     ABSOLUTE,  // Absolute path starting from root "/".
   };
 
+  // Directory to change to before executing commands.
+  std::string chdir;
+
+  // The mode of operation.
   Options::Mode mode = Options::Mode::UNSET;
 
+  // Path formatting mode.
   PathFormatting path_format = Options::PathFormatting::NONE;
 
+  // If true, execute mode recursively.
   bool recursive = false;
 
+  // List of paths specified on the command line.
   std::vector<std::string> paths;
 
+  // The type of formatter to use.
   FormatterType formatter_type;
+
+  // Instance of the formatter.
   std::unique_ptr<iquery::Formatter> formatter;
 
+  // Create |Options| by parsing the given command line.
   Options(const fxl::CommandLine& command_line);
 
+  // Returns true if the command line was parsed correctly.
   bool Valid() { return valid_; }
+
+  // Print out usage string to stdout.
   void Usage(const std::string& argv0);
 
  private:
diff --git a/garnet/bin/iquery/testing/BUILD.gn b/garnet/bin/iquery/testing/BUILD.gn
index 4f6d6c3..5e67b95 100644
--- a/garnet/bin/iquery/testing/BUILD.gn
+++ b/garnet/bin/iquery/testing/BUILD.gn
@@ -39,12 +39,16 @@
   deps = [
     ":bin",
     ":test",
+    "//garnet/bin/iquery:bin",
   ]
 
   binaries = [
     {
       name = "iquery_example_component"
     },
+    {
+      name = "iquery"
+    },
   ]
 
   meta = [
diff --git a/garnet/bin/iquery/testing/goldens/cat-recursive-absolute.txt b/garnet/bin/iquery/testing/goldens/cat-recursive-absolute.txt
index 2679eec..1a2be50 100644
--- a/garnet/bin/iquery/testing/goldens/cat-recursive-absolute.txt
+++ b/garnet/bin/iquery/testing/goldens/cat-recursive-absolute.txt
@@ -1,4 +1,4 @@
-iquery --recursive --absolute_paths objects > /tmp/t; sed 's/\/\d*\//\/*\//g' /tmp/t;
+iquery --recursive --absolute_paths objects
 /hub/r/test/*/c/iquery_example_component.cmx/*/out/objects:
   /hub/r/test/*/c/iquery_example_component.cmx/*/out/objects/table0x0:
     Binary: 
diff --git a/garnet/bin/iquery/testing/goldens/cat-recursive-json-absolute.txt b/garnet/bin/iquery/testing/goldens/cat-recursive-json-absolute.txt
index f7f566e..5f05fb6 100644
--- a/garnet/bin/iquery/testing/goldens/cat-recursive-json-absolute.txt
+++ b/garnet/bin/iquery/testing/goldens/cat-recursive-json-absolute.txt
@@ -1,4 +1,4 @@
-iquery --recursive --format=json --absolute_paths objects > /tmp/t; sed 's/\/\d*\//\/*\//g' /tmp/t;
+iquery --recursive --format=json --absolute_paths objects
 [
     {
         "path": "/hub/r/test/*/c/iquery_example_component.cmx/*/out/objects",
@@ -98,4 +98,3 @@
         }
     }
 ]
-
diff --git a/garnet/bin/iquery/testing/goldens/cat-single-absolute.txt b/garnet/bin/iquery/testing/goldens/cat-single-absolute.txt
index 417655d..1f026ab 100644
--- a/garnet/bin/iquery/testing/goldens/cat-single-absolute.txt
+++ b/garnet/bin/iquery/testing/goldens/cat-single-absolute.txt
@@ -1,5 +1,5 @@
 # Absolute paths gives the full path to each object from the root directory.
-iquery --absolute_paths objects/table0x0 > /tmp/t; sed 's/\/\d*\//\/*\//g' /tmp/t;
+iquery --absolute_paths objects/table0x0
 /hub/r/test/*/c/iquery_example_component.cmx/*/out/objects/table0x0:
   Binary: 
 0000  05 01 02                                          ...              = Binary: 
diff --git a/garnet/bin/iquery/testing/goldens/ls-json-absolute.txt b/garnet/bin/iquery/testing/goldens/ls-json-absolute.txt
index 6e55dde..3b0342e 100644
--- a/garnet/bin/iquery/testing/goldens/ls-json-absolute.txt
+++ b/garnet/bin/iquery/testing/goldens/ls-json-absolute.txt
@@ -1,4 +1,4 @@
-iquery --ls --format=json --absolute_paths objects/table0x0 > /tmp/t; sed 's/\/\d*\//\/*\//g' /tmp/t;
+iquery --ls --format=json --absolute_paths objects/table0x0
 [
     "/hub/r/test/*/c/iquery_example_component.cmx/*/out/objects/table0x0/row0x1",
     "/hub/r/test/*/c/iquery_example_component.cmx/*/out/objects/table0x0/row0x11",
diff --git a/garnet/bin/iquery/testing/test.cc b/garnet/bin/iquery/testing/test.cc
index 002846d..c652dd5 100644
--- a/garnet/bin/iquery/testing/test.cc
+++ b/garnet/bin/iquery/testing/test.cc
@@ -7,6 +7,7 @@
 #include <lib/async/default.h>
 #include <lib/fdio/spawn.h>
 #include <lib/fdio/util.h>
+#include <regex>
 
 #include "gmock/gmock.h"
 #include "lib/component/cpp/environment_services_helper.h"
@@ -22,6 +23,7 @@
 namespace {
 
 constexpr char kGoldenPath[] = "/pkg/data/iquery_goldens";
+constexpr char kBinPrefix[] = "/pkg/bin";
 
 using component::testing::EnclosingEnvironment;
 using ::testing::StartsWith;
@@ -111,14 +113,22 @@
     };
 
     // Run:
-    // /boot/bin/sh -c "cd <hub>; /system/bin/iquery <args>"
-    command_line =
-        fxl::Substitute("cd $0; /bin/$1", hub_directory_path_, command_line);
-    const char* argv[] = {"sh", "-c", command_line.c_str(), nullptr};
+    // iquery --dir=<hub> <args>
+    auto args = fxl::SplitStringCopy(command_line, " ", fxl::kKeepWhitespace,
+                                     fxl::kSplitWantAll);
+    args.insert(args.begin() + 1,
+                fxl::Substitute("--dir=$0", hub_directory_path_));
+    auto command = fxl::Substitute("$0/$1", kBinPrefix, args[0]);
+
+    const char* argv[args.size() + 1];
+    argv[args.size()] = nullptr;
+    for (size_t i = 0; i < args.size(); i++) {
+      argv[i] = args[i].c_str();
+    }
 
     zx_handle_t proc = ZX_HANDLE_INVALID;
     auto status =
-        fdio_spawn_etc(ZX_HANDLE_INVALID, FDIO_SPAWN_CLONE_ALL, "/boot/bin/sh",
+        fdio_spawn_etc(ZX_HANDLE_INVALID, FDIO_SPAWN_CLONE_ALL, command.c_str(),
                        argv, NULL, 2, actions, &proc, nullptr);
     ASSERT_EQ(status, ZX_OK);
 
@@ -129,6 +139,12 @@
 
     std::string output;
     ASSERT_TRUE(files::ReadFileDescriptorToString(out_fd, &output));
+
+    // Replace path components containing ids with "/*/" so the test does not
+    // need to know specific process or realm ids.
+    std::regex match_ids("\\/\\d+\\/");
+    output = std::regex_replace(output, match_ids, "/*/");
+
     auto output_lines = fxl::SplitStringCopy(output, "\n", fxl::kKeepWhitespace,
                                              fxl::kSplitWantAll);