[Fuchsia] Symbolize stderr in blink web tests

Previously call stacks from crashed web tests wasn't symbolized properly.
Updated the scripts to run the symbolizer as a stderr filter, which
makes it easier to debug layout test crashes.

Also fixed file descriptor leak in run_package.py

Change-Id: I2622865ab930535567b16e70ca9e50610a93761f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1584779
Commit-Queue: Sergey Ulanov <sergeyu@chromium.org>
Reviewed-by: Kevin Marshall <kmarshall@chromium.org>
Reviewed-by: Dirk Pranke <dpranke@chromium.org>
Cr-Commit-Position: refs/heads/master@{#654220}
diff --git a/build/fuchsia/run_package.py b/build/fuchsia/run_package.py
index e8ea07d..638d3b7 100644
--- a/build/fuchsia/run_package.py
+++ b/build/fuchsia/run_package.py
@@ -42,16 +42,20 @@
   def __init__(self, streams):
     assert len(streams) > 0
     self._streams = streams
-    self._read_pipe, write_pipe = os.pipe()
+    self._output_stream = None
+    self._thread = None
+
+  def Start(self):
+    """Returns a pipe to the merged output stream."""
+
+    read_pipe, write_pipe = os.pipe()
+
     # Disable buffering for the stream to make sure there is no delay in logs.
     self._output_stream = os.fdopen(write_pipe, 'w', 0)
     self._thread = threading.Thread(target=self._Run)
-
-  def Start(self):
-    """Returns a file descriptor to the merged output stream."""
-
     self._thread.start();
-    return self._read_pipe
+
+    return os.fdopen(read_pipe, 'r')
 
   def _Run(self):
     streams_by_fd = {}
@@ -190,17 +194,17 @@
                                      stderr=subprocess.STDOUT)
 
     if system_logger:
-      output_fd = MergedInputStream([process.stdout,
-                                       system_logger.stdout]).Start()
+      output_stream = MergedInputStream([process.stdout,
+                                         system_logger.stdout]).Start()
     else:
-      output_fd = process.stdout.fileno()
+      output_stream = process.stdout
 
     # Run the log data through the symbolizer process.
     build_ids_paths = map(
         lambda package_path: os.path.join(
             os.path.dirname(package_path), 'ids.txt'),
         [package_path] + package_deps)
-    output_stream = SymbolizerFilter(output_fd, build_ids_paths)
+    output_stream = SymbolizerFilter(output_stream, build_ids_paths)
 
     for next_line in output_stream:
       print next_line.rstrip()
diff --git a/build/fuchsia/symbolizer.py b/build/fuchsia/symbolizer.py
index 0b7c39e..18f583f 100644
--- a/build/fuchsia/symbolizer.py
+++ b/build/fuchsia/symbolizer.py
@@ -9,13 +9,13 @@
 from common import SDK_ROOT
 
 
-def SymbolizerFilter(input_fd, build_ids_files):
-  """Symbolizes an output stream from a process.
+def RunSymbolizer(input_pipe, build_ids_files):
+  """Starts a symbolizer process.
 
-  input_fd: A file descriptor of the stream to be symbolized.
+  input_pipe: Input pipe to be symbolized.
   build_ids_file: Path to the ids.txt file which maps build IDs to
                   unstripped binaries on the filesystem.
-  Returns a generator that yields symbolized process output."""
+  Returns a Popen object for the started process."""
 
   llvm_symbolizer_path = os.path.join(SDK_ROOT, os.pardir, os.pardir,
                                       'llvm-build', 'Release+Asserts', 'bin',
@@ -28,11 +28,19 @@
     symbolizer_cmd.extend(['-ids', build_ids_file])
 
   logging.info('Running "%s".' % ' '.join(symbolizer_cmd))
-  symbolizer_proc = subprocess.Popen(
-      symbolizer_cmd,
-      stdout=subprocess.PIPE,
-      stdin=input_fd,
-      close_fds=True)
+  return subprocess.Popen(symbolizer_cmd, stdout=subprocess.PIPE,
+                          stdin=input_pipe, close_fds=True)
+
+
+def SymbolizerFilter(input_pipe, build_ids_files):
+  """Symbolizes an output stream from a process.
+
+  input_pipe: Input pipe to be symbolized.
+  build_ids_file: Path to the ids.txt file which maps build IDs to
+                  unstripped binaries on the filesystem.
+  Returns a generator that yields symbolized process output."""
+
+  symbolizer_proc = RunSymbolizer(input_pipe, build_ids_files)
 
   while True:
     line = symbolizer_proc.stdout.readline()
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/fuchsia.py b/third_party/blink/tools/blinkpy/web_tests/port/fuchsia.py
index dd5b0539..978db91 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/fuchsia.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/fuchsia.py
@@ -48,6 +48,7 @@
 # pylint: disable=invalid-name
 fuchsia_target = None
 qemu_target = None
+symbolizer = None
 # pylint: enable=invalid-name
 
 
@@ -62,7 +63,9 @@
     global fuchsia_target
     import target as fuchsia_target
     global qemu_target
-    import qemu_target as qemu_target
+    import qemu_target
+    global symbolizer
+    import symbolizer
     # pylint: enable=import-error
     # pylint: enable=invalid-name
     # pylint: disable=redefined-outer-name
@@ -266,6 +269,10 @@
     def get_target_host(self):
         return self._target_host
 
+    def get_build_ids_path(self):
+        package_path = self._path_to_driver()
+        return os.path.join(os.path.dirname(package_path), 'ids.txt')
+
 
 class ChromiumFuchsiaDriver(driver.Driver):
     def __init__(self, port, worker_number, no_timeout=False):
@@ -294,6 +301,7 @@
                  treat_no_data_as_crash=False, more_logging=False):
         super(FuchsiaServerProcess, self).__init__(
             port_obj, name, cmd, env, treat_no_data_as_crash, more_logging)
+        self._symbolizer_proc = None
 
     def _start(self):
         if self._proc:
@@ -335,4 +343,15 @@
         proc.stdin.close()
         proc.stdin = stdin_pipe
 
+        # Run symbolizer to filter the stderr stream.
+        self._symbolizer_proc = symbolizer.RunSymbolizer(
+            proc.stderr, [self._port.get_build_ids_path()]);
+        proc.stderr = self._symbolizer_proc.stdout
+
         self._set_proc(proc)
+
+    def stop(self, timeout_secs=0.0):
+        result = super(FuchsiaServerProcess, self).stop(timeout_secs)
+        if self._symbolizer_proc:
+            self._symbolizer_proc.kill()
+        return result