blob: d4926b338b6909258b4bf815a1194ae00ea5145a [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 Salz0405ab52012-03-16 15:26:52 +080016import logging
17import os
Jon Salz258a40c2012-04-19 12:34:01 +080018import cPickle as pickle
Jon Salz0405ab52012-03-16 15:26:52 +080019import pipes
Jon Salz73e0fd02012-04-04 11:46:38 +080020import Queue
Jon Salz0405ab52012-03-16 15:26:52 +080021import re
22import signal
23import subprocess
24import sys
25import tempfile
26import threading
27import time
28import traceback
Jon Salz258a40c2012-04-19 12:34:01 +080029import unittest
30import uuid
Hung-Te Linf2f78f72012-02-08 19:27:11 +080031from collections import deque
32from optparse import OptionParser
Jon Salz258a40c2012-04-19 12:34:01 +080033from StringIO import StringIO
Hung-Te Linf2f78f72012-02-08 19:27:11 +080034
35import factory_common
Jon Salz8375c2e2012-04-04 15:22:24 +080036from autotest_lib.client.bin.prespawner import Prespawner
Hung-Te Linf2f78f72012-02-08 19:27:11 +080037from autotest_lib.client.cros import factory
38from autotest_lib.client.cros.factory import state
39from autotest_lib.client.cros.factory import TestState
Jon Salz258a40c2012-04-19 12:34:01 +080040from autotest_lib.client.cros.factory import utils
Hung-Te Linf2f78f72012-02-08 19:27:11 +080041from autotest_lib.client.cros.factory.event import Event
42from autotest_lib.client.cros.factory.event import EventClient
43from autotest_lib.client.cros.factory.event import EventServer
Jon Salzeb8d25f2012-05-22 15:17:32 +080044from autotest_lib.client.cros.factory.event_log import EventLog
Jon Salz258a40c2012-04-19 12:34:01 +080045from autotest_lib.client.cros.factory.invocation import TestInvocation
Jon Salz5f2a0672012-05-22 17:14:06 +080046from autotest_lib.client.cros.factory import test_environment
Jon Salz258a40c2012-04-19 12:34:01 +080047from autotest_lib.client.cros.factory.web_socket_manager import WebSocketManager
Hung-Te Linf2f78f72012-02-08 19:27:11 +080048
49
Hung-Te Linf2f78f72012-02-08 19:27:11 +080050DEFAULT_TEST_LIST_PATH = os.path.join(
Jon Salz258a40c2012-04-19 12:34:01 +080051 factory.CLIENT_PATH , 'site_tests', 'suite_Factory', 'test_list')
Hung-Te Linf2f78f72012-02-08 19:27:11 +080052HWID_CFG_PATH = '/usr/local/share/chromeos-hwid/cfg'
53
Jon Salz8796e362012-05-24 11:39:09 +080054# File that suppresses reboot if present (e.g., for development).
55NO_REBOOT_FILE = '/var/log/factory.noreboot'
56
Jon Salz758e6cc2012-04-03 15:47:07 +080057GOOFY_IN_CHROOT_WARNING = '\n' + ('*' * 70) + '''
58You are running Goofy inside the chroot. Autotests are not supported.
59
60To use Goofy in the chroot, first install an Xvnc server:
61
62 sudo apt-get install tightvncserver
63
64...and then start a VNC X server outside the chroot:
65
66 vncserver :10 &
67 vncviewer :10
68
69...and run Goofy as follows:
70
71 env --unset=XAUTHORITY DISPLAY=localhost:10 python goofy.py
72''' + ('*' * 70)
Jon Salz73e0fd02012-04-04 11:46:38 +080073suppress_chroot_warning = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +080074
75def get_hwid_cfg():
76 '''
77 Returns the HWID config tag, or an empty string if none can be found.
78 '''
79 if 'CROS_HWID' in os.environ:
80 return os.environ['CROS_HWID']
81 if os.path.exists(HWID_CFG_PATH):
82 with open(HWID_CFG_PATH, 'rt') as hwid_cfg_handle:
83 return hwid_cfg_handle.read().strip()
84 return ''
85
86
87def find_test_list():
88 '''
89 Returns the path to the active test list, based on the HWID config tag.
90 '''
91 hwid_cfg = get_hwid_cfg()
92
93 # Try in order: test_list, test_list.$hwid_cfg, test_list.all
94 if hwid_cfg:
95 test_list = '%s_%s' % (DEFAULT_TEST_LIST_PATH, hwid_cfg)
96 if os.path.exists(test_list):
97 logging.info('Using special test list: %s', test_list)
98 return test_list
99 logging.info('WARNING: no specific test list for config: %s', hwid_cfg)
100
101 test_list = DEFAULT_TEST_LIST_PATH
102 if os.path.exists(test_list):
103 return test_list
104
105 test_list = ('%s.all' % DEFAULT_TEST_LIST_PATH)
106 if os.path.exists(test_list):
107 logging.info('Using default test list: ' + test_list)
108 return test_list
109 logging.info('ERROR: Cannot find any test list.')
110
Jon Salz73e0fd02012-04-04 11:46:38 +0800111
Jon Salz73e0fd02012-04-04 11:46:38 +0800112_inited_logging = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800113
114class Goofy(object):
115 '''
116 The main factory flow.
117
118 Note that all methods in this class must be invoked from the main
119 (event) thread. Other threads, such as callbacks and TestInvocation
120 methods, should instead post events on the run queue.
121
122 TODO: Unit tests. (chrome-os-partner:7409)
123
124 Properties:
Jon Salz258a40c2012-04-19 12:34:01 +0800125 uuid: A unique UUID for this invocation of Goofy.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800126 state_instance: An instance of FactoryState.
127 state_server: The FactoryState XML/RPC server.
128 state_server_thread: A thread running state_server.
129 event_server: The EventServer socket server.
130 event_server_thread: A thread running event_server.
131 event_client: A client to the event server.
Hung-Te Lin6bb48552012-02-09 14:37:43 +0800132 ui_process: The factory ui process object.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800133 run_queue: A queue of callbacks to invoke from the main thread.
134 invocations: A map from FactoryTest objects to the corresponding
135 TestInvocations objects representing active tests.
136 tests_to_run: A deque of tests that should be run when the current
137 test(s) complete.
138 options: Command-line options.
139 args: Command-line args.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800140 test_list: The test list.
Jon Salz0405ab52012-03-16 15:26:52 +0800141 event_handlers: Map of Event.Type to the method used to handle that
142 event. If the method has an 'event' argument, the event is passed
143 to the handler.
Jon Salz73e0fd02012-04-04 11:46:38 +0800144 exceptions: Exceptions encountered in invocation threads.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800145 '''
146 def __init__(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800147 self.uuid = str(uuid.uuid4())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800148 self.state_instance = None
149 self.state_server = None
150 self.state_server_thread = None
151 self.event_server = None
152 self.event_server_thread = None
153 self.event_client = None
Jon Salzeb8d25f2012-05-22 15:17:32 +0800154 self.event_log = None
Jon Salz8375c2e2012-04-04 15:22:24 +0800155 self.prespawner = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800156 self.ui_process = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800157 self.run_queue = Queue.Queue()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800158 self.invocations = {}
159 self.tests_to_run = deque()
160 self.visible_test = None
Jon Salz258a40c2012-04-19 12:34:01 +0800161 self.chrome = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800162
163 self.options = None
164 self.args = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800165 self.test_list = None
166
Jon Salz258a40c2012-04-19 12:34:01 +0800167 def test_or_root(event):
168 '''Returns the top-level parent for a test (the root node of the
169 tests that need to be run together if the given test path is to
170 be run).'''
171 try:
172 path = event.path
Jon Salzd2ed6cb2012-05-02 09:35:14 +0800173 except AttributeError:
Jon Salz258a40c2012-04-19 12:34:01 +0800174 path = None
175
176 if path:
Jon Salzf617b282012-05-24 14:14:04 +0800177 return (self.test_list.lookup_path(path).
178 get_top_level_parent_or_group())
Jon Salz258a40c2012-04-19 12:34:01 +0800179 else:
180 return self.test_list
181
Jon Salz0405ab52012-03-16 15:26:52 +0800182 self.event_handlers = {
183 Event.Type.SWITCH_TEST: self.handle_switch_test,
Jon Salz968e90b2012-03-18 16:12:43 +0800184 Event.Type.SHOW_NEXT_ACTIVE_TEST:
185 lambda event: self.show_next_active_test(),
186 Event.Type.RESTART_TESTS:
Jon Salz258a40c2012-04-19 12:34:01 +0800187 lambda event: self.restart_tests(root=test_or_root(event)),
Jon Salz968e90b2012-03-18 16:12:43 +0800188 Event.Type.AUTO_RUN:
Jon Salz258a40c2012-04-19 12:34:01 +0800189 lambda event: self.auto_run(root=test_or_root(event)),
Jon Salz968e90b2012-03-18 16:12:43 +0800190 Event.Type.RE_RUN_FAILED:
Jon Salz258a40c2012-04-19 12:34:01 +0800191 lambda event: self.re_run_failed(root=test_or_root(event)),
Jon Salz968e90b2012-03-18 16:12:43 +0800192 Event.Type.REVIEW:
193 lambda event: self.show_review_information(),
Jon Salz5f2a0672012-05-22 17:14:06 +0800194 Event.Type.UPDATE_SYSTEM_INFO:
195 lambda event: self.update_system_info(),
Jon Salz0405ab52012-03-16 15:26:52 +0800196 }
197
Jon Salz73e0fd02012-04-04 11:46:38 +0800198 self.exceptions = []
Jon Salz258a40c2012-04-19 12:34:01 +0800199 self.web_socket_manager = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800200
201 def destroy(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800202 if self.chrome:
203 self.chrome.kill()
204 self.chrome = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800205 if self.ui_process:
Jon Salz258a40c2012-04-19 12:34:01 +0800206 utils.kill_process_tree(self.ui_process, 'ui')
Jon Salz73e0fd02012-04-04 11:46:38 +0800207 self.ui_process = None
Jon Salz258a40c2012-04-19 12:34:01 +0800208 if self.web_socket_manager:
209 logging.info('Stopping web sockets')
210 self.web_socket_manager.close()
211 self.web_socket_manager = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800212 if self.state_server_thread:
213 logging.info('Stopping state server')
214 self.state_server.shutdown()
215 self.state_server_thread.join()
Jon Salz73e0fd02012-04-04 11:46:38 +0800216 self.state_server.server_close()
217 self.state_server_thread = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800218 if self.event_server_thread:
219 logging.info('Stopping event server')
220 self.event_server.shutdown() # pylint: disable=E1101
221 self.event_server_thread.join()
Jon Salz73e0fd02012-04-04 11:46:38 +0800222 self.event_server.server_close()
223 self.event_server_thread = None
Jon Salz8375c2e2012-04-04 15:22:24 +0800224 if self.prespawner:
225 logging.info('Stopping prespawner')
226 self.prespawner.stop()
227 self.prespawner = None
228 if self.event_client:
Jon Salz258a40c2012-04-19 12:34:01 +0800229 logging.info('Closing event client')
Jon Salz8375c2e2012-04-04 15:22:24 +0800230 self.event_client.close()
Jon Salz258a40c2012-04-19 12:34:01 +0800231 self.event_client = None
Jon Salzeb8d25f2012-05-22 15:17:32 +0800232 if self.event_log:
233 self.event_log.Close()
234 self.event_log = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800235 self.check_exceptions()
Jon Salz258a40c2012-04-19 12:34:01 +0800236 logging.info('Done destroying Goofy')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800237
238 def start_state_server(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800239 self.state_instance, self.state_server = (
240 state.create_server(bind_address='0.0.0.0'))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800241 logging.info('Starting state server')
242 self.state_server_thread = threading.Thread(
Jon Salz8375c2e2012-04-04 15:22:24 +0800243 target=self.state_server.serve_forever,
244 name='StateServer')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800245 self.state_server_thread.start()
246
247 def start_event_server(self):
248 self.event_server = EventServer()
249 logging.info('Starting factory event server')
250 self.event_server_thread = threading.Thread(
Jon Salz8375c2e2012-04-04 15:22:24 +0800251 target=self.event_server.serve_forever,
252 name='EventServer') # pylint: disable=E1101
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800253 self.event_server_thread.start()
254
255 self.event_client = EventClient(
256 callback=self.handle_event, event_loop=self.run_queue)
257
Jon Salz258a40c2012-04-19 12:34:01 +0800258 self.web_socket_manager = WebSocketManager(self.uuid)
259 self.state_server.add_handler("/event",
260 self.web_socket_manager.handle_web_socket)
261
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800262 def start_ui(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800263 ui_proc_args = [os.path.join(factory.CROS_FACTORY_LIB_PATH, 'ui'),
264 self.options.test_list]
Jon Salz14bcbb02012-03-17 15:11:50 +0800265 if self.options.verbose:
266 ui_proc_args.append('-v')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800267 logging.info('Starting ui %s', ui_proc_args)
268 self.ui_process = subprocess.Popen(ui_proc_args)
269 logging.info('Waiting for UI to come up...')
270 self.event_client.wait(
271 lambda event: event.type == Event.Type.UI_READY)
272 logging.info('UI has started')
273
274 def set_visible_test(self, test):
275 if self.visible_test == test:
276 return
277
278 if test:
279 test.update_state(visible=True)
280 if self.visible_test:
281 self.visible_test.update_state(visible=False)
282 self.visible_test = test
283
Jon Salz74ad3262012-03-16 14:40:55 +0800284 def handle_shutdown_complete(self, test, state):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800285 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800286 Handles the case where a shutdown was detected during a shutdown step.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800287
Jon Salz74ad3262012-03-16 14:40:55 +0800288 @param test: The ShutdownStep.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800289 @param state: The test state.
290 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800291 state = test.update_state(increment_shutdown_count=1)
292 logging.info('Detected shutdown (%d of %d)',
293 state.shutdown_count, test.iterations)
294 if state.shutdown_count == test.iterations:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800295 # Good!
296 test.update_state(status=TestState.PASSED, error_msg='')
Jon Salz74ad3262012-03-16 14:40:55 +0800297 elif state.shutdown_count > test.iterations:
Jon Salz73e0fd02012-04-04 11:46:38 +0800298 # Shut down too many times
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800299 test.update_state(status=TestState.FAILED,
Jon Salz74ad3262012-03-16 14:40:55 +0800300 error_msg='Too many shutdowns')
Jon Salz258a40c2012-04-19 12:34:01 +0800301 elif utils.are_shift_keys_depressed():
Jon Salz73e0fd02012-04-04 11:46:38 +0800302 logging.info('Shift keys are depressed; cancelling restarts')
303 # Abort shutdown
304 test.update_state(
305 status=TestState.FAILED,
306 error_msg='Shutdown aborted with double shift keys')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800307 else:
Jon Salz74ad3262012-03-16 14:40:55 +0800308 # Need to shutdown again
Jon Salzb9038572012-05-24 10:34:51 +0800309 self.event_log.Log('shutdown', operation='reboot')
Jon Salz73e0fd02012-04-04 11:46:38 +0800310 self.env.shutdown('reboot')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800311
312 def init_states(self):
313 '''
314 Initializes all states on startup.
315 '''
316 for test in self.test_list.get_all_tests():
317 # Make sure the state server knows about all the tests,
318 # defaulting to an untested state.
319 test.update_state(update_parent=False, visible=False)
320
321 # Any 'active' tests should be marked as failed now.
322 for test in self.test_list.walk():
323 state = test.get_state()
Hung-Te Lin96632362012-03-20 21:14:18 +0800324 if state.status != TestState.ACTIVE:
325 continue
326 if isinstance(test, factory.ShutdownStep):
327 # Shutdown while the test was active - that's good.
328 self.handle_shutdown_complete(test, state)
329 else:
330 test.update_state(status=TestState.FAILED,
331 error_msg='Unknown (shutdown?)')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800332
333 def show_next_active_test(self):
334 '''
335 Rotates to the next visible active test.
336 '''
337 self.reap_completed_tests()
338 active_tests = [
339 t for t in self.test_list.walk()
340 if t.is_leaf() and t.get_state().status == TestState.ACTIVE]
341 if not active_tests:
342 return
343
344 try:
345 next_test = active_tests[
346 (active_tests.index(self.visible_test) + 1) % len(active_tests)]
347 except ValueError: # visible_test not present in active_tests
348 next_test = active_tests[0]
349
350 self.set_visible_test(next_test)
351
352 def handle_event(self, event):
353 '''
354 Handles an event from the event server.
355 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800356 handler = self.event_handlers.get(event.type)
357 if handler:
Jon Salz968e90b2012-03-18 16:12:43 +0800358 handler(event)
Jon Salz0405ab52012-03-16 15:26:52 +0800359 else:
Jon Salz968e90b2012-03-18 16:12:43 +0800360 # We don't register handlers for all event types - just ignore
361 # this event.
362 logging.debug('Unbound event type %s', event.type)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800363
364 def run_next_test(self):
365 '''
366 Runs the next eligible test (or tests) in self.tests_to_run.
367 '''
368 self.reap_completed_tests()
369 while self.tests_to_run:
370 logging.debug('Tests to run: %s',
371 [x.path for x in self.tests_to_run])
372
373 test = self.tests_to_run[0]
374
375 if test in self.invocations:
376 logging.info('Next test %s is already running', test.path)
377 self.tests_to_run.popleft()
378 return
379
380 if self.invocations and not (test.backgroundable and all(
381 [x.backgroundable for x in self.invocations])):
382 logging.debug('Waiting for non-backgroundable tests to '
383 'complete before running %s', test.path)
384 return
385
386 self.tests_to_run.popleft()
387
Jon Salz74ad3262012-03-16 14:40:55 +0800388 if isinstance(test, factory.ShutdownStep):
Jon Salz8796e362012-05-24 11:39:09 +0800389 if os.path.exists(NO_REBOOT_FILE):
390 test.update_state(
391 status=TestState.FAILED, increment_count=1,
392 error_msg=('Skipped shutdown since %s is present' %
393 NO_REBOOT_FILE))
394 continue
395
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800396 test.update_state(status=TestState.ACTIVE, increment_count=1,
Jon Salz74ad3262012-03-16 14:40:55 +0800397 error_msg='', shutdown_count=0)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800398 # Save pending test list in the state server
399 self.state_instance.set_shared_data(
Jon Salz74ad3262012-03-16 14:40:55 +0800400 'tests_after_shutdown',
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800401 [t.path for t in self.tests_to_run])
Jon Salz74ad3262012-03-16 14:40:55 +0800402
Jon Salz73e0fd02012-04-04 11:46:38 +0800403 with self.env.lock:
Jon Salzb9038572012-05-24 10:34:51 +0800404 self.event_log.Log('shutdown', operation=test.operation)
Jon Salz73e0fd02012-04-04 11:46:38 +0800405 shutdown_result = self.env.shutdown(test.operation)
406 if shutdown_result:
407 # That's all, folks!
408 self.run_queue.put(None)
409 return
410 else:
411 # Just pass (e.g., in the chroot).
412 test.update_state(status=TestState.PASSED)
413 self.state_instance.set_shared_data(
414 'tests_after_shutdown', None)
415 continue
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800416
417 invoc = TestInvocation(self, test, on_completion=self.run_next_test)
418 self.invocations[test] = invoc
419 if self.visible_test is None and test.has_ui:
420 self.set_visible_test(test)
421 invoc.start()
422
Jon Salz0405ab52012-03-16 15:26:52 +0800423 def run_tests(self, subtrees, untested_only=False):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800424 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800425 Runs tests under subtree.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800426
427 The tests are run in order unless one fails (then stops).
428 Backgroundable tests are run simultaneously; when a foreground test is
429 encountered, we wait for all active tests to finish before continuing.
Jon Salz0405ab52012-03-16 15:26:52 +0800430
431 @param subtrees: Node or nodes containing tests to run (may either be
432 a single test or a list). Duplicates will be ignored.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800433 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800434 if type(subtrees) != list:
435 subtrees = [subtrees]
436
437 # Nodes we've seen so far, to avoid duplicates.
438 seen = set()
439
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800440 self.tests_to_run = deque()
Jon Salz0405ab52012-03-16 15:26:52 +0800441 for subtree in subtrees:
442 for test in subtree.walk():
443 if test in seen:
444 continue
445 seen.add(test)
446
447 if not test.is_leaf():
448 continue
449 if (untested_only and
450 test.get_state().status != TestState.UNTESTED):
451 continue
452 self.tests_to_run.append(test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800453 self.run_next_test()
454
455 def reap_completed_tests(self):
456 '''
457 Removes completed tests from the set of active tests.
458
459 Also updates the visible test if it was reaped.
460 '''
461 for t, v in dict(self.invocations).iteritems():
462 if v.is_completed():
463 del self.invocations[t]
464
465 if (self.visible_test is None or
466 self.visible_test not in self.invocations):
467 self.set_visible_test(None)
468 # Make the first running test, if any, the visible test
469 for t in self.test_list.walk():
470 if t in self.invocations:
471 self.set_visible_test(t)
472 break
473
Hung-Te Lin96632362012-03-20 21:14:18 +0800474 def kill_active_tests(self, abort):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800475 '''
476 Kills and waits for all active tests.
Hung-Te Lin96632362012-03-20 21:14:18 +0800477
478 @param abort: True to change state of killed tests to FAILED, False for
479 UNTESTED.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800480 '''
481 self.reap_completed_tests()
482 for test, invoc in self.invocations.items():
483 factory.console.info('Killing active test %s...' % test.path)
484 invoc.abort_and_join()
485 factory.console.info('Killed %s' % test.path)
486 del self.invocations[test]
Hung-Te Lin96632362012-03-20 21:14:18 +0800487 if not abort:
488 test.update_state(status=TestState.UNTESTED)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800489 self.reap_completed_tests()
490
Hung-Te Lin96632362012-03-20 21:14:18 +0800491 def abort_active_tests(self):
492 self.kill_active_tests(True)
493
Jon Salz73e0fd02012-04-04 11:46:38 +0800494 def main(self):
Jon Salzeb8d25f2012-05-22 15:17:32 +0800495 try:
496 self.init()
Jon Salzb9038572012-05-24 10:34:51 +0800497 self.event_log.Log('goofy_init',
498 success=True)
Jon Salzeb8d25f2012-05-22 15:17:32 +0800499 except:
500 if self.event_log:
501 try:
Jon Salzb9038572012-05-24 10:34:51 +0800502 self.event_log.Log('goofy_init',
503 success=False,
504 trace=traceback.format_exc())
Jon Salzeb8d25f2012-05-22 15:17:32 +0800505 except:
506 pass
507 raise
508
Jon Salz73e0fd02012-04-04 11:46:38 +0800509 self.run()
510
Jon Salz5f2a0672012-05-22 17:14:06 +0800511 def update_system_info(self):
512 '''Updates system info.'''
513 system_info = test_environment.SystemInfo(self.env, self.state_instance)
514 self.state_instance.set_shared_data('system_info', system_info.__dict__)
515 self.event_client.post_event(Event(Event.Type.SYSTEM_INFO,
516 system_info=system_info.__dict__))
517 logging.info('System info: %r', system_info.__dict__)
518
Jon Salz73e0fd02012-04-04 11:46:38 +0800519 def init(self, args=None, env=None):
520 '''Initializes Goofy.
Jon Salz74ad3262012-03-16 14:40:55 +0800521
522 Args:
Jon Salz73e0fd02012-04-04 11:46:38 +0800523 args: A list of command-line arguments. Uses sys.argv if
524 args is None.
525 env: An Environment instance to use (or None to choose
Jon Salz258a40c2012-04-19 12:34:01 +0800526 FakeChrootEnvironment or DUTEnvironment as appropriate).
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800527 '''
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800528 parser = OptionParser()
529 parser.add_option('-v', '--verbose', dest='verbose',
530 action='store_true',
531 help='Enable debug logging')
532 parser.add_option('--print_test_list', dest='print_test_list',
533 metavar='FILE',
534 help='Read and print test list FILE, and exit')
Jon Salz758e6cc2012-04-03 15:47:07 +0800535 parser.add_option('--restart', dest='restart',
536 action='store_true',
537 help='Clear all test state')
Jon Salz258a40c2012-04-19 12:34:01 +0800538 parser.add_option('--ui', dest='ui', type='choice',
539 choices=['none', 'gtk', 'chrome'],
540 default='gtk',
541 help='UI to use')
Jon Salz63585ea2012-05-21 15:03:32 +0800542 parser.add_option('--ui_scale_factor', dest='ui_scale_factor',
543 type='int', default=1,
544 help=('Factor by which to scale UI '
545 '(Chrome UI only)'))
Jon Salz73e0fd02012-04-04 11:46:38 +0800546 parser.add_option('--test_list', dest='test_list',
547 metavar='FILE',
548 help='Use FILE as test list')
549 (self.options, self.args) = parser.parse_args(args)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800550
Jon Salz73e0fd02012-04-04 11:46:38 +0800551 global _inited_logging
552 if not _inited_logging:
553 factory.init_logging('goofy', verbose=self.options.verbose)
554 _inited_logging = True
Jon Salzeb8d25f2012-05-22 15:17:32 +0800555 self.event_log = EventLog('goofy')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800556
Jon Salz73e0fd02012-04-04 11:46:38 +0800557 if (not suppress_chroot_warning and
558 factory.in_chroot() and
Jon Salz258a40c2012-04-19 12:34:01 +0800559 self.options.ui == 'gtk' and
Jon Salz758e6cc2012-04-03 15:47:07 +0800560 os.environ.get('DISPLAY') in [None, '', ':0', ':0.0']):
561 # That's not going to work! Tell the user how to run
562 # this way.
563 logging.warn(GOOFY_IN_CHROOT_WARNING)
564 time.sleep(1)
565
Jon Salz73e0fd02012-04-04 11:46:38 +0800566 if env:
567 self.env = env
568 elif factory.in_chroot():
Jon Salz5f2a0672012-05-22 17:14:06 +0800569 self.env = test_environment.FakeChrootEnvironment()
Jon Salz73e0fd02012-04-04 11:46:38 +0800570 logging.warn(
571 'Using chroot environment: will not actually run autotests')
572 else:
Jon Salz5f2a0672012-05-22 17:14:06 +0800573 self.env = test_environment.DUTEnvironment()
Jon Salz323dd3d2012-04-09 18:40:43 +0800574 self.env.goofy = self
Jon Salz73e0fd02012-04-04 11:46:38 +0800575
Jon Salz758e6cc2012-04-03 15:47:07 +0800576 if self.options.restart:
577 state.clear_state()
578
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800579 if self.options.print_test_list:
580 print (factory.read_test_list(self.options.print_test_list).
581 __repr__(recursive=True))
582 return
583
584 logging.info('Started')
585
586 self.start_state_server()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800587 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
Jon Salz63585ea2012-05-21 15:03:32 +0800588 self.state_instance.set_shared_data('ui_scale_factor',
589 self.options.ui_scale_factor)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800590
Jon Salz73e0fd02012-04-04 11:46:38 +0800591 self.options.test_list = (self.options.test_list or find_test_list())
592 self.test_list = factory.read_test_list(self.options.test_list,
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800593 self.state_instance)
Jon Salz06fbeff2012-05-21 17:06:05 +0800594 if not self.state_instance.has_shared_data('ui_lang'):
595 self.state_instance.set_shared_data('ui_lang',
596 self.test_list.options.ui_lang)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800597 logging.info('TEST_LIST:\n%s', self.test_list.__repr__(recursive=True))
Jon Salz258a40c2012-04-19 12:34:01 +0800598 self.state_instance.test_list = self.test_list
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800599
600 self.init_states()
601 self.start_event_server()
Jon Salz258a40c2012-04-19 12:34:01 +0800602
Jon Salz5f2a0672012-05-22 17:14:06 +0800603 self.update_system_info()
604
Jon Salzb1b39092012-05-03 02:05:09 +0800605 # Set CROS_UI since some behaviors in ui.py depend on the
606 # particular UI in use. TODO(jsalz): Remove this (and all
607 # places it is used) when the GTK UI is removed.
608 os.environ['CROS_UI'] = self.options.ui
Jon Salz258a40c2012-04-19 12:34:01 +0800609
Jon Salzb1b39092012-05-03 02:05:09 +0800610 if self.options.ui == 'chrome':
Jon Salz258a40c2012-04-19 12:34:01 +0800611 self.env.launch_chrome()
612 logging.info('Waiting for a web socket connection')
613 self.web_socket_manager.wait()
Jon Salzb1b39092012-05-03 02:05:09 +0800614
615 # Wait for the test widget size to be set; this is done in
616 # an asynchronous RPC so there is a small chance that the
617 # web socket might be opened first.
618 for i in range(100): # 10 s
Jon Salz63585ea2012-05-21 15:03:32 +0800619 try:
620 if self.state_instance.get_shared_data('test_widget_size'):
621 break
622 except KeyError:
623 pass # Retry
Jon Salzb1b39092012-05-03 02:05:09 +0800624 time.sleep(0.1) # 100 ms
625 else:
626 logging.warn('Never received test_widget_size from UI')
Jon Salz258a40c2012-04-19 12:34:01 +0800627 elif self.options.ui == 'gtk':
Jon Salz73e0fd02012-04-04 11:46:38 +0800628 self.start_ui()
Jon Salz258a40c2012-04-19 12:34:01 +0800629
Jon Salz8375c2e2012-04-04 15:22:24 +0800630 self.prespawner = Prespawner()
Jon Salz323dd3d2012-04-09 18:40:43 +0800631 self.prespawner.start()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800632
633 def state_change_callback(test, state):
634 self.event_client.post_event(
635 Event(Event.Type.STATE_CHANGE,
636 path=test.path, state=state))
637 self.test_list.state_change_callback = state_change_callback
638
639 try:
Jon Salz758e6cc2012-04-03 15:47:07 +0800640 tests_after_shutdown = self.state_instance.get_shared_data(
641 'tests_after_shutdown')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800642 except KeyError:
Jon Salz758e6cc2012-04-03 15:47:07 +0800643 tests_after_shutdown = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800644
Jon Salz758e6cc2012-04-03 15:47:07 +0800645 if tests_after_shutdown is not None:
646 logging.info('Resuming tests after shutdown: %s',
647 tests_after_shutdown)
648 self.state_instance.set_shared_data('tests_after_shutdown', None)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800649 self.tests_to_run.extend(
Jon Salz758e6cc2012-04-03 15:47:07 +0800650 self.test_list.lookup_path(t) for t in tests_after_shutdown)
Jon Salz73e0fd02012-04-04 11:46:38 +0800651 self.run_queue.put(self.run_next_test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800652 else:
Jon Salz57717ca2012-04-04 16:47:25 +0800653 if self.test_list.options.auto_run_on_start:
654 self.run_queue.put(
655 lambda: self.run_tests(self.test_list, untested_only=True))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800656
Jon Salz73e0fd02012-04-04 11:46:38 +0800657 def run(self):
658 '''Runs Goofy.'''
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800659 # Process events forever.
Jon Salz57717ca2012-04-04 16:47:25 +0800660 while self.run_once(True):
Jon Salz73e0fd02012-04-04 11:46:38 +0800661 pass
662
Jon Salz57717ca2012-04-04 16:47:25 +0800663 def run_once(self, block=False):
Jon Salz73e0fd02012-04-04 11:46:38 +0800664 '''Runs all items pending in the event loop.
665
Jon Salz57717ca2012-04-04 16:47:25 +0800666 Args:
667 block: If true, block until at least one event is processed.
668
Jon Salz73e0fd02012-04-04 11:46:38 +0800669 Returns:
670 True to keep going or False to shut down.
671 '''
Jon Salz57717ca2012-04-04 16:47:25 +0800672 events = []
673 if block:
674 # Get at least one event
675 events.append(self.run_queue.get())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800676 while True:
Jon Salz73e0fd02012-04-04 11:46:38 +0800677 try:
678 events.append(self.run_queue.get_nowait())
679 except Queue.Empty:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800680 break
681
Jon Salz73e0fd02012-04-04 11:46:38 +0800682 for event in events:
683 if not event:
684 # Shutdown request.
685 self.run_queue.task_done()
686 return False
687
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800688 try:
689 event()
690 except Exception as e: # pylint: disable=W0703
691 logging.error('Error in event loop: %s', e)
692 traceback.print_exc(sys.stderr)
Jon Salz8375c2e2012-04-04 15:22:24 +0800693 self.record_exception(traceback.format_exception_only(
694 *sys.exc_info()[:2]))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800695 # But keep going
696 finally:
697 self.run_queue.task_done()
Jon Salz73e0fd02012-04-04 11:46:38 +0800698 return True
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800699
Jon Salz258a40c2012-04-19 12:34:01 +0800700 def run_tests_with_status(self, statuses_to_run, starting_at=None,
701 root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800702 '''Runs all top-level tests with a particular status.
703
704 All active tests, plus any tests to re-run, are reset.
Jon Salz57717ca2012-04-04 16:47:25 +0800705
706 Args:
707 starting_at: If provided, only auto-runs tests beginning with
708 this test.
Jon Salz0405ab52012-03-16 15:26:52 +0800709 '''
Jon Salz258a40c2012-04-19 12:34:01 +0800710 root = root or self.test_list
711
Jon Salz57717ca2012-04-04 16:47:25 +0800712 if starting_at:
713 # Make sure they passed a test, not a string.
714 assert isinstance(starting_at, factory.FactoryTest)
715
Jon Salz0405ab52012-03-16 15:26:52 +0800716 tests_to_reset = []
717 tests_to_run = []
718
Jon Salz57717ca2012-04-04 16:47:25 +0800719 found_starting_at = False
720
Jon Salz258a40c2012-04-19 12:34:01 +0800721 for test in root.get_top_level_tests():
Jon Salz57717ca2012-04-04 16:47:25 +0800722 if starting_at:
723 if test == starting_at:
724 # We've found starting_at; do auto-run on all
725 # subsequent tests.
726 found_starting_at = True
727 if not found_starting_at:
728 # Don't start this guy yet
729 continue
730
Jon Salz0405ab52012-03-16 15:26:52 +0800731 status = test.get_state().status
732 if status == TestState.ACTIVE or status in statuses_to_run:
733 # Reset the test (later; we will need to abort
734 # all active tests first).
735 tests_to_reset.append(test)
736 if status in statuses_to_run:
737 tests_to_run.append(test)
738
739 self.abort_active_tests()
740
741 # Reset all statuses of the tests to run (in case any tests were active;
742 # we want them to be run again).
743 for test_to_reset in tests_to_reset:
744 for test in test_to_reset.walk():
745 test.update_state(status=TestState.UNTESTED)
746
747 self.run_tests(tests_to_run, untested_only=True)
748
Jon Salz258a40c2012-04-19 12:34:01 +0800749 def restart_tests(self, root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800750 '''Restarts all tests.'''
Jon Salz258a40c2012-04-19 12:34:01 +0800751 root = root or self.test_list
Jon Salz0405ab52012-03-16 15:26:52 +0800752
Jon Salz258a40c2012-04-19 12:34:01 +0800753 self.abort_active_tests()
754 for test in root.walk():
755 test.update_state(status=TestState.UNTESTED)
756 self.run_tests(root)
757
758 def auto_run(self, starting_at=None, root=None):
Jon Salz57717ca2012-04-04 16:47:25 +0800759 '''"Auto-runs" tests that have not been run yet.
760
761 Args:
762 starting_at: If provide, only auto-runs tests beginning with
763 this test.
764 '''
Jon Salz258a40c2012-04-19 12:34:01 +0800765 root = root or self.test_list
Jon Salz57717ca2012-04-04 16:47:25 +0800766 self.run_tests_with_status([TestState.UNTESTED, TestState.ACTIVE],
Jon Salz258a40c2012-04-19 12:34:01 +0800767 starting_at=starting_at,
768 root=root)
Jon Salz0405ab52012-03-16 15:26:52 +0800769
Jon Salz258a40c2012-04-19 12:34:01 +0800770 def re_run_failed(self, root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800771 '''Re-runs failed tests.'''
Jon Salz258a40c2012-04-19 12:34:01 +0800772 root = root or self.test_list
773 self.run_tests_with_status([TestState.FAILED], root=root)
Jon Salz0405ab52012-03-16 15:26:52 +0800774
Jon Salz968e90b2012-03-18 16:12:43 +0800775 def show_review_information(self):
Hung-Te Lin96632362012-03-20 21:14:18 +0800776 '''Event handler for showing review information screen.
777
778 The information screene is rendered by main UI program (ui.py), so in
779 goofy we only need to kill all active tests, set them as untested, and
780 clear remaining tests.
781 '''
782 self.kill_active_tests(False)
783 self.run_tests([])
784
Jon Salz0405ab52012-03-16 15:26:52 +0800785 def handle_switch_test(self, event):
Jon Salz968e90b2012-03-18 16:12:43 +0800786 '''Switches to a particular test.
787
788 @param event: The SWITCH_TEST event.
789 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800790 test = self.test_list.lookup_path(event.path)
Jon Salz57717ca2012-04-04 16:47:25 +0800791 if not test:
Jon Salz968e90b2012-03-18 16:12:43 +0800792 logging.error('Unknown test %r', event.key)
Jon Salz57717ca2012-04-04 16:47:25 +0800793 return
794
795 invoc = self.invocations.get(test)
796 if invoc and test.backgroundable:
797 # Already running: just bring to the front if it
798 # has a UI.
799 logging.info('Setting visible test to %s', test.path)
800 self.event_client.post_event(
801 Event(Event.Type.SET_VISIBLE_TEST, path=test.path))
802 return
803
804 self.abort_active_tests()
805 for t in test.walk():
806 t.update_state(status=TestState.UNTESTED)
807
808 if self.test_list.options.auto_run_on_keypress:
809 self.auto_run(starting_at=test)
810 else:
811 self.run_tests(test)
Jon Salz0405ab52012-03-16 15:26:52 +0800812
Jon Salz73e0fd02012-04-04 11:46:38 +0800813 def wait(self):
814 '''Waits for all pending invocations.
815
816 Useful for testing.
817 '''
818 for k, v in self.invocations.iteritems():
819 logging.info('Waiting for %s to complete...', k)
820 v.thread.join()
821
822 def check_exceptions(self):
823 '''Raises an error if any exceptions have occurred in
824 invocation threads.'''
825 if self.exceptions:
826 raise RuntimeError('Exception in invocation thread: %r' %
827 self.exceptions)
828
829 def record_exception(self, msg):
830 '''Records an exception in an invocation thread.
831
832 An exception with the given message will be rethrown when
833 Goofy is destroyed.'''
834 self.exceptions.append(msg)
835
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800836
837if __name__ == '__main__':
838 Goofy().main()