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

Side by Side Diff: webkit/tools/layout_tests/layout_package/test_shell_thread.py

Issue 346018: 1. Show crash stacks in run_webkit_tests.py stdio... (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 11 years, 1 month 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) 2006-2009 The Chromium Authors. All rights reserved. 1 # Copyright (c) 2006-2009 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 Thread object for running the test shell and processing URLs from a 5 """A Thread object for running the test shell and processing URLs from a
6 shared queue. 6 shared queue.
7 7
8 Each thread runs a separate instance of the test_shell binary and validates 8 Each thread runs a separate instance of the test_shell binary and validates
9 the output. When there are no more URLs to process in the shared queue, the 9 the output. When there are no more URLs to process in the shared queue, the
10 thread exits. 10 thread exits.
11 """ 11 """
12 12
13 import copy 13 import copy
14 import logging 14 import logging
15 import os 15 import os
16 import Queue 16 import Queue
17 import signal 17 import signal
18 import subprocess 18 import subprocess
19 import sys 19 import sys
20 import thread 20 import thread
21 import threading 21 import threading
22 import time 22 import time
23 23
24 import path_utils 24 import path_utils
25 import test_failures 25 import test_failures
26 26
27 def ProcessOutput(proc, test_info, test_types, test_args, target): 27 def ProcessOutput(proc, test_info, test_types, test_args, target, output_dir):
28 """Receives the output from a test_shell process, subjects it to a number 28 """Receives the output from a test_shell process, subjects it to a number
29 of tests, and returns a list of failure types the test produced. 29 of tests, and returns a list of failure types the test produced.
30 30
31 Args: 31 Args:
32 proc: an active test_shell process 32 proc: an active test_shell process
33 test_info: Object containing the test filename, uri and timeout 33 test_info: Object containing the test filename, uri and timeout
34 test_types: list of test types to subject the output to 34 test_types: list of test types to subject the output to
35 test_args: arguments to be passed to each test 35 test_args: arguments to be passed to each test
36 target: Debug or Release 36 target: Debug or Release
37 output_dir: directory to put crash stack traces into
37 38
38 Returns: a list of failure objects and times for the test being processed 39 Returns: a list of failure objects and times for the test being processed
39 """ 40 """
40 outlines = [] 41 outlines = []
41 extra_lines = [] 42 extra_lines = []
42 failures = [] 43 failures = []
43 crash = False 44 crash = False
44 45
45 # Some test args, such as the image hash, may be added or changed on a 46 # Some test args, such as the image hash, may be added or changed on a
46 # test-by-test basis. 47 # test-by-test basis.
47 local_test_args = copy.copy(test_args) 48 local_test_args = copy.copy(test_args)
48 49
49 start_time = time.time() 50 start_time = time.time()
50 51
51 line = proc.stdout.readline() 52 line = proc.stdout.readline()
52 53
53 # Only start saving output lines once we've loaded the URL for the test. 54 # Only start saving output lines once we've loaded the URL for the test.
54 hit_load_url = False 55 url = None
56 test_string = test_info.uri.strip()
55 57
56 while line.rstrip() != "#EOF": 58 while line.rstrip() != "#EOF":
57 # Make sure we haven't crashed. 59 # Make sure we haven't crashed.
58 if line == '' and proc.poll() is not None: 60 if line == '' and proc.poll() is not None:
59 failures.append(test_failures.FailureCrash()) 61 failures.append(test_failures.FailureCrash())
60 62
61 # This is hex code 0xc000001d, which is used for abrupt termination. 63 # This is hex code 0xc000001d, which is used for abrupt termination.
62 # This happens if we hit ctrl+c from the prompt and we happen to 64 # This happens if we hit ctrl+c from the prompt and we happen to
63 # be waiting on the test_shell. 65 # be waiting on the test_shell.
64 # sdoyon: Not sure for which OS and in what circumstances the 66 # sdoyon: Not sure for which OS and in what circumstances the
65 # above code is valid. What works for me under Linux to detect 67 # above code is valid. What works for me under Linux to detect
66 # ctrl+c is for the subprocess returncode to be negative SIGINT. And 68 # ctrl+c is for the subprocess returncode to be negative SIGINT. And
67 # that agrees with the subprocess documentation. 69 # that agrees with the subprocess documentation.
68 if (-1073741510 == proc.returncode or 70 if (-1073741510 == proc.returncode or
69 -signal.SIGINT == proc.returncode): 71 -signal.SIGINT == proc.returncode):
70 raise KeyboardInterrupt 72 raise KeyboardInterrupt
71 crash = True 73 crash = True
72 break 74 break
73 75
74 # Don't include #URL lines in our output 76 # Don't include #URL lines in our output
75 if line.startswith("#URL:"): 77 if line.startswith("#URL:"):
76 hit_load_url = True
77 test_string = test_info.uri.strip()
78 url = line.rstrip()[5:] 78 url = line.rstrip()[5:]
79 if url != test_string: 79 if url != test_string:
80 logging.fatal("Test got out of sync:\n|%s|\n|%s|" % 80 logging.fatal("Test got out of sync:\n|%s|\n|%s|" %
81 (url, test_string)) 81 (url, test_string))
82 raise AssertionError("test out of sync") 82 raise AssertionError("test out of sync")
83 elif line.startswith("#MD5:"): 83 elif line.startswith("#MD5:"):
84 local_test_args.hash = line.rstrip()[5:] 84 local_test_args.hash = line.rstrip()[5:]
85 elif line.startswith("#TEST_TIMED_OUT"): 85 elif line.startswith("#TEST_TIMED_OUT"):
86 # Test timed out, but we still need to read until #EOF. 86 # Test timed out, but we still need to read until #EOF.
87 failures.append(test_failures.FailureTimeout()) 87 failures.append(test_failures.FailureTimeout())
88 elif hit_load_url: 88 elif url:
89 outlines.append(line) 89 outlines.append(line)
90 else: 90 else:
91 extra_lines.append(line) 91 extra_lines.append(line)
92 92
93 line = proc.stdout.readline() 93 line = proc.stdout.readline()
94 94
95 end_test_time = time.time() 95 end_test_time = time.time()
96 96
97 if len(extra_lines): 97 if len(extra_lines):
98 logging.warning("Previous test output extra lines after dump:\n%s" % ( 98 extra = "".join(extra_lines)
99 "".join(extra_lines))) 99 if crash:
100 logging.info("Stacktrace for %s:\n%s" % (test_string, extra))
101 # Strip off "file://" since RelativeTestFilename expects filesystem paths.
102 filename = os.path.join(output_dir,
103 path_utils.RelativeTestFilename(test_string[7:]))
104 filename = os.path.splitext(filename)[0] + "-stack.txt"
105 path_utils.MaybeMakeDirectory(os.path.split(filename)[0])
106 open(filename, "wb").write(extra)
107 else:
108 logging.warning("Previous test output extra lines after dump:\n%s" %
109 extra)
100 110
101 # Check the output and save the results. 111 # Check the output and save the results.
102 time_for_diffs = {} 112 time_for_diffs = {}
103 for test_type in test_types: 113 for test_type in test_types:
104 start_diff_time = time.time() 114 start_diff_time = time.time()
105 new_failures = test_type.CompareOutput(test_info.filename, 115 new_failures = test_type.CompareOutput(test_info.filename,
106 proc, 116 proc,
107 ''.join(outlines), 117 ''.join(outlines),
108 local_test_args, 118 local_test_args,
109 target) 119 target)
(...skipping 30 matching lines...) Expand all
140 total_time_for_all_diffs, time_for_diffs): 150 total_time_for_all_diffs, time_for_diffs):
141 self.filename = filename 151 self.filename = filename
142 self.failures = failures 152 self.failures = failures
143 self.test_run_time = test_run_time 153 self.test_run_time = test_run_time
144 self.total_time_for_all_diffs = total_time_for_all_diffs 154 self.total_time_for_all_diffs = total_time_for_all_diffs
145 self.time_for_diffs = time_for_diffs 155 self.time_for_diffs = time_for_diffs
146 156
147 class SingleTestThread(threading.Thread): 157 class SingleTestThread(threading.Thread):
148 """Thread wrapper for running a single test file.""" 158 """Thread wrapper for running a single test file."""
149 def __init__(self, test_shell_command, shell_args, test_info, test_types, 159 def __init__(self, test_shell_command, shell_args, test_info, test_types,
150 test_args, target): 160 test_args, target, output_dir):
151 """ 161 """
152 Args: 162 Args:
153 test_info: Object containing the test filename, uri and timeout 163 test_info: Object containing the test filename, uri and timeout
154 See TestShellThread for documentation of the remaining arguments. 164 See TestShellThread for documentation of the remaining arguments.
155 """ 165 """
156 166
157 threading.Thread.__init__(self) 167 threading.Thread.__init__(self)
158 self._command = test_shell_command 168 self._command = test_shell_command
159 self._shell_args = shell_args 169 self._shell_args = shell_args
160 self._test_info = test_info 170 self._test_info = test_info
161 self._test_types = test_types 171 self._test_types = test_types
162 self._test_args = test_args 172 self._test_args = test_args
163 self._target = target 173 self._target = target
174 self._output_dir = output_dir
164 175
165 def run(self): 176 def run(self):
166 proc = StartTestShell(self._command, self._shell_args + 177 proc = StartTestShell(self._command, self._shell_args +
167 ["--time-out-ms=" + self._test_info.timeout, self._test_info.uri]) 178 ["--time-out-ms=" + self._test_info.timeout, self._test_info.uri])
168 self._test_stats = ProcessOutput(proc, self._test_info, self._test_types, 179 self._test_stats = ProcessOutput(proc, self._test_info, self._test_types,
169 self._test_args, self._target) 180 self._test_args, self._target, self._output_dir)
170 181
171 def GetTestStats(self): 182 def GetTestStats(self):
172 return self._test_stats 183 return self._test_stats
173 184
174 class TestShellThread(threading.Thread): 185 class TestShellThread(threading.Thread):
175 186
176 def __init__(self, filename_list_queue, test_shell_command, test_types, 187 def __init__(self, filename_list_queue, test_shell_command, test_types,
177 test_args, shell_args, options): 188 test_args, shell_args, output_dir, options):
178 """Initialize all the local state for this test shell thread. 189 """Initialize all the local state for this test shell thread.
179 190
180 Args: 191 Args:
181 filename_list_queue: A thread safe Queue class that contains lists of 192 filename_list_queue: A thread safe Queue class that contains lists of
182 tuples of (filename, uri) pairs. 193 tuples of (filename, uri) pairs.
183 test_shell_command: A list specifying the command+args for test_shell 194 test_shell_command: A list specifying the command+args for test_shell
184 test_types: A list of TestType objects to run the test output against. 195 test_types: A list of TestType objects to run the test output against.
185 test_args: A TestArguments object to pass to each TestType. 196 test_args: A TestArguments object to pass to each TestType.
186 shell_args: Any extra arguments to be passed to test_shell.exe. 197 shell_args: Any extra arguments to be passed to test_shell.exe.
198 output_dir: Directory to put crash stacks into.
187 options: A property dictionary as produced by optparse. The command-line 199 options: A property dictionary as produced by optparse. The command-line
188 options should match those expected by run_webkit_tests; they 200 options should match those expected by run_webkit_tests; they
189 are typically passed via the run_webkit_tests.TestRunner class. 201 are typically passed via the run_webkit_tests.TestRunner class.
190 """ 202 """
191 threading.Thread.__init__(self) 203 threading.Thread.__init__(self)
192 self._filename_list_queue = filename_list_queue 204 self._filename_list_queue = filename_list_queue
193 self._filename_list = [] 205 self._filename_list = []
194 self._test_shell_command = test_shell_command 206 self._test_shell_command = test_shell_command
195 self._test_types = test_types 207 self._test_types = test_types
196 self._test_args = test_args 208 self._test_args = test_args
197 self._test_shell_proc = None 209 self._test_shell_proc = None
198 self._shell_args = shell_args 210 self._shell_args = shell_args
211 self._output_dir = output_dir
tony 2009/10/29 23:13:51 Nit: Maybe just use options.results_directory rath
199 self._options = options 212 self._options = options
200 self._failures = {} 213 self._failures = {}
201 self._canceled = False 214 self._canceled = False
202 self._exception_info = None 215 self._exception_info = None
203 self._directory_timing_stats = {} 216 self._directory_timing_stats = {}
204 self._test_stats = [] 217 self._test_stats = []
205 self._num_tests = 0 218 self._num_tests = 0
206 self._start_time = 0 219 self._start_time = 0
207 self._stop_time = 0 220 self._stop_time = 0
208 221
(...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after
350 test_info: Object containing the test filename, uri and timeout 363 test_info: Object containing the test filename, uri and timeout
351 364
352 Return: 365 Return:
353 A list of TestFailure objects describing the error. 366 A list of TestFailure objects describing the error.
354 """ 367 """
355 worker = SingleTestThread(self._test_shell_command, 368 worker = SingleTestThread(self._test_shell_command,
356 self._shell_args, 369 self._shell_args,
357 test_info, 370 test_info,
358 self._test_types, 371 self._test_types,
359 self._test_args, 372 self._test_args,
360 self._options.target) 373 self._options.target,
374 self._output_dir)
361 375
362 worker.start() 376 worker.start()
363 377
364 # When we're running one test per test_shell process, we can enforce 378 # When we're running one test per test_shell process, we can enforce
365 # a hard timeout. the test_shell watchdog uses 2.5x the timeout 379 # a hard timeout. the test_shell watchdog uses 2.5x the timeout
366 # We want to be larger than that. 380 # We want to be larger than that.
367 worker.join(int(test_info.timeout) * 3.0 / 1000.0) 381 worker.join(int(test_info.timeout) * 3.0 / 1000.0)
368 if worker.isAlive(): 382 if worker.isAlive():
369 # If join() returned with the thread still running, the test_shell.exe is 383 # If join() returned with the thread still running, the test_shell.exe is
370 # completely hung and there's nothing more we can do with it. We have 384 # completely hung and there's nothing more we can do with it. We have
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
409 423
410 # If the test shell is dead, the above may cause an IOError as we 424 # If the test shell is dead, the above may cause an IOError as we
411 # try to write onto the broken pipe. If this is the first test for 425 # try to write onto the broken pipe. If this is the first test for
412 # this test shell process, than the test shell did not 426 # this test shell process, than the test shell did not
413 # successfully start. If this is not the first test, then the 427 # successfully start. If this is not the first test, then the
414 # previous tests have caused some kind of delayed crash. We don't 428 # previous tests have caused some kind of delayed crash. We don't
415 # try to recover here. 429 # try to recover here.
416 self._test_shell_proc.stdin.flush() 430 self._test_shell_proc.stdin.flush()
417 431
418 stats = ProcessOutput(self._test_shell_proc, test_info, self._test_types, 432 stats = ProcessOutput(self._test_shell_proc, test_info, self._test_types,
419 self._test_args, self._options.target) 433 self._test_args, self._options.target, self._output_dir)
420 434
421 self._test_stats.append(stats) 435 self._test_stats.append(stats)
422 return stats.failures 436 return stats.failures
423 437
424 438
425 def _EnsureTestShellIsRunning(self): 439 def _EnsureTestShellIsRunning(self):
426 """Start the shared test shell, if it's not running. Not for use when 440 """Start the shared test shell, if it's not running. Not for use when
427 running tests singly, since those each start a separate test shell in 441 running tests singly, since those each start a separate test shell in
428 their own thread. 442 their own thread.
429 """ 443 """
430 if (not self._test_shell_proc or 444 if (not self._test_shell_proc or
431 self._test_shell_proc.poll() is not None): 445 self._test_shell_proc.poll() is not None):
432 self._test_shell_proc = StartTestShell(self._test_shell_command, 446 self._test_shell_proc = StartTestShell(self._test_shell_command,
433 self._shell_args) 447 self._shell_args)
434 448
435 def _KillTestShell(self): 449 def _KillTestShell(self):
436 """Kill the test shell process if it's running.""" 450 """Kill the test shell process if it's running."""
437 if self._test_shell_proc: 451 if self._test_shell_proc:
438 self._test_shell_proc.stdin.close() 452 self._test_shell_proc.stdin.close()
439 self._test_shell_proc.stdout.close() 453 self._test_shell_proc.stdout.close()
440 if self._test_shell_proc.stderr: 454 if self._test_shell_proc.stderr:
441 self._test_shell_proc.stderr.close() 455 self._test_shell_proc.stderr.close()
442 if sys.platform not in ('win32', 'cygwin'): 456 if sys.platform not in ('win32', 'cygwin'):
443 # Closing stdin/stdout/stderr hangs sometimes on OS X. 457 # Closing stdin/stdout/stderr hangs sometimes on OS X.
444 subprocess.Popen(["kill", "-9", str(self._test_shell_proc.pid)]) 458 subprocess.Popen(["kill", "-9", str(self._test_shell_proc.pid)])
445 self._test_shell_proc = None 459 self._test_shell_proc = None
OLDNEW
« no previous file with comments | « webkit/tools/layout_tests/layout_package/test_failures.py ('k') | webkit/tools/layout_tests/run_webkit_tests.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698