blob: 869d56dfb68a2818c9949c993e2902e872686fbe [file] [log] [blame]
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001#!/usr/bin/python -u
2#
3# -*- coding: utf-8 -*-
4#
5# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
6# Use of this source code is governed by a BSD-style license that can be
7# found in the LICENSE file.
8
9'''
10The main factory flow that runs the factory test and finalizes a device.
11'''
12
Hung-Te Lin02988092012-03-16 12:35:17 +080013import logging, os, pickle, pipes, re, signal, subprocess, sys, tempfile
Hung-Te Linf2f78f72012-02-08 19:27:11 +080014import threading, time, traceback
15from collections import deque
16from optparse import OptionParser
17from Queue import Queue
18
19import factory_common
20from autotest_lib.client.bin import prespawner
21from autotest_lib.client.cros import factory
22from autotest_lib.client.cros.factory import state
23from autotest_lib.client.cros.factory import TestState
24from autotest_lib.client.cros.factory.event import Event
25from autotest_lib.client.cros.factory.event import EventClient
26from autotest_lib.client.cros.factory.event import EventServer
27
28
29SCRIPT_PATH = os.path.realpath(__file__)
30CROS_FACTORY_LIB_PATH = os.path.dirname(SCRIPT_PATH)
Hung-Te Lin6bb48552012-02-09 14:37:43 +080031FACTORY_UI_PATH = os.path.join(CROS_FACTORY_LIB_PATH, 'ui')
Hung-Te Linf2f78f72012-02-08 19:27:11 +080032CLIENT_PATH = os.path.realpath(os.path.join(CROS_FACTORY_LIB_PATH, '..', '..'))
33DEFAULT_TEST_LIST_PATH = os.path.join(
34 CLIENT_PATH , 'site_tests', 'suite_Factory', 'test_list')
35HWID_CFG_PATH = '/usr/local/share/chromeos-hwid/cfg'
36
37
38def get_hwid_cfg():
39 '''
40 Returns the HWID config tag, or an empty string if none can be found.
41 '''
42 if 'CROS_HWID' in os.environ:
43 return os.environ['CROS_HWID']
44 if os.path.exists(HWID_CFG_PATH):
45 with open(HWID_CFG_PATH, 'rt') as hwid_cfg_handle:
46 return hwid_cfg_handle.read().strip()
47 return ''
48
49
50def find_test_list():
51 '''
52 Returns the path to the active test list, based on the HWID config tag.
53 '''
54 hwid_cfg = get_hwid_cfg()
55
56 # Try in order: test_list, test_list.$hwid_cfg, test_list.all
57 if hwid_cfg:
58 test_list = '%s_%s' % (DEFAULT_TEST_LIST_PATH, hwid_cfg)
59 if os.path.exists(test_list):
60 logging.info('Using special test list: %s', test_list)
61 return test_list
62 logging.info('WARNING: no specific test list for config: %s', hwid_cfg)
63
64 test_list = DEFAULT_TEST_LIST_PATH
65 if os.path.exists(test_list):
66 return test_list
67
68 test_list = ('%s.all' % DEFAULT_TEST_LIST_PATH)
69 if os.path.exists(test_list):
70 logging.info('Using default test list: ' + test_list)
71 return test_list
72 logging.info('ERROR: Cannot find any test list.')
73
74
75def is_process_alive(pid):
76 '''
77 Returns true if the named process is alive and not a zombie.
78 '''
79 try:
80 with open("/proc/%d/stat" % pid) as f:
81 return f.readline().split()[2] != 'Z'
82 except IOError:
83 return False
84
85
86def kill_process_tree(process, caption):
87 '''
88 Kills a process and all its subprocesses.
89
90 @param process: The process to kill (opened with the subprocess module).
91 @param caption: A caption describing the process.
92 '''
Hung-Te Lin1b094702012-03-15 18:14:15 +080093 # os.kill does not kill child processes. os.killpg kills all processes
94 # sharing same group (and is usually used for killing process tree). But in
95 # our case, to preserve PGID for autotest and upstart service, we need to
96 # iterate through each level until leaf of the tree.
97
98 def get_all_pids(root):
99 ps_output = subprocess.Popen(['ps','--no-headers','-eo','pid,ppid'],
100 stdout=subprocess.PIPE)
101 children = {}
102 for line in ps_output.stdout:
103 match = re.findall('\d+', line)
104 children.setdefault(int(match[1]), []).append(int(match[0]))
105 pids = []
106 def add_children(pid):
107 pids.append(pid)
108 map(add_children, children.get(pid, []))
109 add_children(root)
Hung-Te Lin02988092012-03-16 12:35:17 +0800110 # Reverse the list to first kill children then parents.
111 # Note reversed(pids) will return an iterator instead of real list, so
112 # we must explicitly call pids.reverse() here.
113 pids.reverse()
114 return pids
Hung-Te Lin1b094702012-03-15 18:14:15 +0800115
Hung-Te Lin02988092012-03-16 12:35:17 +0800116 pids = get_all_pids(process.pid)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800117 for sig in [signal.SIGTERM, signal.SIGKILL]:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800118 logging.info('Stopping %s (pid=%s)...', caption, sorted(pids))
119
120 for i in range(25): # Try 25 times (200 ms between tries)
121 for pid in pids:
122 try:
Hung-Te Lin02988092012-03-16 12:35:17 +0800123 logging.info("Sending signal %s to %d", sig, pid)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800124 os.kill(pid, sig)
125 except OSError:
126 pass
Jon Salza751ce52012-03-15 14:59:33 +0800127 pids = filter(is_process_alive, pids)
128 if not pids:
129 return
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800130 time.sleep(0.2) # Sleep 200 ms and try again
131
132 logging.warn('Failed to stop %s process. Ignoring.', caption)
133
134
135class TestInvocation(object):
136 '''
137 State for an active test.
138 '''
139 def __init__(self, goofy, test, on_completion=None):
140 '''Constructor.
141
142 @param goofy: The controlling Goofy object.
143 @param test: The FactoryTest object to test.
144 @param on_completion: Callback to invoke in the goofy event queue
145 on completion.
146 '''
147 self.goofy = goofy
148 self.test = test
149 self.thread = threading.Thread(target=self._run)
150 self.on_completion = on_completion
151
152 self._lock = threading.Lock()
153 # The following properties are guarded by the lock.
154 self._aborted = False
155 self._completed = False
156 self._process = None
157
158 def start(self):
159 '''Starts the test thread.'''
160 self.thread.start()
161
162 def abort_and_join(self):
163 '''
164 Aborts a test (must be called from the event controller thread).
165 '''
166 with self._lock:
167 self._aborted = True
168 if self._process:
169 kill_process_tree(self._process, 'autotest')
170 if self.thread:
171 self.thread.join()
172 with self._lock:
173 # Should be set by the thread itself, but just in case...
174 self._completed = True
175
176 def is_completed(self):
177 '''
178 Returns true if the test has finished.
179 '''
180 with self._lock:
181 return self._completed
182
183 def _invoke_autotest(self, test, dargs):
184 '''
185 Invokes an autotest test.
186
187 This method encapsulates all the magic necessary to run a single
188 autotest test using the 'autotest' command-line tool and get a
189 sane pass/fail status and error message out. It may be better
190 to just write our own command-line wrapper for job.run_test
191 instead.
192
193 @param test: the autotest to run
194 @param dargs: the argument map
195 @return: tuple of status (TestState.PASSED or TestState.FAILED) and
196 error message, if any
197 '''
198 status = TestState.FAILED
199 error_msg = 'Unknown'
200
201 try:
202 client_dir = CLIENT_PATH
203
204 output_dir = '%s/results/%s' % (client_dir, test.path)
205 if not os.path.exists(output_dir):
206 os.makedirs(output_dir)
207 tmp_dir = tempfile.mkdtemp(prefix='tmp', dir=output_dir)
208
209 control_file = os.path.join(tmp_dir, 'control')
210 result_file = os.path.join(tmp_dir, 'result')
211 args_file = os.path.join(tmp_dir, 'args')
212
213 with open(args_file, 'w') as f:
214 pickle.dump(dargs, f)
215
216 # Create a new control file to use to run the test
217 with open(control_file, 'w') as f:
218 print >> f, 'import common, traceback, utils'
219 print >> f, 'import cPickle as pickle'
220 print >> f, ("success = job.run_test("
221 "'%s', **pickle.load(open('%s')))" % (
222 test.autotest_name, args_file))
223
224 print >> f, (
225 "pickle.dump((success, "
226 "str(job.last_error) if job.last_error else None), "
227 "open('%s', 'w'), protocol=2)"
228 % result_file)
229
230 args = ['%s/bin/autotest' % client_dir,
231 '--output_dir', output_dir,
232 control_file]
233
234 factory.console.info('Running test %s' % test.path)
235 logging.debug('Test command line: %s', ' '.join(
236 [pipes.quote(arg) for arg in args]))
237
238 with self._lock:
239 self._process = prespawner.spawn(
240 args, {'CROS_FACTORY_TEST_PATH': test.path})
241
242 returncode = self._process.wait()
243 with self._lock:
244 if self._aborted:
245 error_msg = 'Aborted by operator'
246 return
247
248 if returncode:
249 # Only happens when there is an autotest-level problem (not when
250 # the test actually failed).
251 error_msg = 'autotest returned with code %d' % returncode
252 return
253
254 with open(result_file) as f:
255 try:
256 success, error_msg = pickle.load(f)
257 except: # pylint: disable=W0702
258 logging.exception('Unable to retrieve autotest results')
259 error_msg = 'Unable to retrieve autotest results'
260 return
261
262 if success:
263 status = TestState.PASSED
264 error_msg = ''
265 except Exception: # pylint: disable=W0703
266 traceback.print_exc(sys.stderr)
267 finally:
268 factory.console.info('Test %s: %s' % (test.path, status))
269 return status, error_msg # pylint: disable=W0150
270
271 def _run(self):
272 with self._lock:
273 if self._aborted:
274 return
275
276 count = self.test.update_state(
277 status=TestState.ACTIVE, increment_count=1, error_msg='').count
278
279 test_tag = '%s_%s' % (self.test.path, count)
280 dargs = dict(self.test.dargs)
281 dargs.update({'tag': test_tag,
282 'test_list_path': self.goofy.test_list_path})
283
284 status, error_msg = self._invoke_autotest(self.test, dargs)
285 self.test.update_state(status=status, error_msg=error_msg,
286 visible=False)
287 with self._lock:
288 self._completed = True
289 self.goofy.run_queue.put(self.goofy.reap_completed_tests)
290 self.goofy.run_queue.put(self.on_completion)
291
292
293class Goofy(object):
294 '''
295 The main factory flow.
296
297 Note that all methods in this class must be invoked from the main
298 (event) thread. Other threads, such as callbacks and TestInvocation
299 methods, should instead post events on the run queue.
300
301 TODO: Unit tests. (chrome-os-partner:7409)
302
303 Properties:
304 state_instance: An instance of FactoryState.
305 state_server: The FactoryState XML/RPC server.
306 state_server_thread: A thread running state_server.
307 event_server: The EventServer socket server.
308 event_server_thread: A thread running event_server.
309 event_client: A client to the event server.
Hung-Te Lin6bb48552012-02-09 14:37:43 +0800310 ui_process: The factory ui process object.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800311 run_queue: A queue of callbacks to invoke from the main thread.
312 invocations: A map from FactoryTest objects to the corresponding
313 TestInvocations objects representing active tests.
314 tests_to_run: A deque of tests that should be run when the current
315 test(s) complete.
316 options: Command-line options.
317 args: Command-line args.
318 test_list_path: The path to the test list.
319 test_list: The test list.
320 '''
321 def __init__(self):
322 self.state_instance = None
323 self.state_server = None
324 self.state_server_thread = None
325 self.event_server = None
326 self.event_server_thread = None
327 self.event_client = None
328 self.ui_process = None
329 self.run_queue = Queue()
330 self.invocations = {}
331 self.tests_to_run = deque()
332 self.visible_test = None
333
334 self.options = None
335 self.args = None
336 self.test_list_path = None
337 self.test_list = None
338
339 def __del__(self):
340 if self.ui_process:
341 kill_process_tree(self.ui_process, 'ui')
342 if self.state_server_thread:
343 logging.info('Stopping state server')
344 self.state_server.shutdown()
345 self.state_server_thread.join()
346 if self.event_server_thread:
347 logging.info('Stopping event server')
348 self.event_server.shutdown() # pylint: disable=E1101
349 self.event_server_thread.join()
350 prespawner.stop()
351
352 def start_state_server(self):
353 self.state_instance, self.state_server = state.create_server()
354 logging.info('Starting state server')
355 self.state_server_thread = threading.Thread(
356 target=self.state_server.serve_forever)
357 self.state_server_thread.start()
358
359 def start_event_server(self):
360 self.event_server = EventServer()
361 logging.info('Starting factory event server')
362 self.event_server_thread = threading.Thread(
363 target=self.event_server.serve_forever) # pylint: disable=E1101
364 self.event_server_thread.start()
365
366 self.event_client = EventClient(
367 callback=self.handle_event, event_loop=self.run_queue)
368
369 def start_ui(self):
370 ui_proc_args = [FACTORY_UI_PATH, self.test_list_path]
Jon Salz14bcbb02012-03-17 15:11:50 +0800371 if self.options.verbose:
372 ui_proc_args.append('-v')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800373 logging.info('Starting ui %s', ui_proc_args)
374 self.ui_process = subprocess.Popen(ui_proc_args)
375 logging.info('Waiting for UI to come up...')
376 self.event_client.wait(
377 lambda event: event.type == Event.Type.UI_READY)
378 logging.info('UI has started')
379
380 def set_visible_test(self, test):
381 if self.visible_test == test:
382 return
383
384 if test:
385 test.update_state(visible=True)
386 if self.visible_test:
387 self.visible_test.update_state(visible=False)
388 self.visible_test = test
389
Jon Salz74ad3262012-03-16 14:40:55 +0800390 def handle_shutdown_complete(self, test, state):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800391 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800392 Handles the case where a shutdown was detected during a shutdown step.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800393
Jon Salz74ad3262012-03-16 14:40:55 +0800394 @param test: The ShutdownStep.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800395 @param state: The test state.
396 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800397 state = test.update_state(increment_shutdown_count=1)
398 logging.info('Detected shutdown (%d of %d)',
399 state.shutdown_count, test.iterations)
400 if state.shutdown_count == test.iterations:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800401 # Good!
402 test.update_state(status=TestState.PASSED, error_msg='')
Jon Salz74ad3262012-03-16 14:40:55 +0800403 elif state.shutdown_count > test.iterations:
404 # Shutdowned too many times
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800405 test.update_state(status=TestState.FAILED,
Jon Salz74ad3262012-03-16 14:40:55 +0800406 error_msg='Too many shutdowns')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800407 else:
Jon Salz74ad3262012-03-16 14:40:55 +0800408 # Need to shutdown again
409 self.shutdown()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800410
411 def init_states(self):
412 '''
413 Initializes all states on startup.
414 '''
415 for test in self.test_list.get_all_tests():
416 # Make sure the state server knows about all the tests,
417 # defaulting to an untested state.
418 test.update_state(update_parent=False, visible=False)
419
420 # Any 'active' tests should be marked as failed now.
421 for test in self.test_list.walk():
422 state = test.get_state()
423 if isinstance(test, factory.InformationScreen):
424 test.update_state(status=TestState.UNTESTED, error_msg='')
425 elif state.status == TestState.ACTIVE:
Jon Salz74ad3262012-03-16 14:40:55 +0800426 if isinstance(test, factory.ShutdownStep):
427 # Shutdown while the test was active - that's good.
428 self.handle_shutdown_complete(test, state)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800429 else:
430 test.update_state(status=TestState.FAILED,
Jon Salz74ad3262012-03-16 14:40:55 +0800431 error_msg='Unknown (shutdown?)')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800432
433 def show_next_active_test(self):
434 '''
435 Rotates to the next visible active test.
436 '''
437 self.reap_completed_tests()
438 active_tests = [
439 t for t in self.test_list.walk()
440 if t.is_leaf() and t.get_state().status == TestState.ACTIVE]
441 if not active_tests:
442 return
443
444 try:
445 next_test = active_tests[
446 (active_tests.index(self.visible_test) + 1) % len(active_tests)]
447 except ValueError: # visible_test not present in active_tests
448 next_test = active_tests[0]
449
450 self.set_visible_test(next_test)
451
452 def handle_event(self, event):
453 '''
454 Handles an event from the event server.
455 '''
Tammo Spalinkdb8d7112012-03-13 18:54:37 +0800456 if event.type == Event.Type.SWITCH_TEST:
457 test = self.test_list.lookup_path(event.key)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800458 if test:
459 invoc = self.invocations.get(test)
460 if invoc and test.backgroundable:
461 # Already running: just bring to the front if it
462 # has a UI.
463 logging.info('Setting visible test to %s', test.path)
464 self.event_client.post_event(
Tammo Spalinkdb8d7112012-03-13 18:54:37 +0800465 Event(Event.Type.SET_VISIBLE_TEST, path=test.path))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800466 self.abort_active_tests()
467 for t in test.walk():
468 t.update_state(status=TestState.UNTESTED)
469 self.run_tests(test)
Tammo Spalinkdb8d7112012-03-13 18:54:37 +0800470 else:
471 logging.error('unknown test %r' % event.key)
472 elif event.type == Event.Type.SHOW_NEXT_ACTIVE_TEST:
473 self.show_next_active_test()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800474
475 def run_next_test(self):
476 '''
477 Runs the next eligible test (or tests) in self.tests_to_run.
478 '''
479 self.reap_completed_tests()
480 while self.tests_to_run:
481 logging.debug('Tests to run: %s',
482 [x.path for x in self.tests_to_run])
483
484 test = self.tests_to_run[0]
485
486 if test in self.invocations:
487 logging.info('Next test %s is already running', test.path)
488 self.tests_to_run.popleft()
489 return
490
491 if self.invocations and not (test.backgroundable and all(
492 [x.backgroundable for x in self.invocations])):
493 logging.debug('Waiting for non-backgroundable tests to '
494 'complete before running %s', test.path)
495 return
496
497 self.tests_to_run.popleft()
498
Jon Salz74ad3262012-03-16 14:40:55 +0800499 if isinstance(test, factory.ShutdownStep):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800500 test.update_state(status=TestState.ACTIVE, increment_count=1,
Jon Salz74ad3262012-03-16 14:40:55 +0800501 error_msg='', shutdown_count=0)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800502 # Save pending test list in the state server
503 self.state_instance.set_shared_data(
Jon Salz74ad3262012-03-16 14:40:55 +0800504 'tests_after_shutdown',
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800505 [t.path for t in self.tests_to_run])
Jon Salz74ad3262012-03-16 14:40:55 +0800506
507 self.shutdown(test.operation)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800508
509 invoc = TestInvocation(self, test, on_completion=self.run_next_test)
510 self.invocations[test] = invoc
511 if self.visible_test is None and test.has_ui:
512 self.set_visible_test(test)
513 invoc.start()
514
515 def run_tests(self, root, untested_only=False):
516 '''
517 Runs tests under root.
518
519 The tests are run in order unless one fails (then stops).
520 Backgroundable tests are run simultaneously; when a foreground test is
521 encountered, we wait for all active tests to finish before continuing.
522 '''
523 self.tests_to_run = deque()
524 for x in root.walk():
525 if not x.is_leaf():
526 continue
527 if untested_only and x.get_state().status != TestState.UNTESTED:
528 continue
529 self.tests_to_run.append(x)
530 self.run_next_test()
531
532 def reap_completed_tests(self):
533 '''
534 Removes completed tests from the set of active tests.
535
536 Also updates the visible test if it was reaped.
537 '''
538 for t, v in dict(self.invocations).iteritems():
539 if v.is_completed():
540 del self.invocations[t]
541
542 if (self.visible_test is None or
543 self.visible_test not in self.invocations):
544 self.set_visible_test(None)
545 # Make the first running test, if any, the visible test
546 for t in self.test_list.walk():
547 if t in self.invocations:
548 self.set_visible_test(t)
549 break
550
551 def abort_active_tests(self):
552 '''
553 Kills and waits for all active tests.
554 '''
555 self.reap_completed_tests()
556 for test, invoc in self.invocations.items():
557 factory.console.info('Killing active test %s...' % test.path)
558 invoc.abort_and_join()
559 factory.console.info('Killed %s' % test.path)
560 del self.invocations[test]
561 self.reap_completed_tests()
562
Jon Salz74ad3262012-03-16 14:40:55 +0800563 def shutdown(self, operation):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800564 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800565 Shuts the machine (from a ShutdownStep).
566
567 Args:
568 operation: 'reboot' or 'halt'.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800569 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800570 assert operation in ['reboot', 'halt']
571 logging.info('Shutting down: %s', operation)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800572 subprocess.check_call('sync')
Jon Salz74ad3262012-03-16 14:40:55 +0800573 subprocess.check_call(operation)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800574 time.sleep(30)
Jon Salz74ad3262012-03-16 14:40:55 +0800575 assert False, 'Never reached (should %s)' % operation
576
577 def reboot(self):
578 self.shutdown('reboot')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800579
580 def main(self):
581 parser = OptionParser()
582 parser.add_option('-v', '--verbose', dest='verbose',
583 action='store_true',
584 help='Enable debug logging')
585 parser.add_option('--print_test_list', dest='print_test_list',
586 metavar='FILE',
587 help='Read and print test list FILE, and exit')
588 (self.options, self.args) = parser.parse_args()
589
590 factory.init_logging('goofy', verbose=self.options.verbose)
591
592 if self.options.print_test_list:
593 print (factory.read_test_list(self.options.print_test_list).
594 __repr__(recursive=True))
595 return
596
597 logging.info('Started')
598
599 self.start_state_server()
600 # Update HWID configuration tag.
601 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
602
603 self.test_list_path = find_test_list()
604 self.test_list = factory.read_test_list(self.test_list_path,
605 self.state_instance)
606 logging.info('TEST_LIST:\n%s', self.test_list.__repr__(recursive=True))
607
608 self.init_states()
609 self.start_event_server()
610 self.start_ui()
611 prespawner.start()
612
613 def state_change_callback(test, state):
614 self.event_client.post_event(
615 Event(Event.Type.STATE_CHANGE,
616 path=test.path, state=state))
617 self.test_list.state_change_callback = state_change_callback
618
619 try:
620 tests_after_reboot = self.state_instance.get_shared_data(
621 'tests_after_reboot')
622 except KeyError:
623 tests_after_reboot = None
624
625 if tests_after_reboot is not None:
626 logging.info('Resuming tests after reboot: %s', tests_after_reboot)
627 self.state_instance.set_shared_data('tests_after_reboot', None)
628 self.tests_to_run.extend(
629 self.test_list.lookup_path(t) for t in tests_after_reboot)
630 self.run_next_test()
631 else:
632 self.run_tests(self.test_list, untested_only=True)
633
634 # Process events forever.
635 while True:
636 event = self.run_queue.get()
637 if not event:
638 # Shutdown request (although this currently never happens).
639 self.run_queue.task_done()
640 break
641
642 try:
643 event()
644 except Exception as e: # pylint: disable=W0703
645 logging.error('Error in event loop: %s', e)
646 traceback.print_exc(sys.stderr)
647 # But keep going
648 finally:
649 self.run_queue.task_done()
650
651
652if __name__ == '__main__':
653 Goofy().main()