blob: 35cb758c8dedbcf7eb7a22dc00fb960100a2716f [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 Salz758e6cc2012-04-03 15:47:07 +080054GOOFY_IN_CHROOT_WARNING = '\n' + ('*' * 70) + '''
55You are running Goofy inside the chroot. Autotests are not supported.
56
57To use Goofy in the chroot, first install an Xvnc server:
58
59 sudo apt-get install tightvncserver
60
61...and then start a VNC X server outside the chroot:
62
63 vncserver :10 &
64 vncviewer :10
65
66...and run Goofy as follows:
67
68 env --unset=XAUTHORITY DISPLAY=localhost:10 python goofy.py
69''' + ('*' * 70)
Jon Salz73e0fd02012-04-04 11:46:38 +080070suppress_chroot_warning = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +080071
72def get_hwid_cfg():
73 '''
74 Returns the HWID config tag, or an empty string if none can be found.
75 '''
76 if 'CROS_HWID' in os.environ:
77 return os.environ['CROS_HWID']
78 if os.path.exists(HWID_CFG_PATH):
79 with open(HWID_CFG_PATH, 'rt') as hwid_cfg_handle:
80 return hwid_cfg_handle.read().strip()
81 return ''
82
83
84def find_test_list():
85 '''
86 Returns the path to the active test list, based on the HWID config tag.
87 '''
88 hwid_cfg = get_hwid_cfg()
89
90 # Try in order: test_list, test_list.$hwid_cfg, test_list.all
91 if hwid_cfg:
92 test_list = '%s_%s' % (DEFAULT_TEST_LIST_PATH, hwid_cfg)
93 if os.path.exists(test_list):
94 logging.info('Using special test list: %s', test_list)
95 return test_list
96 logging.info('WARNING: no specific test list for config: %s', hwid_cfg)
97
98 test_list = DEFAULT_TEST_LIST_PATH
99 if os.path.exists(test_list):
100 return test_list
101
102 test_list = ('%s.all' % DEFAULT_TEST_LIST_PATH)
103 if os.path.exists(test_list):
104 logging.info('Using default test list: ' + test_list)
105 return test_list
106 logging.info('ERROR: Cannot find any test list.')
107
Jon Salz73e0fd02012-04-04 11:46:38 +0800108
Jon Salz73e0fd02012-04-04 11:46:38 +0800109_inited_logging = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800110
111class Goofy(object):
112 '''
113 The main factory flow.
114
115 Note that all methods in this class must be invoked from the main
116 (event) thread. Other threads, such as callbacks and TestInvocation
117 methods, should instead post events on the run queue.
118
119 TODO: Unit tests. (chrome-os-partner:7409)
120
121 Properties:
Jon Salz258a40c2012-04-19 12:34:01 +0800122 uuid: A unique UUID for this invocation of Goofy.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800123 state_instance: An instance of FactoryState.
124 state_server: The FactoryState XML/RPC server.
125 state_server_thread: A thread running state_server.
126 event_server: The EventServer socket server.
127 event_server_thread: A thread running event_server.
128 event_client: A client to the event server.
Hung-Te Lin6bb48552012-02-09 14:37:43 +0800129 ui_process: The factory ui process object.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800130 run_queue: A queue of callbacks to invoke from the main thread.
131 invocations: A map from FactoryTest objects to the corresponding
132 TestInvocations objects representing active tests.
133 tests_to_run: A deque of tests that should be run when the current
134 test(s) complete.
135 options: Command-line options.
136 args: Command-line args.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800137 test_list: The test list.
Jon Salz0405ab52012-03-16 15:26:52 +0800138 event_handlers: Map of Event.Type to the method used to handle that
139 event. If the method has an 'event' argument, the event is passed
140 to the handler.
Jon Salz73e0fd02012-04-04 11:46:38 +0800141 exceptions: Exceptions encountered in invocation threads.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800142 '''
143 def __init__(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800144 self.uuid = str(uuid.uuid4())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800145 self.state_instance = None
146 self.state_server = None
147 self.state_server_thread = None
148 self.event_server = None
149 self.event_server_thread = None
150 self.event_client = None
Jon Salzeb8d25f2012-05-22 15:17:32 +0800151 self.event_log = None
Jon Salz8375c2e2012-04-04 15:22:24 +0800152 self.prespawner = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800153 self.ui_process = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800154 self.run_queue = Queue.Queue()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800155 self.invocations = {}
156 self.tests_to_run = deque()
157 self.visible_test = None
Jon Salz258a40c2012-04-19 12:34:01 +0800158 self.chrome = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800159
160 self.options = None
161 self.args = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800162 self.test_list = None
163
Jon Salz258a40c2012-04-19 12:34:01 +0800164 def test_or_root(event):
165 '''Returns the top-level parent for a test (the root node of the
166 tests that need to be run together if the given test path is to
167 be run).'''
168 try:
169 path = event.path
Jon Salzd2ed6cb2012-05-02 09:35:14 +0800170 except AttributeError:
Jon Salz258a40c2012-04-19 12:34:01 +0800171 path = None
172
173 if path:
Jon Salzf617b282012-05-24 14:14:04 +0800174 return (self.test_list.lookup_path(path).
175 get_top_level_parent_or_group())
Jon Salz258a40c2012-04-19 12:34:01 +0800176 else:
177 return self.test_list
178
Jon Salz0405ab52012-03-16 15:26:52 +0800179 self.event_handlers = {
180 Event.Type.SWITCH_TEST: self.handle_switch_test,
Jon Salz968e90b2012-03-18 16:12:43 +0800181 Event.Type.SHOW_NEXT_ACTIVE_TEST:
182 lambda event: self.show_next_active_test(),
183 Event.Type.RESTART_TESTS:
Jon Salz258a40c2012-04-19 12:34:01 +0800184 lambda event: self.restart_tests(root=test_or_root(event)),
Jon Salz968e90b2012-03-18 16:12:43 +0800185 Event.Type.AUTO_RUN:
Jon Salz258a40c2012-04-19 12:34:01 +0800186 lambda event: self.auto_run(root=test_or_root(event)),
Jon Salz968e90b2012-03-18 16:12:43 +0800187 Event.Type.RE_RUN_FAILED:
Jon Salz258a40c2012-04-19 12:34:01 +0800188 lambda event: self.re_run_failed(root=test_or_root(event)),
Jon Salz968e90b2012-03-18 16:12:43 +0800189 Event.Type.REVIEW:
190 lambda event: self.show_review_information(),
Jon Salz5f2a0672012-05-22 17:14:06 +0800191 Event.Type.UPDATE_SYSTEM_INFO:
192 lambda event: self.update_system_info(),
Jon Salz0405ab52012-03-16 15:26:52 +0800193 }
194
Jon Salz73e0fd02012-04-04 11:46:38 +0800195 self.exceptions = []
Jon Salz258a40c2012-04-19 12:34:01 +0800196 self.web_socket_manager = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800197
198 def destroy(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800199 if self.chrome:
200 self.chrome.kill()
201 self.chrome = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800202 if self.ui_process:
Jon Salz258a40c2012-04-19 12:34:01 +0800203 utils.kill_process_tree(self.ui_process, 'ui')
Jon Salz73e0fd02012-04-04 11:46:38 +0800204 self.ui_process = None
Jon Salz258a40c2012-04-19 12:34:01 +0800205 if self.web_socket_manager:
206 logging.info('Stopping web sockets')
207 self.web_socket_manager.close()
208 self.web_socket_manager = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800209 if self.state_server_thread:
210 logging.info('Stopping state server')
211 self.state_server.shutdown()
212 self.state_server_thread.join()
Jon Salz73e0fd02012-04-04 11:46:38 +0800213 self.state_server.server_close()
214 self.state_server_thread = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800215 if self.event_server_thread:
216 logging.info('Stopping event server')
217 self.event_server.shutdown() # pylint: disable=E1101
218 self.event_server_thread.join()
Jon Salz73e0fd02012-04-04 11:46:38 +0800219 self.event_server.server_close()
220 self.event_server_thread = None
Jon Salz8375c2e2012-04-04 15:22:24 +0800221 if self.prespawner:
222 logging.info('Stopping prespawner')
223 self.prespawner.stop()
224 self.prespawner = None
225 if self.event_client:
Jon Salz258a40c2012-04-19 12:34:01 +0800226 logging.info('Closing event client')
Jon Salz8375c2e2012-04-04 15:22:24 +0800227 self.event_client.close()
Jon Salz258a40c2012-04-19 12:34:01 +0800228 self.event_client = None
Jon Salzeb8d25f2012-05-22 15:17:32 +0800229 if self.event_log:
230 self.event_log.Close()
231 self.event_log = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800232 self.check_exceptions()
Jon Salz258a40c2012-04-19 12:34:01 +0800233 logging.info('Done destroying Goofy')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800234
235 def start_state_server(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800236 self.state_instance, self.state_server = (
237 state.create_server(bind_address='0.0.0.0'))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800238 logging.info('Starting state server')
239 self.state_server_thread = threading.Thread(
Jon Salz8375c2e2012-04-04 15:22:24 +0800240 target=self.state_server.serve_forever,
241 name='StateServer')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800242 self.state_server_thread.start()
243
244 def start_event_server(self):
245 self.event_server = EventServer()
246 logging.info('Starting factory event server')
247 self.event_server_thread = threading.Thread(
Jon Salz8375c2e2012-04-04 15:22:24 +0800248 target=self.event_server.serve_forever,
249 name='EventServer') # pylint: disable=E1101
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800250 self.event_server_thread.start()
251
252 self.event_client = EventClient(
253 callback=self.handle_event, event_loop=self.run_queue)
254
Jon Salz258a40c2012-04-19 12:34:01 +0800255 self.web_socket_manager = WebSocketManager(self.uuid)
256 self.state_server.add_handler("/event",
257 self.web_socket_manager.handle_web_socket)
258
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800259 def start_ui(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800260 ui_proc_args = [os.path.join(factory.CROS_FACTORY_LIB_PATH, 'ui'),
261 self.options.test_list]
Jon Salz14bcbb02012-03-17 15:11:50 +0800262 if self.options.verbose:
263 ui_proc_args.append('-v')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800264 logging.info('Starting ui %s', ui_proc_args)
265 self.ui_process = subprocess.Popen(ui_proc_args)
266 logging.info('Waiting for UI to come up...')
267 self.event_client.wait(
268 lambda event: event.type == Event.Type.UI_READY)
269 logging.info('UI has started')
270
271 def set_visible_test(self, test):
272 if self.visible_test == test:
273 return
274
275 if test:
276 test.update_state(visible=True)
277 if self.visible_test:
278 self.visible_test.update_state(visible=False)
279 self.visible_test = test
280
Jon Salz74ad3262012-03-16 14:40:55 +0800281 def handle_shutdown_complete(self, test, state):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800282 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800283 Handles the case where a shutdown was detected during a shutdown step.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800284
Jon Salz74ad3262012-03-16 14:40:55 +0800285 @param test: The ShutdownStep.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800286 @param state: The test state.
287 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800288 state = test.update_state(increment_shutdown_count=1)
289 logging.info('Detected shutdown (%d of %d)',
290 state.shutdown_count, test.iterations)
291 if state.shutdown_count == test.iterations:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800292 # Good!
293 test.update_state(status=TestState.PASSED, error_msg='')
Jon Salz74ad3262012-03-16 14:40:55 +0800294 elif state.shutdown_count > test.iterations:
Jon Salz73e0fd02012-04-04 11:46:38 +0800295 # Shut down too many times
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800296 test.update_state(status=TestState.FAILED,
Jon Salz74ad3262012-03-16 14:40:55 +0800297 error_msg='Too many shutdowns')
Jon Salz258a40c2012-04-19 12:34:01 +0800298 elif utils.are_shift_keys_depressed():
Jon Salz73e0fd02012-04-04 11:46:38 +0800299 logging.info('Shift keys are depressed; cancelling restarts')
300 # Abort shutdown
301 test.update_state(
302 status=TestState.FAILED,
303 error_msg='Shutdown aborted with double shift keys')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800304 else:
Jon Salz74ad3262012-03-16 14:40:55 +0800305 # Need to shutdown again
Jon Salz73e0fd02012-04-04 11:46:38 +0800306 self.env.shutdown('reboot')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800307
308 def init_states(self):
309 '''
310 Initializes all states on startup.
311 '''
312 for test in self.test_list.get_all_tests():
313 # Make sure the state server knows about all the tests,
314 # defaulting to an untested state.
315 test.update_state(update_parent=False, visible=False)
316
317 # Any 'active' tests should be marked as failed now.
318 for test in self.test_list.walk():
319 state = test.get_state()
Hung-Te Lin96632362012-03-20 21:14:18 +0800320 if state.status != TestState.ACTIVE:
321 continue
322 if isinstance(test, factory.ShutdownStep):
323 # Shutdown while the test was active - that's good.
324 self.handle_shutdown_complete(test, state)
325 else:
326 test.update_state(status=TestState.FAILED,
327 error_msg='Unknown (shutdown?)')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800328
329 def show_next_active_test(self):
330 '''
331 Rotates to the next visible active test.
332 '''
333 self.reap_completed_tests()
334 active_tests = [
335 t for t in self.test_list.walk()
336 if t.is_leaf() and t.get_state().status == TestState.ACTIVE]
337 if not active_tests:
338 return
339
340 try:
341 next_test = active_tests[
342 (active_tests.index(self.visible_test) + 1) % len(active_tests)]
343 except ValueError: # visible_test not present in active_tests
344 next_test = active_tests[0]
345
346 self.set_visible_test(next_test)
347
348 def handle_event(self, event):
349 '''
350 Handles an event from the event server.
351 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800352 handler = self.event_handlers.get(event.type)
353 if handler:
Jon Salz968e90b2012-03-18 16:12:43 +0800354 handler(event)
Jon Salz0405ab52012-03-16 15:26:52 +0800355 else:
Jon Salz968e90b2012-03-18 16:12:43 +0800356 # We don't register handlers for all event types - just ignore
357 # this event.
358 logging.debug('Unbound event type %s', event.type)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800359
360 def run_next_test(self):
361 '''
362 Runs the next eligible test (or tests) in self.tests_to_run.
363 '''
364 self.reap_completed_tests()
365 while self.tests_to_run:
366 logging.debug('Tests to run: %s',
367 [x.path for x in self.tests_to_run])
368
369 test = self.tests_to_run[0]
370
371 if test in self.invocations:
372 logging.info('Next test %s is already running', test.path)
373 self.tests_to_run.popleft()
374 return
375
376 if self.invocations and not (test.backgroundable and all(
377 [x.backgroundable for x in self.invocations])):
378 logging.debug('Waiting for non-backgroundable tests to '
379 'complete before running %s', test.path)
380 return
381
382 self.tests_to_run.popleft()
383
Jon Salz74ad3262012-03-16 14:40:55 +0800384 if isinstance(test, factory.ShutdownStep):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800385 test.update_state(status=TestState.ACTIVE, increment_count=1,
Jon Salz74ad3262012-03-16 14:40:55 +0800386 error_msg='', shutdown_count=0)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800387 # Save pending test list in the state server
388 self.state_instance.set_shared_data(
Jon Salz74ad3262012-03-16 14:40:55 +0800389 'tests_after_shutdown',
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800390 [t.path for t in self.tests_to_run])
Jon Salz74ad3262012-03-16 14:40:55 +0800391
Jon Salz73e0fd02012-04-04 11:46:38 +0800392 with self.env.lock:
393 shutdown_result = self.env.shutdown(test.operation)
394 if shutdown_result:
395 # That's all, folks!
396 self.run_queue.put(None)
397 return
398 else:
399 # Just pass (e.g., in the chroot).
400 test.update_state(status=TestState.PASSED)
401 self.state_instance.set_shared_data(
402 'tests_after_shutdown', None)
403 continue
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800404
405 invoc = TestInvocation(self, test, on_completion=self.run_next_test)
406 self.invocations[test] = invoc
407 if self.visible_test is None and test.has_ui:
408 self.set_visible_test(test)
409 invoc.start()
410
Jon Salz0405ab52012-03-16 15:26:52 +0800411 def run_tests(self, subtrees, untested_only=False):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800412 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800413 Runs tests under subtree.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800414
415 The tests are run in order unless one fails (then stops).
416 Backgroundable tests are run simultaneously; when a foreground test is
417 encountered, we wait for all active tests to finish before continuing.
Jon Salz0405ab52012-03-16 15:26:52 +0800418
419 @param subtrees: Node or nodes containing tests to run (may either be
420 a single test or a list). Duplicates will be ignored.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800421 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800422 if type(subtrees) != list:
423 subtrees = [subtrees]
424
425 # Nodes we've seen so far, to avoid duplicates.
426 seen = set()
427
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800428 self.tests_to_run = deque()
Jon Salz0405ab52012-03-16 15:26:52 +0800429 for subtree in subtrees:
430 for test in subtree.walk():
431 if test in seen:
432 continue
433 seen.add(test)
434
435 if not test.is_leaf():
436 continue
437 if (untested_only and
438 test.get_state().status != TestState.UNTESTED):
439 continue
440 self.tests_to_run.append(test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800441 self.run_next_test()
442
443 def reap_completed_tests(self):
444 '''
445 Removes completed tests from the set of active tests.
446
447 Also updates the visible test if it was reaped.
448 '''
449 for t, v in dict(self.invocations).iteritems():
450 if v.is_completed():
451 del self.invocations[t]
452
453 if (self.visible_test is None or
454 self.visible_test not in self.invocations):
455 self.set_visible_test(None)
456 # Make the first running test, if any, the visible test
457 for t in self.test_list.walk():
458 if t in self.invocations:
459 self.set_visible_test(t)
460 break
461
Hung-Te Lin96632362012-03-20 21:14:18 +0800462 def kill_active_tests(self, abort):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800463 '''
464 Kills and waits for all active tests.
Hung-Te Lin96632362012-03-20 21:14:18 +0800465
466 @param abort: True to change state of killed tests to FAILED, False for
467 UNTESTED.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800468 '''
469 self.reap_completed_tests()
470 for test, invoc in self.invocations.items():
471 factory.console.info('Killing active test %s...' % test.path)
472 invoc.abort_and_join()
473 factory.console.info('Killed %s' % test.path)
474 del self.invocations[test]
Hung-Te Lin96632362012-03-20 21:14:18 +0800475 if not abort:
476 test.update_state(status=TestState.UNTESTED)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800477 self.reap_completed_tests()
478
Hung-Te Lin96632362012-03-20 21:14:18 +0800479 def abort_active_tests(self):
480 self.kill_active_tests(True)
481
Jon Salz73e0fd02012-04-04 11:46:38 +0800482 def main(self):
Jon Salzeb8d25f2012-05-22 15:17:32 +0800483 try:
484 self.init()
485 self.event_log.AppendEvent('init',
486 success=True)
487 except:
488 if self.event_log:
489 try:
490 self.event_log.AppendEvent('init',
491 success=False,
492 trace=traceback.format_exc())
493 except:
494 pass
495 raise
496
Jon Salz73e0fd02012-04-04 11:46:38 +0800497 self.run()
498
Jon Salz5f2a0672012-05-22 17:14:06 +0800499 def update_system_info(self):
500 '''Updates system info.'''
501 system_info = test_environment.SystemInfo(self.env, self.state_instance)
502 self.state_instance.set_shared_data('system_info', system_info.__dict__)
503 self.event_client.post_event(Event(Event.Type.SYSTEM_INFO,
504 system_info=system_info.__dict__))
505 logging.info('System info: %r', system_info.__dict__)
506
Jon Salz73e0fd02012-04-04 11:46:38 +0800507 def init(self, args=None, env=None):
508 '''Initializes Goofy.
Jon Salz74ad3262012-03-16 14:40:55 +0800509
510 Args:
Jon Salz73e0fd02012-04-04 11:46:38 +0800511 args: A list of command-line arguments. Uses sys.argv if
512 args is None.
513 env: An Environment instance to use (or None to choose
Jon Salz258a40c2012-04-19 12:34:01 +0800514 FakeChrootEnvironment or DUTEnvironment as appropriate).
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800515 '''
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800516 parser = OptionParser()
517 parser.add_option('-v', '--verbose', dest='verbose',
518 action='store_true',
519 help='Enable debug logging')
520 parser.add_option('--print_test_list', dest='print_test_list',
521 metavar='FILE',
522 help='Read and print test list FILE, and exit')
Jon Salz758e6cc2012-04-03 15:47:07 +0800523 parser.add_option('--restart', dest='restart',
524 action='store_true',
525 help='Clear all test state')
Jon Salz258a40c2012-04-19 12:34:01 +0800526 parser.add_option('--ui', dest='ui', type='choice',
527 choices=['none', 'gtk', 'chrome'],
528 default='gtk',
529 help='UI to use')
Jon Salz63585ea2012-05-21 15:03:32 +0800530 parser.add_option('--ui_scale_factor', dest='ui_scale_factor',
531 type='int', default=1,
532 help=('Factor by which to scale UI '
533 '(Chrome UI only)'))
Jon Salz73e0fd02012-04-04 11:46:38 +0800534 parser.add_option('--test_list', dest='test_list',
535 metavar='FILE',
536 help='Use FILE as test list')
537 (self.options, self.args) = parser.parse_args(args)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800538
Jon Salz73e0fd02012-04-04 11:46:38 +0800539 global _inited_logging
540 if not _inited_logging:
541 factory.init_logging('goofy', verbose=self.options.verbose)
542 _inited_logging = True
Jon Salzeb8d25f2012-05-22 15:17:32 +0800543 self.event_log = EventLog('goofy')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800544
Jon Salz73e0fd02012-04-04 11:46:38 +0800545 if (not suppress_chroot_warning and
546 factory.in_chroot() and
Jon Salz258a40c2012-04-19 12:34:01 +0800547 self.options.ui == 'gtk' and
Jon Salz758e6cc2012-04-03 15:47:07 +0800548 os.environ.get('DISPLAY') in [None, '', ':0', ':0.0']):
549 # That's not going to work! Tell the user how to run
550 # this way.
551 logging.warn(GOOFY_IN_CHROOT_WARNING)
552 time.sleep(1)
553
Jon Salz73e0fd02012-04-04 11:46:38 +0800554 if env:
555 self.env = env
556 elif factory.in_chroot():
Jon Salz5f2a0672012-05-22 17:14:06 +0800557 self.env = test_environment.FakeChrootEnvironment()
Jon Salz73e0fd02012-04-04 11:46:38 +0800558 logging.warn(
559 'Using chroot environment: will not actually run autotests')
560 else:
Jon Salz5f2a0672012-05-22 17:14:06 +0800561 self.env = test_environment.DUTEnvironment()
Jon Salz323dd3d2012-04-09 18:40:43 +0800562 self.env.goofy = self
Jon Salz73e0fd02012-04-04 11:46:38 +0800563
Jon Salz758e6cc2012-04-03 15:47:07 +0800564 if self.options.restart:
565 state.clear_state()
566
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800567 if self.options.print_test_list:
568 print (factory.read_test_list(self.options.print_test_list).
569 __repr__(recursive=True))
570 return
571
572 logging.info('Started')
573
574 self.start_state_server()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800575 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
Jon Salz63585ea2012-05-21 15:03:32 +0800576 self.state_instance.set_shared_data('ui_scale_factor',
577 self.options.ui_scale_factor)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800578
Jon Salz73e0fd02012-04-04 11:46:38 +0800579 self.options.test_list = (self.options.test_list or find_test_list())
580 self.test_list = factory.read_test_list(self.options.test_list,
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800581 self.state_instance)
Jon Salz06fbeff2012-05-21 17:06:05 +0800582 if not self.state_instance.has_shared_data('ui_lang'):
583 self.state_instance.set_shared_data('ui_lang',
584 self.test_list.options.ui_lang)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800585 logging.info('TEST_LIST:\n%s', self.test_list.__repr__(recursive=True))
Jon Salz258a40c2012-04-19 12:34:01 +0800586 self.state_instance.test_list = self.test_list
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800587
588 self.init_states()
589 self.start_event_server()
Jon Salz258a40c2012-04-19 12:34:01 +0800590
Jon Salz5f2a0672012-05-22 17:14:06 +0800591 self.update_system_info()
592
Jon Salzb1b39092012-05-03 02:05:09 +0800593 # Set CROS_UI since some behaviors in ui.py depend on the
594 # particular UI in use. TODO(jsalz): Remove this (and all
595 # places it is used) when the GTK UI is removed.
596 os.environ['CROS_UI'] = self.options.ui
Jon Salz258a40c2012-04-19 12:34:01 +0800597
Jon Salzb1b39092012-05-03 02:05:09 +0800598 if self.options.ui == 'chrome':
Jon Salz258a40c2012-04-19 12:34:01 +0800599 self.env.launch_chrome()
600 logging.info('Waiting for a web socket connection')
601 self.web_socket_manager.wait()
Jon Salzb1b39092012-05-03 02:05:09 +0800602
603 # Wait for the test widget size to be set; this is done in
604 # an asynchronous RPC so there is a small chance that the
605 # web socket might be opened first.
606 for i in range(100): # 10 s
Jon Salz63585ea2012-05-21 15:03:32 +0800607 try:
608 if self.state_instance.get_shared_data('test_widget_size'):
609 break
610 except KeyError:
611 pass # Retry
Jon Salzb1b39092012-05-03 02:05:09 +0800612 time.sleep(0.1) # 100 ms
613 else:
614 logging.warn('Never received test_widget_size from UI')
Jon Salz258a40c2012-04-19 12:34:01 +0800615 elif self.options.ui == 'gtk':
Jon Salz73e0fd02012-04-04 11:46:38 +0800616 self.start_ui()
Jon Salz258a40c2012-04-19 12:34:01 +0800617
Jon Salz8375c2e2012-04-04 15:22:24 +0800618 self.prespawner = Prespawner()
Jon Salz323dd3d2012-04-09 18:40:43 +0800619 self.prespawner.start()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800620
621 def state_change_callback(test, state):
622 self.event_client.post_event(
623 Event(Event.Type.STATE_CHANGE,
624 path=test.path, state=state))
625 self.test_list.state_change_callback = state_change_callback
626
627 try:
Jon Salz758e6cc2012-04-03 15:47:07 +0800628 tests_after_shutdown = self.state_instance.get_shared_data(
629 'tests_after_shutdown')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800630 except KeyError:
Jon Salz758e6cc2012-04-03 15:47:07 +0800631 tests_after_shutdown = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800632
Jon Salz758e6cc2012-04-03 15:47:07 +0800633 if tests_after_shutdown is not None:
634 logging.info('Resuming tests after shutdown: %s',
635 tests_after_shutdown)
636 self.state_instance.set_shared_data('tests_after_shutdown', None)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800637 self.tests_to_run.extend(
Jon Salz758e6cc2012-04-03 15:47:07 +0800638 self.test_list.lookup_path(t) for t in tests_after_shutdown)
Jon Salz73e0fd02012-04-04 11:46:38 +0800639 self.run_queue.put(self.run_next_test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800640 else:
Jon Salz57717ca2012-04-04 16:47:25 +0800641 if self.test_list.options.auto_run_on_start:
642 self.run_queue.put(
643 lambda: self.run_tests(self.test_list, untested_only=True))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800644
Jon Salz73e0fd02012-04-04 11:46:38 +0800645 def run(self):
646 '''Runs Goofy.'''
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800647 # Process events forever.
Jon Salz57717ca2012-04-04 16:47:25 +0800648 while self.run_once(True):
Jon Salz73e0fd02012-04-04 11:46:38 +0800649 pass
650
Jon Salz57717ca2012-04-04 16:47:25 +0800651 def run_once(self, block=False):
Jon Salz73e0fd02012-04-04 11:46:38 +0800652 '''Runs all items pending in the event loop.
653
Jon Salz57717ca2012-04-04 16:47:25 +0800654 Args:
655 block: If true, block until at least one event is processed.
656
Jon Salz73e0fd02012-04-04 11:46:38 +0800657 Returns:
658 True to keep going or False to shut down.
659 '''
Jon Salz57717ca2012-04-04 16:47:25 +0800660 events = []
661 if block:
662 # Get at least one event
663 events.append(self.run_queue.get())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800664 while True:
Jon Salz73e0fd02012-04-04 11:46:38 +0800665 try:
666 events.append(self.run_queue.get_nowait())
667 except Queue.Empty:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800668 break
669
Jon Salz73e0fd02012-04-04 11:46:38 +0800670 for event in events:
671 if not event:
672 # Shutdown request.
673 self.run_queue.task_done()
674 return False
675
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800676 try:
677 event()
678 except Exception as e: # pylint: disable=W0703
679 logging.error('Error in event loop: %s', e)
680 traceback.print_exc(sys.stderr)
Jon Salz8375c2e2012-04-04 15:22:24 +0800681 self.record_exception(traceback.format_exception_only(
682 *sys.exc_info()[:2]))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800683 # But keep going
684 finally:
685 self.run_queue.task_done()
Jon Salz73e0fd02012-04-04 11:46:38 +0800686 return True
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800687
Jon Salz258a40c2012-04-19 12:34:01 +0800688 def run_tests_with_status(self, statuses_to_run, starting_at=None,
689 root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800690 '''Runs all top-level tests with a particular status.
691
692 All active tests, plus any tests to re-run, are reset.
Jon Salz57717ca2012-04-04 16:47:25 +0800693
694 Args:
695 starting_at: If provided, only auto-runs tests beginning with
696 this test.
Jon Salz0405ab52012-03-16 15:26:52 +0800697 '''
Jon Salz258a40c2012-04-19 12:34:01 +0800698 root = root or self.test_list
699
Jon Salz57717ca2012-04-04 16:47:25 +0800700 if starting_at:
701 # Make sure they passed a test, not a string.
702 assert isinstance(starting_at, factory.FactoryTest)
703
Jon Salz0405ab52012-03-16 15:26:52 +0800704 tests_to_reset = []
705 tests_to_run = []
706
Jon Salz57717ca2012-04-04 16:47:25 +0800707 found_starting_at = False
708
Jon Salz258a40c2012-04-19 12:34:01 +0800709 for test in root.get_top_level_tests():
Jon Salz57717ca2012-04-04 16:47:25 +0800710 if starting_at:
711 if test == starting_at:
712 # We've found starting_at; do auto-run on all
713 # subsequent tests.
714 found_starting_at = True
715 if not found_starting_at:
716 # Don't start this guy yet
717 continue
718
Jon Salz0405ab52012-03-16 15:26:52 +0800719 status = test.get_state().status
720 if status == TestState.ACTIVE or status in statuses_to_run:
721 # Reset the test (later; we will need to abort
722 # all active tests first).
723 tests_to_reset.append(test)
724 if status in statuses_to_run:
725 tests_to_run.append(test)
726
727 self.abort_active_tests()
728
729 # Reset all statuses of the tests to run (in case any tests were active;
730 # we want them to be run again).
731 for test_to_reset in tests_to_reset:
732 for test in test_to_reset.walk():
733 test.update_state(status=TestState.UNTESTED)
734
735 self.run_tests(tests_to_run, untested_only=True)
736
Jon Salz258a40c2012-04-19 12:34:01 +0800737 def restart_tests(self, root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800738 '''Restarts all tests.'''
Jon Salz258a40c2012-04-19 12:34:01 +0800739 root = root or self.test_list
Jon Salz0405ab52012-03-16 15:26:52 +0800740
Jon Salz258a40c2012-04-19 12:34:01 +0800741 self.abort_active_tests()
742 for test in root.walk():
743 test.update_state(status=TestState.UNTESTED)
744 self.run_tests(root)
745
746 def auto_run(self, starting_at=None, root=None):
Jon Salz57717ca2012-04-04 16:47:25 +0800747 '''"Auto-runs" tests that have not been run yet.
748
749 Args:
750 starting_at: If provide, only auto-runs tests beginning with
751 this test.
752 '''
Jon Salz258a40c2012-04-19 12:34:01 +0800753 root = root or self.test_list
Jon Salz57717ca2012-04-04 16:47:25 +0800754 self.run_tests_with_status([TestState.UNTESTED, TestState.ACTIVE],
Jon Salz258a40c2012-04-19 12:34:01 +0800755 starting_at=starting_at,
756 root=root)
Jon Salz0405ab52012-03-16 15:26:52 +0800757
Jon Salz258a40c2012-04-19 12:34:01 +0800758 def re_run_failed(self, root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800759 '''Re-runs failed tests.'''
Jon Salz258a40c2012-04-19 12:34:01 +0800760 root = root or self.test_list
761 self.run_tests_with_status([TestState.FAILED], root=root)
Jon Salz0405ab52012-03-16 15:26:52 +0800762
Jon Salz968e90b2012-03-18 16:12:43 +0800763 def show_review_information(self):
Hung-Te Lin96632362012-03-20 21:14:18 +0800764 '''Event handler for showing review information screen.
765
766 The information screene is rendered by main UI program (ui.py), so in
767 goofy we only need to kill all active tests, set them as untested, and
768 clear remaining tests.
769 '''
770 self.kill_active_tests(False)
771 self.run_tests([])
772
Jon Salz0405ab52012-03-16 15:26:52 +0800773 def handle_switch_test(self, event):
Jon Salz968e90b2012-03-18 16:12:43 +0800774 '''Switches to a particular test.
775
776 @param event: The SWITCH_TEST event.
777 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800778 test = self.test_list.lookup_path(event.path)
Jon Salz57717ca2012-04-04 16:47:25 +0800779 if not test:
Jon Salz968e90b2012-03-18 16:12:43 +0800780 logging.error('Unknown test %r', event.key)
Jon Salz57717ca2012-04-04 16:47:25 +0800781 return
782
783 invoc = self.invocations.get(test)
784 if invoc and test.backgroundable:
785 # Already running: just bring to the front if it
786 # has a UI.
787 logging.info('Setting visible test to %s', test.path)
788 self.event_client.post_event(
789 Event(Event.Type.SET_VISIBLE_TEST, path=test.path))
790 return
791
792 self.abort_active_tests()
793 for t in test.walk():
794 t.update_state(status=TestState.UNTESTED)
795
796 if self.test_list.options.auto_run_on_keypress:
797 self.auto_run(starting_at=test)
798 else:
799 self.run_tests(test)
Jon Salz0405ab52012-03-16 15:26:52 +0800800
Jon Salz73e0fd02012-04-04 11:46:38 +0800801 def wait(self):
802 '''Waits for all pending invocations.
803
804 Useful for testing.
805 '''
806 for k, v in self.invocations.iteritems():
807 logging.info('Waiting for %s to complete...', k)
808 v.thread.join()
809
810 def check_exceptions(self):
811 '''Raises an error if any exceptions have occurred in
812 invocation threads.'''
813 if self.exceptions:
814 raise RuntimeError('Exception in invocation thread: %r' %
815 self.exceptions)
816
817 def record_exception(self, msg):
818 '''Records an exception in an invocation thread.
819
820 An exception with the given message will be rethrown when
821 Goofy is destroyed.'''
822 self.exceptions.append(msg)
823
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800824
825if __name__ == '__main__':
826 Goofy().main()