summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorphajdan.jr <phajdan.jr@chromium.org>2015-08-21 03:32:19 -0700
committerCommit bot <commit-bot@chromium.org>2015-08-21 10:32:44 +0000
commit51dfdf1b3ea843caf5f2d226b06a5d7c02afe813 (patch)
tree7861e88ef498451643d6d3c5983ff3571cfda02a
parentb5a7881b267d37cc488e63d4076971a9ee87b0a1 (diff)
downloadchromium_src-51dfdf1b3ea843caf5f2d226b06a5d7c02afe813.zip
chromium_src-51dfdf1b3ea843caf5f2d226b06a5d7c02afe813.tar.gz
chromium_src-51dfdf1b3ea843caf5f2d226b06a5d7c02afe813.tar.bz2
runtest.py: replace chromium_utils.RunCommand with simpler subprocess calls
BUG=506498 Review URL: https://codereview.chromium.org/1305753002 Cr-Commit-Position: refs/heads/master@{#344732}
-rw-r--r--infra/scripts/legacy/scripts/common/chromium_utils.py290
-rwxr-xr-xinfra/scripts/legacy/scripts/slave/runtest.py66
-rw-r--r--infra/scripts/legacy/scripts/slave/slave_utils.py3
3 files changed, 32 insertions, 327 deletions
diff --git a/infra/scripts/legacy/scripts/common/chromium_utils.py b/infra/scripts/legacy/scripts/common/chromium_utils.py
index 9371cb7..3aa1e2b 100644
--- a/infra/scripts/legacy/scripts/common/chromium_utils.py
+++ b/infra/scripts/legacy/scripts/common/chromium_utils.py
@@ -180,296 +180,6 @@ def FindUpward(start_dir, *desired_list):
return os.path.join(parent, *desired_list)
-class RunCommandFilter(object):
- """Class that should be subclassed to provide a filter for RunCommand."""
- # Method could be a function
- # pylint: disable=R0201
-
- def FilterLine(self, a_line):
- """Called for each line of input. The \n is included on a_line. Should
- return what is to be recorded as the output for this line. A result of
- None suppresses the line."""
- return a_line
-
- def FilterDone(self, last_bits):
- """Acts just like FilterLine, but is called with any data collected after
- the last newline of the command."""
- return last_bits
-
-
-def RunCommand(command, parser_func=None, filter_obj=None, pipes=None,
- print_cmd=True, timeout=None, max_time=None, **kwargs):
- """Runs the command list, printing its output and returning its exit status.
-
- Prints the given command (which should be a list of one or more strings),
- then runs it and writes its stdout and stderr to the appropriate file handles.
-
- If timeout is set, the process will be killed if output is stopped after
- timeout seconds. If max_time is set, the process will be killed if it runs for
- more than max_time.
-
- If parser_func is not given, the subprocess's output is passed to stdout
- and stderr directly. If the func is given, each line of the subprocess's
- stdout/stderr is passed to the func and then written to stdout.
-
- If filter_obj is given, all output is run through the filter a line
- at a time before it is written to stdout.
-
- We do not currently support parsing stdout and stderr independent of
- each other. In previous attempts, this led to output ordering issues.
- By merging them when either needs to be parsed, we avoid those ordering
- issues completely.
-
- pipes is a list of commands (also a list) that will receive the output of
- the intial command. For example, if you want to run "python a | python b | c",
- the "command" will be set to ['python', 'a'], while pipes will be set to
- [['python', 'b'],['c']]
- """
-
- def TimedFlush(timeout, fh, kill_event):
- """Flush fh every timeout seconds until kill_event is true."""
- while True:
- try:
- fh.flush()
- # File handle is closed, exit.
- except ValueError:
- break
- # Wait for kill signal or timeout.
- if kill_event.wait(timeout):
- break
-
- # TODO(all): nsylvain's CommandRunner in buildbot_slave is based on this
- # method. Update it when changes are introduced here.
- def ProcessRead(readfh, writefh, parser_func=None, filter_obj=None,
- log_event=None):
- writefh.flush()
-
- # Python on Windows writes the buffer only when it reaches 4k. Ideally
- # we would flush a minimum of 10 seconds. However, we only write and
- # flush no more often than 20 seconds to avoid flooding the master with
- # network traffic from unbuffered output.
- kill_event = threading.Event()
- flush_thread = threading.Thread(
- target=TimedFlush, args=(20, writefh, kill_event))
- flush_thread.daemon = True
- flush_thread.start()
-
- try:
- in_byte = readfh.read(1)
- in_line = cStringIO.StringIO()
- while in_byte:
- # Capture all characters except \r.
- if in_byte != '\r':
- in_line.write(in_byte)
-
- # Write and flush on newline.
- if in_byte == '\n':
- if log_event:
- log_event.set()
- if parser_func:
- parser_func(in_line.getvalue().strip())
-
- if filter_obj:
- filtered_line = filter_obj.FilterLine(in_line.getvalue())
- if filtered_line is not None:
- writefh.write(filtered_line)
- else:
- writefh.write(in_line.getvalue())
- in_line = cStringIO.StringIO()
- in_byte = readfh.read(1)
-
- if log_event and in_line.getvalue():
- log_event.set()
-
- # Write remaining data and flush on EOF.
- if parser_func:
- parser_func(in_line.getvalue().strip())
-
- if filter_obj:
- if in_line.getvalue():
- filtered_line = filter_obj.FilterDone(in_line.getvalue())
- if filtered_line is not None:
- writefh.write(filtered_line)
- else:
- if in_line.getvalue():
- writefh.write(in_line.getvalue())
- finally:
- kill_event.set()
- flush_thread.join()
- writefh.flush()
-
- pipes = pipes or []
-
- # Print the given command (which should be a list of one or more strings).
- if print_cmd:
- print '\n' + subprocess.list2cmdline(command) + '\n',
- for pipe in pipes:
- print ' | ' + subprocess.list2cmdline(pipe) + '\n',
-
- sys.stdout.flush()
- sys.stderr.flush()
-
- if not (parser_func or filter_obj or pipes or timeout or max_time):
- # Run the command. The stdout and stderr file handles are passed to the
- # subprocess directly for writing. No processing happens on the output of
- # the subprocess.
- proc = subprocess.Popen(command, stdout=sys.stdout, stderr=sys.stderr,
- bufsize=0, **kwargs)
-
- else:
- if not (parser_func or filter_obj):
- filter_obj = RunCommandFilter()
-
- # Start the initial process.
- proc = subprocess.Popen(command, stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT, bufsize=0, **kwargs)
- proc_handles = [proc]
-
- if pipes:
- pipe_number = 0
- for pipe in pipes:
- pipe_number = pipe_number + 1
- if pipe_number == len(pipes) and not (parser_func or filter_obj):
- # The last pipe process needs to output to sys.stdout or filter
- stdout = sys.stdout
- else:
- # Output to a pipe, since another pipe is on top of us.
- stdout = subprocess.PIPE
- pipe_proc = subprocess.Popen(pipe, stdin=proc_handles[0].stdout,
- stdout=stdout, stderr=subprocess.STDOUT)
- proc_handles.insert(0, pipe_proc)
-
- # Allow proc to receive a SIGPIPE if the piped process exits.
- for handle in proc_handles[1:]:
- handle.stdout.close()
-
- log_event = threading.Event()
-
- # Launch and start the reader thread.
- thread = threading.Thread(target=ProcessRead,
- args=(proc_handles[0].stdout, sys.stdout),
- kwargs={'parser_func': parser_func,
- 'filter_obj': filter_obj,
- 'log_event': log_event})
-
- kill_lock = threading.Lock()
-
-
- def term_then_kill(handle, initial_timeout, numtimeouts, interval):
- def timed_check():
- for _ in range(numtimeouts):
- if handle.poll() is not None:
- return True
- time.sleep(interval)
-
- handle.terminate()
- time.sleep(initial_timeout)
- timed_check()
- if handle.poll() is None:
- handle.kill()
- timed_check()
- return handle.poll() is not None
-
-
- def kill_proc(proc_handles, message=None):
- with kill_lock:
- if proc_handles:
- killed = term_then_kill(proc_handles[0], 0.1, 5, 1)
-
- if message:
- print >> sys.stderr, message
-
- if not killed:
- print >> sys.stderr, 'could not kill pid %d!' % proc_handles[0].pid
- else:
- print >> sys.stderr, 'program finished with exit code %d' % (
- proc_handles[0].returncode)
-
- # Prevent other timeouts from double-killing.
- del proc_handles[:]
-
- def timeout_func(timeout, proc_handles, log_event, finished_event):
- while log_event.wait(timeout):
- log_event.clear()
- if finished_event.is_set():
- return
-
- message = ('command timed out: %d seconds without output, attempting to '
- 'kill' % timeout)
- kill_proc(proc_handles, message)
-
- def maxtimeout_func(timeout, proc_handles, finished_event):
- if not finished_event.wait(timeout):
- message = ('command timed out: %d seconds elapsed' % timeout)
- kill_proc(proc_handles, message)
-
- timeout_thread = None
- maxtimeout_thread = None
- finished_event = threading.Event()
-
- if timeout:
- timeout_thread = threading.Thread(target=timeout_func,
- args=(timeout, proc_handles, log_event,
- finished_event))
- timeout_thread.daemon = True
- if max_time:
- maxtimeout_thread = threading.Thread(target=maxtimeout_func,
- args=(max_time, proc_handles,
- finished_event))
- maxtimeout_thread.daemon = True
-
- thread.start()
- if timeout_thread:
- timeout_thread.start()
- if maxtimeout_thread:
- maxtimeout_thread.start()
-
- # Wait for the commands to terminate.
- for handle in proc_handles:
- handle.wait()
-
- # Wake up timeout threads.
- finished_event.set()
- log_event.set()
-
- # Wait for the reader thread to complete (implies EOF reached on stdout/
- # stderr pipes).
- thread.join()
-
- # Check whether any of the sub commands has failed.
- for handle in proc_handles:
- if handle.returncode:
- return handle.returncode
-
- # Wait for the command to terminate.
- proc.wait()
- return proc.returncode
-
-
-def GetStatusOutput(command, **kwargs):
- """Runs the command list, returning its result and output."""
- proc = subprocess.Popen(command, stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT, bufsize=1,
- **kwargs)
- output = proc.communicate()[0]
- result = proc.returncode
-
- return (result, output)
-
-
-def GetCommandOutput(command):
- """Runs the command list, returning its output.
-
- Run the command and returns its output (stdout and stderr) as a string.
-
- If the command exits with an error, raises ExternalError.
- """
- (result, output) = GetStatusOutput(command)
- if result:
- raise ExternalError('%s: %s' % (subprocess.list2cmdline(command), output))
- return output
-
-
def convert_json(option, _, value, parser):
"""Provide an OptionParser callback to unmarshal a JSON string."""
setattr(parser.values, option.dest, json.loads(value))
diff --git a/infra/scripts/legacy/scripts/slave/runtest.py b/infra/scripts/legacy/scripts/slave/runtest.py
index 3e09630..cd6ad75 100755
--- a/infra/scripts/legacy/scripts/slave/runtest.py
+++ b/infra/scripts/legacy/scripts/slave/runtest.py
@@ -94,36 +94,6 @@ def _ShutdownDBus():
print ' cleared DBUS_SESSION_BUS_ADDRESS environment variable'
-def _RunGTestCommand(
- options, command, extra_env, pipes=None):
- """Runs a test, printing and possibly processing the output.
-
- Args:
- options: Options passed for this invocation of runtest.py.
- command: A list of strings in a command (the command and its arguments).
- extra_env: A dictionary of extra environment variables to set.
- pipes: A list of command string lists which the output will be piped to.
-
- Returns:
- The process return code.
- """
- env = os.environ.copy()
- if extra_env:
- print 'Additional test environment:'
- for k, v in sorted(extra_env.items()):
- print ' %s=%s' % (k, v)
- env.update(extra_env or {})
-
- # Trigger bot mode (test retries, redirection of stdio, possibly faster,
- # etc.) - using an environment variable instead of command-line flags because
- # some internal waterfalls run this (_RunGTestCommand) for totally non-gtest
- # code.
- # TODO(phajdan.jr): Clean this up when internal waterfalls are fixed.
- env.update({'CHROMIUM_TEST_LAUNCHER_BOT_MODE': '1'})
-
- return chromium_utils.RunCommand(command, pipes=pipes, env=env)
-
-
def _BuildTestBinaryCommand(_build_dir, test_exe_path, options):
"""Builds a command to run a test binary.
@@ -307,16 +277,40 @@ def _Main(options, args, extra_env):
options.test_launcher_summary_output)
command.append('--test-launcher-summary-output=%s' % json_file_name)
- pipes = []
- # See the comment in main() regarding offline symbolization.
+ command = _GenerateRunIsolatedCommand(build_dir, test_exe_path, options,
+ command)
+
+ env = os.environ.copy()
+ if extra_env:
+ print 'Additional test environment:'
+ for k, v in sorted(extra_env.items()):
+ print ' %s=%s' % (k, v)
+ env.update(extra_env or {})
+
+ # Trigger bot mode (test retries, redirection of stdio, possibly faster,
+ # etc.) - using an environment variable instead of command-line flags
+ # because some internal waterfalls run this for totally non-gtest code.
+ # TODO(phajdan.jr): Clean this up when internal waterfalls are fixed.
+ env.update({'CHROMIUM_TEST_LAUNCHER_BOT_MODE': '1'})
+
if options.use_symbolization_script:
symbolize_command = _GetSanitizerSymbolizeCommand(
strip_path_prefix=options.strip_path_prefix)
- pipes = [symbolize_command]
- command = _GenerateRunIsolatedCommand(build_dir, test_exe_path, options,
- command)
- result = _RunGTestCommand(options, command, extra_env, pipes=pipes)
+ command_process = subprocess.Popen(
+ command, env=env, stdout=subprocess.PIPE)
+ symbolize_process = subprocess.Popen(
+ symbolize_command, env=env, stdin=command_process.stdout)
+ command_process.stdout.close()
+
+ command_process.wait()
+ symbolize_process.wait()
+
+ result = command_process.returncode
+ if result == 0:
+ result = symbolize_process.returncode
+ else:
+ result = subprocess.call(command, env=env)
finally:
if start_xvfb:
xvfb.StopVirtualX(slave_name)
diff --git a/infra/scripts/legacy/scripts/slave/slave_utils.py b/infra/scripts/legacy/scripts/slave/slave_utils.py
index 76cf4e3..72a192d 100644
--- a/infra/scripts/legacy/scripts/slave/slave_utils.py
+++ b/infra/scripts/legacy/scripts/slave/slave_utils.py
@@ -10,6 +10,7 @@ import glob
import os
import re
import shutil
+import subprocess
import sys
import tempfile
import time
@@ -212,7 +213,7 @@ def RemoveChromeTemporaryFiles():
elif chromium_utils.IsMac():
nstempdir_path = '/usr/local/libexec/nstempdir'
if os.path.exists(nstempdir_path):
- ns_temp_dir = chromium_utils.GetCommandOutput([nstempdir_path]).strip()
+ ns_temp_dir = subprocess.check_output([nstempdir_path]).strip()
if ns_temp_dir:
LogAndRemoveFiles(ns_temp_dir, kLogRegex)
for i in ('Chromium', 'Google Chrome'):