blob: bbc31aa7f00acb9c006f43ba7e586c3edf78e66c [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
Jon Salz73e0fd02012-04-04 11:46:38 +080013import array
14import fcntl
15import glob
Jon Salz258a40c2012-04-19 12:34:01 +080016import hashlib
Jon Salz0405ab52012-03-16 15:26:52 +080017import logging
18import os
Jon Salz258a40c2012-04-19 12:34:01 +080019import cPickle as pickle
Jon Salz0405ab52012-03-16 15:26:52 +080020import pipes
Jon Salz73e0fd02012-04-04 11:46:38 +080021import Queue
Jon Salz0405ab52012-03-16 15:26:52 +080022import re
23import signal
24import subprocess
25import sys
26import tempfile
27import threading
28import time
29import traceback
Jon Salz258a40c2012-04-19 12:34:01 +080030import unittest
31import uuid
Hung-Te Linf2f78f72012-02-08 19:27:11 +080032from collections import deque
33from optparse import OptionParser
Jon Salz258a40c2012-04-19 12:34:01 +080034from StringIO import StringIO
Hung-Te Linf2f78f72012-02-08 19:27:11 +080035
36import factory_common
Jon Salz8375c2e2012-04-04 15:22:24 +080037from autotest_lib.client.bin.prespawner import Prespawner
Hung-Te Linf2f78f72012-02-08 19:27:11 +080038from autotest_lib.client.cros import factory
39from autotest_lib.client.cros.factory import state
40from autotest_lib.client.cros.factory import TestState
Jon Salz258a40c2012-04-19 12:34:01 +080041from autotest_lib.client.cros.factory import utils
Hung-Te Linf2f78f72012-02-08 19:27:11 +080042from autotest_lib.client.cros.factory.event import Event
43from autotest_lib.client.cros.factory.event import EventClient
44from autotest_lib.client.cros.factory.event import EventServer
Jon Salz258a40c2012-04-19 12:34:01 +080045from autotest_lib.client.cros.factory.invocation import TestInvocation
46from autotest_lib.client.cros.factory.web_socket_manager import WebSocketManager
Hung-Te Linf2f78f72012-02-08 19:27:11 +080047
48
Hung-Te Linf2f78f72012-02-08 19:27:11 +080049DEFAULT_TEST_LIST_PATH = os.path.join(
Jon Salz258a40c2012-04-19 12:34:01 +080050 factory.CLIENT_PATH , 'site_tests', 'suite_Factory', 'test_list')
Hung-Te Linf2f78f72012-02-08 19:27:11 +080051HWID_CFG_PATH = '/usr/local/share/chromeos-hwid/cfg'
52
Jon Salz758e6cc2012-04-03 15:47:07 +080053GOOFY_IN_CHROOT_WARNING = '\n' + ('*' * 70) + '''
54You are running Goofy inside the chroot. Autotests are not supported.
55
56To use Goofy in the chroot, first install an Xvnc server:
57
58 sudo apt-get install tightvncserver
59
60...and then start a VNC X server outside the chroot:
61
62 vncserver :10 &
63 vncviewer :10
64
65...and run Goofy as follows:
66
67 env --unset=XAUTHORITY DISPLAY=localhost:10 python goofy.py
68''' + ('*' * 70)
Jon Salz73e0fd02012-04-04 11:46:38 +080069suppress_chroot_warning = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +080070
71def get_hwid_cfg():
72 '''
73 Returns the HWID config tag, or an empty string if none can be found.
74 '''
75 if 'CROS_HWID' in os.environ:
76 return os.environ['CROS_HWID']
77 if os.path.exists(HWID_CFG_PATH):
78 with open(HWID_CFG_PATH, 'rt') as hwid_cfg_handle:
79 return hwid_cfg_handle.read().strip()
80 return ''
81
82
83def find_test_list():
84 '''
85 Returns the path to the active test list, based on the HWID config tag.
86 '''
87 hwid_cfg = get_hwid_cfg()
88
89 # Try in order: test_list, test_list.$hwid_cfg, test_list.all
90 if hwid_cfg:
91 test_list = '%s_%s' % (DEFAULT_TEST_LIST_PATH, hwid_cfg)
92 if os.path.exists(test_list):
93 logging.info('Using special test list: %s', test_list)
94 return test_list
95 logging.info('WARNING: no specific test list for config: %s', hwid_cfg)
96
97 test_list = DEFAULT_TEST_LIST_PATH
98 if os.path.exists(test_list):
99 return test_list
100
101 test_list = ('%s.all' % DEFAULT_TEST_LIST_PATH)
102 if os.path.exists(test_list):
103 logging.info('Using default test list: ' + test_list)
104 return test_list
105 logging.info('ERROR: Cannot find any test list.')
106
Jon Salz73e0fd02012-04-04 11:46:38 +0800107
108class Environment(object):
109 '''
110 Abstract base class for external test operations, e.g., run an autotest,
111 shutdown, or reboot.
112
113 The Environment is assumed not to be thread-safe: callers must grab the lock
114 before calling any methods. This is primarily necessary because we mock out
115 this Environment with mox, and unfortunately mox is not thread-safe.
116 TODO(jsalz): Try to write a thread-safe wrapper for mox.
117 '''
118 lock = threading.Lock()
119
120 def shutdown(self, operation):
121 '''
122 Shuts the machine down (from a ShutdownStep).
123
124 Args:
125 operation: 'reboot' or 'halt'.
126
127 Returns:
128 True if Goofy should gracefully exit, or False if Goofy
129 should just consider the shutdown to have suceeded (e.g.,
130 in the chroot).
131 '''
132 raise NotImplementedError()
133
Jon Salz258a40c2012-04-19 12:34:01 +0800134 def launch_chrome(self):
135 '''
136 Launches Chrome.
137
138 Returns:
139 The Chrome subprocess (or None if none).
140 '''
141 raise NotImplementedError()
Jon Salz73e0fd02012-04-04 11:46:38 +0800142
143 def spawn_autotest(self, name, args, env_additions, result_file):
144 '''
145 Spawns a process to run an autotest.
146
147 Args:
148 name: Name of the autotest to spawn.
149 args: Command-line arguments.
150 env_additions: Additions to the environment.
151 result_file: Expected location of the result file.
152 '''
153 raise NotImplementedError()
154
155
156class DUTEnvironment(Environment):
157 '''
158 A real environment on a device under test.
159 '''
160 def shutdown(self, operation):
161 assert operation in ['reboot', 'halt']
162 logging.info('Shutting down: %s', operation)
163 subprocess.check_call('sync')
164 subprocess.check_call(operation)
165 time.sleep(30)
166 assert False, 'Never reached (should %s)' % operation
167
168 def spawn_autotest(self, name, args, env_additions, result_file):
Jon Salz8375c2e2012-04-04 15:22:24 +0800169 return self.goofy.prespawner.spawn(args, env_additions)
Jon Salz73e0fd02012-04-04 11:46:38 +0800170
Jon Salz258a40c2012-04-19 12:34:01 +0800171 def launch_chrome(self):
172 chrome_command = [
173 '/opt/google/chrome/chrome',
174 '--user-data-dir=%s/factory-chrome-datadir' %
175 factory.get_log_root(),
176 '--aura-host-window-use-fullscreen',
177 '--kiosk',
178 'http://localhost:%d/' % state.DEFAULT_FACTORY_STATE_PORT,
179 ]
Jon Salz73e0fd02012-04-04 11:46:38 +0800180
Jon Salz258a40c2012-04-19 12:34:01 +0800181 chrome_log = os.path.join(factory.get_log_root(), 'factory.chrome.log')
182 chrome_log_file = open(chrome_log, "a")
183 logging.info('Launching Chrome; logs in %s' % chrome_log)
184 return subprocess.Popen(chrome_command,
185 stdout=chrome_log_file,
186 stderr=subprocess.STDOUT)
187
188class FakeChrootEnvironment(Environment):
Jon Salz73e0fd02012-04-04 11:46:38 +0800189 '''
190 A chroot environment that doesn't actually shutdown or run autotests.
191 '''
192 def shutdown(self, operation):
193 assert operation in ['reboot', 'halt']
194 logging.warn('In chroot: skipping %s', operation)
195 return False
196
197 def spawn_autotest(self, name, args, env_additions, result_file):
198 logging.warn('In chroot: skipping autotest %s', name)
Jon Salz258a40c2012-04-19 12:34:01 +0800199 # Mark it as passed with 75% probability, or failed with 25%
200 # probability (depending on a hash of the autotest name).
201 pseudo_random = ord(hashlib.sha1(name).digest()[0]) / 256.0
202 passed = pseudo_random > .25
203
Jon Salz73e0fd02012-04-04 11:46:38 +0800204 with open(result_file, 'w') as out:
Jon Salz258a40c2012-04-19 12:34:01 +0800205 pickle.dump((passed, '' if passed else 'Simulated failure'), out)
Jon Salz73e0fd02012-04-04 11:46:38 +0800206 # Start a process that will return with a true exit status in
207 # 2 seconds (just like a happy autotest).
208 return subprocess.Popen(['sleep', '2'])
209
Jon Salz258a40c2012-04-19 12:34:01 +0800210 def launch_chrome(self):
211 logging.warn('In chroot; not launching Chrome. '
212 'Please open http://localhost:%d/ in Chrome.',
213 state.DEFAULT_FACTORY_STATE_PORT)
Jon Salz73e0fd02012-04-04 11:46:38 +0800214
215
216_inited_logging = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800217
218class Goofy(object):
219 '''
220 The main factory flow.
221
222 Note that all methods in this class must be invoked from the main
223 (event) thread. Other threads, such as callbacks and TestInvocation
224 methods, should instead post events on the run queue.
225
226 TODO: Unit tests. (chrome-os-partner:7409)
227
228 Properties:
Jon Salz258a40c2012-04-19 12:34:01 +0800229 uuid: A unique UUID for this invocation of Goofy.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800230 state_instance: An instance of FactoryState.
231 state_server: The FactoryState XML/RPC server.
232 state_server_thread: A thread running state_server.
233 event_server: The EventServer socket server.
234 event_server_thread: A thread running event_server.
235 event_client: A client to the event server.
Hung-Te Lin6bb48552012-02-09 14:37:43 +0800236 ui_process: The factory ui process object.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800237 run_queue: A queue of callbacks to invoke from the main thread.
238 invocations: A map from FactoryTest objects to the corresponding
239 TestInvocations objects representing active tests.
240 tests_to_run: A deque of tests that should be run when the current
241 test(s) complete.
242 options: Command-line options.
243 args: Command-line args.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800244 test_list: The test list.
Jon Salz0405ab52012-03-16 15:26:52 +0800245 event_handlers: Map of Event.Type to the method used to handle that
246 event. If the method has an 'event' argument, the event is passed
247 to the handler.
Jon Salz73e0fd02012-04-04 11:46:38 +0800248 exceptions: Exceptions encountered in invocation threads.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800249 '''
250 def __init__(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800251 self.uuid = str(uuid.uuid4())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800252 self.state_instance = None
253 self.state_server = None
254 self.state_server_thread = None
255 self.event_server = None
256 self.event_server_thread = None
257 self.event_client = None
Jon Salz8375c2e2012-04-04 15:22:24 +0800258 self.prespawner = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800259 self.ui_process = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800260 self.run_queue = Queue.Queue()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800261 self.invocations = {}
262 self.tests_to_run = deque()
263 self.visible_test = None
Jon Salz258a40c2012-04-19 12:34:01 +0800264 self.chrome = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800265
266 self.options = None
267 self.args = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800268 self.test_list = None
269
Jon Salz258a40c2012-04-19 12:34:01 +0800270 def test_or_root(event):
271 '''Returns the top-level parent for a test (the root node of the
272 tests that need to be run together if the given test path is to
273 be run).'''
274 try:
275 path = event.path
Jon Salzd2ed6cb2012-05-02 09:35:14 +0800276 except AttributeError:
Jon Salz258a40c2012-04-19 12:34:01 +0800277 path = None
278
279 if path:
280 return self.test_list.lookup_path(path).get_top_level_parent()
281 else:
282 return self.test_list
283
Jon Salz0405ab52012-03-16 15:26:52 +0800284 self.event_handlers = {
285 Event.Type.SWITCH_TEST: self.handle_switch_test,
Jon Salz968e90b2012-03-18 16:12:43 +0800286 Event.Type.SHOW_NEXT_ACTIVE_TEST:
287 lambda event: self.show_next_active_test(),
288 Event.Type.RESTART_TESTS:
Jon Salz258a40c2012-04-19 12:34:01 +0800289 lambda event: self.restart_tests(root=test_or_root(event)),
Jon Salz968e90b2012-03-18 16:12:43 +0800290 Event.Type.AUTO_RUN:
Jon Salz258a40c2012-04-19 12:34:01 +0800291 lambda event: self.auto_run(root=test_or_root(event)),
Jon Salz968e90b2012-03-18 16:12:43 +0800292 Event.Type.RE_RUN_FAILED:
Jon Salz258a40c2012-04-19 12:34:01 +0800293 lambda event: self.re_run_failed(root=test_or_root(event)),
Jon Salz968e90b2012-03-18 16:12:43 +0800294 Event.Type.REVIEW:
295 lambda event: self.show_review_information(),
Jon Salz0405ab52012-03-16 15:26:52 +0800296 }
297
Jon Salz73e0fd02012-04-04 11:46:38 +0800298 self.exceptions = []
Jon Salz258a40c2012-04-19 12:34:01 +0800299 self.web_socket_manager = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800300
301 def destroy(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800302 if self.chrome:
303 self.chrome.kill()
304 self.chrome = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800305 if self.ui_process:
Jon Salz258a40c2012-04-19 12:34:01 +0800306 utils.kill_process_tree(self.ui_process, 'ui')
Jon Salz73e0fd02012-04-04 11:46:38 +0800307 self.ui_process = None
Jon Salz258a40c2012-04-19 12:34:01 +0800308 if self.web_socket_manager:
309 logging.info('Stopping web sockets')
310 self.web_socket_manager.close()
311 self.web_socket_manager = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800312 if self.state_server_thread:
313 logging.info('Stopping state server')
314 self.state_server.shutdown()
315 self.state_server_thread.join()
Jon Salz73e0fd02012-04-04 11:46:38 +0800316 self.state_server.server_close()
317 self.state_server_thread = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800318 if self.event_server_thread:
319 logging.info('Stopping event server')
320 self.event_server.shutdown() # pylint: disable=E1101
321 self.event_server_thread.join()
Jon Salz73e0fd02012-04-04 11:46:38 +0800322 self.event_server.server_close()
323 self.event_server_thread = None
Jon Salz8375c2e2012-04-04 15:22:24 +0800324 if self.prespawner:
325 logging.info('Stopping prespawner')
326 self.prespawner.stop()
327 self.prespawner = None
328 if self.event_client:
Jon Salz258a40c2012-04-19 12:34:01 +0800329 logging.info('Closing event client')
Jon Salz8375c2e2012-04-04 15:22:24 +0800330 self.event_client.close()
Jon Salz258a40c2012-04-19 12:34:01 +0800331 self.event_client = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800332 self.check_exceptions()
Jon Salz258a40c2012-04-19 12:34:01 +0800333 logging.info('Done destroying Goofy')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800334
335 def start_state_server(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800336 self.state_instance, self.state_server = (
337 state.create_server(bind_address='0.0.0.0'))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800338 logging.info('Starting state server')
339 self.state_server_thread = threading.Thread(
Jon Salz8375c2e2012-04-04 15:22:24 +0800340 target=self.state_server.serve_forever,
341 name='StateServer')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800342 self.state_server_thread.start()
343
344 def start_event_server(self):
345 self.event_server = EventServer()
346 logging.info('Starting factory event server')
347 self.event_server_thread = threading.Thread(
Jon Salz8375c2e2012-04-04 15:22:24 +0800348 target=self.event_server.serve_forever,
349 name='EventServer') # pylint: disable=E1101
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800350 self.event_server_thread.start()
351
352 self.event_client = EventClient(
353 callback=self.handle_event, event_loop=self.run_queue)
354
Jon Salz258a40c2012-04-19 12:34:01 +0800355 self.web_socket_manager = WebSocketManager(self.uuid)
356 self.state_server.add_handler("/event",
357 self.web_socket_manager.handle_web_socket)
358
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800359 def start_ui(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800360 ui_proc_args = [os.path.join(factory.CROS_FACTORY_LIB_PATH, 'ui'),
361 self.options.test_list]
Jon Salz14bcbb02012-03-17 15:11:50 +0800362 if self.options.verbose:
363 ui_proc_args.append('-v')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800364 logging.info('Starting ui %s', ui_proc_args)
365 self.ui_process = subprocess.Popen(ui_proc_args)
366 logging.info('Waiting for UI to come up...')
367 self.event_client.wait(
368 lambda event: event.type == Event.Type.UI_READY)
369 logging.info('UI has started')
370
371 def set_visible_test(self, test):
372 if self.visible_test == test:
373 return
374
375 if test:
376 test.update_state(visible=True)
377 if self.visible_test:
378 self.visible_test.update_state(visible=False)
379 self.visible_test = test
380
Jon Salz74ad3262012-03-16 14:40:55 +0800381 def handle_shutdown_complete(self, test, state):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800382 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800383 Handles the case where a shutdown was detected during a shutdown step.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800384
Jon Salz74ad3262012-03-16 14:40:55 +0800385 @param test: The ShutdownStep.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800386 @param state: The test state.
387 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800388 state = test.update_state(increment_shutdown_count=1)
389 logging.info('Detected shutdown (%d of %d)',
390 state.shutdown_count, test.iterations)
391 if state.shutdown_count == test.iterations:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800392 # Good!
393 test.update_state(status=TestState.PASSED, error_msg='')
Jon Salz74ad3262012-03-16 14:40:55 +0800394 elif state.shutdown_count > test.iterations:
Jon Salz73e0fd02012-04-04 11:46:38 +0800395 # Shut down too many times
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800396 test.update_state(status=TestState.FAILED,
Jon Salz74ad3262012-03-16 14:40:55 +0800397 error_msg='Too many shutdowns')
Jon Salz258a40c2012-04-19 12:34:01 +0800398 elif utils.are_shift_keys_depressed():
Jon Salz73e0fd02012-04-04 11:46:38 +0800399 logging.info('Shift keys are depressed; cancelling restarts')
400 # Abort shutdown
401 test.update_state(
402 status=TestState.FAILED,
403 error_msg='Shutdown aborted with double shift keys')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800404 else:
Jon Salz74ad3262012-03-16 14:40:55 +0800405 # Need to shutdown again
Jon Salz73e0fd02012-04-04 11:46:38 +0800406 self.env.shutdown('reboot')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800407
408 def init_states(self):
409 '''
410 Initializes all states on startup.
411 '''
412 for test in self.test_list.get_all_tests():
413 # Make sure the state server knows about all the tests,
414 # defaulting to an untested state.
415 test.update_state(update_parent=False, visible=False)
416
417 # Any 'active' tests should be marked as failed now.
418 for test in self.test_list.walk():
419 state = test.get_state()
Hung-Te Lin96632362012-03-20 21:14:18 +0800420 if state.status != TestState.ACTIVE:
421 continue
422 if isinstance(test, factory.ShutdownStep):
423 # Shutdown while the test was active - that's good.
424 self.handle_shutdown_complete(test, state)
425 else:
426 test.update_state(status=TestState.FAILED,
427 error_msg='Unknown (shutdown?)')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800428
429 def show_next_active_test(self):
430 '''
431 Rotates to the next visible active test.
432 '''
433 self.reap_completed_tests()
434 active_tests = [
435 t for t in self.test_list.walk()
436 if t.is_leaf() and t.get_state().status == TestState.ACTIVE]
437 if not active_tests:
438 return
439
440 try:
441 next_test = active_tests[
442 (active_tests.index(self.visible_test) + 1) % len(active_tests)]
443 except ValueError: # visible_test not present in active_tests
444 next_test = active_tests[0]
445
446 self.set_visible_test(next_test)
447
448 def handle_event(self, event):
449 '''
450 Handles an event from the event server.
451 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800452 handler = self.event_handlers.get(event.type)
453 if handler:
Jon Salz968e90b2012-03-18 16:12:43 +0800454 handler(event)
Jon Salz0405ab52012-03-16 15:26:52 +0800455 else:
Jon Salz968e90b2012-03-18 16:12:43 +0800456 # We don't register handlers for all event types - just ignore
457 # this event.
458 logging.debug('Unbound event type %s', event.type)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800459
460 def run_next_test(self):
461 '''
462 Runs the next eligible test (or tests) in self.tests_to_run.
463 '''
464 self.reap_completed_tests()
465 while self.tests_to_run:
466 logging.debug('Tests to run: %s',
467 [x.path for x in self.tests_to_run])
468
469 test = self.tests_to_run[0]
470
471 if test in self.invocations:
472 logging.info('Next test %s is already running', test.path)
473 self.tests_to_run.popleft()
474 return
475
476 if self.invocations and not (test.backgroundable and all(
477 [x.backgroundable for x in self.invocations])):
478 logging.debug('Waiting for non-backgroundable tests to '
479 'complete before running %s', test.path)
480 return
481
482 self.tests_to_run.popleft()
483
Jon Salz74ad3262012-03-16 14:40:55 +0800484 if isinstance(test, factory.ShutdownStep):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800485 test.update_state(status=TestState.ACTIVE, increment_count=1,
Jon Salz74ad3262012-03-16 14:40:55 +0800486 error_msg='', shutdown_count=0)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800487 # Save pending test list in the state server
488 self.state_instance.set_shared_data(
Jon Salz74ad3262012-03-16 14:40:55 +0800489 'tests_after_shutdown',
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800490 [t.path for t in self.tests_to_run])
Jon Salz74ad3262012-03-16 14:40:55 +0800491
Jon Salz73e0fd02012-04-04 11:46:38 +0800492 with self.env.lock:
493 shutdown_result = self.env.shutdown(test.operation)
494 if shutdown_result:
495 # That's all, folks!
496 self.run_queue.put(None)
497 return
498 else:
499 # Just pass (e.g., in the chroot).
500 test.update_state(status=TestState.PASSED)
501 self.state_instance.set_shared_data(
502 'tests_after_shutdown', None)
503 continue
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800504
505 invoc = TestInvocation(self, test, on_completion=self.run_next_test)
506 self.invocations[test] = invoc
507 if self.visible_test is None and test.has_ui:
508 self.set_visible_test(test)
509 invoc.start()
510
Jon Salz0405ab52012-03-16 15:26:52 +0800511 def run_tests(self, subtrees, untested_only=False):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800512 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800513 Runs tests under subtree.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800514
515 The tests are run in order unless one fails (then stops).
516 Backgroundable tests are run simultaneously; when a foreground test is
517 encountered, we wait for all active tests to finish before continuing.
Jon Salz0405ab52012-03-16 15:26:52 +0800518
519 @param subtrees: Node or nodes containing tests to run (may either be
520 a single test or a list). Duplicates will be ignored.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800521 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800522 if type(subtrees) != list:
523 subtrees = [subtrees]
524
525 # Nodes we've seen so far, to avoid duplicates.
526 seen = set()
527
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800528 self.tests_to_run = deque()
Jon Salz0405ab52012-03-16 15:26:52 +0800529 for subtree in subtrees:
530 for test in subtree.walk():
531 if test in seen:
532 continue
533 seen.add(test)
534
535 if not test.is_leaf():
536 continue
537 if (untested_only and
538 test.get_state().status != TestState.UNTESTED):
539 continue
540 self.tests_to_run.append(test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800541 self.run_next_test()
542
543 def reap_completed_tests(self):
544 '''
545 Removes completed tests from the set of active tests.
546
547 Also updates the visible test if it was reaped.
548 '''
549 for t, v in dict(self.invocations).iteritems():
550 if v.is_completed():
551 del self.invocations[t]
552
553 if (self.visible_test is None or
554 self.visible_test not in self.invocations):
555 self.set_visible_test(None)
556 # Make the first running test, if any, the visible test
557 for t in self.test_list.walk():
558 if t in self.invocations:
559 self.set_visible_test(t)
560 break
561
Hung-Te Lin96632362012-03-20 21:14:18 +0800562 def kill_active_tests(self, abort):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800563 '''
564 Kills and waits for all active tests.
Hung-Te Lin96632362012-03-20 21:14:18 +0800565
566 @param abort: True to change state of killed tests to FAILED, False for
567 UNTESTED.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800568 '''
569 self.reap_completed_tests()
570 for test, invoc in self.invocations.items():
571 factory.console.info('Killing active test %s...' % test.path)
572 invoc.abort_and_join()
573 factory.console.info('Killed %s' % test.path)
574 del self.invocations[test]
Hung-Te Lin96632362012-03-20 21:14:18 +0800575 if not abort:
576 test.update_state(status=TestState.UNTESTED)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800577 self.reap_completed_tests()
578
Hung-Te Lin96632362012-03-20 21:14:18 +0800579 def abort_active_tests(self):
580 self.kill_active_tests(True)
581
Jon Salz73e0fd02012-04-04 11:46:38 +0800582 def main(self):
583 self.init()
584 self.run()
585
586 def init(self, args=None, env=None):
587 '''Initializes Goofy.
Jon Salz74ad3262012-03-16 14:40:55 +0800588
589 Args:
Jon Salz73e0fd02012-04-04 11:46:38 +0800590 args: A list of command-line arguments. Uses sys.argv if
591 args is None.
592 env: An Environment instance to use (or None to choose
Jon Salz258a40c2012-04-19 12:34:01 +0800593 FakeChrootEnvironment or DUTEnvironment as appropriate).
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800594 '''
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800595 parser = OptionParser()
596 parser.add_option('-v', '--verbose', dest='verbose',
597 action='store_true',
598 help='Enable debug logging')
599 parser.add_option('--print_test_list', dest='print_test_list',
600 metavar='FILE',
601 help='Read and print test list FILE, and exit')
Jon Salz758e6cc2012-04-03 15:47:07 +0800602 parser.add_option('--restart', dest='restart',
603 action='store_true',
604 help='Clear all test state')
Jon Salz258a40c2012-04-19 12:34:01 +0800605 parser.add_option('--ui', dest='ui', type='choice',
606 choices=['none', 'gtk', 'chrome'],
607 default='gtk',
608 help='UI to use')
Jon Salz73e0fd02012-04-04 11:46:38 +0800609 parser.add_option('--test_list', dest='test_list',
610 metavar='FILE',
611 help='Use FILE as test list')
612 (self.options, self.args) = parser.parse_args(args)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800613
Jon Salz73e0fd02012-04-04 11:46:38 +0800614 global _inited_logging
615 if not _inited_logging:
616 factory.init_logging('goofy', verbose=self.options.verbose)
617 _inited_logging = True
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800618
Jon Salz73e0fd02012-04-04 11:46:38 +0800619 if (not suppress_chroot_warning and
620 factory.in_chroot() and
Jon Salz258a40c2012-04-19 12:34:01 +0800621 self.options.ui == 'gtk' and
Jon Salz758e6cc2012-04-03 15:47:07 +0800622 os.environ.get('DISPLAY') in [None, '', ':0', ':0.0']):
623 # That's not going to work! Tell the user how to run
624 # this way.
625 logging.warn(GOOFY_IN_CHROOT_WARNING)
626 time.sleep(1)
627
Jon Salz73e0fd02012-04-04 11:46:38 +0800628 if env:
629 self.env = env
630 elif factory.in_chroot():
Jon Salz258a40c2012-04-19 12:34:01 +0800631 self.env = FakeChrootEnvironment()
Jon Salz73e0fd02012-04-04 11:46:38 +0800632 logging.warn(
633 'Using chroot environment: will not actually run autotests')
634 else:
635 self.env = DUTEnvironment()
Jon Salz323dd3d2012-04-09 18:40:43 +0800636 self.env.goofy = self
Jon Salz73e0fd02012-04-04 11:46:38 +0800637
Jon Salz758e6cc2012-04-03 15:47:07 +0800638 if self.options.restart:
639 state.clear_state()
640
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800641 if self.options.print_test_list:
642 print (factory.read_test_list(self.options.print_test_list).
643 __repr__(recursive=True))
644 return
645
646 logging.info('Started')
647
648 self.start_state_server()
649 # Update HWID configuration tag.
650 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
651
Jon Salz73e0fd02012-04-04 11:46:38 +0800652 self.options.test_list = (self.options.test_list or find_test_list())
653 self.test_list = factory.read_test_list(self.options.test_list,
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800654 self.state_instance)
655 logging.info('TEST_LIST:\n%s', self.test_list.__repr__(recursive=True))
Jon Salz258a40c2012-04-19 12:34:01 +0800656 self.state_instance.test_list = self.test_list
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800657
658 self.init_states()
659 self.start_event_server()
Jon Salz258a40c2012-04-19 12:34:01 +0800660
661 if self.options.ui == 'chrome':
662 # TODO(jsalz): Get dynamically from UI
663 self.state_instance.set_shared_data('test_widget_size', (975,600))
664
665 self.env.launch_chrome()
666 logging.info('Waiting for a web socket connection')
667 self.web_socket_manager.wait()
668 elif self.options.ui == 'gtk':
Jon Salz73e0fd02012-04-04 11:46:38 +0800669 self.start_ui()
Jon Salz258a40c2012-04-19 12:34:01 +0800670
Jon Salz8375c2e2012-04-04 15:22:24 +0800671 self.prespawner = Prespawner()
Jon Salz323dd3d2012-04-09 18:40:43 +0800672 self.prespawner.start()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800673
674 def state_change_callback(test, state):
675 self.event_client.post_event(
676 Event(Event.Type.STATE_CHANGE,
677 path=test.path, state=state))
678 self.test_list.state_change_callback = state_change_callback
679
680 try:
Jon Salz758e6cc2012-04-03 15:47:07 +0800681 tests_after_shutdown = self.state_instance.get_shared_data(
682 'tests_after_shutdown')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800683 except KeyError:
Jon Salz758e6cc2012-04-03 15:47:07 +0800684 tests_after_shutdown = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800685
Jon Salz758e6cc2012-04-03 15:47:07 +0800686 if tests_after_shutdown is not None:
687 logging.info('Resuming tests after shutdown: %s',
688 tests_after_shutdown)
689 self.state_instance.set_shared_data('tests_after_shutdown', None)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800690 self.tests_to_run.extend(
Jon Salz758e6cc2012-04-03 15:47:07 +0800691 self.test_list.lookup_path(t) for t in tests_after_shutdown)
Jon Salz73e0fd02012-04-04 11:46:38 +0800692 self.run_queue.put(self.run_next_test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800693 else:
Jon Salz57717ca2012-04-04 16:47:25 +0800694 if self.test_list.options.auto_run_on_start:
695 self.run_queue.put(
696 lambda: self.run_tests(self.test_list, untested_only=True))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800697
Jon Salz73e0fd02012-04-04 11:46:38 +0800698 def run(self):
699 '''Runs Goofy.'''
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800700 # Process events forever.
Jon Salz57717ca2012-04-04 16:47:25 +0800701 while self.run_once(True):
Jon Salz73e0fd02012-04-04 11:46:38 +0800702 pass
703
Jon Salz57717ca2012-04-04 16:47:25 +0800704 def run_once(self, block=False):
Jon Salz73e0fd02012-04-04 11:46:38 +0800705 '''Runs all items pending in the event loop.
706
Jon Salz57717ca2012-04-04 16:47:25 +0800707 Args:
708 block: If true, block until at least one event is processed.
709
Jon Salz73e0fd02012-04-04 11:46:38 +0800710 Returns:
711 True to keep going or False to shut down.
712 '''
Jon Salz57717ca2012-04-04 16:47:25 +0800713 events = []
714 if block:
715 # Get at least one event
716 events.append(self.run_queue.get())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800717 while True:
Jon Salz73e0fd02012-04-04 11:46:38 +0800718 try:
719 events.append(self.run_queue.get_nowait())
720 except Queue.Empty:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800721 break
722
Jon Salz73e0fd02012-04-04 11:46:38 +0800723 for event in events:
724 if not event:
725 # Shutdown request.
726 self.run_queue.task_done()
727 return False
728
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800729 try:
730 event()
731 except Exception as e: # pylint: disable=W0703
732 logging.error('Error in event loop: %s', e)
733 traceback.print_exc(sys.stderr)
Jon Salz8375c2e2012-04-04 15:22:24 +0800734 self.record_exception(traceback.format_exception_only(
735 *sys.exc_info()[:2]))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800736 # But keep going
737 finally:
738 self.run_queue.task_done()
Jon Salz73e0fd02012-04-04 11:46:38 +0800739 return True
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800740
Jon Salz258a40c2012-04-19 12:34:01 +0800741 def run_tests_with_status(self, statuses_to_run, starting_at=None,
742 root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800743 '''Runs all top-level tests with a particular status.
744
745 All active tests, plus any tests to re-run, are reset.
Jon Salz57717ca2012-04-04 16:47:25 +0800746
747 Args:
748 starting_at: If provided, only auto-runs tests beginning with
749 this test.
Jon Salz0405ab52012-03-16 15:26:52 +0800750 '''
Jon Salz258a40c2012-04-19 12:34:01 +0800751 root = root or self.test_list
752
Jon Salz57717ca2012-04-04 16:47:25 +0800753 if starting_at:
754 # Make sure they passed a test, not a string.
755 assert isinstance(starting_at, factory.FactoryTest)
756
Jon Salz0405ab52012-03-16 15:26:52 +0800757 tests_to_reset = []
758 tests_to_run = []
759
Jon Salz57717ca2012-04-04 16:47:25 +0800760 found_starting_at = False
761
Jon Salz258a40c2012-04-19 12:34:01 +0800762 for test in root.get_top_level_tests():
Jon Salz57717ca2012-04-04 16:47:25 +0800763 if starting_at:
764 if test == starting_at:
765 # We've found starting_at; do auto-run on all
766 # subsequent tests.
767 found_starting_at = True
768 if not found_starting_at:
769 # Don't start this guy yet
770 continue
771
Jon Salz0405ab52012-03-16 15:26:52 +0800772 status = test.get_state().status
773 if status == TestState.ACTIVE or status in statuses_to_run:
774 # Reset the test (later; we will need to abort
775 # all active tests first).
776 tests_to_reset.append(test)
777 if status in statuses_to_run:
778 tests_to_run.append(test)
779
780 self.abort_active_tests()
781
782 # Reset all statuses of the tests to run (in case any tests were active;
783 # we want them to be run again).
784 for test_to_reset in tests_to_reset:
785 for test in test_to_reset.walk():
786 test.update_state(status=TestState.UNTESTED)
787
788 self.run_tests(tests_to_run, untested_only=True)
789
Jon Salz258a40c2012-04-19 12:34:01 +0800790 def restart_tests(self, root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800791 '''Restarts all tests.'''
Jon Salz258a40c2012-04-19 12:34:01 +0800792 root = root or self.test_list
Jon Salz0405ab52012-03-16 15:26:52 +0800793
Jon Salz258a40c2012-04-19 12:34:01 +0800794 self.abort_active_tests()
795 for test in root.walk():
796 test.update_state(status=TestState.UNTESTED)
797 self.run_tests(root)
798
799 def auto_run(self, starting_at=None, root=None):
Jon Salz57717ca2012-04-04 16:47:25 +0800800 '''"Auto-runs" tests that have not been run yet.
801
802 Args:
803 starting_at: If provide, only auto-runs tests beginning with
804 this test.
805 '''
Jon Salz258a40c2012-04-19 12:34:01 +0800806 root = root or self.test_list
Jon Salz57717ca2012-04-04 16:47:25 +0800807 self.run_tests_with_status([TestState.UNTESTED, TestState.ACTIVE],
Jon Salz258a40c2012-04-19 12:34:01 +0800808 starting_at=starting_at,
809 root=root)
Jon Salz0405ab52012-03-16 15:26:52 +0800810
Jon Salz258a40c2012-04-19 12:34:01 +0800811 def re_run_failed(self, root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800812 '''Re-runs failed tests.'''
Jon Salz258a40c2012-04-19 12:34:01 +0800813 root = root or self.test_list
814 self.run_tests_with_status([TestState.FAILED], root=root)
Jon Salz0405ab52012-03-16 15:26:52 +0800815
Jon Salz968e90b2012-03-18 16:12:43 +0800816 def show_review_information(self):
Hung-Te Lin96632362012-03-20 21:14:18 +0800817 '''Event handler for showing review information screen.
818
819 The information screene is rendered by main UI program (ui.py), so in
820 goofy we only need to kill all active tests, set them as untested, and
821 clear remaining tests.
822 '''
823 self.kill_active_tests(False)
824 self.run_tests([])
825
Jon Salz0405ab52012-03-16 15:26:52 +0800826 def handle_switch_test(self, event):
Jon Salz968e90b2012-03-18 16:12:43 +0800827 '''Switches to a particular test.
828
829 @param event: The SWITCH_TEST event.
830 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800831 test = self.test_list.lookup_path(event.path)
Jon Salz57717ca2012-04-04 16:47:25 +0800832 if not test:
Jon Salz968e90b2012-03-18 16:12:43 +0800833 logging.error('Unknown test %r', event.key)
Jon Salz57717ca2012-04-04 16:47:25 +0800834 return
835
836 invoc = self.invocations.get(test)
837 if invoc and test.backgroundable:
838 # Already running: just bring to the front if it
839 # has a UI.
840 logging.info('Setting visible test to %s', test.path)
841 self.event_client.post_event(
842 Event(Event.Type.SET_VISIBLE_TEST, path=test.path))
843 return
844
845 self.abort_active_tests()
846 for t in test.walk():
847 t.update_state(status=TestState.UNTESTED)
848
849 if self.test_list.options.auto_run_on_keypress:
850 self.auto_run(starting_at=test)
851 else:
852 self.run_tests(test)
Jon Salz0405ab52012-03-16 15:26:52 +0800853
Jon Salz73e0fd02012-04-04 11:46:38 +0800854 def wait(self):
855 '''Waits for all pending invocations.
856
857 Useful for testing.
858 '''
859 for k, v in self.invocations.iteritems():
860 logging.info('Waiting for %s to complete...', k)
861 v.thread.join()
862
863 def check_exceptions(self):
864 '''Raises an error if any exceptions have occurred in
865 invocation threads.'''
866 if self.exceptions:
867 raise RuntimeError('Exception in invocation thread: %r' %
868 self.exceptions)
869
870 def record_exception(self, msg):
871 '''Records an exception in an invocation thread.
872
873 An exception with the given message will be rethrown when
874 Goofy is destroyed.'''
875 self.exceptions.append(msg)
876
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800877
878if __name__ == '__main__':
879 Goofy().main()