Changeset 65343 in webkit
- Timestamp:
- Aug 13, 2010 5:34:13 PM (14 years ago)
- Location:
- trunk/WebKitTools
- Files:
-
- 4 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/WebKitTools/ChangeLog
r65308 r65343 1 2010-08-13 Dirk Pranke <dpranke@chromium.org> 2 3 Reviewed by Eric Seidel. 4 5 Rewrite new-run-webkit-test's wait_for_threads_to_finish loop to 6 check for exceptions on all threads, not just the first thread. 7 8 This change also changes the logging behavior for wedged threads 9 to only dump the stacks of threads that are actually wedged. 10 11 Refactor the thread classes in the dump_render_tree_thread module 12 to make the contract between TestRunner and TestShellThread clearer. 13 14 Added a bunch of unit tests. 15 https://bugs.webkit.org/show_bug.cgi?id=38561 16 17 * Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py: 18 * Scripts/webkitpy/layout_tests/run_webkit_tests.py: 19 * Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py: 20 1 21 2010-08-13 Kenichi Ishibashi <bashi@google.com> 2 22 -
trunk/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py
r62541 r65343 55 55 56 56 57 def process_output(port, test_info, test_types, test_args, configuration,58 output_dir, crash, timeout, test_run_time, actual_checksum,59 output, error):57 def _process_output(port, test_info, test_types, test_args, configuration, 58 output_dir, crash, timeout, test_run_time, actual_checksum, 59 output, error): 60 60 """Receives the output from a DumpRenderTree process, subjects it to a 61 61 number of tests, and returns a list of failure types the test produced. … … 119 119 120 120 121 def _pad_timeout(timeout): 122 """Returns a safe multiple of the per-test timeout value to use 123 to detect hung test threads. 124 125 """ 126 # When we're running one test per DumpRenderTree process, we can 127 # enforce a hard timeout. The DumpRenderTree watchdog uses 2.5x 128 # the timeout; we want to be larger than that. 129 return timeout * 3 130 131 132 def _milliseconds_to_seconds(msecs): 133 return float(msecs) / 1000.0 134 135 121 136 class TestResult(object): 122 137 … … 163 178 test_info.image_hash()) 164 179 end = time.time() 165 self._test_result = process_output(self._port,180 self._test_result = _process_output(self._port, 166 181 test_info, self._test_types, self._test_args, 167 182 self._configuration, self._output_dir, crash, timeout, end - start, … … 173 188 174 189 175 class TestShellThread(threading.Thread): 176 190 class WatchableThread(threading.Thread): 191 """This class abstracts an interface used by 192 run_webkit_tests.TestRunner._wait_for_threads_to_finish for thread 193 management.""" 194 def __init__(self): 195 threading.Thread.__init__(self) 196 self._canceled = False 197 self._exception_info = None 198 self._next_timeout = None 199 self._thread_id = None 200 201 def cancel(self): 202 """Set a flag telling this thread to quit.""" 203 self._canceled = True 204 205 def clear_next_timeout(self): 206 """Mark a flag telling this thread to stop setting timeouts.""" 207 self._timeout = 0 208 209 def exception_info(self): 210 """If run() terminated on an uncaught exception, return it here 211 ((type, value, traceback) tuple). 212 Returns None if run() terminated normally. Meant to be called after 213 joining this thread.""" 214 return self._exception_info 215 216 def id(self): 217 """Return a thread identifier.""" 218 return self._thread_id 219 220 def next_timeout(self): 221 """Return the time the test is supposed to finish by.""" 222 return self._next_timeout 223 224 225 class TestShellThread(WatchableThread): 177 226 def __init__(self, port, filename_list_queue, result_queue, 178 227 test_types, test_args, image_path, shell_args, options): … … 193 242 run_webkit_tests; they are typically passed via the 194 243 run_webkit_tests.TestRunner class.""" 195 threading.Thread.__init__(self)244 WatchableThread.__init__(self) 196 245 self._port = port 197 246 self._filename_list_queue = filename_list_queue … … 204 253 self._shell_args = shell_args 205 254 self._options = options 206 self._canceled = False207 self._exception_info = None208 255 self._directory_timing_stats = {} 209 256 self._test_results = [] … … 232 279 return self._test_results 233 280 234 def cancel(self):235 """Set a flag telling this thread to quit."""236 self._canceled = True237 238 def get_exception_info(self):239 """If run() terminated on an uncaught exception, return it here240 ((type, value, traceback) tuple).241 Returns None if run() terminated normally. Meant to be called after242 joining this thread."""243 return self._exception_info244 245 281 def get_total_time(self): 246 282 return max(self._stop_time - self._start_time, 0.0) … … 252 288 """Delegate main work to a helper method and watch for uncaught 253 289 exceptions.""" 290 self._thread_id = thread.get_ident() 254 291 self._start_time = time.time() 255 292 self._num_tests = 0 … … 385 422 worker.start() 386 423 387 # When we're running one test per DumpRenderTree process, we can388 # enforce a hard timeout. The DumpRenderTree watchdog uses 2.5x389 # the timeout; we want to be larger than that.390 worker.join( int(test_info.timeout) * 3.0 / 1000.0)424 thread_timeout = _milliseconds_to_seconds( 425 _pad_timeout(test_info.timeout)) 426 thread._next_timeout = time.time() + thread_timeout 427 worker.join(thread_timeout) 391 428 if worker.isAlive(): 392 429 # If join() returned with the thread still running, the … … 434 471 image_hash = "" 435 472 start = time.time() 473 474 thread_timeout = _milliseconds_to_seconds( 475 _pad_timeout(test_info.timeout)) 476 self._next_timeout = start + thread_timeout 477 436 478 crash, timeout, actual_checksum, output, error = \ 437 479 self._driver.run_test(test_info.uri, test_info.timeout, image_hash) 438 480 end = time.time() 439 481 440 result = process_output(self._port, test_info, self._test_types,482 result = _process_output(self._port, test_info, self._test_types, 441 483 self._test_args, self._options.configuration, 442 484 self._options.results_directory, crash, -
trunk/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests.py
r65011 r65343 54 54 import optparse 55 55 import os 56 import pdb 56 57 import platform 57 58 import Queue … … 630 631 return int(self._options.child_processes) == 1 631 632 632 def _dump_thread_states(self):633 for thread_id, stack in sys._current_frames().items():634 # FIXME: Python 2.6 has thread.ident which we could635 # use to map from thread_id back to thread.name636 print "\n# Thread: %d" % thread_id637 for filename, lineno, name, line in traceback.extract_stack(stack):638 print 'File: "%s", line %d, in %s' % (filename, lineno, name)639 if line:640 print " %s" % (line.strip())641 642 def _dump_thread_states_if_necessary(self):643 # HACK: Dump thread states every minute to figure out what's644 # hanging on the bots.645 if not self._options.verbose:646 return647 dump_threads_every = 60 # Dump every minute648 if not self._last_thread_dump:649 self._last_thread_dump = time.time()650 time_since_last_dump = time.time() - self._last_thread_dump651 if time_since_last_dump > dump_threads_every:652 self._dump_thread_states()653 self._last_thread_dump = time.time()654 655 633 def _run_tests(self, file_list, result_summary): 656 634 """Runs the tests in the file_list. 657 635 658 Return: A tuple ( failures, thread_timings, test_timings,636 Return: A tuple (keyboard_interrupted, thread_timings, test_timings, 659 637 individual_test_timings) 660 failures is a map from test to list of failure types638 keyboard_interrupted is whether someone typed Ctrl^C 661 639 thread_timings is a list of dicts with the total runtime 662 640 of each thread with 'name', 'num_tests', 'total_time' properties … … 677 655 self._printer.print_update("Starting testing ...") 678 656 679 # Wait for the threads to finish and collect test failures. 680 failures = {} 681 test_timings = {} 682 individual_test_timings = [] 683 thread_timings = [] 657 keyboard_interrupted = self._wait_for_threads_to_finish(threads, 658 result_summary) 659 (thread_timings, test_timings, individual_test_timings) = \ 660 self._collect_timing_info(threads) 661 662 return (keyboard_interrupted, thread_timings, test_timings, 663 individual_test_timings) 664 665 def _wait_for_threads_to_finish(self, threads, result_summary): 684 666 keyboard_interrupted = False 685 667 try: 686 668 # Loop through all the threads waiting for them to finish. 687 for thread in threads: 688 # FIXME: We'll end up waiting on the first thread the whole 689 # time. That means we won't notice exceptions on other 690 # threads until the first one exits. 691 # We should instead while True: in the outer loop 692 # and then loop through threads joining and checking 693 # isAlive and get_exception_info. Exiting on any exception. 694 while thread.isAlive(): 695 # Wake the main thread every 0.1 seconds so we 696 # can call update_summary in a timely fashion. 697 thread.join(0.1) 698 # HACK: Used for debugging threads on the bots. 699 self._dump_thread_states_if_necessary() 700 self.update_summary(result_summary) 669 some_thread_is_alive = True 670 while some_thread_is_alive: 671 some_thread_is_alive = False 672 t = time.time() 673 for thread in threads: 674 exception_info = thread.exception_info() 675 if exception_info is not None: 676 # Re-raise the thread's exception here to make it 677 # clear that testing was aborted. Otherwise, 678 # the tests that did not run would be assumed 679 # to have passed. 680 raise (exception_info[0], exception_info[1], 681 exception_info[2]) 682 683 if thread.isAlive(): 684 some_thread_is_alive = True 685 next_timeout = thread.next_timeout() 686 if (next_timeout and t > next_timeout): 687 _log_wedged_thread(thread) 688 thread.clear_next_timeout() 689 690 self.update_summary(result_summary) 691 692 if some_thread_is_alive: 693 time.sleep(0.1) 701 694 702 695 except KeyboardInterrupt: … … 705 698 thread.cancel() 706 699 707 if not keyboard_interrupted: 708 for thread in threads: 709 # Check whether a thread died before normal completion. 710 exception_info = thread.get_exception_info() 711 if exception_info is not None: 712 # Re-raise the thread's exception here to make it clear 713 # something went wrong. Otherwise, the tests that did not 714 # run would be assumed to have passed. 715 raise (exception_info[0], exception_info[1], 716 exception_info[2]) 700 return keyboard_interrupted 701 702 def _collect_timing_info(self, threads): 703 test_timings = {} 704 individual_test_timings = [] 705 thread_timings = [] 717 706 718 707 for thread in threads: … … 722 711 test_timings.update(thread.get_directory_timing_stats()) 723 712 individual_test_timings.extend(thread.get_test_results()) 724 return (keyboard_interrupted, thread_timings, test_timings, 725 713 714 return (thread_timings, test_timings, individual_test_timings) 726 715 727 716 def needs_http(self): … … 1681 1670 1682 1671 1672 def _find_thread_stack(id): 1673 """Returns a stack object that can be used to dump a stack trace for 1674 the given thread id (or None if the id is not found).""" 1675 for thread_id, stack in sys._current_frames().items(): 1676 if thread_id == id: 1677 return stack 1678 return None 1679 1680 1681 def _log_stack(stack): 1682 """Log a stack trace to log.error().""" 1683 for filename, lineno, name, line in traceback.extract_stack(stack): 1684 _log.error('File: "%s", line %d, in %s' % (filename, lineno, name)) 1685 if line: 1686 _log.error(' %s' % line.strip()) 1687 1688 1689 def _log_wedged_thread(thread): 1690 """Log information about the given thread state.""" 1691 id = thread.id() 1692 stack = _find_thread_stack(id) 1693 assert(stack is not None) 1694 _log.error("") 1695 _log.error("thread %s (%d) is wedged" % (thread.getName(), id)) 1696 _log_stack(stack) 1697 _log.error("") 1698 1699 1683 1700 def main(): 1684 1701 options, args = parse_args() -
trunk/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py
r59725 r65343 31 31 32 32 import codecs 33 import logging 33 34 import os 35 import pdb 36 import Queue 34 37 import sys 38 import thread 39 import time 40 import threading 35 41 import unittest 36 42 … … 38 44 from webkitpy.layout_tests import port 39 45 from webkitpy.layout_tests import run_webkit_tests 46 from webkitpy.layout_tests.layout_package import dump_render_tree_thread 40 47 41 48 from webkitpy.thirdparty.mock import Mock … … 91 98 self.assertEqual(regular_output.get(), ['\n\n']) 92 99 self.assertEqual(buildbot_output.get(), []) 100 93 101 94 102 … … 192 200 193 201 202 class TestThread(dump_render_tree_thread.WatchableThread): 203 def __init__(self, started_queue, stopping_queue): 204 dump_render_tree_thread.WatchableThread.__init__(self) 205 self._started_queue = started_queue 206 self._stopping_queue = stopping_queue 207 self._timeout = False 208 self._timeout_queue = Queue.Queue() 209 210 def run(self): 211 self._thread_id = thread.get_ident() 212 try: 213 self._started_queue.put('') 214 msg = self._stopping_queue.get() 215 if msg == 'KeyboardInterrupt': 216 raise KeyboardInterrupt 217 elif msg == 'Exception': 218 raise ValueError() 219 elif msg == 'Timeout': 220 self._timeout = True 221 self._timeout_queue.get() 222 except: 223 self._exception_info = sys.exc_info() 224 225 def next_timeout(self): 226 if self._timeout: 227 self._timeout_queue.put('done') 228 return time.time() - 10 229 return time.time() 230 231 232 class TestHandler(logging.Handler): 233 def __init__(self, astream): 234 logging.Handler.__init__(self) 235 self._stream = astream 236 237 def emit(self, record): 238 self._stream.write(self.format(record)) 239 240 241 class WaitForThreadsToFinishTest(unittest.TestCase): 242 class MockTestRunner(run_webkit_tests.TestRunner): 243 def __init__(self): 244 pass 245 246 def __del__(self): 247 pass 248 249 def update_summary(self, result_summary): 250 pass 251 252 def run_one_thread(self, msg): 253 runner = self.MockTestRunner() 254 starting_queue = Queue.Queue() 255 stopping_queue = Queue.Queue() 256 child_thread = TestThread(starting_queue, stopping_queue) 257 child_thread.start() 258 started_msg = starting_queue.get() 259 stopping_queue.put(msg) 260 threads = [child_thread] 261 return runner._wait_for_threads_to_finish(threads, None) 262 263 def test_basic(self): 264 interrupted = self.run_one_thread('') 265 self.assertFalse(interrupted) 266 267 def test_interrupt(self): 268 interrupted = self.run_one_thread('KeyboardInterrupt') 269 self.assertTrue(interrupted) 270 271 def test_timeout(self): 272 interrupted = self.run_one_thread('Timeout') 273 self.assertFalse(interrupted) 274 275 def test_exception(self): 276 self.assertRaises(ValueError, self.run_one_thread, 'Exception') 277 278 279 class StandaloneFunctionsTest(unittest.TestCase): 280 def test_log_wedged_thread(self): 281 logger = run_webkit_tests._log 282 astream = array_stream.ArrayStream() 283 handler = TestHandler(astream) 284 logger.addHandler(handler) 285 286 starting_queue = Queue.Queue() 287 stopping_queue = Queue.Queue() 288 child_thread = TestThread(starting_queue, stopping_queue) 289 child_thread.start() 290 msg = starting_queue.get() 291 292 run_webkit_tests._log_wedged_thread(child_thread) 293 stopping_queue.put('') 294 child_thread.join(timeout=1.0) 295 296 self.assertFalse(astream.empty()) 297 self.assertFalse(child_thread.isAlive()) 298 299 def test_find_thread_stack(self): 300 id, stack = sys._current_frames().items()[0] 301 found_stack = run_webkit_tests._find_thread_stack(id) 302 self.assertNotEqual(found_stack, None) 303 304 found_stack = run_webkit_tests._find_thread_stack(0) 305 self.assertEqual(found_stack, None) 306 194 307 if __name__ == '__main__': 195 308 unittest.main()
Note: See TracChangeset
for help on using the changeset viewer.