blob: 31ef6aee10e8c2af1fa354a3e996bb680407c55d [file] [log] [blame]
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001#!/usr/bin/python -u
2#
3# -*- coding: utf-8 -*-
4#
Jon Salz37eccbd2012-05-25 16:06:52 +08005# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Hung-Te Linf2f78f72012-02-08 19:27:11 +08006# 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 Salz37eccbd2012-05-25 16:06:52 +080040from autotest_lib.client.cros.factory import updater
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 Salzeb8d25f2012-05-22 15:17:32 +080045from autotest_lib.client.cros.factory.event_log import EventLog
Jon Salz258a40c2012-04-19 12:34:01 +080046from autotest_lib.client.cros.factory.invocation import TestInvocation
Jon Salz5f2a0672012-05-22 17:14:06 +080047from autotest_lib.client.cros.factory import test_environment
Jon Salz258a40c2012-04-19 12:34:01 +080048from autotest_lib.client.cros.factory.web_socket_manager import WebSocketManager
Hung-Te Linf2f78f72012-02-08 19:27:11 +080049
50
Hung-Te Linf2f78f72012-02-08 19:27:11 +080051DEFAULT_TEST_LIST_PATH = os.path.join(
Jon Salz258a40c2012-04-19 12:34:01 +080052 factory.CLIENT_PATH , 'site_tests', 'suite_Factory', 'test_list')
Hung-Te Linf2f78f72012-02-08 19:27:11 +080053HWID_CFG_PATH = '/usr/local/share/chromeos-hwid/cfg'
54
Jon Salz8796e362012-05-24 11:39:09 +080055# File that suppresses reboot if present (e.g., for development).
56NO_REBOOT_FILE = '/var/log/factory.noreboot'
57
Jon Salz758e6cc2012-04-03 15:47:07 +080058GOOFY_IN_CHROOT_WARNING = '\n' + ('*' * 70) + '''
59You are running Goofy inside the chroot. Autotests are not supported.
60
61To use Goofy in the chroot, first install an Xvnc server:
62
63 sudo apt-get install tightvncserver
64
65...and then start a VNC X server outside the chroot:
66
67 vncserver :10 &
68 vncviewer :10
69
70...and run Goofy as follows:
71
72 env --unset=XAUTHORITY DISPLAY=localhost:10 python goofy.py
73''' + ('*' * 70)
Jon Salz73e0fd02012-04-04 11:46:38 +080074suppress_chroot_warning = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +080075
76def get_hwid_cfg():
77 '''
78 Returns the HWID config tag, or an empty string if none can be found.
79 '''
80 if 'CROS_HWID' in os.environ:
81 return os.environ['CROS_HWID']
82 if os.path.exists(HWID_CFG_PATH):
83 with open(HWID_CFG_PATH, 'rt') as hwid_cfg_handle:
84 return hwid_cfg_handle.read().strip()
85 return ''
86
87
88def find_test_list():
89 '''
90 Returns the path to the active test list, based on the HWID config tag.
91 '''
92 hwid_cfg = get_hwid_cfg()
93
94 # Try in order: test_list, test_list.$hwid_cfg, test_list.all
95 if hwid_cfg:
96 test_list = '%s_%s' % (DEFAULT_TEST_LIST_PATH, hwid_cfg)
97 if os.path.exists(test_list):
98 logging.info('Using special test list: %s', test_list)
99 return test_list
100 logging.info('WARNING: no specific test list for config: %s', hwid_cfg)
101
102 test_list = DEFAULT_TEST_LIST_PATH
103 if os.path.exists(test_list):
104 return test_list
105
106 test_list = ('%s.all' % DEFAULT_TEST_LIST_PATH)
107 if os.path.exists(test_list):
108 logging.info('Using default test list: ' + test_list)
109 return test_list
110 logging.info('ERROR: Cannot find any test list.')
111
Jon Salz73e0fd02012-04-04 11:46:38 +0800112
Jon Salz73e0fd02012-04-04 11:46:38 +0800113_inited_logging = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800114
115class Goofy(object):
116 '''
117 The main factory flow.
118
119 Note that all methods in this class must be invoked from the main
120 (event) thread. Other threads, such as callbacks and TestInvocation
121 methods, should instead post events on the run queue.
122
123 TODO: Unit tests. (chrome-os-partner:7409)
124
125 Properties:
Jon Salz258a40c2012-04-19 12:34:01 +0800126 uuid: A unique UUID for this invocation of Goofy.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800127 state_instance: An instance of FactoryState.
128 state_server: The FactoryState XML/RPC server.
129 state_server_thread: A thread running state_server.
130 event_server: The EventServer socket server.
131 event_server_thread: A thread running event_server.
132 event_client: A client to the event server.
Hung-Te Lin6bb48552012-02-09 14:37:43 +0800133 ui_process: The factory ui process object.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800134 run_queue: A queue of callbacks to invoke from the main thread.
135 invocations: A map from FactoryTest objects to the corresponding
136 TestInvocations objects representing active tests.
137 tests_to_run: A deque of tests that should be run when the current
138 test(s) complete.
139 options: Command-line options.
140 args: Command-line args.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800141 test_list: The test list.
Jon Salz0405ab52012-03-16 15:26:52 +0800142 event_handlers: Map of Event.Type to the method used to handle that
143 event. If the method has an 'event' argument, the event is passed
144 to the handler.
Jon Salz73e0fd02012-04-04 11:46:38 +0800145 exceptions: Exceptions encountered in invocation threads.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800146 '''
147 def __init__(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800148 self.uuid = str(uuid.uuid4())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800149 self.state_instance = None
150 self.state_server = None
151 self.state_server_thread = None
152 self.event_server = None
153 self.event_server_thread = None
154 self.event_client = None
Jon Salzeb8d25f2012-05-22 15:17:32 +0800155 self.event_log = None
Jon Salz8375c2e2012-04-04 15:22:24 +0800156 self.prespawner = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800157 self.ui_process = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800158 self.run_queue = Queue.Queue()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800159 self.invocations = {}
160 self.tests_to_run = deque()
161 self.visible_test = None
Jon Salz258a40c2012-04-19 12:34:01 +0800162 self.chrome = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800163
164 self.options = None
165 self.args = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800166 self.test_list = None
167
Jon Salz258a40c2012-04-19 12:34:01 +0800168 def test_or_root(event):
169 '''Returns the top-level parent for a test (the root node of the
170 tests that need to be run together if the given test path is to
171 be run).'''
172 try:
173 path = event.path
Jon Salzd2ed6cb2012-05-02 09:35:14 +0800174 except AttributeError:
Jon Salz258a40c2012-04-19 12:34:01 +0800175 path = None
176
177 if path:
Jon Salzf617b282012-05-24 14:14:04 +0800178 return (self.test_list.lookup_path(path).
179 get_top_level_parent_or_group())
Jon Salz258a40c2012-04-19 12:34:01 +0800180 else:
181 return self.test_list
182
Jon Salz0405ab52012-03-16 15:26:52 +0800183 self.event_handlers = {
184 Event.Type.SWITCH_TEST: self.handle_switch_test,
Jon Salz968e90b2012-03-18 16:12:43 +0800185 Event.Type.SHOW_NEXT_ACTIVE_TEST:
186 lambda event: self.show_next_active_test(),
187 Event.Type.RESTART_TESTS:
Jon Salz258a40c2012-04-19 12:34:01 +0800188 lambda event: self.restart_tests(root=test_or_root(event)),
Jon Salz968e90b2012-03-18 16:12:43 +0800189 Event.Type.AUTO_RUN:
Jon Salz258a40c2012-04-19 12:34:01 +0800190 lambda event: self.auto_run(root=test_or_root(event)),
Jon Salz968e90b2012-03-18 16:12:43 +0800191 Event.Type.RE_RUN_FAILED:
Jon Salz258a40c2012-04-19 12:34:01 +0800192 lambda event: self.re_run_failed(root=test_or_root(event)),
Jon Salz968e90b2012-03-18 16:12:43 +0800193 Event.Type.REVIEW:
194 lambda event: self.show_review_information(),
Jon Salz5f2a0672012-05-22 17:14:06 +0800195 Event.Type.UPDATE_SYSTEM_INFO:
196 lambda event: self.update_system_info(),
Jon Salz37eccbd2012-05-25 16:06:52 +0800197 Event.Type.UPDATE_FACTORY:
198 lambda event: self.update_factory(),
Jon Salzf00cdc82012-05-28 18:56:17 +0800199 Event.Type.STOP:
200 lambda event: self.stop(),
Jon Salz0405ab52012-03-16 15:26:52 +0800201 }
202
Jon Salz73e0fd02012-04-04 11:46:38 +0800203 self.exceptions = []
Jon Salz258a40c2012-04-19 12:34:01 +0800204 self.web_socket_manager = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800205
206 def destroy(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800207 if self.chrome:
208 self.chrome.kill()
209 self.chrome = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800210 if self.ui_process:
Jon Salz258a40c2012-04-19 12:34:01 +0800211 utils.kill_process_tree(self.ui_process, 'ui')
Jon Salz73e0fd02012-04-04 11:46:38 +0800212 self.ui_process = None
Jon Salz258a40c2012-04-19 12:34:01 +0800213 if self.web_socket_manager:
214 logging.info('Stopping web sockets')
215 self.web_socket_manager.close()
216 self.web_socket_manager = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800217 if self.state_server_thread:
218 logging.info('Stopping state server')
219 self.state_server.shutdown()
220 self.state_server_thread.join()
Jon Salz73e0fd02012-04-04 11:46:38 +0800221 self.state_server.server_close()
222 self.state_server_thread = None
Jon Salz66f65e62012-05-24 17:40:26 +0800223 if self.state_instance:
224 self.state_instance.close()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800225 if self.event_server_thread:
226 logging.info('Stopping event server')
227 self.event_server.shutdown() # pylint: disable=E1101
228 self.event_server_thread.join()
Jon Salz73e0fd02012-04-04 11:46:38 +0800229 self.event_server.server_close()
230 self.event_server_thread = None
Jon Salz8375c2e2012-04-04 15:22:24 +0800231 if self.prespawner:
232 logging.info('Stopping prespawner')
233 self.prespawner.stop()
234 self.prespawner = None
235 if self.event_client:
Jon Salz258a40c2012-04-19 12:34:01 +0800236 logging.info('Closing event client')
Jon Salz8375c2e2012-04-04 15:22:24 +0800237 self.event_client.close()
Jon Salz258a40c2012-04-19 12:34:01 +0800238 self.event_client = None
Jon Salzeb8d25f2012-05-22 15:17:32 +0800239 if self.event_log:
240 self.event_log.Close()
241 self.event_log = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800242 self.check_exceptions()
Jon Salz258a40c2012-04-19 12:34:01 +0800243 logging.info('Done destroying Goofy')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800244
245 def start_state_server(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800246 self.state_instance, self.state_server = (
247 state.create_server(bind_address='0.0.0.0'))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800248 logging.info('Starting state server')
249 self.state_server_thread = threading.Thread(
Jon Salz8375c2e2012-04-04 15:22:24 +0800250 target=self.state_server.serve_forever,
251 name='StateServer')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800252 self.state_server_thread.start()
253
254 def start_event_server(self):
255 self.event_server = EventServer()
256 logging.info('Starting factory event server')
257 self.event_server_thread = threading.Thread(
Jon Salz8375c2e2012-04-04 15:22:24 +0800258 target=self.event_server.serve_forever,
259 name='EventServer') # pylint: disable=E1101
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800260 self.event_server_thread.start()
261
262 self.event_client = EventClient(
263 callback=self.handle_event, event_loop=self.run_queue)
264
Jon Salz258a40c2012-04-19 12:34:01 +0800265 self.web_socket_manager = WebSocketManager(self.uuid)
266 self.state_server.add_handler("/event",
267 self.web_socket_manager.handle_web_socket)
268
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800269 def start_ui(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800270 ui_proc_args = [os.path.join(factory.CROS_FACTORY_LIB_PATH, 'ui'),
271 self.options.test_list]
Jon Salz14bcbb02012-03-17 15:11:50 +0800272 if self.options.verbose:
273 ui_proc_args.append('-v')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800274 logging.info('Starting ui %s', ui_proc_args)
275 self.ui_process = subprocess.Popen(ui_proc_args)
276 logging.info('Waiting for UI to come up...')
277 self.event_client.wait(
278 lambda event: event.type == Event.Type.UI_READY)
279 logging.info('UI has started')
280
281 def set_visible_test(self, test):
282 if self.visible_test == test:
283 return
284
285 if test:
286 test.update_state(visible=True)
287 if self.visible_test:
288 self.visible_test.update_state(visible=False)
289 self.visible_test = test
290
Jon Salz74ad3262012-03-16 14:40:55 +0800291 def handle_shutdown_complete(self, test, state):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800292 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800293 Handles the case where a shutdown was detected during a shutdown step.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800294
Jon Salz74ad3262012-03-16 14:40:55 +0800295 @param test: The ShutdownStep.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800296 @param state: The test state.
297 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800298 state = test.update_state(increment_shutdown_count=1)
299 logging.info('Detected shutdown (%d of %d)',
300 state.shutdown_count, test.iterations)
301 if state.shutdown_count == test.iterations:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800302 # Good!
303 test.update_state(status=TestState.PASSED, error_msg='')
Jon Salz74ad3262012-03-16 14:40:55 +0800304 elif state.shutdown_count > test.iterations:
Jon Salz73e0fd02012-04-04 11:46:38 +0800305 # Shut down too many times
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800306 test.update_state(status=TestState.FAILED,
Jon Salz74ad3262012-03-16 14:40:55 +0800307 error_msg='Too many shutdowns')
Jon Salz258a40c2012-04-19 12:34:01 +0800308 elif utils.are_shift_keys_depressed():
Jon Salz73e0fd02012-04-04 11:46:38 +0800309 logging.info('Shift keys are depressed; cancelling restarts')
310 # Abort shutdown
311 test.update_state(
312 status=TestState.FAILED,
313 error_msg='Shutdown aborted with double shift keys')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800314 else:
Jon Salz74ad3262012-03-16 14:40:55 +0800315 # Need to shutdown again
Jon Salzb9038572012-05-24 10:34:51 +0800316 self.event_log.Log('shutdown', operation='reboot')
Jon Salz73e0fd02012-04-04 11:46:38 +0800317 self.env.shutdown('reboot')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800318
319 def init_states(self):
320 '''
321 Initializes all states on startup.
322 '''
323 for test in self.test_list.get_all_tests():
324 # Make sure the state server knows about all the tests,
325 # defaulting to an untested state.
326 test.update_state(update_parent=False, visible=False)
327
Jon Salzd6361c22012-06-11 22:23:57 +0800328 var_log_messages = None
329
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800330 # Any 'active' tests should be marked as failed now.
331 for test in self.test_list.walk():
332 state = test.get_state()
Hung-Te Lin96632362012-03-20 21:14:18 +0800333 if state.status != TestState.ACTIVE:
334 continue
335 if isinstance(test, factory.ShutdownStep):
336 # Shutdown while the test was active - that's good.
337 self.handle_shutdown_complete(test, state)
338 else:
Jon Salzd6361c22012-06-11 22:23:57 +0800339 # Unexpected shutdown. Grab /var/log/messages for context.
340 if var_log_messages is None:
341 try:
342 var_log_messages = (
343 utils.var_log_messages_before_reboot())
344 # Write it to the log, to make it easier to
345 # correlate with /var/log/messages.
346 logging.info(
347 'Unexpected shutdown. '
348 'Tail of /var/log/messages before last reboot:\n'
349 '%s', ('\n'.join(
350 ' ' + x for x in var_log_messages)))
351 except:
352 logging.exception('Unable to grok /var/log/messages')
353 var_log_messages = []
354
355 error_msg = 'Unexpected shutdown while test was running'
356 self.event_log.Log('end_test',
357 path=test.path,
358 status=TestState.FAILED,
359 invocation=test.get_state().invocation,
360 error_msg=error_msg,
361 var_log_messages='\n'.join(var_log_messages))
362 test.update_state(
363 status=TestState.FAILED,
364 error_msg=error_msg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800365
366 def show_next_active_test(self):
367 '''
368 Rotates to the next visible active test.
369 '''
370 self.reap_completed_tests()
371 active_tests = [
372 t for t in self.test_list.walk()
373 if t.is_leaf() and t.get_state().status == TestState.ACTIVE]
374 if not active_tests:
375 return
376
377 try:
378 next_test = active_tests[
379 (active_tests.index(self.visible_test) + 1) % len(active_tests)]
380 except ValueError: # visible_test not present in active_tests
381 next_test = active_tests[0]
382
383 self.set_visible_test(next_test)
384
385 def handle_event(self, event):
386 '''
387 Handles an event from the event server.
388 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800389 handler = self.event_handlers.get(event.type)
390 if handler:
Jon Salz968e90b2012-03-18 16:12:43 +0800391 handler(event)
Jon Salz0405ab52012-03-16 15:26:52 +0800392 else:
Jon Salz968e90b2012-03-18 16:12:43 +0800393 # We don't register handlers for all event types - just ignore
394 # this event.
395 logging.debug('Unbound event type %s', event.type)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800396
397 def run_next_test(self):
398 '''
399 Runs the next eligible test (or tests) in self.tests_to_run.
400 '''
401 self.reap_completed_tests()
402 while self.tests_to_run:
403 logging.debug('Tests to run: %s',
404 [x.path for x in self.tests_to_run])
405
406 test = self.tests_to_run[0]
407
408 if test in self.invocations:
409 logging.info('Next test %s is already running', test.path)
410 self.tests_to_run.popleft()
411 return
412
413 if self.invocations and not (test.backgroundable and all(
414 [x.backgroundable for x in self.invocations])):
415 logging.debug('Waiting for non-backgroundable tests to '
416 'complete before running %s', test.path)
417 return
418
419 self.tests_to_run.popleft()
420
Jon Salz74ad3262012-03-16 14:40:55 +0800421 if isinstance(test, factory.ShutdownStep):
Jon Salz8796e362012-05-24 11:39:09 +0800422 if os.path.exists(NO_REBOOT_FILE):
423 test.update_state(
424 status=TestState.FAILED, increment_count=1,
425 error_msg=('Skipped shutdown since %s is present' %
426 NO_REBOOT_FILE))
427 continue
428
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800429 test.update_state(status=TestState.ACTIVE, increment_count=1,
Jon Salz74ad3262012-03-16 14:40:55 +0800430 error_msg='', shutdown_count=0)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800431 # Save pending test list in the state server
432 self.state_instance.set_shared_data(
Jon Salz74ad3262012-03-16 14:40:55 +0800433 'tests_after_shutdown',
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800434 [t.path for t in self.tests_to_run])
Jon Salz74ad3262012-03-16 14:40:55 +0800435
Jon Salz73e0fd02012-04-04 11:46:38 +0800436 with self.env.lock:
Jon Salzb9038572012-05-24 10:34:51 +0800437 self.event_log.Log('shutdown', operation=test.operation)
Jon Salz73e0fd02012-04-04 11:46:38 +0800438 shutdown_result = self.env.shutdown(test.operation)
439 if shutdown_result:
440 # That's all, folks!
441 self.run_queue.put(None)
442 return
443 else:
444 # Just pass (e.g., in the chroot).
445 test.update_state(status=TestState.PASSED)
446 self.state_instance.set_shared_data(
447 'tests_after_shutdown', None)
448 continue
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800449
450 invoc = TestInvocation(self, test, on_completion=self.run_next_test)
451 self.invocations[test] = invoc
452 if self.visible_test is None and test.has_ui:
453 self.set_visible_test(test)
454 invoc.start()
455
Jon Salz0405ab52012-03-16 15:26:52 +0800456 def run_tests(self, subtrees, untested_only=False):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800457 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800458 Runs tests under subtree.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800459
460 The tests are run in order unless one fails (then stops).
461 Backgroundable tests are run simultaneously; when a foreground test is
462 encountered, we wait for all active tests to finish before continuing.
Jon Salz0405ab52012-03-16 15:26:52 +0800463
464 @param subtrees: Node or nodes containing tests to run (may either be
465 a single test or a list). Duplicates will be ignored.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800466 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800467 if type(subtrees) != list:
468 subtrees = [subtrees]
469
470 # Nodes we've seen so far, to avoid duplicates.
471 seen = set()
472
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800473 self.tests_to_run = deque()
Jon Salz0405ab52012-03-16 15:26:52 +0800474 for subtree in subtrees:
475 for test in subtree.walk():
476 if test in seen:
477 continue
478 seen.add(test)
479
480 if not test.is_leaf():
481 continue
482 if (untested_only and
483 test.get_state().status != TestState.UNTESTED):
484 continue
485 self.tests_to_run.append(test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800486 self.run_next_test()
487
488 def reap_completed_tests(self):
489 '''
490 Removes completed tests from the set of active tests.
491
492 Also updates the visible test if it was reaped.
493 '''
494 for t, v in dict(self.invocations).iteritems():
495 if v.is_completed():
496 del self.invocations[t]
497
498 if (self.visible_test is None or
499 self.visible_test not in self.invocations):
500 self.set_visible_test(None)
501 # Make the first running test, if any, the visible test
502 for t in self.test_list.walk():
503 if t in self.invocations:
504 self.set_visible_test(t)
505 break
506
Hung-Te Lin96632362012-03-20 21:14:18 +0800507 def kill_active_tests(self, abort):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800508 '''
509 Kills and waits for all active tests.
Hung-Te Lin96632362012-03-20 21:14:18 +0800510
511 @param abort: True to change state of killed tests to FAILED, False for
512 UNTESTED.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800513 '''
514 self.reap_completed_tests()
515 for test, invoc in self.invocations.items():
516 factory.console.info('Killing active test %s...' % test.path)
517 invoc.abort_and_join()
518 factory.console.info('Killed %s' % test.path)
519 del self.invocations[test]
Hung-Te Lin96632362012-03-20 21:14:18 +0800520 if not abort:
521 test.update_state(status=TestState.UNTESTED)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800522 self.reap_completed_tests()
523
Jon Salzf00cdc82012-05-28 18:56:17 +0800524 def stop(self):
525 self.kill_active_tests(False)
526 self.run_tests([])
527
Hung-Te Lin96632362012-03-20 21:14:18 +0800528 def abort_active_tests(self):
529 self.kill_active_tests(True)
530
Jon Salz73e0fd02012-04-04 11:46:38 +0800531 def main(self):
Jon Salzeb8d25f2012-05-22 15:17:32 +0800532 try:
533 self.init()
Jon Salzb9038572012-05-24 10:34:51 +0800534 self.event_log.Log('goofy_init',
535 success=True)
Jon Salzeb8d25f2012-05-22 15:17:32 +0800536 except:
537 if self.event_log:
538 try:
Jon Salzb9038572012-05-24 10:34:51 +0800539 self.event_log.Log('goofy_init',
540 success=False,
541 trace=traceback.format_exc())
Jon Salzeb8d25f2012-05-22 15:17:32 +0800542 except:
543 pass
544 raise
545
Jon Salz73e0fd02012-04-04 11:46:38 +0800546 self.run()
547
Jon Salz5f2a0672012-05-22 17:14:06 +0800548 def update_system_info(self):
549 '''Updates system info.'''
550 system_info = test_environment.SystemInfo(self.env, self.state_instance)
551 self.state_instance.set_shared_data('system_info', system_info.__dict__)
552 self.event_client.post_event(Event(Event.Type.SYSTEM_INFO,
553 system_info=system_info.__dict__))
554 logging.info('System info: %r', system_info.__dict__)
555
Jon Salz37eccbd2012-05-25 16:06:52 +0800556 def update_factory(self):
557 self.kill_active_tests(False)
558 self.run_tests([])
559
560 try:
561 if updater.TryUpdate(pre_update_hook=self.state_instance.close):
562 self.env.shutdown('reboot')
563 except:
564 factory.console.exception('Unable to update')
565
Jon Salz73e0fd02012-04-04 11:46:38 +0800566 def init(self, args=None, env=None):
567 '''Initializes Goofy.
Jon Salz74ad3262012-03-16 14:40:55 +0800568
569 Args:
Jon Salz73e0fd02012-04-04 11:46:38 +0800570 args: A list of command-line arguments. Uses sys.argv if
571 args is None.
572 env: An Environment instance to use (or None to choose
Jon Salz258a40c2012-04-19 12:34:01 +0800573 FakeChrootEnvironment or DUTEnvironment as appropriate).
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800574 '''
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800575 parser = OptionParser()
576 parser.add_option('-v', '--verbose', dest='verbose',
577 action='store_true',
578 help='Enable debug logging')
579 parser.add_option('--print_test_list', dest='print_test_list',
580 metavar='FILE',
581 help='Read and print test list FILE, and exit')
Jon Salz758e6cc2012-04-03 15:47:07 +0800582 parser.add_option('--restart', dest='restart',
583 action='store_true',
584 help='Clear all test state')
Jon Salz258a40c2012-04-19 12:34:01 +0800585 parser.add_option('--ui', dest='ui', type='choice',
586 choices=['none', 'gtk', 'chrome'],
587 default='gtk',
588 help='UI to use')
Jon Salz63585ea2012-05-21 15:03:32 +0800589 parser.add_option('--ui_scale_factor', dest='ui_scale_factor',
590 type='int', default=1,
591 help=('Factor by which to scale UI '
592 '(Chrome UI only)'))
Jon Salz73e0fd02012-04-04 11:46:38 +0800593 parser.add_option('--test_list', dest='test_list',
594 metavar='FILE',
595 help='Use FILE as test list')
596 (self.options, self.args) = parser.parse_args(args)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800597
Jon Salz73e0fd02012-04-04 11:46:38 +0800598 global _inited_logging
599 if not _inited_logging:
600 factory.init_logging('goofy', verbose=self.options.verbose)
601 _inited_logging = True
Jon Salzeb8d25f2012-05-22 15:17:32 +0800602 self.event_log = EventLog('goofy')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800603
Jon Salz73e0fd02012-04-04 11:46:38 +0800604 if (not suppress_chroot_warning and
605 factory.in_chroot() and
Jon Salz258a40c2012-04-19 12:34:01 +0800606 self.options.ui == 'gtk' and
Jon Salz758e6cc2012-04-03 15:47:07 +0800607 os.environ.get('DISPLAY') in [None, '', ':0', ':0.0']):
608 # That's not going to work! Tell the user how to run
609 # this way.
610 logging.warn(GOOFY_IN_CHROOT_WARNING)
611 time.sleep(1)
612
Jon Salz73e0fd02012-04-04 11:46:38 +0800613 if env:
614 self.env = env
615 elif factory.in_chroot():
Jon Salz5f2a0672012-05-22 17:14:06 +0800616 self.env = test_environment.FakeChrootEnvironment()
Jon Salz73e0fd02012-04-04 11:46:38 +0800617 logging.warn(
618 'Using chroot environment: will not actually run autotests')
619 else:
Jon Salz5f2a0672012-05-22 17:14:06 +0800620 self.env = test_environment.DUTEnvironment()
Jon Salz323dd3d2012-04-09 18:40:43 +0800621 self.env.goofy = self
Jon Salz73e0fd02012-04-04 11:46:38 +0800622
Jon Salz758e6cc2012-04-03 15:47:07 +0800623 if self.options.restart:
624 state.clear_state()
625
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800626 if self.options.print_test_list:
627 print (factory.read_test_list(self.options.print_test_list).
628 __repr__(recursive=True))
629 return
630
Jon Salz9b312912012-06-04 11:27:00 +0800631 if self.options.ui_scale_factor != 1 and factory.in_qemu():
632 logging.warn(
633 'In QEMU; ignoring ui_scale_factor argument')
634 self.options.ui_scale_factor = 1
635
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800636 logging.info('Started')
637
638 self.start_state_server()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800639 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
Jon Salz63585ea2012-05-21 15:03:32 +0800640 self.state_instance.set_shared_data('ui_scale_factor',
641 self.options.ui_scale_factor)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800642
Jon Salz73e0fd02012-04-04 11:46:38 +0800643 self.options.test_list = (self.options.test_list or find_test_list())
644 self.test_list = factory.read_test_list(self.options.test_list,
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800645 self.state_instance)
Jon Salz06fbeff2012-05-21 17:06:05 +0800646 if not self.state_instance.has_shared_data('ui_lang'):
647 self.state_instance.set_shared_data('ui_lang',
648 self.test_list.options.ui_lang)
Jon Salz258a40c2012-04-19 12:34:01 +0800649 self.state_instance.test_list = self.test_list
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800650
651 self.init_states()
652 self.start_event_server()
Jon Salz258a40c2012-04-19 12:34:01 +0800653
Jon Salz5f2a0672012-05-22 17:14:06 +0800654 self.update_system_info()
655
Jon Salz5da61e62012-05-31 13:06:22 +0800656 os.environ['CROS_FACTORY'] = '1'
Jon Salzeebd12e2012-06-08 15:34:56 +0800657 os.environ['CROS_DISABLE_SITE_SYSINFO'] = '1'
Jon Salz5da61e62012-05-31 13:06:22 +0800658
Jon Salzb1b39092012-05-03 02:05:09 +0800659 # Set CROS_UI since some behaviors in ui.py depend on the
660 # particular UI in use. TODO(jsalz): Remove this (and all
661 # places it is used) when the GTK UI is removed.
662 os.environ['CROS_UI'] = self.options.ui
Jon Salz258a40c2012-04-19 12:34:01 +0800663
Jon Salzb1b39092012-05-03 02:05:09 +0800664 if self.options.ui == 'chrome':
Jon Salz258a40c2012-04-19 12:34:01 +0800665 self.env.launch_chrome()
666 logging.info('Waiting for a web socket connection')
667 self.web_socket_manager.wait()
Jon Salzb1b39092012-05-03 02:05:09 +0800668
669 # Wait for the test widget size to be set; this is done in
670 # an asynchronous RPC so there is a small chance that the
671 # web socket might be opened first.
672 for i in range(100): # 10 s
Jon Salz63585ea2012-05-21 15:03:32 +0800673 try:
674 if self.state_instance.get_shared_data('test_widget_size'):
675 break
676 except KeyError:
677 pass # Retry
Jon Salzb1b39092012-05-03 02:05:09 +0800678 time.sleep(0.1) # 100 ms
679 else:
680 logging.warn('Never received test_widget_size from UI')
Jon Salz258a40c2012-04-19 12:34:01 +0800681 elif self.options.ui == 'gtk':
Jon Salz73e0fd02012-04-04 11:46:38 +0800682 self.start_ui()
Jon Salz258a40c2012-04-19 12:34:01 +0800683
Jon Salz8375c2e2012-04-04 15:22:24 +0800684 self.prespawner = Prespawner()
Jon Salz323dd3d2012-04-09 18:40:43 +0800685 self.prespawner.start()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800686
687 def state_change_callback(test, state):
688 self.event_client.post_event(
689 Event(Event.Type.STATE_CHANGE,
690 path=test.path, state=state))
691 self.test_list.state_change_callback = state_change_callback
692
693 try:
Jon Salz758e6cc2012-04-03 15:47:07 +0800694 tests_after_shutdown = self.state_instance.get_shared_data(
695 'tests_after_shutdown')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800696 except KeyError:
Jon Salz758e6cc2012-04-03 15:47:07 +0800697 tests_after_shutdown = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800698
Jon Salz758e6cc2012-04-03 15:47:07 +0800699 if tests_after_shutdown is not None:
700 logging.info('Resuming tests after shutdown: %s',
701 tests_after_shutdown)
702 self.state_instance.set_shared_data('tests_after_shutdown', None)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800703 self.tests_to_run.extend(
Jon Salz758e6cc2012-04-03 15:47:07 +0800704 self.test_list.lookup_path(t) for t in tests_after_shutdown)
Jon Salz73e0fd02012-04-04 11:46:38 +0800705 self.run_queue.put(self.run_next_test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800706 else:
Jon Salz57717ca2012-04-04 16:47:25 +0800707 if self.test_list.options.auto_run_on_start:
708 self.run_queue.put(
709 lambda: self.run_tests(self.test_list, untested_only=True))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800710
Jon Salz73e0fd02012-04-04 11:46:38 +0800711 def run(self):
712 '''Runs Goofy.'''
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800713 # Process events forever.
Jon Salz57717ca2012-04-04 16:47:25 +0800714 while self.run_once(True):
Jon Salz73e0fd02012-04-04 11:46:38 +0800715 pass
716
Jon Salz57717ca2012-04-04 16:47:25 +0800717 def run_once(self, block=False):
Jon Salz73e0fd02012-04-04 11:46:38 +0800718 '''Runs all items pending in the event loop.
719
Jon Salz57717ca2012-04-04 16:47:25 +0800720 Args:
721 block: If true, block until at least one event is processed.
722
Jon Salz73e0fd02012-04-04 11:46:38 +0800723 Returns:
724 True to keep going or False to shut down.
725 '''
Jon Salz57717ca2012-04-04 16:47:25 +0800726 events = []
727 if block:
728 # Get at least one event
729 events.append(self.run_queue.get())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800730 while True:
Jon Salz73e0fd02012-04-04 11:46:38 +0800731 try:
732 events.append(self.run_queue.get_nowait())
733 except Queue.Empty:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800734 break
735
Jon Salz73e0fd02012-04-04 11:46:38 +0800736 for event in events:
737 if not event:
738 # Shutdown request.
739 self.run_queue.task_done()
740 return False
741
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800742 try:
743 event()
744 except Exception as e: # pylint: disable=W0703
745 logging.error('Error in event loop: %s', e)
746 traceback.print_exc(sys.stderr)
Jon Salz8375c2e2012-04-04 15:22:24 +0800747 self.record_exception(traceback.format_exception_only(
748 *sys.exc_info()[:2]))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800749 # But keep going
750 finally:
751 self.run_queue.task_done()
Jon Salz73e0fd02012-04-04 11:46:38 +0800752 return True
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800753
Jon Salz258a40c2012-04-19 12:34:01 +0800754 def run_tests_with_status(self, statuses_to_run, starting_at=None,
755 root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800756 '''Runs all top-level tests with a particular status.
757
758 All active tests, plus any tests to re-run, are reset.
Jon Salz57717ca2012-04-04 16:47:25 +0800759
760 Args:
761 starting_at: If provided, only auto-runs tests beginning with
762 this test.
Jon Salz0405ab52012-03-16 15:26:52 +0800763 '''
Jon Salz258a40c2012-04-19 12:34:01 +0800764 root = root or self.test_list
765
Jon Salz57717ca2012-04-04 16:47:25 +0800766 if starting_at:
767 # Make sure they passed a test, not a string.
768 assert isinstance(starting_at, factory.FactoryTest)
769
Jon Salz0405ab52012-03-16 15:26:52 +0800770 tests_to_reset = []
771 tests_to_run = []
772
Jon Salz57717ca2012-04-04 16:47:25 +0800773 found_starting_at = False
774
Jon Salz258a40c2012-04-19 12:34:01 +0800775 for test in root.get_top_level_tests():
Jon Salz57717ca2012-04-04 16:47:25 +0800776 if starting_at:
777 if test == starting_at:
778 # We've found starting_at; do auto-run on all
779 # subsequent tests.
780 found_starting_at = True
781 if not found_starting_at:
782 # Don't start this guy yet
783 continue
784
Jon Salz0405ab52012-03-16 15:26:52 +0800785 status = test.get_state().status
786 if status == TestState.ACTIVE or status in statuses_to_run:
787 # Reset the test (later; we will need to abort
788 # all active tests first).
789 tests_to_reset.append(test)
790 if status in statuses_to_run:
791 tests_to_run.append(test)
792
793 self.abort_active_tests()
794
795 # Reset all statuses of the tests to run (in case any tests were active;
796 # we want them to be run again).
797 for test_to_reset in tests_to_reset:
798 for test in test_to_reset.walk():
799 test.update_state(status=TestState.UNTESTED)
800
801 self.run_tests(tests_to_run, untested_only=True)
802
Jon Salz258a40c2012-04-19 12:34:01 +0800803 def restart_tests(self, root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800804 '''Restarts all tests.'''
Jon Salz258a40c2012-04-19 12:34:01 +0800805 root = root or self.test_list
Jon Salz0405ab52012-03-16 15:26:52 +0800806
Jon Salz258a40c2012-04-19 12:34:01 +0800807 self.abort_active_tests()
808 for test in root.walk():
809 test.update_state(status=TestState.UNTESTED)
810 self.run_tests(root)
811
812 def auto_run(self, starting_at=None, root=None):
Jon Salz57717ca2012-04-04 16:47:25 +0800813 '''"Auto-runs" tests that have not been run yet.
814
815 Args:
816 starting_at: If provide, only auto-runs tests beginning with
817 this test.
818 '''
Jon Salz258a40c2012-04-19 12:34:01 +0800819 root = root or self.test_list
Jon Salz57717ca2012-04-04 16:47:25 +0800820 self.run_tests_with_status([TestState.UNTESTED, TestState.ACTIVE],
Jon Salz258a40c2012-04-19 12:34:01 +0800821 starting_at=starting_at,
822 root=root)
Jon Salz0405ab52012-03-16 15:26:52 +0800823
Jon Salz258a40c2012-04-19 12:34:01 +0800824 def re_run_failed(self, root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800825 '''Re-runs failed tests.'''
Jon Salz258a40c2012-04-19 12:34:01 +0800826 root = root or self.test_list
827 self.run_tests_with_status([TestState.FAILED], root=root)
Jon Salz0405ab52012-03-16 15:26:52 +0800828
Jon Salz968e90b2012-03-18 16:12:43 +0800829 def show_review_information(self):
Hung-Te Lin96632362012-03-20 21:14:18 +0800830 '''Event handler for showing review information screen.
831
832 The information screene is rendered by main UI program (ui.py), so in
833 goofy we only need to kill all active tests, set them as untested, and
834 clear remaining tests.
835 '''
836 self.kill_active_tests(False)
837 self.run_tests([])
838
Jon Salz0405ab52012-03-16 15:26:52 +0800839 def handle_switch_test(self, event):
Jon Salz968e90b2012-03-18 16:12:43 +0800840 '''Switches to a particular test.
841
842 @param event: The SWITCH_TEST event.
843 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800844 test = self.test_list.lookup_path(event.path)
Jon Salz57717ca2012-04-04 16:47:25 +0800845 if not test:
Jon Salz968e90b2012-03-18 16:12:43 +0800846 logging.error('Unknown test %r', event.key)
Jon Salz57717ca2012-04-04 16:47:25 +0800847 return
848
849 invoc = self.invocations.get(test)
850 if invoc and test.backgroundable:
851 # Already running: just bring to the front if it
852 # has a UI.
853 logging.info('Setting visible test to %s', test.path)
854 self.event_client.post_event(
855 Event(Event.Type.SET_VISIBLE_TEST, path=test.path))
856 return
857
858 self.abort_active_tests()
859 for t in test.walk():
860 t.update_state(status=TestState.UNTESTED)
861
862 if self.test_list.options.auto_run_on_keypress:
863 self.auto_run(starting_at=test)
864 else:
865 self.run_tests(test)
Jon Salz0405ab52012-03-16 15:26:52 +0800866
Jon Salz73e0fd02012-04-04 11:46:38 +0800867 def wait(self):
868 '''Waits for all pending invocations.
869
870 Useful for testing.
871 '''
872 for k, v in self.invocations.iteritems():
873 logging.info('Waiting for %s to complete...', k)
874 v.thread.join()
875
876 def check_exceptions(self):
877 '''Raises an error if any exceptions have occurred in
878 invocation threads.'''
879 if self.exceptions:
880 raise RuntimeError('Exception in invocation thread: %r' %
881 self.exceptions)
882
883 def record_exception(self, msg):
884 '''Records an exception in an invocation thread.
885
886 An exception with the given message will be rethrown when
887 Goofy is destroyed.'''
888 self.exceptions.append(msg)
889
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800890
891if __name__ == '__main__':
892 Goofy().main()