summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/build/fuchsia/run_test_package.py
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/build/fuchsia/run_test_package.py')
-rw-r--r--third_party/libwebrtc/build/fuchsia/run_test_package.py278
1 files changed, 278 insertions, 0 deletions
diff --git a/third_party/libwebrtc/build/fuchsia/run_test_package.py b/third_party/libwebrtc/build/fuchsia/run_test_package.py
new file mode 100644
index 0000000000..7e93461027
--- /dev/null
+++ b/third_party/libwebrtc/build/fuchsia/run_test_package.py
@@ -0,0 +1,278 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Contains a helper function for deploying and executing a packaged
+executable on a Target."""
+
+from __future__ import print_function
+
+import common
+import hashlib
+import logging
+import multiprocessing
+import os
+import re
+import select
+import subprocess
+import sys
+import threading
+import uuid
+
+from symbolizer import BuildIdsPaths, RunSymbolizer, SymbolizerFilter
+
+FAR = common.GetHostToolPathFromPlatform('far')
+
+# Amount of time to wait for the termination of the system log output thread.
+_JOIN_TIMEOUT_SECS = 5
+
+
+def _AttachKernelLogReader(target):
+ """Attaches a kernel log reader as a long-running SSH task."""
+
+ logging.info('Attaching kernel logger.')
+ return target.RunCommandPiped(['dlog', '-f'],
+ stdin=open(os.devnull, 'r'),
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+
+
+class SystemLogReader(object):
+ """Collects and symbolizes Fuchsia system log to a file."""
+
+ def __init__(self):
+ self._listener_proc = None
+ self._symbolizer_proc = None
+ self._system_log = None
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ """Stops the system logging processes and closes the output file."""
+ if self._symbolizer_proc:
+ self._symbolizer_proc.kill()
+ if self._listener_proc:
+ self._listener_proc.kill()
+ if self._system_log:
+ self._system_log.close()
+
+ def Start(self, target, package_paths, system_log_file):
+ """Start a system log reader as a long-running SSH task."""
+ logging.debug('Writing fuchsia system log to %s' % system_log_file)
+
+ self._listener_proc = target.RunCommandPiped(['log_listener'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+
+ self._system_log = open(system_log_file, 'w', buffering=1)
+ self._symbolizer_proc = RunSymbolizer(self._listener_proc.stdout,
+ self._system_log,
+ BuildIdsPaths(package_paths))
+
+
+class MergedInputStream(object):
+ """Merges a number of input streams into a UNIX pipe on a dedicated thread.
+ Terminates when the file descriptor of the primary stream (the first in
+ the sequence) is closed."""
+
+ def __init__(self, streams):
+ assert len(streams) > 0
+ self._streams = streams
+ self._output_stream = None
+ self._thread = None
+
+ def Start(self):
+ """Returns a pipe to the merged output stream."""
+
+ read_pipe, write_pipe = os.pipe()
+
+ self._output_stream = os.fdopen(write_pipe, 'wb', 1)
+ self._thread = threading.Thread(target=self._Run)
+ self._thread.start()
+
+ return os.fdopen(read_pipe, 'r')
+
+ def _Run(self):
+ streams_by_fd = {}
+ primary_fd = self._streams[0].fileno()
+ for s in self._streams:
+ streams_by_fd[s.fileno()] = s
+
+ # Set when the primary FD is closed. Input from other FDs will continue to
+ # be processed until select() runs dry.
+ flush = False
+
+ # The lifetime of the MergedInputStream is bound to the lifetime of
+ # |primary_fd|.
+ while primary_fd:
+ # When not flushing: block until data is read or an exception occurs.
+ rlist, _, xlist = select.select(streams_by_fd, [], streams_by_fd)
+
+ if len(rlist) == 0 and flush:
+ break
+
+ for fileno in xlist:
+ del streams_by_fd[fileno]
+ if fileno == primary_fd:
+ primary_fd = None
+
+ for fileno in rlist:
+ line = streams_by_fd[fileno].readline()
+ if line:
+ self._output_stream.write(line)
+ else:
+ del streams_by_fd[fileno]
+ if fileno == primary_fd:
+ primary_fd = None
+
+ # Flush the streams by executing nonblocking reads from the input file
+ # descriptors until no more data is available, or all the streams are
+ # closed.
+ while streams_by_fd:
+ rlist, _, _ = select.select(streams_by_fd, [], [], 0)
+
+ if not rlist:
+ break
+
+ for fileno in rlist:
+ line = streams_by_fd[fileno].readline()
+ if line:
+ self._output_stream.write(line)
+ else:
+ del streams_by_fd[fileno]
+
+
+def _GetComponentUri(package_name):
+ return 'fuchsia-pkg://fuchsia.com/%s#meta/%s.cmx' % (package_name,
+ package_name)
+
+
+class RunTestPackageArgs:
+ """RunTestPackage() configuration arguments structure.
+
+ code_coverage: If set, the test package will be run via 'runtests', and the
+ output will be saved to /tmp folder on the device.
+ system_logging: If set, connects a system log reader to the target.
+ test_realm_label: Specifies the realm name that run-test-component should use.
+ This must be specified if a filter file is to be set, or a results summary
+ file fetched after the test suite has run.
+ use_run_test_component: If True then the test package will be run hermetically
+ via 'run-test-component', rather than using 'run'.
+ """
+
+ def __init__(self):
+ self.code_coverage = False
+ self.system_logging = False
+ self.test_realm_label = None
+ self.use_run_test_component = False
+
+ @staticmethod
+ def FromCommonArgs(args):
+ run_test_package_args = RunTestPackageArgs()
+ run_test_package_args.code_coverage = args.code_coverage
+ run_test_package_args.system_logging = args.include_system_logs
+ return run_test_package_args
+
+
+def _DrainStreamToStdout(stream, quit_event):
+ """Outputs the contents of |stream| until |quit_event| is set."""
+
+ while not quit_event.is_set():
+ rlist, _, _ = select.select([stream], [], [], 0.1)
+ if rlist:
+ line = rlist[0].readline()
+ if not line:
+ return
+ print(line.rstrip())
+
+
+def RunTestPackage(output_dir, target, package_paths, package_name,
+ package_args, args):
+ """Installs the Fuchsia package at |package_path| on the target,
+ executes it with |package_args|, and symbolizes its output.
+
+ output_dir: The path containing the build output files.
+ target: The deployment Target object that will run the package.
+ package_paths: The paths to the .far packages to be installed.
+ package_name: The name of the primary package to run.
+ package_args: The arguments which will be passed to the Fuchsia process.
+ args: RunTestPackageArgs instance configuring how the package will be run.
+
+ Returns the exit code of the remote package process."""
+
+ system_logger = (_AttachKernelLogReader(target)
+ if args.system_logging else None)
+ try:
+ if system_logger:
+ # Spin up a thread to asynchronously dump the system log to stdout
+ # for easier diagnoses of early, pre-execution failures.
+ log_output_quit_event = multiprocessing.Event()
+ log_output_thread = threading.Thread(target=lambda: _DrainStreamToStdout(
+ system_logger.stdout, log_output_quit_event))
+ log_output_thread.daemon = True
+ log_output_thread.start()
+
+ with target.GetPkgRepo():
+ target.InstallPackage(package_paths)
+
+ if system_logger:
+ log_output_quit_event.set()
+ log_output_thread.join(timeout=_JOIN_TIMEOUT_SECS)
+
+ logging.info('Running application.')
+
+ # TODO(crbug.com/1156768): Deprecate runtests.
+ if args.code_coverage:
+ # runtests requires specifying an output directory and a double dash
+ # before the argument list.
+ command = ['runtests', '-o', '/tmp', _GetComponentUri(package_name)]
+ if args.test_realm_label:
+ command += ['--realm-label', args.test_realm_label]
+ command += ['--']
+ elif args.use_run_test_component:
+ command = ['run-test-component']
+ if args.test_realm_label:
+ command += ['--realm-label=%s' % args.test_realm_label]
+ command.append(_GetComponentUri(package_name))
+ else:
+ command = ['run', _GetComponentUri(package_name)]
+
+ command.extend(package_args)
+
+ process = target.RunCommandPiped(command,
+ stdin=open(os.devnull, 'r'),
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+
+ if system_logger:
+ output_stream = MergedInputStream(
+ [process.stdout, system_logger.stdout]).Start()
+ else:
+ output_stream = process.stdout
+
+ # Run the log data through the symbolizer process.
+ output_stream = SymbolizerFilter(output_stream,
+ BuildIdsPaths(package_paths))
+
+ for next_line in output_stream:
+ # TODO(crbug/1198733): Switch to having stream encode to utf-8 directly
+ # once we drop Python 2 support.
+ print(next_line.encode('utf-8').rstrip())
+
+ process.wait()
+ if process.returncode == 0:
+ logging.info('Process exited normally with status code 0.')
+ else:
+ # The test runner returns an error status code if *any* tests fail,
+ # so we should proceed anyway.
+ logging.warning('Process exited with status code %d.' %
+ process.returncode)
+
+ finally:
+ if system_logger:
+ logging.info('Terminating kernel log reader.')
+ log_output_quit_event.set()
+ log_output_thread.join()
+ system_logger.kill()
+
+ return process.returncode