blob: 65a53167460d39202827cefdd2ae2be67763706c [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',
Jon Salz63585ea2012-05-21 15:03:32 +0800178 ('--default-device-scale-factor=%d' %
179 self.goofy.options.ui_scale_factor),
Jon Salz258a40c2012-04-19 12:34:01 +0800180 'http://localhost:%d/' % state.DEFAULT_FACTORY_STATE_PORT,
181 ]
Jon Salz73e0fd02012-04-04 11:46:38 +0800182
Jon Salz258a40c2012-04-19 12:34:01 +0800183 chrome_log = os.path.join(factory.get_log_root(), 'factory.chrome.log')
184 chrome_log_file = open(chrome_log, "a")
185 logging.info('Launching Chrome; logs in %s' % chrome_log)
186 return subprocess.Popen(chrome_command,
187 stdout=chrome_log_file,
188 stderr=subprocess.STDOUT)
189
190class FakeChrootEnvironment(Environment):
Jon Salz73e0fd02012-04-04 11:46:38 +0800191 '''
192 A chroot environment that doesn't actually shutdown or run autotests.
193 '''
194 def shutdown(self, operation):
195 assert operation in ['reboot', 'halt']
196 logging.warn('In chroot: skipping %s', operation)
197 return False
198
199 def spawn_autotest(self, name, args, env_additions, result_file):
200 logging.warn('In chroot: skipping autotest %s', name)
Jon Salz258a40c2012-04-19 12:34:01 +0800201 # Mark it as passed with 75% probability, or failed with 25%
202 # probability (depending on a hash of the autotest name).
203 pseudo_random = ord(hashlib.sha1(name).digest()[0]) / 256.0
204 passed = pseudo_random > .25
205
Jon Salz73e0fd02012-04-04 11:46:38 +0800206 with open(result_file, 'w') as out:
Jon Salz258a40c2012-04-19 12:34:01 +0800207 pickle.dump((passed, '' if passed else 'Simulated failure'), out)
Jon Salz73e0fd02012-04-04 11:46:38 +0800208 # Start a process that will return with a true exit status in
209 # 2 seconds (just like a happy autotest).
210 return subprocess.Popen(['sleep', '2'])
211
Jon Salz258a40c2012-04-19 12:34:01 +0800212 def launch_chrome(self):
213 logging.warn('In chroot; not launching Chrome. '
214 'Please open http://localhost:%d/ in Chrome.',
215 state.DEFAULT_FACTORY_STATE_PORT)
Jon Salz73e0fd02012-04-04 11:46:38 +0800216
217
218_inited_logging = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800219
220class Goofy(object):
221 '''
222 The main factory flow.
223
224 Note that all methods in this class must be invoked from the main
225 (event) thread. Other threads, such as callbacks and TestInvocation
226 methods, should instead post events on the run queue.
227
228 TODO: Unit tests. (chrome-os-partner:7409)
229
230 Properties:
Jon Salz258a40c2012-04-19 12:34:01 +0800231 uuid: A unique UUID for this invocation of Goofy.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800232 state_instance: An instance of FactoryState.
233 state_server: The FactoryState XML/RPC server.
234 state_server_thread: A thread running state_server.
235 event_server: The EventServer socket server.
236 event_server_thread: A thread running event_server.
237 event_client: A client to the event server.
Hung-Te Lin6bb48552012-02-09 14:37:43 +0800238 ui_process: The factory ui process object.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800239 run_queue: A queue of callbacks to invoke from the main thread.
240 invocations: A map from FactoryTest objects to the corresponding
241 TestInvocations objects representing active tests.
242 tests_to_run: A deque of tests that should be run when the current
243 test(s) complete.
244 options: Command-line options.
245 args: Command-line args.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800246 test_list: The test list.
Jon Salz0405ab52012-03-16 15:26:52 +0800247 event_handlers: Map of Event.Type to the method used to handle that
248 event. If the method has an 'event' argument, the event is passed
249 to the handler.
Jon Salz73e0fd02012-04-04 11:46:38 +0800250 exceptions: Exceptions encountered in invocation threads.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800251 '''
252 def __init__(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800253 self.uuid = str(uuid.uuid4())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800254 self.state_instance = None
255 self.state_server = None
256 self.state_server_thread = None
257 self.event_server = None
258 self.event_server_thread = None
259 self.event_client = None
Jon Salz8375c2e2012-04-04 15:22:24 +0800260 self.prespawner = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800261 self.ui_process = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800262 self.run_queue = Queue.Queue()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800263 self.invocations = {}
264 self.tests_to_run = deque()
265 self.visible_test = None
Jon Salz258a40c2012-04-19 12:34:01 +0800266 self.chrome = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800267
268 self.options = None
269 self.args = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800270 self.test_list = None
271
Jon Salz258a40c2012-04-19 12:34:01 +0800272 def test_or_root(event):
273 '''Returns the top-level parent for a test (the root node of the
274 tests that need to be run together if the given test path is to
275 be run).'''
276 try:
277 path = event.path
Jon Salzd2ed6cb2012-05-02 09:35:14 +0800278 except AttributeError:
Jon Salz258a40c2012-04-19 12:34:01 +0800279 path = None
280
281 if path:
282 return self.test_list.lookup_path(path).get_top_level_parent()
283 else:
284 return self.test_list
285
Jon Salz0405ab52012-03-16 15:26:52 +0800286 self.event_handlers = {
287 Event.Type.SWITCH_TEST: self.handle_switch_test,
Jon Salz968e90b2012-03-18 16:12:43 +0800288 Event.Type.SHOW_NEXT_ACTIVE_TEST:
289 lambda event: self.show_next_active_test(),
290 Event.Type.RESTART_TESTS:
Jon Salz258a40c2012-04-19 12:34:01 +0800291 lambda event: self.restart_tests(root=test_or_root(event)),
Jon Salz968e90b2012-03-18 16:12:43 +0800292 Event.Type.AUTO_RUN:
Jon Salz258a40c2012-04-19 12:34:01 +0800293 lambda event: self.auto_run(root=test_or_root(event)),
Jon Salz968e90b2012-03-18 16:12:43 +0800294 Event.Type.RE_RUN_FAILED:
Jon Salz258a40c2012-04-19 12:34:01 +0800295 lambda event: self.re_run_failed(root=test_or_root(event)),
Jon Salz968e90b2012-03-18 16:12:43 +0800296 Event.Type.REVIEW:
297 lambda event: self.show_review_information(),
Jon Salz0405ab52012-03-16 15:26:52 +0800298 }
299
Jon Salz73e0fd02012-04-04 11:46:38 +0800300 self.exceptions = []
Jon Salz258a40c2012-04-19 12:34:01 +0800301 self.web_socket_manager = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800302
303 def destroy(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800304 if self.chrome:
305 self.chrome.kill()
306 self.chrome = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800307 if self.ui_process:
Jon Salz258a40c2012-04-19 12:34:01 +0800308 utils.kill_process_tree(self.ui_process, 'ui')
Jon Salz73e0fd02012-04-04 11:46:38 +0800309 self.ui_process = None
Jon Salz258a40c2012-04-19 12:34:01 +0800310 if self.web_socket_manager:
311 logging.info('Stopping web sockets')
312 self.web_socket_manager.close()
313 self.web_socket_manager = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800314 if self.state_server_thread:
315 logging.info('Stopping state server')
316 self.state_server.shutdown()
317 self.state_server_thread.join()
Jon Salz73e0fd02012-04-04 11:46:38 +0800318 self.state_server.server_close()
319 self.state_server_thread = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800320 if self.event_server_thread:
321 logging.info('Stopping event server')
322 self.event_server.shutdown() # pylint: disable=E1101
323 self.event_server_thread.join()
Jon Salz73e0fd02012-04-04 11:46:38 +0800324 self.event_server.server_close()
325 self.event_server_thread = None
Jon Salz8375c2e2012-04-04 15:22:24 +0800326 if self.prespawner:
327 logging.info('Stopping prespawner')
328 self.prespawner.stop()
329 self.prespawner = None
330 if self.event_client:
Jon Salz258a40c2012-04-19 12:34:01 +0800331 logging.info('Closing event client')
Jon Salz8375c2e2012-04-04 15:22:24 +0800332 self.event_client.close()
Jon Salz258a40c2012-04-19 12:34:01 +0800333 self.event_client = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800334 self.check_exceptions()
Jon Salz258a40c2012-04-19 12:34:01 +0800335 logging.info('Done destroying Goofy')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800336
337 def start_state_server(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800338 self.state_instance, self.state_server = (
339 state.create_server(bind_address='0.0.0.0'))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800340 logging.info('Starting state server')
341 self.state_server_thread = threading.Thread(
Jon Salz8375c2e2012-04-04 15:22:24 +0800342 target=self.state_server.serve_forever,
343 name='StateServer')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800344 self.state_server_thread.start()
345
346 def start_event_server(self):
347 self.event_server = EventServer()
348 logging.info('Starting factory event server')
349 self.event_server_thread = threading.Thread(
Jon Salz8375c2e2012-04-04 15:22:24 +0800350 target=self.event_server.serve_forever,
351 name='EventServer') # pylint: disable=E1101
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800352 self.event_server_thread.start()
353
354 self.event_client = EventClient(
355 callback=self.handle_event, event_loop=self.run_queue)
356
Jon Salz258a40c2012-04-19 12:34:01 +0800357 self.web_socket_manager = WebSocketManager(self.uuid)
358 self.state_server.add_handler("/event",
359 self.web_socket_manager.handle_web_socket)
360
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800361 def start_ui(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800362 ui_proc_args = [os.path.join(factory.CROS_FACTORY_LIB_PATH, 'ui'),
363 self.options.test_list]
Jon Salz14bcbb02012-03-17 15:11:50 +0800364 if self.options.verbose:
365 ui_proc_args.append('-v')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800366 logging.info('Starting ui %s', ui_proc_args)
367 self.ui_process = subprocess.Popen(ui_proc_args)
368 logging.info('Waiting for UI to come up...')
369 self.event_client.wait(
370 lambda event: event.type == Event.Type.UI_READY)
371 logging.info('UI has started')
372
373 def set_visible_test(self, test):
374 if self.visible_test == test:
375 return
376
377 if test:
378 test.update_state(visible=True)
379 if self.visible_test:
380 self.visible_test.update_state(visible=False)
381 self.visible_test = test
382
Jon Salz74ad3262012-03-16 14:40:55 +0800383 def handle_shutdown_complete(self, test, state):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800384 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800385 Handles the case where a shutdown was detected during a shutdown step.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800386
Jon Salz74ad3262012-03-16 14:40:55 +0800387 @param test: The ShutdownStep.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800388 @param state: The test state.
389 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800390 state = test.update_state(increment_shutdown_count=1)
391 logging.info('Detected shutdown (%d of %d)',
392 state.shutdown_count, test.iterations)
393 if state.shutdown_count == test.iterations:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800394 # Good!
395 test.update_state(status=TestState.PASSED, error_msg='')
Jon Salz74ad3262012-03-16 14:40:55 +0800396 elif state.shutdown_count > test.iterations:
Jon Salz73e0fd02012-04-04 11:46:38 +0800397 # Shut down too many times
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800398 test.update_state(status=TestState.FAILED,
Jon Salz74ad3262012-03-16 14:40:55 +0800399 error_msg='Too many shutdowns')
Jon Salz258a40c2012-04-19 12:34:01 +0800400 elif utils.are_shift_keys_depressed():
Jon Salz73e0fd02012-04-04 11:46:38 +0800401 logging.info('Shift keys are depressed; cancelling restarts')
402 # Abort shutdown
403 test.update_state(
404 status=TestState.FAILED,
405 error_msg='Shutdown aborted with double shift keys')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800406 else:
Jon Salz74ad3262012-03-16 14:40:55 +0800407 # Need to shutdown again
Jon Salz73e0fd02012-04-04 11:46:38 +0800408 self.env.shutdown('reboot')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800409
410 def init_states(self):
411 '''
412 Initializes all states on startup.
413 '''
414 for test in self.test_list.get_all_tests():
415 # Make sure the state server knows about all the tests,
416 # defaulting to an untested state.
417 test.update_state(update_parent=False, visible=False)
418
419 # Any 'active' tests should be marked as failed now.
420 for test in self.test_list.walk():
421 state = test.get_state()
Hung-Te Lin96632362012-03-20 21:14:18 +0800422 if state.status != TestState.ACTIVE:
423 continue
424 if isinstance(test, factory.ShutdownStep):
425 # Shutdown while the test was active - that's good.
426 self.handle_shutdown_complete(test, state)
427 else:
428 test.update_state(status=TestState.FAILED,
429 error_msg='Unknown (shutdown?)')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800430
431 def show_next_active_test(self):
432 '''
433 Rotates to the next visible active test.
434 '''
435 self.reap_completed_tests()
436 active_tests = [
437 t for t in self.test_list.walk()
438 if t.is_leaf() and t.get_state().status == TestState.ACTIVE]
439 if not active_tests:
440 return
441
442 try:
443 next_test = active_tests[
444 (active_tests.index(self.visible_test) + 1) % len(active_tests)]
445 except ValueError: # visible_test not present in active_tests
446 next_test = active_tests[0]
447
448 self.set_visible_test(next_test)
449
450 def handle_event(self, event):
451 '''
452 Handles an event from the event server.
453 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800454 handler = self.event_handlers.get(event.type)
455 if handler:
Jon Salz968e90b2012-03-18 16:12:43 +0800456 handler(event)
Jon Salz0405ab52012-03-16 15:26:52 +0800457 else:
Jon Salz968e90b2012-03-18 16:12:43 +0800458 # We don't register handlers for all event types - just ignore
459 # this event.
460 logging.debug('Unbound event type %s', event.type)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800461
462 def run_next_test(self):
463 '''
464 Runs the next eligible test (or tests) in self.tests_to_run.
465 '''
466 self.reap_completed_tests()
467 while self.tests_to_run:
468 logging.debug('Tests to run: %s',
469 [x.path for x in self.tests_to_run])
470
471 test = self.tests_to_run[0]
472
473 if test in self.invocations:
474 logging.info('Next test %s is already running', test.path)
475 self.tests_to_run.popleft()
476 return
477
478 if self.invocations and not (test.backgroundable and all(
479 [x.backgroundable for x in self.invocations])):
480 logging.debug('Waiting for non-backgroundable tests to '
481 'complete before running %s', test.path)
482 return
483
484 self.tests_to_run.popleft()
485
Jon Salz74ad3262012-03-16 14:40:55 +0800486 if isinstance(test, factory.ShutdownStep):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800487 test.update_state(status=TestState.ACTIVE, increment_count=1,
Jon Salz74ad3262012-03-16 14:40:55 +0800488 error_msg='', shutdown_count=0)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800489 # Save pending test list in the state server
490 self.state_instance.set_shared_data(
Jon Salz74ad3262012-03-16 14:40:55 +0800491 'tests_after_shutdown',
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800492 [t.path for t in self.tests_to_run])
Jon Salz74ad3262012-03-16 14:40:55 +0800493
Jon Salz73e0fd02012-04-04 11:46:38 +0800494 with self.env.lock:
495 shutdown_result = self.env.shutdown(test.operation)
496 if shutdown_result:
497 # That's all, folks!
498 self.run_queue.put(None)
499 return
500 else:
501 # Just pass (e.g., in the chroot).
502 test.update_state(status=TestState.PASSED)
503 self.state_instance.set_shared_data(
504 'tests_after_shutdown', None)
505 continue
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800506
507 invoc = TestInvocation(self, test, on_completion=self.run_next_test)
508 self.invocations[test] = invoc
509 if self.visible_test is None and test.has_ui:
510 self.set_visible_test(test)
511 invoc.start()
512
Jon Salz0405ab52012-03-16 15:26:52 +0800513 def run_tests(self, subtrees, untested_only=False):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800514 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800515 Runs tests under subtree.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800516
517 The tests are run in order unless one fails (then stops).
518 Backgroundable tests are run simultaneously; when a foreground test is
519 encountered, we wait for all active tests to finish before continuing.
Jon Salz0405ab52012-03-16 15:26:52 +0800520
521 @param subtrees: Node or nodes containing tests to run (may either be
522 a single test or a list). Duplicates will be ignored.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800523 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800524 if type(subtrees) != list:
525 subtrees = [subtrees]
526
527 # Nodes we've seen so far, to avoid duplicates.
528 seen = set()
529
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800530 self.tests_to_run = deque()
Jon Salz0405ab52012-03-16 15:26:52 +0800531 for subtree in subtrees:
532 for test in subtree.walk():
533 if test in seen:
534 continue
535 seen.add(test)
536
537 if not test.is_leaf():
538 continue
539 if (untested_only and
540 test.get_state().status != TestState.UNTESTED):
541 continue
542 self.tests_to_run.append(test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800543 self.run_next_test()
544
545 def reap_completed_tests(self):
546 '''
547 Removes completed tests from the set of active tests.
548
549 Also updates the visible test if it was reaped.
550 '''
551 for t, v in dict(self.invocations).iteritems():
552 if v.is_completed():
553 del self.invocations[t]
554
555 if (self.visible_test is None or
556 self.visible_test not in self.invocations):
557 self.set_visible_test(None)
558 # Make the first running test, if any, the visible test
559 for t in self.test_list.walk():
560 if t in self.invocations:
561 self.set_visible_test(t)
562 break
563
Hung-Te Lin96632362012-03-20 21:14:18 +0800564 def kill_active_tests(self, abort):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800565 '''
566 Kills and waits for all active tests.
Hung-Te Lin96632362012-03-20 21:14:18 +0800567
568 @param abort: True to change state of killed tests to FAILED, False for
569 UNTESTED.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800570 '''
571 self.reap_completed_tests()
572 for test, invoc in self.invocations.items():
573 factory.console.info('Killing active test %s...' % test.path)
574 invoc.abort_and_join()
575 factory.console.info('Killed %s' % test.path)
576 del self.invocations[test]
Hung-Te Lin96632362012-03-20 21:14:18 +0800577 if not abort:
578 test.update_state(status=TestState.UNTESTED)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800579 self.reap_completed_tests()
580
Hung-Te Lin96632362012-03-20 21:14:18 +0800581 def abort_active_tests(self):
582 self.kill_active_tests(True)
583
Jon Salz73e0fd02012-04-04 11:46:38 +0800584 def main(self):
585 self.init()
586 self.run()
587
588 def init(self, args=None, env=None):
589 '''Initializes Goofy.
Jon Salz74ad3262012-03-16 14:40:55 +0800590
591 Args:
Jon Salz73e0fd02012-04-04 11:46:38 +0800592 args: A list of command-line arguments. Uses sys.argv if
593 args is None.
594 env: An Environment instance to use (or None to choose
Jon Salz258a40c2012-04-19 12:34:01 +0800595 FakeChrootEnvironment or DUTEnvironment as appropriate).
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800596 '''
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800597 parser = OptionParser()
598 parser.add_option('-v', '--verbose', dest='verbose',
599 action='store_true',
600 help='Enable debug logging')
601 parser.add_option('--print_test_list', dest='print_test_list',
602 metavar='FILE',
603 help='Read and print test list FILE, and exit')
Jon Salz758e6cc2012-04-03 15:47:07 +0800604 parser.add_option('--restart', dest='restart',
605 action='store_true',
606 help='Clear all test state')
Jon Salz258a40c2012-04-19 12:34:01 +0800607 parser.add_option('--ui', dest='ui', type='choice',
608 choices=['none', 'gtk', 'chrome'],
609 default='gtk',
610 help='UI to use')
Jon Salz63585ea2012-05-21 15:03:32 +0800611 parser.add_option('--ui_scale_factor', dest='ui_scale_factor',
612 type='int', default=1,
613 help=('Factor by which to scale UI '
614 '(Chrome UI only)'))
Jon Salz73e0fd02012-04-04 11:46:38 +0800615 parser.add_option('--test_list', dest='test_list',
616 metavar='FILE',
617 help='Use FILE as test list')
618 (self.options, self.args) = parser.parse_args(args)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800619
Jon Salz73e0fd02012-04-04 11:46:38 +0800620 global _inited_logging
621 if not _inited_logging:
622 factory.init_logging('goofy', verbose=self.options.verbose)
623 _inited_logging = True
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800624
Jon Salz73e0fd02012-04-04 11:46:38 +0800625 if (not suppress_chroot_warning and
626 factory.in_chroot() and
Jon Salz258a40c2012-04-19 12:34:01 +0800627 self.options.ui == 'gtk' and
Jon Salz758e6cc2012-04-03 15:47:07 +0800628 os.environ.get('DISPLAY') in [None, '', ':0', ':0.0']):
629 # That's not going to work! Tell the user how to run
630 # this way.
631 logging.warn(GOOFY_IN_CHROOT_WARNING)
632 time.sleep(1)
633
Jon Salz73e0fd02012-04-04 11:46:38 +0800634 if env:
635 self.env = env
636 elif factory.in_chroot():
Jon Salz258a40c2012-04-19 12:34:01 +0800637 self.env = FakeChrootEnvironment()
Jon Salz73e0fd02012-04-04 11:46:38 +0800638 logging.warn(
639 'Using chroot environment: will not actually run autotests')
640 else:
641 self.env = DUTEnvironment()
Jon Salz323dd3d2012-04-09 18:40:43 +0800642 self.env.goofy = self
Jon Salz73e0fd02012-04-04 11:46:38 +0800643
Jon Salz758e6cc2012-04-03 15:47:07 +0800644 if self.options.restart:
645 state.clear_state()
646
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800647 if self.options.print_test_list:
648 print (factory.read_test_list(self.options.print_test_list).
649 __repr__(recursive=True))
650 return
651
652 logging.info('Started')
653
654 self.start_state_server()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800655 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
Jon Salz63585ea2012-05-21 15:03:32 +0800656 self.state_instance.set_shared_data('ui_scale_factor',
657 self.options.ui_scale_factor)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800658
Jon Salz73e0fd02012-04-04 11:46:38 +0800659 self.options.test_list = (self.options.test_list or find_test_list())
660 self.test_list = factory.read_test_list(self.options.test_list,
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800661 self.state_instance)
662 logging.info('TEST_LIST:\n%s', self.test_list.__repr__(recursive=True))
Jon Salz258a40c2012-04-19 12:34:01 +0800663 self.state_instance.test_list = self.test_list
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800664
665 self.init_states()
666 self.start_event_server()
Jon Salz258a40c2012-04-19 12:34:01 +0800667
Jon Salzb1b39092012-05-03 02:05:09 +0800668 # Set CROS_UI since some behaviors in ui.py depend on the
669 # particular UI in use. TODO(jsalz): Remove this (and all
670 # places it is used) when the GTK UI is removed.
671 os.environ['CROS_UI'] = self.options.ui
Jon Salz258a40c2012-04-19 12:34:01 +0800672
Jon Salzb1b39092012-05-03 02:05:09 +0800673 if self.options.ui == 'chrome':
Jon Salz258a40c2012-04-19 12:34:01 +0800674 self.env.launch_chrome()
675 logging.info('Waiting for a web socket connection')
676 self.web_socket_manager.wait()
Jon Salzb1b39092012-05-03 02:05:09 +0800677
678 # Wait for the test widget size to be set; this is done in
679 # an asynchronous RPC so there is a small chance that the
680 # web socket might be opened first.
681 for i in range(100): # 10 s
Jon Salz63585ea2012-05-21 15:03:32 +0800682 try:
683 if self.state_instance.get_shared_data('test_widget_size'):
684 break
685 except KeyError:
686 pass # Retry
Jon Salzb1b39092012-05-03 02:05:09 +0800687 time.sleep(0.1) # 100 ms
688 else:
689 logging.warn('Never received test_widget_size from UI')
Jon Salz258a40c2012-04-19 12:34:01 +0800690 elif self.options.ui == 'gtk':
Jon Salz73e0fd02012-04-04 11:46:38 +0800691 self.start_ui()
Jon Salz258a40c2012-04-19 12:34:01 +0800692
Jon Salz8375c2e2012-04-04 15:22:24 +0800693 self.prespawner = Prespawner()
Jon Salz323dd3d2012-04-09 18:40:43 +0800694 self.prespawner.start()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800695
696 def state_change_callback(test, state):
697 self.event_client.post_event(
698 Event(Event.Type.STATE_CHANGE,
699 path=test.path, state=state))
700 self.test_list.state_change_callback = state_change_callback
701
702 try:
Jon Salz758e6cc2012-04-03 15:47:07 +0800703 tests_after_shutdown = self.state_instance.get_shared_data(
704 'tests_after_shutdown')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800705 except KeyError:
Jon Salz758e6cc2012-04-03 15:47:07 +0800706 tests_after_shutdown = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800707
Jon Salz758e6cc2012-04-03 15:47:07 +0800708 if tests_after_shutdown is not None:
709 logging.info('Resuming tests after shutdown: %s',
710 tests_after_shutdown)
711 self.state_instance.set_shared_data('tests_after_shutdown', None)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800712 self.tests_to_run.extend(
Jon Salz758e6cc2012-04-03 15:47:07 +0800713 self.test_list.lookup_path(t) for t in tests_after_shutdown)
Jon Salz73e0fd02012-04-04 11:46:38 +0800714 self.run_queue.put(self.run_next_test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800715 else:
Jon Salz57717ca2012-04-04 16:47:25 +0800716 if self.test_list.options.auto_run_on_start:
717 self.run_queue.put(
718 lambda: self.run_tests(self.test_list, untested_only=True))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800719
Jon Salz73e0fd02012-04-04 11:46:38 +0800720 def run(self):
721 '''Runs Goofy.'''
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800722 # Process events forever.
Jon Salz57717ca2012-04-04 16:47:25 +0800723 while self.run_once(True):
Jon Salz73e0fd02012-04-04 11:46:38 +0800724 pass
725
Jon Salz57717ca2012-04-04 16:47:25 +0800726 def run_once(self, block=False):
Jon Salz73e0fd02012-04-04 11:46:38 +0800727 '''Runs all items pending in the event loop.
728
Jon Salz57717ca2012-04-04 16:47:25 +0800729 Args:
730 block: If true, block until at least one event is processed.
731
Jon Salz73e0fd02012-04-04 11:46:38 +0800732 Returns:
733 True to keep going or False to shut down.
734 '''
Jon Salz57717ca2012-04-04 16:47:25 +0800735 events = []
736 if block:
737 # Get at least one event
738 events.append(self.run_queue.get())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800739 while True:
Jon Salz73e0fd02012-04-04 11:46:38 +0800740 try:
741 events.append(self.run_queue.get_nowait())
742 except Queue.Empty:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800743 break
744
Jon Salz73e0fd02012-04-04 11:46:38 +0800745 for event in events:
746 if not event:
747 # Shutdown request.
748 self.run_queue.task_done()
749 return False
750
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800751 try:
752 event()
753 except Exception as e: # pylint: disable=W0703
754 logging.error('Error in event loop: %s', e)
755 traceback.print_exc(sys.stderr)
Jon Salz8375c2e2012-04-04 15:22:24 +0800756 self.record_exception(traceback.format_exception_only(
757 *sys.exc_info()[:2]))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800758 # But keep going
759 finally:
760 self.run_queue.task_done()
Jon Salz73e0fd02012-04-04 11:46:38 +0800761 return True
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800762
Jon Salz258a40c2012-04-19 12:34:01 +0800763 def run_tests_with_status(self, statuses_to_run, starting_at=None,
764 root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800765 '''Runs all top-level tests with a particular status.
766
767 All active tests, plus any tests to re-run, are reset.
Jon Salz57717ca2012-04-04 16:47:25 +0800768
769 Args:
770 starting_at: If provided, only auto-runs tests beginning with
771 this test.
Jon Salz0405ab52012-03-16 15:26:52 +0800772 '''
Jon Salz258a40c2012-04-19 12:34:01 +0800773 root = root or self.test_list
774
Jon Salz57717ca2012-04-04 16:47:25 +0800775 if starting_at:
776 # Make sure they passed a test, not a string.
777 assert isinstance(starting_at, factory.FactoryTest)
778
Jon Salz0405ab52012-03-16 15:26:52 +0800779 tests_to_reset = []
780 tests_to_run = []
781
Jon Salz57717ca2012-04-04 16:47:25 +0800782 found_starting_at = False
783
Jon Salz258a40c2012-04-19 12:34:01 +0800784 for test in root.get_top_level_tests():
Jon Salz57717ca2012-04-04 16:47:25 +0800785 if starting_at:
786 if test == starting_at:
787 # We've found starting_at; do auto-run on all
788 # subsequent tests.
789 found_starting_at = True
790 if not found_starting_at:
791 # Don't start this guy yet
792 continue
793
Jon Salz0405ab52012-03-16 15:26:52 +0800794 status = test.get_state().status
795 if status == TestState.ACTIVE or status in statuses_to_run:
796 # Reset the test (later; we will need to abort
797 # all active tests first).
798 tests_to_reset.append(test)
799 if status in statuses_to_run:
800 tests_to_run.append(test)
801
802 self.abort_active_tests()
803
804 # Reset all statuses of the tests to run (in case any tests were active;
805 # we want them to be run again).
806 for test_to_reset in tests_to_reset:
807 for test in test_to_reset.walk():
808 test.update_state(status=TestState.UNTESTED)
809
810 self.run_tests(tests_to_run, untested_only=True)
811
Jon Salz258a40c2012-04-19 12:34:01 +0800812 def restart_tests(self, root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800813 '''Restarts all tests.'''
Jon Salz258a40c2012-04-19 12:34:01 +0800814 root = root or self.test_list
Jon Salz0405ab52012-03-16 15:26:52 +0800815
Jon Salz258a40c2012-04-19 12:34:01 +0800816 self.abort_active_tests()
817 for test in root.walk():
818 test.update_state(status=TestState.UNTESTED)
819 self.run_tests(root)
820
821 def auto_run(self, starting_at=None, root=None):
Jon Salz57717ca2012-04-04 16:47:25 +0800822 '''"Auto-runs" tests that have not been run yet.
823
824 Args:
825 starting_at: If provide, only auto-runs tests beginning with
826 this test.
827 '''
Jon Salz258a40c2012-04-19 12:34:01 +0800828 root = root or self.test_list
Jon Salz57717ca2012-04-04 16:47:25 +0800829 self.run_tests_with_status([TestState.UNTESTED, TestState.ACTIVE],
Jon Salz258a40c2012-04-19 12:34:01 +0800830 starting_at=starting_at,
831 root=root)
Jon Salz0405ab52012-03-16 15:26:52 +0800832
Jon Salz258a40c2012-04-19 12:34:01 +0800833 def re_run_failed(self, root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800834 '''Re-runs failed tests.'''
Jon Salz258a40c2012-04-19 12:34:01 +0800835 root = root or self.test_list
836 self.run_tests_with_status([TestState.FAILED], root=root)
Jon Salz0405ab52012-03-16 15:26:52 +0800837
Jon Salz968e90b2012-03-18 16:12:43 +0800838 def show_review_information(self):
Hung-Te Lin96632362012-03-20 21:14:18 +0800839 '''Event handler for showing review information screen.
840
841 The information screene is rendered by main UI program (ui.py), so in
842 goofy we only need to kill all active tests, set them as untested, and
843 clear remaining tests.
844 '''
845 self.kill_active_tests(False)
846 self.run_tests([])
847
Jon Salz0405ab52012-03-16 15:26:52 +0800848 def handle_switch_test(self, event):
Jon Salz968e90b2012-03-18 16:12:43 +0800849 '''Switches to a particular test.
850
851 @param event: The SWITCH_TEST event.
852 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800853 test = self.test_list.lookup_path(event.path)
Jon Salz57717ca2012-04-04 16:47:25 +0800854 if not test:
Jon Salz968e90b2012-03-18 16:12:43 +0800855 logging.error('Unknown test %r', event.key)
Jon Salz57717ca2012-04-04 16:47:25 +0800856 return
857
858 invoc = self.invocations.get(test)
859 if invoc and test.backgroundable:
860 # Already running: just bring to the front if it
861 # has a UI.
862 logging.info('Setting visible test to %s', test.path)
863 self.event_client.post_event(
864 Event(Event.Type.SET_VISIBLE_TEST, path=test.path))
865 return
866
867 self.abort_active_tests()
868 for t in test.walk():
869 t.update_state(status=TestState.UNTESTED)
870
871 if self.test_list.options.auto_run_on_keypress:
872 self.auto_run(starting_at=test)
873 else:
874 self.run_tests(test)
Jon Salz0405ab52012-03-16 15:26:52 +0800875
Jon Salz73e0fd02012-04-04 11:46:38 +0800876 def wait(self):
877 '''Waits for all pending invocations.
878
879 Useful for testing.
880 '''
881 for k, v in self.invocations.iteritems():
882 logging.info('Waiting for %s to complete...', k)
883 v.thread.join()
884
885 def check_exceptions(self):
886 '''Raises an error if any exceptions have occurred in
887 invocation threads.'''
888 if self.exceptions:
889 raise RuntimeError('Exception in invocation thread: %r' %
890 self.exceptions)
891
892 def record_exception(self, msg):
893 '''Records an exception in an invocation thread.
894
895 An exception with the given message will be rethrown when
896 Goofy is destroyed.'''
897 self.exceptions.append(msg)
898
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800899
900if __name__ == '__main__':
901 Goofy().main()