blob: a3e2db3079bf1eefda3d7f1186f06aae4bd4e6c4 [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)
Jon Salz4f6c7172012-06-11 20:45:36 +0800301
302 def log_and_update_state(status, error_msg, **kw):
303 self.event_log.Log('rebooted',
304 status=status, error_msg=error_msg, **kw)
305 test.update_state(status=status, error_msg=error_msg)
306
307 if not self.last_shutdown_time:
308 log_and_update_state(status=TestState.FAILED,
309 error_msg='Unable to read shutdown_time')
310 return
311
312 now = time.time()
313 logging.info('%.03f s passed since reboot',
314 now - self.last_shutdown_time)
315
316 if self.last_shutdown_time > now:
317 test.update_state(status=TestState.FAILED,
318 error_msg='Time moved backward during reboot')
319 elif (isinstance(test, factory.RebootStep) and
320 self.test_list.options.max_reboot_time_secs and
321 (now - self.last_shutdown_time >
322 self.test_list.options.max_reboot_time_secs)):
323 # A reboot took too long; fail. (We don't check this for
324 # HaltSteps, because the machine could be halted for a
325 # very long time, and even unplugged with battery backup,
326 # thus hosing the clock.)
327 log_and_update_state(
328 status=TestState.FAILED,
329 error_msg=('More than %d s elapsed during reboot '
330 '(%.03f s, from %s to %s)' % (
331 self.test_list.options.max_reboot_time_secs,
332 now - self.last_shutdown_time,
333 utils.TimeString(self.last_shutdown_time),
334 utils.TimeString(now))),
335 duration=(now-self.last_shutdown_time))
336 elif state.shutdown_count == test.iterations:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800337 # Good!
Jon Salz4f6c7172012-06-11 20:45:36 +0800338 log_and_update_state(status=TestState.PASSED,
339 duration=(now - self.last_shutdown_time),
340 error_msg='')
Jon Salz74ad3262012-03-16 14:40:55 +0800341 elif state.shutdown_count > test.iterations:
Jon Salz73e0fd02012-04-04 11:46:38 +0800342 # Shut down too many times
Jon Salz4f6c7172012-06-11 20:45:36 +0800343 log_and_update_state(status=TestState.FAILED,
344 error_msg='Too many shutdowns')
Jon Salz258a40c2012-04-19 12:34:01 +0800345 elif utils.are_shift_keys_depressed():
Jon Salz73e0fd02012-04-04 11:46:38 +0800346 logging.info('Shift keys are depressed; cancelling restarts')
347 # Abort shutdown
Jon Salz4f6c7172012-06-11 20:45:36 +0800348 log_and_update_state(
Jon Salz73e0fd02012-04-04 11:46:38 +0800349 status=TestState.FAILED,
350 error_msg='Shutdown aborted with double shift keys')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800351 else:
Jon Salz74ad3262012-03-16 14:40:55 +0800352 # Need to shutdown again
Jon Salz4f6c7172012-06-11 20:45:36 +0800353 log_and_update_state(
354 status=TestState.ACTIVE,
355 error_msg='',
356 iteration=state.shutdown_count)
Jon Salzb9038572012-05-24 10:34:51 +0800357 self.event_log.Log('shutdown', operation='reboot')
Jon Salz4f6c7172012-06-11 20:45:36 +0800358 self.state_instance.set_shared_data('shutdown_time',
359 time.time())
Jon Salz73e0fd02012-04-04 11:46:38 +0800360 self.env.shutdown('reboot')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800361
362 def init_states(self):
363 '''
364 Initializes all states on startup.
365 '''
366 for test in self.test_list.get_all_tests():
367 # Make sure the state server knows about all the tests,
368 # defaulting to an untested state.
369 test.update_state(update_parent=False, visible=False)
370
Jon Salzd6361c22012-06-11 22:23:57 +0800371 var_log_messages = None
372
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800373 # Any 'active' tests should be marked as failed now.
374 for test in self.test_list.walk():
375 state = test.get_state()
Hung-Te Lin96632362012-03-20 21:14:18 +0800376 if state.status != TestState.ACTIVE:
377 continue
378 if isinstance(test, factory.ShutdownStep):
379 # Shutdown while the test was active - that's good.
380 self.handle_shutdown_complete(test, state)
381 else:
Jon Salzd6361c22012-06-11 22:23:57 +0800382 # Unexpected shutdown. Grab /var/log/messages for context.
383 if var_log_messages is None:
384 try:
385 var_log_messages = (
386 utils.var_log_messages_before_reboot())
387 # Write it to the log, to make it easier to
388 # correlate with /var/log/messages.
389 logging.info(
390 'Unexpected shutdown. '
391 'Tail of /var/log/messages before last reboot:\n'
392 '%s', ('\n'.join(
393 ' ' + x for x in var_log_messages)))
394 except:
395 logging.exception('Unable to grok /var/log/messages')
396 var_log_messages = []
397
398 error_msg = 'Unexpected shutdown while test was running'
399 self.event_log.Log('end_test',
400 path=test.path,
401 status=TestState.FAILED,
402 invocation=test.get_state().invocation,
403 error_msg=error_msg,
404 var_log_messages='\n'.join(var_log_messages))
405 test.update_state(
406 status=TestState.FAILED,
407 error_msg=error_msg)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800408
409 def show_next_active_test(self):
410 '''
411 Rotates to the next visible active test.
412 '''
413 self.reap_completed_tests()
414 active_tests = [
415 t for t in self.test_list.walk()
416 if t.is_leaf() and t.get_state().status == TestState.ACTIVE]
417 if not active_tests:
418 return
419
420 try:
421 next_test = active_tests[
422 (active_tests.index(self.visible_test) + 1) % len(active_tests)]
423 except ValueError: # visible_test not present in active_tests
424 next_test = active_tests[0]
425
426 self.set_visible_test(next_test)
427
428 def handle_event(self, event):
429 '''
430 Handles an event from the event server.
431 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800432 handler = self.event_handlers.get(event.type)
433 if handler:
Jon Salz968e90b2012-03-18 16:12:43 +0800434 handler(event)
Jon Salz0405ab52012-03-16 15:26:52 +0800435 else:
Jon Salz968e90b2012-03-18 16:12:43 +0800436 # We don't register handlers for all event types - just ignore
437 # this event.
438 logging.debug('Unbound event type %s', event.type)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800439
440 def run_next_test(self):
441 '''
442 Runs the next eligible test (or tests) in self.tests_to_run.
443 '''
444 self.reap_completed_tests()
445 while self.tests_to_run:
446 logging.debug('Tests to run: %s',
447 [x.path for x in self.tests_to_run])
448
449 test = self.tests_to_run[0]
450
451 if test in self.invocations:
452 logging.info('Next test %s is already running', test.path)
453 self.tests_to_run.popleft()
454 return
455
456 if self.invocations and not (test.backgroundable and all(
457 [x.backgroundable for x in self.invocations])):
458 logging.debug('Waiting for non-backgroundable tests to '
459 'complete before running %s', test.path)
460 return
461
462 self.tests_to_run.popleft()
463
Jon Salz74ad3262012-03-16 14:40:55 +0800464 if isinstance(test, factory.ShutdownStep):
Jon Salz8796e362012-05-24 11:39:09 +0800465 if os.path.exists(NO_REBOOT_FILE):
466 test.update_state(
467 status=TestState.FAILED, increment_count=1,
468 error_msg=('Skipped shutdown since %s is present' %
469 NO_REBOOT_FILE))
470 continue
471
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800472 test.update_state(status=TestState.ACTIVE, increment_count=1,
Jon Salz74ad3262012-03-16 14:40:55 +0800473 error_msg='', shutdown_count=0)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800474 # Save pending test list in the state server
475 self.state_instance.set_shared_data(
Jon Salz74ad3262012-03-16 14:40:55 +0800476 'tests_after_shutdown',
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800477 [t.path for t in self.tests_to_run])
Jon Salz4f6c7172012-06-11 20:45:36 +0800478 # Save shutdown time
479 self.state_instance.set_shared_data('shutdown_time',
480 time.time())
Jon Salz74ad3262012-03-16 14:40:55 +0800481
Jon Salz73e0fd02012-04-04 11:46:38 +0800482 with self.env.lock:
Jon Salzb9038572012-05-24 10:34:51 +0800483 self.event_log.Log('shutdown', operation=test.operation)
Jon Salz73e0fd02012-04-04 11:46:38 +0800484 shutdown_result = self.env.shutdown(test.operation)
485 if shutdown_result:
486 # That's all, folks!
487 self.run_queue.put(None)
488 return
489 else:
490 # Just pass (e.g., in the chroot).
491 test.update_state(status=TestState.PASSED)
492 self.state_instance.set_shared_data(
493 'tests_after_shutdown', None)
494 continue
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800495
496 invoc = TestInvocation(self, test, on_completion=self.run_next_test)
497 self.invocations[test] = invoc
498 if self.visible_test is None and test.has_ui:
499 self.set_visible_test(test)
500 invoc.start()
501
Jon Salz0405ab52012-03-16 15:26:52 +0800502 def run_tests(self, subtrees, untested_only=False):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800503 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800504 Runs tests under subtree.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800505
506 The tests are run in order unless one fails (then stops).
507 Backgroundable tests are run simultaneously; when a foreground test is
508 encountered, we wait for all active tests to finish before continuing.
Jon Salz0405ab52012-03-16 15:26:52 +0800509
510 @param subtrees: Node or nodes containing tests to run (may either be
511 a single test or a list). Duplicates will be ignored.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800512 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800513 if type(subtrees) != list:
514 subtrees = [subtrees]
515
516 # Nodes we've seen so far, to avoid duplicates.
517 seen = set()
518
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800519 self.tests_to_run = deque()
Jon Salz0405ab52012-03-16 15:26:52 +0800520 for subtree in subtrees:
521 for test in subtree.walk():
522 if test in seen:
523 continue
524 seen.add(test)
525
526 if not test.is_leaf():
527 continue
528 if (untested_only and
529 test.get_state().status != TestState.UNTESTED):
530 continue
531 self.tests_to_run.append(test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800532 self.run_next_test()
533
534 def reap_completed_tests(self):
535 '''
536 Removes completed tests from the set of active tests.
537
538 Also updates the visible test if it was reaped.
539 '''
540 for t, v in dict(self.invocations).iteritems():
541 if v.is_completed():
542 del self.invocations[t]
543
544 if (self.visible_test is None or
545 self.visible_test not in self.invocations):
546 self.set_visible_test(None)
547 # Make the first running test, if any, the visible test
548 for t in self.test_list.walk():
549 if t in self.invocations:
550 self.set_visible_test(t)
551 break
552
Hung-Te Lin96632362012-03-20 21:14:18 +0800553 def kill_active_tests(self, abort):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800554 '''
555 Kills and waits for all active tests.
Hung-Te Lin96632362012-03-20 21:14:18 +0800556
557 @param abort: True to change state of killed tests to FAILED, False for
558 UNTESTED.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800559 '''
560 self.reap_completed_tests()
561 for test, invoc in self.invocations.items():
562 factory.console.info('Killing active test %s...' % test.path)
563 invoc.abort_and_join()
564 factory.console.info('Killed %s' % test.path)
565 del self.invocations[test]
Hung-Te Lin96632362012-03-20 21:14:18 +0800566 if not abort:
567 test.update_state(status=TestState.UNTESTED)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800568 self.reap_completed_tests()
569
Jon Salzf00cdc82012-05-28 18:56:17 +0800570 def stop(self):
571 self.kill_active_tests(False)
572 self.run_tests([])
573
Hung-Te Lin96632362012-03-20 21:14:18 +0800574 def abort_active_tests(self):
575 self.kill_active_tests(True)
576
Jon Salz73e0fd02012-04-04 11:46:38 +0800577 def main(self):
Jon Salzeb8d25f2012-05-22 15:17:32 +0800578 try:
579 self.init()
Jon Salzb9038572012-05-24 10:34:51 +0800580 self.event_log.Log('goofy_init',
581 success=True)
Jon Salzeb8d25f2012-05-22 15:17:32 +0800582 except:
583 if self.event_log:
584 try:
Jon Salzb9038572012-05-24 10:34:51 +0800585 self.event_log.Log('goofy_init',
586 success=False,
587 trace=traceback.format_exc())
Jon Salzeb8d25f2012-05-22 15:17:32 +0800588 except:
589 pass
590 raise
591
Jon Salz73e0fd02012-04-04 11:46:38 +0800592 self.run()
593
Jon Salz5f2a0672012-05-22 17:14:06 +0800594 def update_system_info(self):
595 '''Updates system info.'''
596 system_info = test_environment.SystemInfo(self.env, self.state_instance)
597 self.state_instance.set_shared_data('system_info', system_info.__dict__)
598 self.event_client.post_event(Event(Event.Type.SYSTEM_INFO,
599 system_info=system_info.__dict__))
600 logging.info('System info: %r', system_info.__dict__)
601
Jon Salz37eccbd2012-05-25 16:06:52 +0800602 def update_factory(self):
603 self.kill_active_tests(False)
604 self.run_tests([])
605
606 try:
607 if updater.TryUpdate(pre_update_hook=self.state_instance.close):
608 self.env.shutdown('reboot')
609 except:
610 factory.console.exception('Unable to update')
611
Jon Salz73e0fd02012-04-04 11:46:38 +0800612 def init(self, args=None, env=None):
613 '''Initializes Goofy.
Jon Salz74ad3262012-03-16 14:40:55 +0800614
615 Args:
Jon Salz73e0fd02012-04-04 11:46:38 +0800616 args: A list of command-line arguments. Uses sys.argv if
617 args is None.
618 env: An Environment instance to use (or None to choose
Jon Salz258a40c2012-04-19 12:34:01 +0800619 FakeChrootEnvironment or DUTEnvironment as appropriate).
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800620 '''
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800621 parser = OptionParser()
622 parser.add_option('-v', '--verbose', dest='verbose',
623 action='store_true',
624 help='Enable debug logging')
625 parser.add_option('--print_test_list', dest='print_test_list',
626 metavar='FILE',
627 help='Read and print test list FILE, and exit')
Jon Salz758e6cc2012-04-03 15:47:07 +0800628 parser.add_option('--restart', dest='restart',
629 action='store_true',
630 help='Clear all test state')
Jon Salz258a40c2012-04-19 12:34:01 +0800631 parser.add_option('--ui', dest='ui', type='choice',
632 choices=['none', 'gtk', 'chrome'],
633 default='gtk',
634 help='UI to use')
Jon Salz63585ea2012-05-21 15:03:32 +0800635 parser.add_option('--ui_scale_factor', dest='ui_scale_factor',
636 type='int', default=1,
637 help=('Factor by which to scale UI '
638 '(Chrome UI only)'))
Jon Salz73e0fd02012-04-04 11:46:38 +0800639 parser.add_option('--test_list', dest='test_list',
640 metavar='FILE',
641 help='Use FILE as test list')
642 (self.options, self.args) = parser.parse_args(args)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800643
Jon Salz73e0fd02012-04-04 11:46:38 +0800644 global _inited_logging
645 if not _inited_logging:
646 factory.init_logging('goofy', verbose=self.options.verbose)
647 _inited_logging = True
Jon Salzeb8d25f2012-05-22 15:17:32 +0800648 self.event_log = EventLog('goofy')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800649
Jon Salz73e0fd02012-04-04 11:46:38 +0800650 if (not suppress_chroot_warning and
651 factory.in_chroot() and
Jon Salz258a40c2012-04-19 12:34:01 +0800652 self.options.ui == 'gtk' and
Jon Salz758e6cc2012-04-03 15:47:07 +0800653 os.environ.get('DISPLAY') in [None, '', ':0', ':0.0']):
654 # That's not going to work! Tell the user how to run
655 # this way.
656 logging.warn(GOOFY_IN_CHROOT_WARNING)
657 time.sleep(1)
658
Jon Salz73e0fd02012-04-04 11:46:38 +0800659 if env:
660 self.env = env
661 elif factory.in_chroot():
Jon Salz5f2a0672012-05-22 17:14:06 +0800662 self.env = test_environment.FakeChrootEnvironment()
Jon Salz73e0fd02012-04-04 11:46:38 +0800663 logging.warn(
664 'Using chroot environment: will not actually run autotests')
665 else:
Jon Salz5f2a0672012-05-22 17:14:06 +0800666 self.env = test_environment.DUTEnvironment()
Jon Salz323dd3d2012-04-09 18:40:43 +0800667 self.env.goofy = self
Jon Salz73e0fd02012-04-04 11:46:38 +0800668
Jon Salz758e6cc2012-04-03 15:47:07 +0800669 if self.options.restart:
670 state.clear_state()
671
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800672 if self.options.print_test_list:
673 print (factory.read_test_list(self.options.print_test_list).
674 __repr__(recursive=True))
675 return
676
Jon Salz9b312912012-06-04 11:27:00 +0800677 if self.options.ui_scale_factor != 1 and factory.in_qemu():
678 logging.warn(
679 'In QEMU; ignoring ui_scale_factor argument')
680 self.options.ui_scale_factor = 1
681
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800682 logging.info('Started')
683
684 self.start_state_server()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800685 self.state_instance.set_shared_data('hwid_cfg', get_hwid_cfg())
Jon Salz63585ea2012-05-21 15:03:32 +0800686 self.state_instance.set_shared_data('ui_scale_factor',
687 self.options.ui_scale_factor)
Jon Salz4f6c7172012-06-11 20:45:36 +0800688 self.last_shutdown_time = (
689 self.state_instance.get_shared_data('shutdown_time', optional=True))
690 self.state_instance.del_shared_data('shutdown_time', optional=True)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800691
Jon Salz73e0fd02012-04-04 11:46:38 +0800692 self.options.test_list = (self.options.test_list or find_test_list())
693 self.test_list = factory.read_test_list(self.options.test_list,
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800694 self.state_instance)
Jon Salz06fbeff2012-05-21 17:06:05 +0800695 if not self.state_instance.has_shared_data('ui_lang'):
696 self.state_instance.set_shared_data('ui_lang',
697 self.test_list.options.ui_lang)
Jon Salz258a40c2012-04-19 12:34:01 +0800698 self.state_instance.test_list = self.test_list
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800699
700 self.init_states()
701 self.start_event_server()
Jon Salz258a40c2012-04-19 12:34:01 +0800702
Jon Salz5f2a0672012-05-22 17:14:06 +0800703 self.update_system_info()
704
Jon Salz5da61e62012-05-31 13:06:22 +0800705 os.environ['CROS_FACTORY'] = '1'
Jon Salzeebd12e2012-06-08 15:34:56 +0800706 os.environ['CROS_DISABLE_SITE_SYSINFO'] = '1'
Jon Salz5da61e62012-05-31 13:06:22 +0800707
Jon Salzb1b39092012-05-03 02:05:09 +0800708 # Set CROS_UI since some behaviors in ui.py depend on the
709 # particular UI in use. TODO(jsalz): Remove this (and all
710 # places it is used) when the GTK UI is removed.
711 os.environ['CROS_UI'] = self.options.ui
Jon Salz258a40c2012-04-19 12:34:01 +0800712
Jon Salzb1b39092012-05-03 02:05:09 +0800713 if self.options.ui == 'chrome':
Jon Salz258a40c2012-04-19 12:34:01 +0800714 self.env.launch_chrome()
715 logging.info('Waiting for a web socket connection')
716 self.web_socket_manager.wait()
Jon Salzb1b39092012-05-03 02:05:09 +0800717
718 # Wait for the test widget size to be set; this is done in
719 # an asynchronous RPC so there is a small chance that the
720 # web socket might be opened first.
721 for i in range(100): # 10 s
Jon Salz63585ea2012-05-21 15:03:32 +0800722 try:
723 if self.state_instance.get_shared_data('test_widget_size'):
724 break
725 except KeyError:
726 pass # Retry
Jon Salzb1b39092012-05-03 02:05:09 +0800727 time.sleep(0.1) # 100 ms
728 else:
729 logging.warn('Never received test_widget_size from UI')
Jon Salz258a40c2012-04-19 12:34:01 +0800730 elif self.options.ui == 'gtk':
Jon Salz73e0fd02012-04-04 11:46:38 +0800731 self.start_ui()
Jon Salz258a40c2012-04-19 12:34:01 +0800732
Jon Salz8375c2e2012-04-04 15:22:24 +0800733 self.prespawner = Prespawner()
Jon Salz323dd3d2012-04-09 18:40:43 +0800734 self.prespawner.start()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800735
736 def state_change_callback(test, state):
737 self.event_client.post_event(
738 Event(Event.Type.STATE_CHANGE,
739 path=test.path, state=state))
740 self.test_list.state_change_callback = state_change_callback
741
742 try:
Jon Salz758e6cc2012-04-03 15:47:07 +0800743 tests_after_shutdown = self.state_instance.get_shared_data(
744 'tests_after_shutdown')
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800745 except KeyError:
Jon Salz758e6cc2012-04-03 15:47:07 +0800746 tests_after_shutdown = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800747
Jon Salz758e6cc2012-04-03 15:47:07 +0800748 if tests_after_shutdown is not None:
749 logging.info('Resuming tests after shutdown: %s',
750 tests_after_shutdown)
751 self.state_instance.set_shared_data('tests_after_shutdown', None)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800752 self.tests_to_run.extend(
Jon Salz758e6cc2012-04-03 15:47:07 +0800753 self.test_list.lookup_path(t) for t in tests_after_shutdown)
Jon Salz73e0fd02012-04-04 11:46:38 +0800754 self.run_queue.put(self.run_next_test)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800755 else:
Jon Salz57717ca2012-04-04 16:47:25 +0800756 if self.test_list.options.auto_run_on_start:
757 self.run_queue.put(
758 lambda: self.run_tests(self.test_list, untested_only=True))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800759
Jon Salz73e0fd02012-04-04 11:46:38 +0800760 def run(self):
761 '''Runs Goofy.'''
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800762 # Process events forever.
Jon Salz57717ca2012-04-04 16:47:25 +0800763 while self.run_once(True):
Jon Salz73e0fd02012-04-04 11:46:38 +0800764 pass
765
Jon Salz57717ca2012-04-04 16:47:25 +0800766 def run_once(self, block=False):
Jon Salz73e0fd02012-04-04 11:46:38 +0800767 '''Runs all items pending in the event loop.
768
Jon Salz57717ca2012-04-04 16:47:25 +0800769 Args:
770 block: If true, block until at least one event is processed.
771
Jon Salz73e0fd02012-04-04 11:46:38 +0800772 Returns:
773 True to keep going or False to shut down.
774 '''
Jon Salz57717ca2012-04-04 16:47:25 +0800775 events = []
776 if block:
777 # Get at least one event
778 events.append(self.run_queue.get())
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800779 while True:
Jon Salz73e0fd02012-04-04 11:46:38 +0800780 try:
781 events.append(self.run_queue.get_nowait())
782 except Queue.Empty:
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800783 break
784
Jon Salz73e0fd02012-04-04 11:46:38 +0800785 for event in events:
786 if not event:
787 # Shutdown request.
788 self.run_queue.task_done()
789 return False
790
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800791 try:
792 event()
793 except Exception as e: # pylint: disable=W0703
794 logging.error('Error in event loop: %s', e)
795 traceback.print_exc(sys.stderr)
Jon Salz8375c2e2012-04-04 15:22:24 +0800796 self.record_exception(traceback.format_exception_only(
797 *sys.exc_info()[:2]))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800798 # But keep going
799 finally:
800 self.run_queue.task_done()
Jon Salz73e0fd02012-04-04 11:46:38 +0800801 return True
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800802
Jon Salz258a40c2012-04-19 12:34:01 +0800803 def run_tests_with_status(self, statuses_to_run, starting_at=None,
804 root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800805 '''Runs all top-level tests with a particular status.
806
807 All active tests, plus any tests to re-run, are reset.
Jon Salz57717ca2012-04-04 16:47:25 +0800808
809 Args:
810 starting_at: If provided, only auto-runs tests beginning with
811 this test.
Jon Salz0405ab52012-03-16 15:26:52 +0800812 '''
Jon Salz258a40c2012-04-19 12:34:01 +0800813 root = root or self.test_list
814
Jon Salz57717ca2012-04-04 16:47:25 +0800815 if starting_at:
816 # Make sure they passed a test, not a string.
817 assert isinstance(starting_at, factory.FactoryTest)
818
Jon Salz0405ab52012-03-16 15:26:52 +0800819 tests_to_reset = []
820 tests_to_run = []
821
Jon Salz57717ca2012-04-04 16:47:25 +0800822 found_starting_at = False
823
Jon Salz258a40c2012-04-19 12:34:01 +0800824 for test in root.get_top_level_tests():
Jon Salz57717ca2012-04-04 16:47:25 +0800825 if starting_at:
826 if test == starting_at:
827 # We've found starting_at; do auto-run on all
828 # subsequent tests.
829 found_starting_at = True
830 if not found_starting_at:
831 # Don't start this guy yet
832 continue
833
Jon Salz0405ab52012-03-16 15:26:52 +0800834 status = test.get_state().status
835 if status == TestState.ACTIVE or status in statuses_to_run:
836 # Reset the test (later; we will need to abort
837 # all active tests first).
838 tests_to_reset.append(test)
839 if status in statuses_to_run:
840 tests_to_run.append(test)
841
842 self.abort_active_tests()
843
844 # Reset all statuses of the tests to run (in case any tests were active;
845 # we want them to be run again).
846 for test_to_reset in tests_to_reset:
847 for test in test_to_reset.walk():
848 test.update_state(status=TestState.UNTESTED)
849
850 self.run_tests(tests_to_run, untested_only=True)
851
Jon Salz258a40c2012-04-19 12:34:01 +0800852 def restart_tests(self, root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800853 '''Restarts all tests.'''
Jon Salz258a40c2012-04-19 12:34:01 +0800854 root = root or self.test_list
Jon Salz0405ab52012-03-16 15:26:52 +0800855
Jon Salz258a40c2012-04-19 12:34:01 +0800856 self.abort_active_tests()
857 for test in root.walk():
858 test.update_state(status=TestState.UNTESTED)
859 self.run_tests(root)
860
861 def auto_run(self, starting_at=None, root=None):
Jon Salz57717ca2012-04-04 16:47:25 +0800862 '''"Auto-runs" tests that have not been run yet.
863
864 Args:
865 starting_at: If provide, only auto-runs tests beginning with
866 this test.
867 '''
Jon Salz258a40c2012-04-19 12:34:01 +0800868 root = root or self.test_list
Jon Salz57717ca2012-04-04 16:47:25 +0800869 self.run_tests_with_status([TestState.UNTESTED, TestState.ACTIVE],
Jon Salz258a40c2012-04-19 12:34:01 +0800870 starting_at=starting_at,
871 root=root)
Jon Salz0405ab52012-03-16 15:26:52 +0800872
Jon Salz258a40c2012-04-19 12:34:01 +0800873 def re_run_failed(self, root=None):
Jon Salz0405ab52012-03-16 15:26:52 +0800874 '''Re-runs failed tests.'''
Jon Salz258a40c2012-04-19 12:34:01 +0800875 root = root or self.test_list
876 self.run_tests_with_status([TestState.FAILED], root=root)
Jon Salz0405ab52012-03-16 15:26:52 +0800877
Jon Salz968e90b2012-03-18 16:12:43 +0800878 def show_review_information(self):
Hung-Te Lin96632362012-03-20 21:14:18 +0800879 '''Event handler for showing review information screen.
880
881 The information screene is rendered by main UI program (ui.py), so in
882 goofy we only need to kill all active tests, set them as untested, and
883 clear remaining tests.
884 '''
885 self.kill_active_tests(False)
886 self.run_tests([])
887
Jon Salz0405ab52012-03-16 15:26:52 +0800888 def handle_switch_test(self, event):
Jon Salz968e90b2012-03-18 16:12:43 +0800889 '''Switches to a particular test.
890
891 @param event: The SWITCH_TEST event.
892 '''
Jon Salz0405ab52012-03-16 15:26:52 +0800893 test = self.test_list.lookup_path(event.path)
Jon Salz57717ca2012-04-04 16:47:25 +0800894 if not test:
Jon Salz968e90b2012-03-18 16:12:43 +0800895 logging.error('Unknown test %r', event.key)
Jon Salz57717ca2012-04-04 16:47:25 +0800896 return
897
898 invoc = self.invocations.get(test)
899 if invoc and test.backgroundable:
900 # Already running: just bring to the front if it
901 # has a UI.
902 logging.info('Setting visible test to %s', test.path)
903 self.event_client.post_event(
904 Event(Event.Type.SET_VISIBLE_TEST, path=test.path))
905 return
906
907 self.abort_active_tests()
908 for t in test.walk():
909 t.update_state(status=TestState.UNTESTED)
910
911 if self.test_list.options.auto_run_on_keypress:
912 self.auto_run(starting_at=test)
913 else:
914 self.run_tests(test)
Jon Salz0405ab52012-03-16 15:26:52 +0800915
Jon Salz73e0fd02012-04-04 11:46:38 +0800916 def wait(self):
917 '''Waits for all pending invocations.
918
919 Useful for testing.
920 '''
921 for k, v in self.invocations.iteritems():
922 logging.info('Waiting for %s to complete...', k)
923 v.thread.join()
924
925 def check_exceptions(self):
926 '''Raises an error if any exceptions have occurred in
927 invocation threads.'''
928 if self.exceptions:
929 raise RuntimeError('Exception in invocation thread: %r' %
930 self.exceptions)
931
932 def record_exception(self, msg):
933 '''Records an exception in an invocation thread.
934
935 An exception with the given message will be rethrown when
936 Goofy is destroyed.'''
937 self.exceptions.append(msg)
938
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800939
940if __name__ == '__main__':
941 Goofy().main()