Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(297)

Side by Side Diff: build/android/pylib/cmd_helper.py

Issue 300063017: Fix silent failures in test runner that lead to long timeouts (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """A wrapper for subprocess to make calling shell commands easier.""" 5 """A wrapper for subprocess to make calling shell commands easier."""
6 6
7 import fcntl
7 import logging 8 import logging
9 import os
8 import pipes 10 import pipes
11 import select
9 import signal 12 import signal
13 import StringIO
10 import subprocess 14 import subprocess
11 import tempfile 15 import time
12
13 from pylib.utils import timeout_retry
14 16
15 17
16 def Popen(args, stdout=None, stderr=None, shell=None, cwd=None, env=None): 18 def Popen(args, stdout=None, stderr=None, shell=None, cwd=None, env=None):
17 return subprocess.Popen( 19 return subprocess.Popen(
18 args=args, cwd=cwd, stdout=stdout, stderr=stderr, 20 args=args, cwd=cwd, stdout=stdout, stderr=stderr,
19 shell=shell, close_fds=True, env=env, 21 shell=shell, close_fds=True, env=env,
20 preexec_fn=lambda: signal.signal(signal.SIGPIPE, signal.SIG_DFL)) 22 preexec_fn=lambda: signal.signal(signal.SIGPIPE, signal.SIG_DFL))
21 23
22 24
23 def Call(args, stdout=None, stderr=None, shell=None, cwd=None, env=None): 25 def Call(args, stdout=None, stderr=None, shell=None, cwd=None, env=None):
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after
81 elif shell: 83 elif shell:
82 raise Exception('array args must be run with shell=False') 84 raise Exception('array args must be run with shell=False')
83 else: 85 else:
84 args_repr = ' '.join(map(pipes.quote, args)) 86 args_repr = ' '.join(map(pipes.quote, args))
85 87
86 s = '[host]' 88 s = '[host]'
87 if cwd: 89 if cwd:
88 s += ':' + cwd 90 s += ':' + cwd
89 s += '> ' + args_repr 91 s += '> ' + args_repr
90 logging.info(s) 92 logging.info(s)
91 tmpout = tempfile.TemporaryFile(bufsize=0) 93 pipe = Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
92 tmperr = tempfile.TemporaryFile(bufsize=0) 94 shell=shell, cwd=cwd)
93 exit_code = Call(args, cwd=cwd, stdout=tmpout, stderr=tmperr, shell=shell) 95 stdout, stderr = pipe.communicate()
94 tmperr.seek(0) 96
95 stderr = tmperr.read()
96 tmperr.close()
97 if stderr: 97 if stderr:
98 logging.critical(stderr) 98 logging.critical(stderr)
99 tmpout.seek(0)
100 stdout = tmpout.read()
101 tmpout.close()
102 if len(stdout) > 4096: 99 if len(stdout) > 4096:
103 logging.debug('Truncated output:') 100 logging.debug('Truncated output:')
104 logging.debug(stdout[:4096]) 101 logging.debug(stdout[:4096])
105 return (exit_code, stdout) 102 return (pipe.returncode, stdout)
106 103
107 104
108 def GetCmdStatusAndOutputWithTimeoutAndRetries(args, timeout, retries): 105 class TimeoutError(Exception):
109 """Executes a subprocess with a timeout and retries. 106 """Module-specific timeout exception."""
107 pass
108
109
110 def GetCmdStatusAndOutputWithTimeout(args, timeout, cwd=None, shell=False,
111 logfile=None):
112 """Executes a subprocess with a timeout.
110 113
111 Args: 114 Args:
112 args: List of arguments to the program, the program to execute is the first 115 args: List of arguments to the program, the program to execute is the first
113 element. 116 element.
114 timeout: the timeout in seconds. 117 timeout: the timeout in seconds or None to wait forever.
115 retries: the number of retries. 118 cwd: If not None, the subprocess's current directory will be changed to
119 |cwd| before it's executed.
120 shell: Whether to execute args as a shell command.
121 logfile: Optional file-like object that will receive output from the
122 command as it is running.
116 123
117 Returns: 124 Returns:
118 The 2-tuple (exit code, output). 125 The 2-tuple (exit code, output).
119 """ 126 """
120 return timeout_retry.Run(GetCmdStatusAndOutput, timeout, retries, [args]) 127 try:
128 process = Popen(args, cwd=cwd, stdout=subprocess.PIPE, shell=shell)
129 end_time = (time.time() + timeout) if timeout else None
130 poll_interval = 1
131 buffer_size = 4096
132 child_fd = process.stdout.fileno()
133 output = StringIO.StringIO()
134
135 # Enable non-blocking reads from the child's stdout.
136 fl = fcntl.fcntl(child_fd, fcntl.F_GETFL)
137 fcntl.fcntl(child_fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
138
139 while not process.poll():
140 if end_time and time.time() > end_time:
141 raise TimeoutError
142 read_fds, _, _ = select.select([child_fd], [], [], poll_interval)
143 if child_fd in read_fds:
144 data = os.read(child_fd, buffer_size)
145 if not data:
146 break
147 if logfile:
148 logfile.write(data)
149 output.write(data)
150 return process.returncode, output.getvalue()
151 finally:
152 if not process.poll():
153 process.kill()
154 process.wait()
OLDNEW
« no previous file with comments | « no previous file | build/android/pylib/perf/test_runner.py » ('j') | build/android/pylib/perf/test_runner.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698