blob: cef40496f7cc93ee14b40ed2600792abfef0dc0d [file] [log] [blame]
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001#!/usr/bin/python -u
2#
3# -*- coding: utf-8 -*-
4#
5# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
6# Use of this source code is governed by a BSD-style license that can be
7# found in the LICENSE file.
8
9'''
10The main factory flow that runs the factory test and finalizes a device.
11'''
12
Jon Salz73e0fd02012-04-04 11:46:38 +080013import array
14import fcntl
15import glob
Jon Salz258a40c2012-04-19 12:34:01 +080016import hashlib
Jon Salz0405ab52012-03-16 15:26:52 +080017import logging
18import os
Jon Salz258a40c2012-04-19 12:34:01 +080019import cPickle as pickle
Jon Salz0405ab52012-03-16 15:26:52 +080020import pipes
Jon Salz73e0fd02012-04-04 11:46:38 +080021import Queue
Jon Salz0405ab52012-03-16 15:26:52 +080022import re
23import signal
24import subprocess
25import sys
26import tempfile
27import threading
28import time
29import traceback
Jon Salz258a40c2012-04-19 12:34:01 +080030import unittest
31import uuid
Hung-Te Linf2f78f72012-02-08 19:27:11 +080032from collections import deque
33from optparse import OptionParser
Jon Salz258a40c2012-04-19 12:34:01 +080034from StringIO import StringIO
Hung-Te Linf2f78f72012-02-08 19:27:11 +080035
36import factory_common
Jon Salz8375c2e2012-04-04 15:22:24 +080037from autotest_lib.client.bin.prespawner import Prespawner
Hung-Te Linf2f78f72012-02-08 19:27:11 +080038from autotest_lib.client.cros import factory
39from autotest_lib.client.cros.factory import state
40from autotest_lib.client.cros.factory import TestState
Jon Salz258a40c2012-04-19 12:34:01 +080041from autotest_lib.client.cros.factory import utils
Hung-Te Linf2f78f72012-02-08 19:27:11 +080042from autotest_lib.client.cros.factory.event import Event
43from autotest_lib.client.cros.factory.event import EventClient
44from autotest_lib.client.cros.factory.event import EventServer
Jon 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
47from 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
109class Environment(object):
110 '''
111 Abstract base class for external test operations, e.g., run an autotest,
112 shutdown, or reboot.
113
114 The Environment is assumed not to be thread-safe: callers must grab the lock
115 before calling any methods. This is primarily necessary because we mock out
116 this Environment with mox, and unfortunately mox is not thread-safe.
117 TODO(jsalz): Try to write a thread-safe wrapper for mox.
118 '''
119 lock = threading.Lock()
120
121 def shutdown(self, operation):
122 '''
123 Shuts the machine down (from a ShutdownStep).
124
125 Args:
126 operation: 'reboot' or 'halt'.
127
128 Returns:
129 True if Goofy should gracefully exit, or False if Goofy
130 should just consider the shutdown to have suceeded (e.g.,
131 in the chroot).
132 '''
133 raise NotImplementedError()
134
Jon Salz258a40c2012-04-19 12:34:01 +0800135 def launch_chrome(self):
136 '''
137 Launches Chrome.
138
139 Returns:
140 The Chrome subprocess (or None if none).
141 '''
142 raise NotImplementedError()
Jon Salz73e0fd02012-04-04 11:46:38 +0800143
144 def spawn_autotest(self, name, args, env_additions, result_file):
145 '''
146 Spawns a process to run an autotest.
147
148 Args:
149 name: Name of the autotest to spawn.
150 args: Command-line arguments.
151 env_additions: Additions to the environment.
152 result_file: Expected location of the result file.
153 '''
154 raise NotImplementedError()
155
156
157class DUTEnvironment(Environment):
158 '''
159 A real environment on a device under test.
160 '''
161 def shutdown(self, operation):
162 assert operation in ['reboot', 'halt']
163 logging.info('Shutting down: %s', operation)
164 subprocess.check_call('sync')
165 subprocess.check_call(operation)
166 time.sleep(30)
167 assert False, 'Never reached (should %s)' % operation
168
169 def spawn_autotest(self, name, args, env_additions, result_file):
Jon Salz8375c2e2012-04-04 15:22:24 +0800170 return self.goofy.prespawner.spawn(args, env_additions)
Jon Salz73e0fd02012-04-04 11:46:38 +0800171
Jon Salz258a40c2012-04-19 12:34:01 +0800172 def launch_chrome(self):
173 chrome_command = [
174 '/opt/google/chrome/chrome',
175 '--user-data-dir=%s/factory-chrome-datadir' %
176 factory.get_log_root(),
177 '--aura-host-window-use-fullscreen',
178 '--kiosk',
Jon Salz63585ea2012-05-21 15:03:32 +0800179 ('--default-device-scale-factor=%d' %
180 self.goofy.options.ui_scale_factor),
Jon Salz258a40c2012-04-19 12:34:01 +0800181 'http://localhost:%d/' % state.DEFAULT_FACTORY_STATE_PORT,
182 ]
Jon Salz73e0fd02012-04-04 11:46:38 +0800183
Jon Salz258a40c2012-04-19 12:34:01 +0800184 chrome_log = os.path.join(factory.get_log_root(), 'factory.chrome.log')
185 chrome_log_file = open(chrome_log, "a")
186 logging.info('Launching Chrome; logs in %s' % chrome_log)
187 return subprocess.Popen(chrome_command,
188 stdout=chrome_log_file,
189 stderr=subprocess.STDOUT)
190
191class FakeChrootEnvironment(Environment):
Jon Salz73e0fd02012-04-04 11:46:38 +0800192 '''
193 A chroot environment that doesn't actually shutdown or run autotests.
194 '''
195 def shutdown(self, operation):
196 assert operation in ['reboot', 'halt']
197 logging.warn('In chroot: skipping %s', operation)
198 return False
199
200 def spawn_autotest(self, name, args, env_additions, result_file):
201 logging.warn('In chroot: skipping autotest %s', name)
Jon Salz258a40c2012-04-19 12:34:01 +0800202 # Mark it as passed with 75% probability, or failed with 25%
203 # probability (depending on a hash of the autotest name).
204 pseudo_random = ord(hashlib.sha1(name).digest()[0]) / 256.0
205 passed = pseudo_random > .25
206
Jon Salz73e0fd02012-04-04 11:46:38 +0800207 with open(result_file, 'w') as out:
Jon Salz258a40c2012-04-19 12:34:01 +0800208 pickle.dump((passed, '' if passed else 'Simulated failure'), out)
Jon Salz73e0fd02012-04-04 11:46:38 +0800209 # Start a process that will return with a true exit status in
210 # 2 seconds (just like a happy autotest).
211 return subprocess.Popen(['sleep', '2'])
212
Jon Salz258a40c2012-04-19 12:34:01 +0800213 def launch_chrome(self):
214 logging.warn('In chroot; not launching Chrome. '
215 'Please open http://localhost:%d/ in Chrome.',
216 state.DEFAULT_FACTORY_STATE_PORT)
Jon Salz73e0fd02012-04-04 11:46:38 +0800217
218
219_inited_logging = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800220
221class Goofy(object):
222 '''
223 The main factory flow.
224
225 Note that all methods in this class must be invoked from the main
226 (event) thread. Other threads, such as callbacks and TestInvocation
227 methods, should instead post events on the run queue.
228
229 TODO: Unit tests. (chrome-os-partner:7409)
230
231 Properties:
Jon Salz258a40c2012-04-19 12:34:01 +0800232 uuid: A unique UUID for this invocation of Goofy.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800233 state_instance: An instance of FactoryState.
234 state_server: The FactoryState XML/RPC server.
235 state_server_thread: A thread running state_server.
236 event_server: The EventServer socket server.
237 event_server_thread: A thread running event_server.
238 event_client: A client to the event server.
Hung-Te Lin6bb48552012-02-09 14:37:43 +0800239 ui_process: The factory ui process object.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800240 run_queue: A queue of callbacks to invoke from the main thread.
241 invocations: A map from FactoryTest objects to the corresponding
242 TestInvocations objects representing active tests.
243 tests_to_run: A deque of tests that should be run when the current
244 test(s) complete.
245 options: Command-line options.
246 args: Command-line args.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800247 test_list: The test list.
Jon Salz0405ab52012-03-16 15:26:52 +0800248 event_handlers: Map of Event.Type to the method used to handle that
249 event. If the method has an 'event' argument, the event is passed
250 to the handler.
Jon Salz73e0fd02012-04-04 11:46:38 +0800251 exceptions: Exceptions encountered in invocation threads.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800252 '''
253 def __init__(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800254 self.uuid = str(uuid.uuid4())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800255 self.state_instance = None
256 self.state_server = None
257 self.state_server_thread = None
258 self.event_server = None
259 self.event_server_thread = None
260 self.event_client = None
Jon Salzeb8d25f2012-05-22 15:17:32 +0800261 self.event_log = None
Jon Salz8375c2e2012-04-04 15:22:24 +0800262 self.prespawner = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800263 self.ui_process = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800264 self.run_queue = Queue.Queue()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800265 self.invocations = {}
266 self.tests_to_run = deque()
267 self.visible_test = None
Jon Salz258a40c2012-04-19 12:34:01 +0800268 self.chrome = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800269
270 self.options = None
271 self.args = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800272 self.test_list = None
273
Jon Salz258a40c2012-04-19 12:34:01 +0800274 def test_or_root(event):
275 '''Returns the top-level parent for a test (the root node of the
276 tests that need to be run together if the given test path is to
277 be run).'''
278 try:
279 path = event.path
Jon Salzd2ed6cb2012-05-02 09:35:14 +0800280 except AttributeError:
Jon Salz258a40c2012-04-19 12:34:01 +0800281 path = None
282
283 if path:
Jon Salzf617b282012-05-24 14:14:04 +0800284 return (self.test_list.lookup_path(path).
285 get_top_level_parent_or_group())
Jon Salz258a40c2012-04-19 12:34:01 +0800286 else:
287 return self.test_list
288
Jon Salz0405ab52012-03-16 15:26:52 +0800289 self.event_handlers = {
290 Event.Type.SWITCH_TEST: self.handle_switch_test,
Jon Salz968e90b2012-03-18 16:12:43 +0800291 Event.Type.SHOW_NEXT_ACTIVE_TEST:
292 lambda event: self.show_next_active_test(),
293 Event.Type.RESTART_TESTS:
Jon Salz258a40c2012-04-19 12:34:01 +0800294 lambda event: self.restart_tests(root=test_or_root(event)),
Jon Salz968e90b2012-03-18 16:12:43 +0800295 Event.Type.AUTO_RUN:
Jon Salz258a40c2012-04-19 12:34:01 +0800296 lambda event: self.auto_run(root=test_or_root(event)),
Jon Salz968e90b2012-03-18 16:12:43 +0800297 Event.Type.RE_RUN_FAILED:
Jon Salz258a40c2012-04-19 12:34:01 +0800298 lambda event: self.re_run_failed(root=test_or_root(event)),
Jon Salz968e90b2012-03-18 16:12:43 +0800299 Event.Type.REVIEW:
300 lambda event: self.show_review_information(),
Jon Salz0405ab52012-03-16 15:26:52 +0800301 }
302
Jon Salz73e0fd02012-04-04 11:46:38 +0800303 self.exceptions = []
Jon Salz258a40c2012-04-19 12:34:01 +0800304 self.web_socket_manager = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800305
306 def destroy(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800307 if self.chrome:
308 self.chrome.kill()
309 self.chrome = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800310 if self.ui_process:
Jon Salz258a40c2012-04-19 12:34:01 +0800311 utils.kill_process_tree(self.ui_process, 'ui')
Jon Salz73e0fd02012-04-04 11:46:38 +0800312 self.ui_process = None
Jon Salz258a40c2012-04-19 12:34:01 +0800313 if self.web_socket_manager:
314 logging.info('Stopping web sockets')
315 self.web_socket_manager.close()
316 self.web_socket_manager = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800317 if self.state_server_thread:
318 logging.info('Stopping state server')
319 self.state_server.shutdown()
320 self.state_server_thread.join()
Jon Salz73e0fd02012-04-04 11:46:38 +0800321 self.state_server.server_close()
322 self.state_server_thread = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800323 if self.event_server_thread:
324 logging.info('Stopping event server')
325 self.event_server.shutdown() # pylint: disable=E1101
326 self.event_server_thread.join()
Jon Salz73e0fd02012-04-04 11:46:38 +0800327 self.event_server.server_close()
328 self.event_server_thread = None
Jon Salz8375c2e2012-04-04 15:22:24 +0800329 if self.prespawner:
330 logging.info('Stopping prespawner')
331 self.prespawner.stop()
332 self.prespawner = None
333 if self.event_client:
Jon Salz258a40c2012-04-19 12:34:01 +0800334 logging.info('Closing event client')
Jon Salz8375c2e2012-04-04 15:22:24 +0800335 self.event_client.close()
Jon Salz258a40c2012-04-19 12:34:01 +0800336 self.event_client = None
Jon Salzeb8d25f2012-05-22 15:17:32 +0800337 if self.event_log:
338 self.event_log.Close()
339 self.event_log = None
Jon Salz73e0fd02012-04-04 11:46:38 +0800340 self.check_exceptions()
Jon Salz258a40c2012-04-19 12:34:01 +0800341 logging.info('Done destroying Goofy')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800342
343 def start_state_server(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800344 self.state_instance, self.state_server = (
345 state.create_server(bind_address='0.0.0.0'))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800346 logging.info('Starting state server')
347 self.state_server_thread = threading.Thread(
Jon Salz8375c2e2012-04-04 15:22:24 +0800348 target=self.state_server.serve_forever,
349 name='StateServer')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800350 self.state_server_thread.start()
351
352 def start_event_server(self):
353 self.event_server = EventServer()
354 logging.info('Starting factory event server')
355 self.event_server_thread = threading.Thread(
Jon Salz8375c2e2012-04-04 15:22:24 +0800356 target=self.event_server.serve_forever,
357 name='EventServer') # pylint: disable=E1101
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800358 self.event_server_thread.start()
359
360 self.event_client = EventClient(
361 callback=self.handle_event, event_loop=self.run_queue)
362
Jon Salz258a40c2012-04-19 12:34:01 +0800363 self.web_socket_manager = WebSocketManager(self.uuid)
364 self.state_server.add_handler("/event",
365 self.web_socket_manager.handle_web_socket)
366
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800367 def start_ui(self):
Jon Salz258a40c2012-04-19 12:34:01 +0800368 ui_proc_args = [os.path.join(factory.CROS_FACTORY_LIB_PATH, 'ui'),
369 self.options.test_list]
Jon Salz14bcbb02012-03-17 15:11:50 +0800370 if self.options.verbose:
371 ui_proc_args.append('-v')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800372 logging.info('Starting ui %s', ui_proc_args)
373 self.ui_process = subprocess.Popen(ui_proc_args)
374 logging.info('Waiting for UI to come up...')
375 self.event_client.wait(
376 lambda event: event.type == Event.Type.UI_READY)
377 logging.info('UI has started')
378
379 def set_visible_test(self, test):
380 if self.visible_test == test:
381 return
382
383 if test:
384 test.update_state(visible=True)
385 if self.visible_test:
386 self.visible_test.update_state(visible=False)
387 self.visible_test = test
388
Jon Salz74ad3262012-03-16 14:40:55 +0800389 def handle_shutdown_complete(self, test, state):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800390 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800391 Handles the case where a shutdown was detected during a shutdown step.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800392
Jon Salz74ad3262012-03-16 14:40:55 +0800393 @param test: The ShutdownStep.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800394 @param state: The test state.
395 '''
Jon Salz74ad3262012-03-16 14:40:55 +0800396 state = test.update_state(increment_shutdown_count=1)
397 logging.info('Detected shutdown (%d of %d)',
398 state.shutdown_count, test.iterations)
399 if state.shutdown_count == test.iterations:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800400 # Good!
401 test.update_state(status=TestState.PASSED, error_msg='')
Jon Salz74ad3262012-03-16 14:40:55 +0800402 elif state.shutdown_count > test.iterations:
Jon Salz73e0fd02012-04-04 11:46:38 +0800403 # Shut down too many times
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800404 test.update_state(status=TestState.FAILED,
Jon Salz74ad3262012-03-16 14:40:55 +0800405 error_msg='Too many shutdowns')
Jon Salz258a40c2012-04-19 12:34:01 +0800406 elif utils.are_shift_keys_depressed():
Jon Salz73e0fd02012-04-04 11:46:38 +0800407 logging.info('Shift keys are depressed; cancelling restarts')
408 # Abort shutdown
409 test.update_state(
410 status=TestState.FAILED,
411 error_msg='Shutdown aborted with double shift keys')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800412 else:
Jon Salz74ad3262012-03-16 14:40:55 +0800413 # Need to shutdown again
Jon Salz73e0fd02012-04-04 11:46:38 +0800414 self.env.shutdown('reboot')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800415
416 def init_states(self):
417 '''
418 Initializes all states on startup.
419 '''
420 for test in self.test_list.get_all_tests():
421 # Make sure the state server knows about all the tests,
422 # defaulting to an untested state.
423 test.update_state(update_parent=False, visible=False)
424
425 # Any 'active' tests should be marked as failed now.
426 for test in self.test_list.walk():
427 state = test.get_state()
Hung-Te Lin96632362012-03-20 21:14:18 +0800428 if state.status != TestState.ACTIVE:
429 continue
430 if isinstance(test, factory.ShutdownStep):
431 # Shutdown while the test was active - that's good.
432 self.handle_shutdown_complete(test, state)
433 else:
434 test.update_state(status=TestState.FAILED,
435 error_msg='Unknown (shutdown?)')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800436
437 def show_next_active_test(self):
438 '''
439 Rotates to the next visible active test.
440 '''
441 self.reap_completed_tests()
442 active_tests = [
443 t for t in self.test_list.walk()
444 if t.is_leaf() and t.get_state().status == TestState.ACTIVE]
445 if not active_tests:
446 return
447
448 try:
449 next_test = active_tests[
450 (active_tests.index(self.visible_test) + 1) % len(active_tests)]
451 except ValueError: # visible_test not present in active_tests
452 next_test = active_tests[0]
453
454 self.set_visible_test(next_test)
455
456 def handle_event(self, event):
457 '''
458 Handles an event from the event server.
459 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800460 handler = self.event_handlers.get(event.type)
461 if handler:
Jon Salz968e90b2012-03-18 16:12:43 +0800462 handler(event)
Jon Salz0405ab52012-03-16 15:26:52 +0800463 else:
Jon Salz968e90b2012-03-18 16:12:43 +0800464 # We don't register handlers for all event types - just ignore
465 # this event.
466 logging.debug('Unbound event type %s', event.type)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800467
468 def run_next_test(self):
469 '''
470 Runs the next eligible test (or tests) in self.tests_to_run.
471 '''
472 self.reap_completed_tests()
473 while self.tests_to_run:
474 logging.debug('Tests to run: %s',
475 [x.path for x in self.tests_to_run])
476
477 test = self.tests_to_run[0]
478
479 if test in self.invocations:
480 logging.info('Next test %s is already running', test.path)
481 self.tests_to_run.popleft()
482 return
483
484 if self.invocations and not (test.backgroundable and all(
485 [x.backgroundable for x in self.invocations])):
486 logging.debug('Waiting for non-backgroundable tests to '
487 'complete before running %s', test.path)
488 return
489
490 self.tests_to_run.popleft()
491
Jon Salz74ad3262012-03-16 14:40:55 +0800492 if isinstance(test, factory.ShutdownStep):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800493 test.update_state(status=TestState.ACTIVE, increment_count=1,
Jon Salz74ad3262012-03-16 14:40:55 +0800494 error_msg='', shutdown_count=0)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800495 # Save pending test list in the state server
496 self.state_instance.set_shared_data(
Jon Salz74ad3262012-03-16 14:40:55 +0800497 'tests_after_shutdown',
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800498 [t.path for t in self.tests_to_run])
Jon Salz74ad3262012-03-16 14:40:55 +0800499
Jon Salz73e0fd02012-04-04 11:46:38 +0800500 with self.env.lock:
501 shutdown_result = self.env.shutdown(test.operation)
502 if shutdown_result:
503 # That's all, folks!
504 self.run_queue.put(None)
505 return
506 else:
507 # Just pass (e.g., in the chroot).
508 test.update_state(status=TestState.PASSED)
509 self.state_instance.set_shared_data(
510 'tests_after_shutdown', None)
511 continue
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800512
513 invoc = TestInvocation(self, test, on_completion=self.run_next_test)
514 self.invocations[test] = invoc
515 if self.visible_test is None and test.has_ui:
516 self.set_visible_test(test)
517 invoc.start()
518
Jon Salz0405ab52012-03-16 15:26:52 +0800519 def run_tests(self, subtrees, untested_only=False):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800520 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800521 Runs tests under subtree.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800522
523 The tests are run in order unless one fails (then stops).
524 Backgroundable tests are run simultaneously; when a foreground test is
525 encountered, we wait for all active tests to finish before continuing.
Jon Salz0405ab52012-03-16 15:26:52 +0800526
527 @param subtrees: Node or nodes containing tests to run (may either be
528 a single test or a list). Duplicates will be ignored.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800529 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800530 if type(subtrees) != list:
531 subtrees = [subtrees]
532
533 # Nodes we've seen so far, to avoid duplicates.
534 seen = set()
535
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800536 self.tests_to_run = deque()
Jon Salz0405ab52012-03-16 15:26:52 +0800537 for subtree in subtrees:
538 for test in subtree.walk():
539 if test in seen:
540 continue
541 seen.add(test)
542
543 if not test.is_leaf():
544 continue
545 if (untested_only and
546 test.get_state().status != TestState.UNTESTED):
547 continue
548 self.tests_to_run.append(test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800549 self.run_next_test()
550
551 def reap_completed_tests(self):
552 '''
553 Removes completed tests from the set of active tests.
554
555 Also updates the visible test if it was reaped.
556 '''
557 for t, v in dict(self.invocations).iteritems():
558 if v.is_completed():
559 del self.invocations[t]
560
561 if (self.visible_test is None or
562 self.visible_test not in self.invocations):
563 self.set_visible_test(None)
564 # Make the first running test, if any, the visible test
565 for t in self.test_list.walk():
566 if t in self.invocations:
567 self.set_visible_test(t)
568 break
569
Hung-Te Lin96632362012-03-20 21:14:18 +0800570 def kill_active_tests(self, abort):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800571 '''
572 Kills and waits for all active tests.
Hung-Te Lin96632362012-03-20 21:14:18 +0800573
574 @param abort: True to change state of killed tests to FAILED, False for
575 UNTESTED.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800576 '''
577 self.reap_completed_tests()
578 for test, invoc in self.invocations.items():
579 factory.console.info('Killing active test %s...' % test.path)
580 invoc.abort_and_join()
581 factory.console.info('Killed %s' % test.path)
582 del self.invocations[test]
Hung-Te Lin96632362012-03-20 21:14:18 +0800583 if not abort:
584 test.update_state(status=TestState.UNTESTED)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800585 self.reap_completed_tests()
586
Hung-Te Lin96632362012-03-20 21:14:18 +0800587 def abort_active_tests(self):
588 self.kill_active_tests(True)
589
Jon Salz73e0fd02012-04-04 11:46:38 +0800590 def main(self):
Jon Salzeb8d25f2012-05-22 15:17:32 +0800591 try:
592 self.init()
593 self.event_log.AppendEvent('init',
594 success=True)
595 except:
596 if self.event_log:
597 try:
598 self.event_log.AppendEvent('init',
599 success=False,
600 trace=traceback.format_exc())
601 except:
602 pass
603 raise
604
Jon Salz73e0fd02012-04-04 11:46:38 +0800605 self.run()
606
607 def init(self, args=None, env=None):
608 '''Initializes Goofy.
Jon Salz74ad3262012-03-16 14:40:55 +0800609
610 Args:
Jon Salz73e0fd02012-04-04 11:46:38 +0800611 args: A list of command-line arguments. Uses sys.argv if
612 args is None.
613 env: An Environment instance to use (or None to choose
Jon Salz258a40c2012-04-19 12:34:01 +0800614 FakeChrootEnvironment or DUTEnvironment as appropriate).
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800615 '''
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800616 parser = OptionParser()
617 parser.add_option('-v', '--verbose', dest='verbose',
618 action='store_true',
619 help='Enable debug logging')
620 parser.add_option('--print_test_list', dest='print_test_list',
621 metavar='FILE',
622 help='Read and print test list FILE, and exit')
Jon Salz758e6cc2012-04-03 15:47:07 +0800623 parser.add_option('--restart', dest='restart',
624 action='store_true',
625 help='Clear all test state')
Jon Salz258a40c2012-04-19 12:34:01 +0800626 parser.add_option('--ui', dest='ui', type='choice',
627 choices=['none', 'gtk', 'chrome'],
628 default='gtk',
629 help='UI to use')
Jon Salz63585ea2012-05-21 15:03:32 +0800630 parser.add_option('--ui_scale_factor', dest='ui_scale_factor',
631 type='int', default=1,
632 help=('Factor by which to scale UI '
633 '(Chrome UI only)'))
Jon Salz73e0fd02012-04-04 11:46:38 +0800634 parser.add_option('--test_list', dest='test_list',
635 metavar='FILE',
636 help='Use FILE as test list')
637 (self.options, self.args) = parser.parse_args(args)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800638
Jon Salz73e0fd02012-04-04 11:46:38 +0800639 global _inited_logging
640 if not _inited_logging:
641 factory.init_logging('goofy', verbose=self.options.verbose)
642 _inited_logging = True
Jon Salzeb8d25f2012-05-22 15:17:32 +0800643 self.event_log = EventLog('goofy')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800644
Jon Salz73e0fd02012-04-04 11:46:38 +0800645 if (not suppress_chroot_warning and
646 factory.in_chroot() and
Jon Salz258a40c2012-04-19 12:34:01 +0800647 self.options.ui == 'gtk' and
Jon Salz758e6cc2012-04-03 15:47:07 +0800648 os.environ.get('DISPLAY') in [None, '', ':0', ':0.0']):
649 # That's not going to work! Tell the user how to run
650 # this way.
651 logging.warn(GOOFY_IN_CHROOT_WARNING)
652 time.sleep(1)
653
Jon Salz73e0fd02012-04-04 11:46:38 +0800654 if env:
655 self.env = env
656 elif factory.in_chroot():
Jon Salz258a40c2012-04-19 12:34:01 +0800657 self.env = FakeChrootEnvironment()
Jon Salz73e0fd02012-04-04 11:46:38 +0800658 logging.warn(
659 'Using chroot environment: will not actually run autotests')
660 else:
661 self.env = DUTEnvironment()
Jon Salz323dd3d2012-04-09 18:40:43 +0800662 self.env.goofy = self
Jon Salz73e0fd02012-04-04 11:46:38 +0800663
Jon Salz758e6cc2012-04-03 15:47:07 +0800664 if self.options.restart:
665 state.clear_state()
666
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800667 if self.options.print_test_list:
668 print (factory.read_test_list(self.options.print_test_list).
669 __repr__(recursive=True))
670 return
671
672 logging.info('Started')
673
674 self.start_state_server()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800675 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
Jon Salz63585ea2012-05-21 15:03:32 +0800676 self.state_instance.set_shared_data('ui_scale_factor',
677 self.options.ui_scale_factor)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800678
Jon Salz73e0fd02012-04-04 11:46:38 +0800679 self.options.test_list = (self.options.test_list or find_test_list())
680 self.test_list = factory.read_test_list(self.options.test_list,
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800681 self.state_instance)
Jon Salz06fbeff2012-05-21 17:06:05 +0800682 if not self.state_instance.has_shared_data('ui_lang'):
683 self.state_instance.set_shared_data('ui_lang',
684 self.test_list.options.ui_lang)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800685 logging.info('TEST_LIST:\n%s', self.test_list.__repr__(recursive=True))
Jon Salz258a40c2012-04-19 12:34:01 +0800686 self.state_instance.test_list = self.test_list
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800687
688 self.init_states()
689 self.start_event_server()
Jon Salz258a40c2012-04-19 12:34:01 +0800690
Jon Salzb1b39092012-05-03 02:05:09 +0800691 # Set CROS_UI since some behaviors in ui.py depend on the
692 # particular UI in use. TODO(jsalz): Remove this (and all
693 # places it is used) when the GTK UI is removed.
694 os.environ['CROS_UI'] = self.options.ui
Jon Salz258a40c2012-04-19 12:34:01 +0800695
Jon Salzb1b39092012-05-03 02:05:09 +0800696 if self.options.ui == 'chrome':
Jon Salz258a40c2012-04-19 12:34:01 +0800697 self.env.launch_chrome()
698 logging.info('Waiting for a web socket connection')
699 self.web_socket_manager.wait()
Jon Salzb1b39092012-05-03 02:05:09 +0800700
701 # Wait for the test widget size to be set; this is done in
702 # an asynchronous RPC so there is a small chance that the
703 # web socket might be opened first.
704 for i in range(100): # 10 s
Jon Salz63585ea2012-05-21 15:03:32 +0800705 try:
706 if self.state_instance.get_shared_data('test_widget_size'):
707 break
708 except KeyError:
709 pass # Retry
Jon Salzb1b39092012-05-03 02:05:09 +0800710 time.sleep(0.1) # 100 ms
711 else:
712 logging.warn('Never received test_widget_size from UI')
Jon Salz258a40c2012-04-19 12:34:01 +0800713 elif self.options.ui == 'gtk':
Jon Salz73e0fd02012-04-04 11:46:38 +0800714 self.start_ui()
Jon Salz258a40c2012-04-19 12:34:01 +0800715
Jon Salz8375c2e2012-04-04 15:22:24 +0800716 self.prespawner = Prespawner()
Jon Salz323dd3d2012-04-09 18:40:43 +0800717 self.prespawner.start()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800718
719 def state_change_callback(test, state):
720 self.event_client.post_event(
721 Event(Event.Type.STATE_CHANGE,
722 path=test.path, state=state))
723 self.test_list.state_change_callback = state_change_callback
724
725 try:
Jon Salz758e6cc2012-04-03 15:47:07 +0800726 tests_after_shutdown = self.state_instance.get_shared_data(
727 'tests_after_shutdown')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800728 except KeyError:
Jon Salz758e6cc2012-04-03 15:47:07 +0800729 tests_after_shutdown = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800730
Jon Salz758e6cc2012-04-03 15:47:07 +0800731 if tests_after_shutdown is not None:
732 logging.info('Resuming tests after shutdown: %s',
733 tests_after_shutdown)
734 self.state_instance.set_shared_data('tests_after_shutdown', None)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800735 self.tests_to_run.extend(
Jon Salz758e6cc2012-04-03 15:47:07 +0800736 self.test_list.lookup_path(t) for t in tests_after_shutdown)
Jon Salz73e0fd02012-04-04 11:46:38 +0800737 self.run_queue.put(self.run_next_test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800738 else:
Jon Salz57717ca2012-04-04 16:47:25 +0800739 if self.test_list.options.auto_run_on_start:
740 self.run_queue.put(
741 lambda: self.run_tests(self.test_list, untested_only=True))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800742
Jon Salz73e0fd02012-04-04 11:46:38 +0800743 def run(self):
744 '''Runs Goofy.'''
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800745 # Process events forever.
Jon Salz57717ca2012-04-04 16:47:25 +0800746 while self.run_once(True):
Jon Salz73e0fd02012-04-04 11:46:38 +0800747 pass
748
Jon Salz57717ca2012-04-04 16:47:25 +0800749 def run_once(self, block=False):
Jon Salz73e0fd02012-04-04 11:46:38 +0800750 '''Runs all items pending in the event loop.
751
Jon Salz57717ca2012-04-04 16:47:25 +0800752 Args:
753 block: If true, block until at least one event is processed.
754
Jon Salz73e0fd02012-04-04 11:46:38 +0800755 Returns:
756 True to keep going or False to shut down.
757 '''
Jon Salz57717ca2012-04-04 16:47:25 +0800758 events = []
759 if block:
760 # Get at least one event
761 events.append(self.run_queue.get())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800762 while True:
Jon Salz73e0fd02012-04-04 11:46:38 +0800763 try:
764 events.append(self.run_queue.get_nowait())
765 except Queue.Empty:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800766 break
767
Jon Salz73e0fd02012-04-04 11:46:38 +0800768 for event in events:
769 if not event:
770 # Shutdown request.
771 self.run_queue.task_done()
772 return False
773
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800774 try:
775 event()
776 except Exception as e: # pylint: disable=W0703
777 logging.error('Error in event loop: %s', e)
778 traceback.print_exc(sys.stderr)
Jon Salz8375c2e2012-04-04 15:22:24 +0800779 self.record_exception(traceback.format_exception_only(
780 *sys.exc_info()[:2]))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800781 # But keep going
782 finally:
783 self.run_queue.task_done()
Jon Salz73e0fd02012-04-04 11:46:38 +0800784 return True
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800785
Jon Salz258a40c2012-04-19 12:34:01 +0800786 def run_tests_with_status(self, statuses_to_run, starting_at=None,
787 root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800788 '''Runs all top-level tests with a particular status.
789
790 All active tests, plus any tests to re-run, are reset.
Jon Salz57717ca2012-04-04 16:47:25 +0800791
792 Args:
793 starting_at: If provided, only auto-runs tests beginning with
794 this test.
Jon Salz0405ab52012-03-16 15:26:52 +0800795 '''
Jon Salz258a40c2012-04-19 12:34:01 +0800796 root = root or self.test_list
797
Jon Salz57717ca2012-04-04 16:47:25 +0800798 if starting_at:
799 # Make sure they passed a test, not a string.
800 assert isinstance(starting_at, factory.FactoryTest)
801
Jon Salz0405ab52012-03-16 15:26:52 +0800802 tests_to_reset = []
803 tests_to_run = []
804
Jon Salz57717ca2012-04-04 16:47:25 +0800805 found_starting_at = False
806
Jon Salz258a40c2012-04-19 12:34:01 +0800807 for test in root.get_top_level_tests():
Jon Salz57717ca2012-04-04 16:47:25 +0800808 if starting_at:
809 if test == starting_at:
810 # We've found starting_at; do auto-run on all
811 # subsequent tests.
812 found_starting_at = True
813 if not found_starting_at:
814 # Don't start this guy yet
815 continue
816
Jon Salz0405ab52012-03-16 15:26:52 +0800817 status = test.get_state().status
818 if status == TestState.ACTIVE or status in statuses_to_run:
819 # Reset the test (later; we will need to abort
820 # all active tests first).
821 tests_to_reset.append(test)
822 if status in statuses_to_run:
823 tests_to_run.append(test)
824
825 self.abort_active_tests()
826
827 # Reset all statuses of the tests to run (in case any tests were active;
828 # we want them to be run again).
829 for test_to_reset in tests_to_reset:
830 for test in test_to_reset.walk():
831 test.update_state(status=TestState.UNTESTED)
832
833 self.run_tests(tests_to_run, untested_only=True)
834
Jon Salz258a40c2012-04-19 12:34:01 +0800835 def restart_tests(self, root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800836 '''Restarts all tests.'''
Jon Salz258a40c2012-04-19 12:34:01 +0800837 root = root or self.test_list
Jon Salz0405ab52012-03-16 15:26:52 +0800838
Jon Salz258a40c2012-04-19 12:34:01 +0800839 self.abort_active_tests()
840 for test in root.walk():
841 test.update_state(status=TestState.UNTESTED)
842 self.run_tests(root)
843
844 def auto_run(self, starting_at=None, root=None):
Jon Salz57717ca2012-04-04 16:47:25 +0800845 '''"Auto-runs" tests that have not been run yet.
846
847 Args:
848 starting_at: If provide, only auto-runs tests beginning with
849 this test.
850 '''
Jon Salz258a40c2012-04-19 12:34:01 +0800851 root = root or self.test_list
Jon Salz57717ca2012-04-04 16:47:25 +0800852 self.run_tests_with_status([TestState.UNTESTED, TestState.ACTIVE],
Jon Salz258a40c2012-04-19 12:34:01 +0800853 starting_at=starting_at,
854 root=root)
Jon Salz0405ab52012-03-16 15:26:52 +0800855
Jon Salz258a40c2012-04-19 12:34:01 +0800856 def re_run_failed(self, root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800857 '''Re-runs failed tests.'''
Jon Salz258a40c2012-04-19 12:34:01 +0800858 root = root or self.test_list
859 self.run_tests_with_status([TestState.FAILED], root=root)
Jon Salz0405ab52012-03-16 15:26:52 +0800860
Jon Salz968e90b2012-03-18 16:12:43 +0800861 def show_review_information(self):
Hung-Te Lin96632362012-03-20 21:14:18 +0800862 '''Event handler for showing review information screen.
863
864 The information screene is rendered by main UI program (ui.py), so in
865 goofy we only need to kill all active tests, set them as untested, and
866 clear remaining tests.
867 '''
868 self.kill_active_tests(False)
869 self.run_tests([])
870
Jon Salz0405ab52012-03-16 15:26:52 +0800871 def handle_switch_test(self, event):
Jon Salz968e90b2012-03-18 16:12:43 +0800872 '''Switches to a particular test.
873
874 @param event: The SWITCH_TEST event.
875 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800876 test = self.test_list.lookup_path(event.path)
Jon Salz57717ca2012-04-04 16:47:25 +0800877 if not test:
Jon Salz968e90b2012-03-18 16:12:43 +0800878 logging.error('Unknown test %r', event.key)
Jon Salz57717ca2012-04-04 16:47:25 +0800879 return
880
881 invoc = self.invocations.get(test)
882 if invoc and test.backgroundable:
883 # Already running: just bring to the front if it
884 # has a UI.
885 logging.info('Setting visible test to %s', test.path)
886 self.event_client.post_event(
887 Event(Event.Type.SET_VISIBLE_TEST, path=test.path))
888 return
889
890 self.abort_active_tests()
891 for t in test.walk():
892 t.update_state(status=TestState.UNTESTED)
893
894 if self.test_list.options.auto_run_on_keypress:
895 self.auto_run(starting_at=test)
896 else:
897 self.run_tests(test)
Jon Salz0405ab52012-03-16 15:26:52 +0800898
Jon Salz73e0fd02012-04-04 11:46:38 +0800899 def wait(self):
900 '''Waits for all pending invocations.
901
902 Useful for testing.
903 '''
904 for k, v in self.invocations.iteritems():
905 logging.info('Waiting for %s to complete...', k)
906 v.thread.join()
907
908 def check_exceptions(self):
909 '''Raises an error if any exceptions have occurred in
910 invocation threads.'''
911 if self.exceptions:
912 raise RuntimeError('Exception in invocation thread: %r' %
913 self.exceptions)
914
915 def record_exception(self, msg):
916 '''Records an exception in an invocation thread.
917
918 An exception with the given message will be rethrown when
919 Goofy is destroyed.'''
920 self.exceptions.append(msg)
921
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800922
923if __name__ == '__main__':
924 Goofy().main()