12fc2651226baac27029e38c9d6ef883fa32084dbSteve Block#!/usr/bin/env python
22fc2651226baac27029e38c9d6ef883fa32084dbSteve Block# Copyright (C) 2011 Google Inc. All rights reserved.
32fc2651226baac27029e38c9d6ef883fa32084dbSteve Block#
42fc2651226baac27029e38c9d6ef883fa32084dbSteve Block# Redistribution and use in source and binary forms, with or without
52fc2651226baac27029e38c9d6ef883fa32084dbSteve Block# modification, are permitted provided that the following conditions are
62fc2651226baac27029e38c9d6ef883fa32084dbSteve Block# met:
72fc2651226baac27029e38c9d6ef883fa32084dbSteve Block#
82fc2651226baac27029e38c9d6ef883fa32084dbSteve Block#     * Redistributions of source code must retain the above copyright
92fc2651226baac27029e38c9d6ef883fa32084dbSteve Block# notice, this list of conditions and the following disclaimer.
102fc2651226baac27029e38c9d6ef883fa32084dbSteve Block#     * Redistributions in binary form must reproduce the above
112fc2651226baac27029e38c9d6ef883fa32084dbSteve Block# copyright notice, this list of conditions and the following disclaimer
122fc2651226baac27029e38c9d6ef883fa32084dbSteve Block# in the documentation and/or other materials provided with the
132fc2651226baac27029e38c9d6ef883fa32084dbSteve Block# distribution.
142fc2651226baac27029e38c9d6ef883fa32084dbSteve Block#     * Neither the name of Google Inc. nor the names of its
152fc2651226baac27029e38c9d6ef883fa32084dbSteve Block# contributors may be used to endorse or promote products derived from
162fc2651226baac27029e38c9d6ef883fa32084dbSteve Block# this software without specific prior written permission.
172fc2651226baac27029e38c9d6ef883fa32084dbSteve Block#
182fc2651226baac27029e38c9d6ef883fa32084dbSteve Block# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
192fc2651226baac27029e38c9d6ef883fa32084dbSteve Block# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
202fc2651226baac27029e38c9d6ef883fa32084dbSteve Block# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
212fc2651226baac27029e38c9d6ef883fa32084dbSteve Block# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
222fc2651226baac27029e38c9d6ef883fa32084dbSteve Block# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
232fc2651226baac27029e38c9d6ef883fa32084dbSteve Block# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
242fc2651226baac27029e38c9d6ef883fa32084dbSteve Block# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
252fc2651226baac27029e38c9d6ef883fa32084dbSteve Block# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
262fc2651226baac27029e38c9d6ef883fa32084dbSteve Block# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
272fc2651226baac27029e38c9d6ef883fa32084dbSteve Block# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
282fc2651226baac27029e38c9d6ef883fa32084dbSteve Block# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
292fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
302fc2651226baac27029e38c9d6ef883fa32084dbSteve Block"""
312fc2651226baac27029e38c9d6ef883fa32084dbSteve BlockThe TestRunner2 package is an alternate implementation of the TestRunner
322fc2651226baac27029e38c9d6ef883fa32084dbSteve Blockclass that uses the manager_worker_broker module to send sets of tests to
332fc2651226baac27029e38c9d6ef883fa32084dbSteve Blockworkers and receive their completion messages accordingly.
342fc2651226baac27029e38c9d6ef883fa32084dbSteve Block"""
352fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
362fc2651226baac27029e38c9d6ef883fa32084dbSteve Blockimport logging
3781bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdochimport time
382fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
3981bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdochfrom webkitpy.tool import grammar
402fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
412fc2651226baac27029e38c9d6ef883fa32084dbSteve Blockfrom webkitpy.layout_tests.layout_package import manager_worker_broker
422fc2651226baac27029e38c9d6ef883fa32084dbSteve Blockfrom webkitpy.layout_tests.layout_package import test_runner
432fc2651226baac27029e38c9d6ef883fa32084dbSteve Blockfrom webkitpy.layout_tests.layout_package import worker
442fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
4581bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch
462fc2651226baac27029e38c9d6ef883fa32084dbSteve Block_log = logging.getLogger(__name__)
472fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
482fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
4981bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdochclass _WorkerState(object):
5081bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch    """A class for the TestRunner/manager to use to track the current state
5181bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch    of the workers."""
5281bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch    def __init__(self, number, worker_connection):
5381bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        self.worker_connection = worker_connection
5481bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        self.number = number
5581bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        self.done = False
5681bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        self.current_test_name = None
5781bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        self.next_timeout = None
5881bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        self.wedged = False
5981bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        self.stats = {}
6081bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        self.stats['name'] = worker_connection.name
6181bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        self.stats['num_tests'] = 0
6281bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        self.stats['total_time'] = 0
6381bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch
6481bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch    def __repr__(self):
6581bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        return "_WorkerState(" + str(self.__dict__) + ")"
6681bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch
6781bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch
682fc2651226baac27029e38c9d6ef883fa32084dbSteve Blockclass TestRunner2(test_runner.TestRunner):
692fc2651226baac27029e38c9d6ef883fa32084dbSteve Block    def __init__(self, port, options, printer):
702fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        test_runner.TestRunner.__init__(self, port, options, printer)
712fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        self._all_results = []
722fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        self._group_stats = {}
732fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        self._current_result_summary = None
7481bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch
7581bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        # This maps worker names to the state we are tracking for each of them.
7681bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        self._worker_states = {}
772fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
782fc2651226baac27029e38c9d6ef883fa32084dbSteve Block    def is_done(self):
7981bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        worker_states = self._worker_states.values()
8081bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        return worker_states and all(self._worker_is_done(worker_state) for worker_state in worker_states)
8181bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch
8281bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch    def _worker_is_done(self, worker_state):
8381bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        t = time.time()
8481bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        if worker_state.done or worker_state.wedged:
8581bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            return True
8681bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch
8781bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        next_timeout = worker_state.next_timeout
8881bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        WEDGE_PADDING = 40.0
8981bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        if next_timeout and t > next_timeout + WEDGE_PADDING:
9081bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            _log.error('')
9181bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            worker_state.worker_connection.log_wedged_worker(worker_state.current_test_name)
9281bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            _log.error('')
9381bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            worker_state.wedged = True
9481bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            return True
9581bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        return False
962fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
972fc2651226baac27029e38c9d6ef883fa32084dbSteve Block    def name(self):
982fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        return 'TestRunner2'
992fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
1002fc2651226baac27029e38c9d6ef883fa32084dbSteve Block    def _run_tests(self, file_list, result_summary):
1012fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        """Runs the tests in the file_list.
1022fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
10381bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        Return: A tuple (interrupted, keyboard_interrupted, thread_timings,
10481bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            test_timings, individual_test_timings)
10581bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            interrupted is whether the run was interrupted
1062fc2651226baac27029e38c9d6ef883fa32084dbSteve Block            keyboard_interrupted is whether someone typed Ctrl^C
1072fc2651226baac27029e38c9d6ef883fa32084dbSteve Block            thread_timings is a list of dicts with the total runtime
1082fc2651226baac27029e38c9d6ef883fa32084dbSteve Block              of each thread with 'name', 'num_tests', 'total_time' properties
1092fc2651226baac27029e38c9d6ef883fa32084dbSteve Block            test_timings is a list of timings for each sharded subdirectory
1102fc2651226baac27029e38c9d6ef883fa32084dbSteve Block              of the form [time, directory_name, num_tests]
1112fc2651226baac27029e38c9d6ef883fa32084dbSteve Block            individual_test_timings is a list of run times for each test
1122fc2651226baac27029e38c9d6ef883fa32084dbSteve Block              in the form {filename:filename, test_run_time:test_run_time}
1132fc2651226baac27029e38c9d6ef883fa32084dbSteve Block            result_summary: summary object to populate with the results
1142fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        """
1152fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        self._current_result_summary = result_summary
11681bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        self._all_results = []
11781bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        self._group_stats = {}
11881bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        self._worker_states = {}
11981bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch
12081bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        keyboard_interrupted = False
12181bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        interrupted = False
12281bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        thread_timings = []
12381bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch
12481bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        self._printer.print_update('Sharding tests ...')
12581bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        test_lists = self._shard_tests(file_list,
1262daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch            (int(self._options.child_processes) > 1) and not self._options.experimental_fully_parallel)
1272daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch
1282daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch        num_workers = self._num_workers(len(test_lists))
1292fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
13081bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        manager_connection = manager_worker_broker.get(self._port, self._options,
13181bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch                                                       self, worker.Worker)
1322fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
13381bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        if self._options.dry_run:
13481bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            return (keyboard_interrupted, interrupted, thread_timings,
13581bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch                    self._group_stats, self._all_results)
1362fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
13781bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        self._printer.print_update('Starting %s ...' %
13881bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch                                   grammar.pluralize('worker', num_workers))
13981bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        for worker_number in xrange(num_workers):
14081bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            worker_connection = manager_connection.start_worker(worker_number)
14181bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            worker_state = _WorkerState(worker_number, worker_connection)
14281bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            self._worker_states[worker_connection.name] = worker_state
1432fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
14481bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            # FIXME: If we start workers up too quickly, DumpRenderTree appears
14581bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            # to thrash on something and time out its first few tests. Until
14681bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            # we can figure out what's going on, sleep a bit in between
14781bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            # workers.
14881bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            time.sleep(0.1)
1492fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
15081bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        self._printer.print_update("Starting testing ...")
1512fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        for test_list in test_lists:
1522fc2651226baac27029e38c9d6ef883fa32084dbSteve Block            manager_connection.post_message('test_list', test_list[0], test_list[1])
1532fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
15481bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        # We post one 'stop' message for each worker. Because the stop message
15581bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        # are sent after all of the tests, and because each worker will stop
15681bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        # reading messsages after receiving a stop, we can be sure each
15781bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        # worker will get a stop message and hence they will all shut down.
15881bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        for i in xrange(num_workers):
15981bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            manager_connection.post_message('stop')
1602fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
16181bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        try:
16281bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            while not self.is_done():
16381bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch                # We loop with a timeout in order to be able to detect wedged threads.
1642fc2651226baac27029e38c9d6ef883fa32084dbSteve Block                manager_connection.run_message_loop(delay_secs=1.0)
1652fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
16681bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            if any(worker_state.wedged for worker_state in self._worker_states.values()):
16781bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch                _log.error('')
16881bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch                _log.error('Remaining workers are wedged, bailing out.')
16981bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch                _log.error('')
17081bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            else:
17181bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch                _log.debug('No wedged threads')
17281bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch
17381bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            # Make sure all of the workers have shut down (if possible).
17481bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            for worker_state in self._worker_states.values():
17581bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch                if not worker_state.wedged and worker_state.worker_connection.is_alive():
17681bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch                    worker_state.worker_connection.join(0.5)
17781bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch                    assert not worker_state.worker_connection.is_alive()
17881bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch
17981bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        except KeyboardInterrupt:
18081bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            _log.info("Interrupted, exiting")
18181bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            self.cancel_workers()
18281bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            keyboard_interrupted = True
18381bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        except test_runner.TestRunInterruptedException, e:
18481bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            _log.info(e.reason)
18581bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            self.cancel_workers()
18681bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            interrupted = True
18781bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        except:
18881bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            # Unexpected exception; don't try to clean up workers.
18981bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            _log.info("Exception raised, exiting")
19081bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            raise
19181bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch
19281bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        thread_timings = [worker_state.stats for worker_state in self._worker_states.values()]
1932fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
1942fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        # FIXME: should this be a class instead of a tuple?
19581bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        return (interrupted, keyboard_interrupted, thread_timings,
1962fc2651226baac27029e38c9d6ef883fa32084dbSteve Block                self._group_stats, self._all_results)
1972fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
19881bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch    def cancel_workers(self):
19981bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        for worker_state in self._worker_states.values():
20081bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            worker_state.worker_connection.cancel()
2012fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
20281bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch    def handle_started_test(self, source, test_info, hang_timeout):
20381bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        worker_state = self._worker_states[source]
20481bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        worker_state.current_test_name = self._port.relative_test_filename(test_info.filename)
20581bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        worker_state.next_timeout = time.time() + hang_timeout
2062fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
20781bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch    def handle_done(self, source):
20881bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        worker_state = self._worker_states[source]
20981bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        worker_state.done = True
2102fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
21181bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch    def handle_exception(self, source, exception_info):
21281bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        exception_type, exception_value, exception_traceback = exception_info
21381bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        raise exception_type, exception_value, exception_traceback
2142fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
21581bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch    def handle_finished_list(self, source, list_name, num_tests, elapsed_time):
21681bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        self._group_stats[list_name] = (num_tests, elapsed_time)
2172fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
21881bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch    def handle_finished_test(self, source, result, elapsed_time):
21981bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        worker_state = self._worker_states[source]
22081bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        worker_state.next_timeout = None
22181bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        worker_state.current_test_name = None
22281bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        worker_state.stats['total_time'] += elapsed_time
22381bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        worker_state.stats['num_tests'] += 1
22481bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch
22581bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        if worker_state.wedged:
22681bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch            # This shouldn't happen if we have our timeouts tuned properly.
2272bde8e466a4451c7319e3a072d118917957d6554Steve Block            _log.error("%s unwedged", source)
2282fc2651226baac27029e38c9d6ef883fa32084dbSteve Block
2292fc2651226baac27029e38c9d6ef883fa32084dbSteve Block        self._all_results.append(result)
23081bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch        self._update_summary_with_result(self._current_result_summary, result)
231