blob: 76df6e32b9de4a14ac74cf390d84e873dd988c68 [file] [log] [blame]
You-Cheng Syud5692942018-01-04 14:40:59 +08001#!/usr/bin/env python
Hung-Te Lin1990b742017-08-09 17:34:57 +08002# Copyright 2012 The Chromium OS Authors. All rights reserved.
Hung-Te Linf2f78f72012-02-08 19:27:11 +08003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +08006"""The main factory flow that runs the factory test and finalizes a device."""
Hung-Te Linf2f78f72012-02-08 19:27:11 +08007
Joel Kitchingb85ed7f2014-10-08 18:24:39 +08008from __future__ import print_function
9
chuntsen61522442017-08-11 14:40:29 +080010import datetime
Jon Salz0405ab52012-03-16 15:26:52 +080011import logging
Wei-Han Chenc17b4112016-11-22 14:56:51 +080012from optparse import OptionParser
Jon Salz0405ab52012-03-16 15:26:52 +080013import os
Hung-Te Lina452d4d2017-10-25 17:46:14 +080014import Queue
Jon Salz77c151e2012-08-28 07:20:37 +080015import signal
Jon Salz0405ab52012-03-16 15:26:52 +080016import sys
Jon Salz0405ab52012-03-16 15:26:52 +080017import threading
18import time
19import traceback
Jon Salz258a40c2012-04-19 12:34:01 +080020import uuid
Jon Salzb10cf512012-08-09 17:29:21 +080021from xmlrpclib import Binary
Hung-Te Linf2f78f72012-02-08 19:27:11 +080022
Peter Shihfdf17682017-05-26 11:38:39 +080023import factory_common # pylint: disable=unused-import
Hung-Te Linb6287242016-05-18 14:39:05 +080024from cros.factory.device import device_utils
Vic Yangd80ea752014-09-24 16:07:14 +080025from cros.factory.goofy.goofy_rpc import GoofyRPC
Earl Ouacbe99c2017-02-21 16:04:19 +080026from cros.factory.goofy import goofy_server
Wei-Han Chen1a114682017-10-02 10:33:54 +080027from cros.factory.goofy import hooks
Vic Yangd80ea752014-09-24 16:07:14 +080028from cros.factory.goofy.invocation import TestInvocation
Earl Oua3bca122016-10-21 16:00:30 +080029from cros.factory.goofy.plugins import plugin_controller
Vic Yange2c76a82014-10-30 12:48:19 -070030from cros.factory.goofy import prespawner
Earl Oua3bca122016-10-21 16:00:30 +080031from cros.factory.goofy import test_environment
Wei-Han Chenc17b4112016-11-22 14:56:51 +080032from cros.factory.goofy.test_list_iterator import TestListIterator
Earl Oua3bca122016-10-21 16:00:30 +080033from cros.factory.goofy import updater
Vic Yangd80ea752014-09-24 16:07:14 +080034from cros.factory.goofy.web_socket_manager import WebSocketManager
Wei-Han Chen109d76f2017-08-08 18:50:35 +080035from cros.factory.test import device_data
Earl Ouacbe99c2017-02-21 16:04:19 +080036from cros.factory.test.env import goofy_proxy
Hung-Te Linb6287242016-05-18 14:39:05 +080037from cros.factory.test.env import paths
Jon Salz83591782012-06-26 11:09:58 +080038from cros.factory.test.event import Event
Jon Salz83591782012-06-26 11:09:58 +080039from cros.factory.test.event import EventServer
Peter Shih67c7c0f2018-02-26 11:23:59 +080040from cros.factory.test.event import ThreadingEventClient
Hung-Te Linb6287242016-05-18 14:39:05 +080041from cros.factory.test import event_log
42from cros.factory.test.event_log import EventLog
Hung-Te Linb6287242016-05-18 14:39:05 +080043from cros.factory.test.event_log import GetBootSequence
Hung-Te Lin91492a12014-11-25 18:56:30 +080044from cros.factory.test.event_log_watcher import EventLogWatcher
Peter Shihf65db932017-03-22 17:06:34 +080045from cros.factory.test.i18n import test_ui as i18n_test_ui
Peter Shih80e78b42017-03-10 17:00:56 +080046from cros.factory.test.i18n import translation
Hung-Te Lin3f096842016-01-13 17:37:06 +080047from cros.factory.test.rules import phase
Hung-Te Lind151bf32017-08-30 11:05:47 +080048from cros.factory.test import server_proxy
Hung-Te Linda8eb992017-09-28 03:27:12 +080049from cros.factory.test import session
Earl Oua3bca122016-10-21 16:00:30 +080050from cros.factory.test import state
Wei-Han Chen3b030492017-10-12 11:43:27 +080051from cros.factory.test.state import TestState
Wei-Han Chen16cc5dd2017-04-27 17:38:53 +080052from cros.factory.test.test_lists import manager
Wei-Han Chen03113912017-09-29 15:58:25 +080053from cros.factory.test.test_lists import test_object
chuntseneb33f9d2017-05-12 13:38:17 +080054from cros.factory.testlog import testlog
Hung-Te Linb6287242016-05-18 14:39:05 +080055from cros.factory.tools.key_filter import KeyFilter
Wei-Han Chen78f35f62017-03-06 20:11:20 +080056from cros.factory.utils import config_utils
Hung-Te Linf707b242016-01-08 23:11:42 +080057from cros.factory.utils import debug_utils
Jon Salz2af235d2013-06-24 14:47:21 +080058from cros.factory.utils import file_utils
Hung-Te Lin8fc0d652017-09-21 13:05:44 +080059from cros.factory.utils import log_utils
Joel Kitchingb85ed7f2014-10-08 18:24:39 +080060from cros.factory.utils import net_utils
Hung-Te Lin4e6357c2016-01-08 14:32:00 +080061from cros.factory.utils import sys_utils
Hung-Te Linf707b242016-01-08 23:11:42 +080062from cros.factory.utils import type_utils
Hung-Te Linf2f78f72012-02-08 19:27:11 +080063
Earl Ou6de96c02017-05-19 18:51:28 +080064from cros.factory.external import syslog
65
Hung-Te Linf2f78f72012-02-08 19:27:11 +080066
Hung-Te Linf2f78f72012-02-08 19:27:11 +080067HWID_CFG_PATH = '/usr/local/share/chromeos-hwid/cfg'
Peter Shihb4e49352017-05-25 17:35:11 +080068CACHES_DIR = os.path.join(paths.DATA_STATE_DIR, 'caches')
Hung-Te Linf2f78f72012-02-08 19:27:11 +080069
Jon Salz5c344f62012-07-13 14:31:16 +080070# Value for tests_after_shutdown that forces auto-run (e.g., after
71# a factory update, when the available set of tests might change).
72FORCE_AUTO_RUN = 'force_auto_run'
73
Wei-Han Chenc17b4112016-11-22 14:56:51 +080074# Key to load the test list iterator after shutdown test
75TESTS_AFTER_SHUTDOWN = 'tests_after_shutdown'
76
Hung-Te Linf707b242016-01-08 23:11:42 +080077Status = type_utils.Enum(['UNINITIALIZED', 'INITIALIZING', 'RUNNING',
Wei-Han Chen2ebb92d2016-01-12 14:51:41 +080078 'TERMINATING', 'TERMINATED'])
Jon Salzd7550792013-07-12 05:49:27 +080079
Hung-Te Lina452d4d2017-10-25 17:46:14 +080080RUN_QUEUE_TIMEOUT_SECS = 10
81
82class Goofy(object):
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +080083 """The main factory flow.
Jon Salz0697cbf2012-07-04 15:14:04 +080084
85 Note that all methods in this class must be invoked from the main
86 (event) thread. Other threads, such as callbacks and TestInvocation
87 methods, should instead post events on the run queue.
88
89 TODO: Unit tests. (chrome-os-partner:7409)
90
91 Properties:
Hung-Te Lina452d4d2017-10-25 17:46:14 +080092 run_queue: A queue of callbacks to invoke from the main thread.
93 exceptions: List of exceptions encountered in invocation threads.
94 last_idle: The most recent time of invoking the idle queue handler, or none.
Jon Salz0697cbf2012-07-04 15:14:04 +080095 uuid: A unique UUID for this invocation of Goofy.
96 state_instance: An instance of FactoryState.
97 state_server: The FactoryState XML/RPC server.
98 state_server_thread: A thread running state_server.
99 event_server: The EventServer socket server.
100 event_server_thread: A thread running event_server.
101 event_client: A client to the event server.
Earl Oua3bca122016-10-21 16:00:30 +0800102 plugin_controller: The PluginController object.
Peter Shih06d08212018-01-19 17:15:57 +0800103 invocations: A map from TestInvocation uuid to the corresponding
Jon Salz0697cbf2012-07-04 15:14:04 +0800104 TestInvocations objects representing active tests.
Jon Salz0697cbf2012-07-04 15:14:04 +0800105 options: Command-line options.
106 args: Command-line args.
107 test_list: The test list.
Jon Salz128b0932013-07-03 16:55:26 +0800108 test_lists: All new-style test lists.
Ricky Liang4bff3e32014-02-20 18:46:11 +0800109 run_id: The identifier for latest test run.
110 scheduled_run_tests: The list of tests scheduled for latest test run.
Jon Salz0697cbf2012-07-04 15:14:04 +0800111 event_handlers: Map of Event.Type to the method used to handle that
112 event. If the method has an 'event' argument, the event is passed
113 to the handler.
Jon Salz416f9cc2013-05-10 18:32:50 +0800114 hooks: A Hooks object containing hooks for various Goofy actions.
Jon Salzd7550792013-07-12 05:49:27 +0800115 status: The current Goofy status (a member of the Status enum).
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +0800116 """
Ricky Liang45c73e72015-01-15 15:00:30 +0800117
Jon Salz0697cbf2012-07-04 15:14:04 +0800118 def __init__(self):
Hung-Te Lina452d4d2017-10-25 17:46:14 +0800119 self.run_queue = Queue.Queue()
120 self.exceptions = []
121 self.last_idle = None
122
Jon Salz0697cbf2012-07-04 15:14:04 +0800123 self.uuid = str(uuid.uuid4())
124 self.state_instance = None
Earl Ouacbe99c2017-02-21 16:04:19 +0800125 self.goofy_server = None
126 self.goofy_server_thread = None
Jon Salz16d10542012-07-23 12:18:45 +0800127 self.goofy_rpc = None
Jon Salz0697cbf2012-07-04 15:14:04 +0800128 self.event_server = None
129 self.event_server_thread = None
130 self.event_client = None
Jon Salz0697cbf2012-07-04 15:14:04 +0800131 self.log_watcher = None
Jon Salz0697cbf2012-07-04 15:14:04 +0800132 self.event_log = None
Chun-Ta Lin53cbbd52016-06-08 21:42:19 +0800133 self.testlog = None
Earl Oua3bca122016-10-21 16:00:30 +0800134 self.plugin_controller = None
Vic Yange2c76a82014-10-30 12:48:19 -0700135 self.pytest_prespawner = None
Vic Yanga3cecf82014-12-26 00:44:21 -0800136 self._ui_initialized = False
Jon Salz0697cbf2012-07-04 15:14:04 +0800137 self.invocations = {}
Jon Salz0697cbf2012-07-04 15:14:04 +0800138 self.chrome = None
Jon Salz416f9cc2013-05-10 18:32:50 +0800139 self.hooks = None
Jon Salz0697cbf2012-07-04 15:14:04 +0800140
141 self.options = None
142 self.args = None
143 self.test_list = None
Jon Salz128b0932013-07-03 16:55:26 +0800144 self.test_lists = None
Ricky Liang4bff3e32014-02-20 18:46:11 +0800145 self.run_id = None
146 self.scheduled_run_tests = None
Jon Salz0697cbf2012-07-04 15:14:04 +0800147 self.env = None
Jon Salzb22d1172012-08-06 10:38:57 +0800148 self.last_idle = None
Jon Salz0697cbf2012-07-04 15:14:04 +0800149 self.last_shutdown_time = None
cychiang21886742012-07-05 15:16:32 +0800150 self.last_update_check = None
Cheng-Yi Chiang194d3c02015-03-16 14:37:15 +0800151 self._suppress_periodic_update_messages = False
Cheng-Yi Chiangf5b21012015-03-17 15:37:14 +0800152 self._suppress_event_log_error_messages = False
Earl Ouab979142016-10-25 16:48:06 +0800153 self.exclusive_resources = set()
Dean Liao592e4d52013-01-10 20:06:39 +0800154 self.key_filter = None
Jon Salzd7550792013-07-12 05:49:27 +0800155 self.status = Status.UNINITIALIZED
Ricky Liang36512a32014-07-25 11:47:04 +0800156 self.ready_for_ui_connection = False
Hung-Te Linef7f2be2015-07-20 20:38:51 +0800157 self.is_restart_requested = False
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800158 self.test_list_iterator = None
Jon Salz0697cbf2012-07-04 15:14:04 +0800159
Wei-Han Chen16cc5dd2017-04-27 17:38:53 +0800160 self.test_list_manager = manager.Manager()
161
Hung-Te Lin6a72c642015-12-13 22:09:09 +0800162 # TODO(hungte) Support controlling remote DUT.
Hung-Te Linb6287242016-05-18 14:39:05 +0800163 self.dut = device_utils.CreateDUTInterface()
Hung-Te Lin6a72c642015-12-13 22:09:09 +0800164
Jon Salz85a39882012-07-05 16:45:04 +0800165 def test_or_root(event, parent_or_group=True):
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +0800166 """Returns the test affected by a particular event.
Jon Salz85a39882012-07-05 16:45:04 +0800167
168 Args:
169 event: The event containing an optional 'path' attribute.
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +0800170 parent_or_group: If True, returns the top-level parent for a test (the
Jon Salz85a39882012-07-05 16:45:04 +0800171 root node of the tests that need to be run together if the given test
172 path is to be run).
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +0800173 """
Jon Salz0697cbf2012-07-04 15:14:04 +0800174 try:
175 path = event.path
176 except AttributeError:
177 path = None
178
179 if path:
Wei-Han Chen3ae204c2017-04-28 19:36:55 +0800180 test = self.test_list.LookupPath(path)
Jon Salz85a39882012-07-05 16:45:04 +0800181 if parent_or_group:
Wei-Han Chen3ae204c2017-04-28 19:36:55 +0800182 test = test.GetTopLevelParentOrGroup()
Jon Salz85a39882012-07-05 16:45:04 +0800183 return test
Jon Salz0697cbf2012-07-04 15:14:04 +0800184 else:
Peter Shih999faf72017-07-07 11:32:42 +0800185 return self.test_list.ToFactoryTestList()
Jon Salz0697cbf2012-07-04 15:14:04 +0800186
187 self.event_handlers = {
Ricky Liang45c73e72015-01-15 15:00:30 +0800188 Event.Type.RESTART_TESTS:
189 lambda event: self.restart_tests(root=test_or_root(event)),
190 Event.Type.AUTO_RUN:
191 lambda event: self.auto_run(root=test_or_root(event)),
Ricky Liang45c73e72015-01-15 15:00:30 +0800192 Event.Type.RUN_TESTS_WITH_STATUS:
193 lambda event: self.run_tests_with_status(
194 event.status,
195 root=test_or_root(event)),
Ricky Liang45c73e72015-01-15 15:00:30 +0800196 Event.Type.UPDATE_SYSTEM_INFO:
197 lambda event: self.update_system_info(),
198 Event.Type.STOP:
199 lambda event: self.stop(root=test_or_root(event, False),
200 fail=getattr(event, 'fail', False),
201 reason=getattr(event, 'reason', None)),
Ricky Liang45c73e72015-01-15 15:00:30 +0800202 Event.Type.CLEAR_STATE:
203 lambda event: self.clear_state(
Wei-Han Chen3ae204c2017-04-28 19:36:55 +0800204 self.test_list.LookupPath(event.path)),
Wei-Ning Huang38b75f02015-02-25 18:25:14 +0800205 Event.Type.KEY_FILTER_MODE: self.handle_key_filter_mode,
Jon Salz0697cbf2012-07-04 15:14:04 +0800206 }
207
Jon Salz0697cbf2012-07-04 15:14:04 +0800208 self.web_socket_manager = None
209
210 def destroy(self):
Hung-Te Lina452d4d2017-10-25 17:46:14 +0800211 """Performs any shutdown tasks."""
chuntsen9d675c62017-06-20 14:35:30 +0800212 # To avoid race condition when running shutdown test.
Peter Shih06d08212018-01-19 17:15:57 +0800213 for invoc in self.invocations.itervalues():
214 logging.info('Waiting for %s to complete...', invoc.test)
chuntsen9d675c62017-06-20 14:35:30 +0800215 invoc.thread.join(3) # Timeout in 3 seconds.
216
Jon Salzd7550792013-07-12 05:49:27 +0800217 self.status = Status.TERMINATING
Jon Salz0697cbf2012-07-04 15:14:04 +0800218 if self.chrome:
219 self.chrome.kill()
220 self.chrome = None
Jon Salz0697cbf2012-07-04 15:14:04 +0800221 if self.web_socket_manager:
222 logging.info('Stopping web sockets')
223 self.web_socket_manager.close()
224 self.web_socket_manager = None
Earl Ouacbe99c2017-02-21 16:04:19 +0800225 if self.goofy_server_thread:
226 logging.info('Stopping goofy server')
Peter Shih0a6ae8d2017-10-23 17:59:42 +0800227 net_utils.ShutdownTCPServer(self.goofy_server)
Earl Ouacbe99c2017-02-21 16:04:19 +0800228 self.goofy_server_thread.join()
229 self.goofy_server.server_close()
230 self.goofy_server_thread = None
Jon Salz0697cbf2012-07-04 15:14:04 +0800231 if self.state_instance:
232 self.state_instance.close()
233 if self.event_server_thread:
234 logging.info('Stopping event server')
Peter Shihce9490e2017-05-11 14:32:12 +0800235 net_utils.ShutdownTCPServer(self.event_server)
Jon Salz0697cbf2012-07-04 15:14:04 +0800236 self.event_server_thread.join()
237 self.event_server.server_close()
238 self.event_server_thread = None
239 if self.log_watcher:
240 if self.log_watcher.IsThreadStarted():
241 self.log_watcher.StopWatchThread()
242 self.log_watcher = None
Vic Yange2c76a82014-10-30 12:48:19 -0700243 if self.pytest_prespawner:
244 logging.info('Stopping pytest prespawner')
245 self.pytest_prespawner.stop()
246 self.pytest_prespawner = None
Jon Salz0697cbf2012-07-04 15:14:04 +0800247 if self.event_client:
248 logging.info('Closing event client')
249 self.event_client.close()
250 self.event_client = None
251 if self.event_log:
252 self.event_log.Close()
253 self.event_log = None
Chun-Ta Lin53cbbd52016-06-08 21:42:19 +0800254 if self.testlog:
255 self.testlog.Close()
256 self.testlog = None
Dean Liao592e4d52013-01-10 20:06:39 +0800257 if self.key_filter:
258 self.key_filter.Stop()
Earl Oua3bca122016-10-21 16:00:30 +0800259 if self.plugin_controller:
260 self.plugin_controller.StopAndDestroyAllPlugins()
261 self.plugin_controller = None
Dean Liao592e4d52013-01-10 20:06:39 +0800262
Hung-Te Lina452d4d2017-10-25 17:46:14 +0800263 self.check_exceptions()
Jon Salz0697cbf2012-07-04 15:14:04 +0800264 logging.info('Done destroying Goofy')
Jon Salzd7550792013-07-12 05:49:27 +0800265 self.status = Status.TERMINATED
Jon Salz0697cbf2012-07-04 15:14:04 +0800266
Peter Shihf821c6a2018-03-05 13:31:22 +0800267 def init_goofy_server(self):
Earl Ouacbe99c2017-02-21 16:04:19 +0800268 self.goofy_server = goofy_server.GoofyServer(
Shen-En Shihd5b96bf2017-08-09 17:47:21 +0800269 (goofy_proxy.DEFAULT_GOOFY_BIND, goofy_proxy.DEFAULT_GOOFY_PORT))
Earl Ouacbe99c2017-02-21 16:04:19 +0800270 self.goofy_server_thread = threading.Thread(
271 target=self.goofy_server.serve_forever,
272 name='GoofyServer')
Earl Ouacbe99c2017-02-21 16:04:19 +0800273
Peter Shihbb771342017-10-19 16:42:28 +0800274 def init_static_files(self):
Peter Shih7cc81b12017-08-24 13:04:46 +0800275 static_path = os.path.join(paths.FACTORY_PYTHON_PACKAGE_DIR, 'goofy/static')
Earl Ouacbe99c2017-02-21 16:04:19 +0800276 # Setup static file path
Peter Shih7cc81b12017-08-24 13:04:46 +0800277 self.goofy_server.RegisterPath('/', static_path)
Earl Ouacbe99c2017-02-21 16:04:19 +0800278
279 def init_state_instance(self):
Jon Salz2af235d2013-06-24 14:47:21 +0800280 # Before starting state server, remount stateful partitions with
281 # no commit flag. The default commit time (commit=600) makes corruption
282 # too likely.
Hung-Te Lin1968d9c2016-01-08 22:55:46 +0800283 sys_utils.ResetCommitTime()
Earl Ouacbe99c2017-02-21 16:04:19 +0800284 self.state_instance = state.FactoryState()
285 self.goofy_server.AddRPCInstance(goofy_proxy.STATE_URL, self.state_instance)
Jon Salz2af235d2013-06-24 14:47:21 +0800286
Earl Ouacbe99c2017-02-21 16:04:19 +0800287 # Setup Goofy RPC.
288 # TODO(shunhsingou): separate goofy_rpc and state server instead of
289 # injecting goofy_rpc functions into state.
Jon Salz16d10542012-07-23 12:18:45 +0800290 self.goofy_rpc = GoofyRPC(self)
291 self.goofy_rpc.RegisterMethods(self.state_instance)
Jon Salz0697cbf2012-07-04 15:14:04 +0800292
Peter Shih80e78b42017-03-10 17:00:56 +0800293 def init_i18n(self):
294 js_data = 'var goofy_i18n_data = %s;' % translation.GetAllI18nDataJS()
Peter Shihce03c2e2017-03-21 17:36:10 +0800295 self.goofy_server.RegisterData('/js/goofy-translations.js',
296 'application/javascript', js_data)
Peter Shihf65db932017-03-22 17:06:34 +0800297 self.goofy_server.RegisterData('/css/i18n.css',
298 'text/css', i18n_test_ui.GetStyleSheet())
Peter Shih80e78b42017-03-10 17:00:56 +0800299
Jon Salz0697cbf2012-07-04 15:14:04 +0800300 def start_event_server(self):
301 self.event_server = EventServer()
302 logging.info('Starting factory event server')
303 self.event_server_thread = threading.Thread(
Ricky Liang45c73e72015-01-15 15:00:30 +0800304 target=self.event_server.serve_forever,
Peter Shihfdf17682017-05-26 11:38:39 +0800305 name='EventServer')
Jon Salz0697cbf2012-07-04 15:14:04 +0800306 self.event_server_thread.start()
307
Shen-En Shihd7359cc2017-10-03 16:01:47 +0800308 self.event_client = ThreadingEventClient(
Shen-En Shihba564cb2017-10-05 11:54:16 +0800309 callback=lambda event: self.run_queue.put(
310 lambda: self.handle_event(event)))
Jon Salz0697cbf2012-07-04 15:14:04 +0800311
312 self.web_socket_manager = WebSocketManager(self.uuid)
Earl Ouacbe99c2017-02-21 16:04:19 +0800313 self.goofy_server.AddHTTPGetHandler(
314 '/event', self.web_socket_manager.handle_web_socket)
Jon Salz0697cbf2012-07-04 15:14:04 +0800315
Ricky Liang48e47f92014-02-26 19:31:51 +0800316 def shutdown(self, operation):
317 """Starts shutdown procedure.
318
319 Args:
Vic (Chun-Ju) Yang05b0d952014-04-28 17:39:09 +0800320 operation: The shutdown operation (reboot, full_reboot, or halt).
Ricky Liang48e47f92014-02-26 19:31:51 +0800321 """
322 active_tests = []
Wei-Han Chen3ae204c2017-04-28 19:36:55 +0800323 for test in self.test_list.Walk():
324 if not test.IsLeaf():
Ricky Liang48e47f92014-02-26 19:31:51 +0800325 continue
326
Wei-Han Chen3ae204c2017-04-28 19:36:55 +0800327 test_state = test.GetState()
Ricky Liang48e47f92014-02-26 19:31:51 +0800328 if test_state.status == TestState.ACTIVE:
329 active_tests.append(test)
330
Ricky Liang48e47f92014-02-26 19:31:51 +0800331 if not (len(active_tests) == 1 and
Wei-Han Chen03113912017-09-29 15:58:25 +0800332 isinstance(active_tests[0], test_object.ShutdownStep)):
Ricky Liang48e47f92014-02-26 19:31:51 +0800333 logging.error(
334 'Calling Goofy shutdown outside of the shutdown factory test')
335 return
336
337 logging.info('Start Goofy shutdown (%s)', operation)
338 # Save pending test list in the state server
339 self.state_instance.set_shared_data(
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800340 TESTS_AFTER_SHUTDOWN, self.test_list_iterator)
Ricky Liang48e47f92014-02-26 19:31:51 +0800341 # Save shutdown time
342 self.state_instance.set_shared_data('shutdown_time', time.time())
343
344 with self.env.lock:
345 self.event_log.Log('shutdown', operation=operation)
346 shutdown_result = self.env.shutdown(operation)
347 if shutdown_result:
348 # That's all, folks!
Peter Ammon1e1ec572014-06-26 17:56:32 -0700349 self.run_enqueue(None)
Ricky Liang48e47f92014-02-26 19:31:51 +0800350 else:
351 # Just pass (e.g., in the chroot).
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800352 self.state_instance.set_shared_data(TESTS_AFTER_SHUTDOWN, None)
Ricky Liang48e47f92014-02-26 19:31:51 +0800353 # Send event with no fields to indicate that there is no
354 # longer a pending shutdown.
355 self.event_client.post_event(Event(Event.Type.PENDING_SHUTDOWN))
356
357 def handle_shutdown_complete(self, test):
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +0800358 """Handles the case where a shutdown was detected during a shutdown step.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800359
Ricky Liang6fe218c2013-12-27 15:17:17 +0800360 Args:
361 test: The ShutdownStep.
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +0800362 """
Wei-Han Chen3ae204c2017-04-28 19:36:55 +0800363 test_state = test.UpdateState(increment_shutdown_count=1)
Jon Salz0697cbf2012-07-04 15:14:04 +0800364 logging.info('Detected shutdown (%d of %d)',
Ricky Liang48e47f92014-02-26 19:31:51 +0800365 test_state.shutdown_count, test.iterations)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800366
Ricky Liang48e47f92014-02-26 19:31:51 +0800367 tests_after_shutdown = self.state_instance.get_shared_data(
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800368 TESTS_AFTER_SHUTDOWN, optional=True)
369
370 # Make this shutdown test the next test to run. This is to continue on
371 # post-shutdown verification in the shutdown step.
Ricky Liang48e47f92014-02-26 19:31:51 +0800372 if not tests_after_shutdown:
Wei-Han Chen29663c12017-06-27 10:28:54 +0800373 goofy_error = 'TESTS_AFTER_SHTUDOWN is not set'
Ricky Liang48e47f92014-02-26 19:31:51 +0800374 self.state_instance.set_shared_data(
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800375 TESTS_AFTER_SHUTDOWN, TestListIterator(test))
376 else:
Wei-Han Chen29663c12017-06-27 10:28:54 +0800377 goofy_error = tests_after_shutdown.RestartLastTest()
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800378 self.state_instance.set_shared_data(
379 TESTS_AFTER_SHUTDOWN, tests_after_shutdown)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800380
Ricky Liang48e47f92014-02-26 19:31:51 +0800381 # Set 'post_shutdown' to inform shutdown test that a shutdown just occurred.
Ricky Liangb7eb8772014-09-15 18:05:22 +0800382 self.state_instance.set_shared_data(
Wei-Han Chen29663c12017-06-27 10:28:54 +0800383 state.KEY_POST_SHUTDOWN % test.path,
384 {'invocation': self.state_instance.get_test_state(test.path).invocation,
385 'goofy_error': goofy_error})
Jon Salz258a40c2012-04-19 12:34:01 +0800386
Jon Salz0697cbf2012-07-04 15:14:04 +0800387 def init_states(self):
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +0800388 """Initializes all states on startup."""
Wei-Han Chen3ae204c2017-04-28 19:36:55 +0800389 for test in self.test_list.GetAllTests():
Jon Salz0697cbf2012-07-04 15:14:04 +0800390 # Make sure the state server knows about all the tests,
391 # defaulting to an untested state.
Peter Shih6e578272017-09-12 17:41:43 +0800392 test.UpdateState(update_parent=False)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800393
Earl Ouf76e55c2017-03-07 11:48:34 +0800394 is_unexpected_shutdown = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800395
Jon Salz0697cbf2012-07-04 15:14:04 +0800396 # Any 'active' tests should be marked as failed now.
Wei-Han Chen3ae204c2017-04-28 19:36:55 +0800397 for test in self.test_list.Walk():
398 if not test.IsLeaf():
Jon Salza6711d72012-07-18 14:33:03 +0800399 # Don't bother with parents; they will be updated when their
400 # children are updated.
401 continue
402
Wei-Han Chen3ae204c2017-04-28 19:36:55 +0800403 test_state = test.GetState()
Jon Salz0697cbf2012-07-04 15:14:04 +0800404 if test_state.status != TestState.ACTIVE:
405 continue
Wei-Han Chen03113912017-09-29 15:58:25 +0800406 if isinstance(test, test_object.ShutdownStep):
Jon Salz0697cbf2012-07-04 15:14:04 +0800407 # Shutdown while the test was active - that's good.
Ricky Liang48e47f92014-02-26 19:31:51 +0800408 self.handle_shutdown_complete(test)
Shen-En Shihef6c5ae2017-12-21 14:26:47 +0800409 elif test.allow_reboot:
410 is_unexpected_shutdown = True
411 test.UpdateState(status=TestState.UNTESTED)
412 # For "allow_reboot" tests (such as "Start"), don't cancel
413 # pending tests, since reboot is expected.
414 session.console.info('Unexpected shutdown while test %s was running. '
415 'The test is marked as allow_reboot, continuing '
416 'on pending tests.',
417 test.path)
Jon Salz0697cbf2012-07-04 15:14:04 +0800418 else:
chuntsen61522442017-08-11 14:40:29 +0800419 def get_unexpected_shutdown_test_run():
420 """Returns a StationTestRun for test not collected properly"""
421 station_test_run = testlog.StationTestRun()
422 station_test_run['status'] = testlog.StationTestRun.STATUS.FAILED
423 station_test_run['endTime'] = datetime.datetime.now()
424 station_test_run.AddFailure(
425 'GoofyErrorMsg', 'Unexpected shutdown while test was running')
426 return station_test_run
427
Earl Ouf76e55c2017-03-07 11:48:34 +0800428 is_unexpected_shutdown = True
Jon Salz0697cbf2012-07-04 15:14:04 +0800429 error_msg = 'Unexpected shutdown while test was running'
430 self.event_log.Log('end_test',
Ricky Liang45c73e72015-01-15 15:00:30 +0800431 path=test.path,
432 status=TestState.FAILED,
Wei-Han Chen3ae204c2017-04-28 19:36:55 +0800433 invocation=test.GetState().invocation,
Earl Ouf76e55c2017-03-07 11:48:34 +0800434 error_msg=error_msg)
chuntsen61522442017-08-11 14:40:29 +0800435 testlog.CollectExpiredSessions(paths.DATA_LOG_DIR,
436 get_unexpected_shutdown_test_run())
Wei-Han Chen3ae204c2017-04-28 19:36:55 +0800437 test.UpdateState(
Ricky Liang45c73e72015-01-15 15:00:30 +0800438 status=TestState.FAILED,
439 error_msg=error_msg)
Chun-Ta Lin87c2dac2015-05-02 01:35:01 -0700440 # Trigger the OnTestFailure callback.
Claire Changd1961a22015-08-05 16:15:55 +0800441 self.run_queue.put(lambda: self.test_fail(test))
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800442
Shen-En Shihef6c5ae2017-12-21 14:26:47 +0800443 session.console.info('Unexpected shutdown while test %s '
444 'running; cancelling any pending tests',
445 test.path)
446 # cancel pending tests by replace the iterator with an empty one
447 self.state_instance.set_shared_data(
448 TESTS_AFTER_SHUTDOWN,
449 TestListIterator(None))
Jon Salz008f4ea2012-08-28 05:39:45 +0800450
Earl Ouf76e55c2017-03-07 11:48:34 +0800451 if is_unexpected_shutdown:
452 logging.warning("Unexpected shutdown.")
Wei-Han Chen8d7fbc42017-10-18 19:20:47 +0800453 self.hooks.OnUnexpectedReboot(self)
Earl Ouf76e55c2017-03-07 11:48:34 +0800454
Wei-Han Chen109d76f2017-08-08 18:50:35 +0800455 if self.test_list.options.read_device_data_from_vpd_on_init:
456 vpd_data = {}
457 for section in [device_data.NAME_RO, device_data.NAME_RW]:
458 try:
459 vpd_data[section] = self.dut.vpd.boot.GetPartition(section).GetAll()
460 except Exception:
461 logging.exception('Failed to read %s_VPD, ignored...',
462 section.upper())
463 # using None for key_map will use default key_map
464 device_data.UpdateDeviceDataFromVPD(None, vpd_data)
465
Wei-Han Chen212d2af2017-08-03 18:12:23 +0800466 # state_instance is initialized, we can mark skipped and waived tests now.
467 self.test_list.SetSkippedAndWaivedTests()
468
Jon Salz0697cbf2012-07-04 15:14:04 +0800469 def handle_event(self, event):
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +0800470 """Handles an event from the event server."""
Jon Salz0697cbf2012-07-04 15:14:04 +0800471 handler = self.event_handlers.get(event.type)
472 if handler:
473 handler(event)
474 else:
475 # We don't register handlers for all event types - just ignore
476 # this event.
477 logging.debug('Unbound event type %s', event.type)
Jon Salz4f6c7172012-06-11 20:45:36 +0800478
Vic Yangaabf9fd2013-04-09 18:56:13 +0800479 def check_critical_factory_note(self):
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +0800480 """Returns True if the last factory note is critical."""
Vic Yangaabf9fd2013-04-09 18:56:13 +0800481 notes = self.state_instance.get_shared_data('factory_note', True)
482 return notes and notes[-1]['level'] == 'CRITICAL'
483
Hung-Te Linef7f2be2015-07-20 20:38:51 +0800484 def schedule_restart(self):
485 """Schedules a restart event when any invocation is completed."""
486 self.is_restart_requested = True
487
488 def invocation_completion(self):
489 """Callback when an invocation is completed."""
490 if self.is_restart_requested:
491 logging.info('Restart by scheduled event.')
492 self.is_restart_requested = False
493 self.restart_tests()
494 else:
495 self.run_next_test()
496
Jon Salz0697cbf2012-07-04 15:14:04 +0800497 def run_next_test(self):
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800498 """Runs the next eligible test.
henryhsu4cc6b022014-04-22 17:12:42 +0800499
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800500 self.test_list_iterator (a TestListIterator object) will determine which
501 test should be run.
henryhsu4cc6b022014-04-22 17:12:42 +0800502 """
Jon Salz0697cbf2012-07-04 15:14:04 +0800503 self.reap_completed_tests()
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800504
505 if self.invocations:
506 # there are tests still running, we cannot start new tests
Vic Yangaabf9fd2013-04-09 18:56:13 +0800507 return
Jon Salz94eb56f2012-06-12 18:01:12 +0800508
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800509 if self.check_critical_factory_note():
510 logging.info('has critical factory note, stop running')
Wei-Han Chenbcac7252017-04-21 19:46:51 +0800511 self.test_list_iterator.Stop()
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800512 return
Jon Salz94eb56f2012-06-12 18:01:12 +0800513
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800514 while True:
515 try:
516 path = self.test_list_iterator.next()
Wei-Han Chen3ae204c2017-04-28 19:36:55 +0800517 test = self.test_list.LookupPath(path)
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800518 except StopIteration:
519 logging.info('no next test, stop running')
Jon Salz0697cbf2012-07-04 15:14:04 +0800520 return
Jon Salz94eb56f2012-06-12 18:01:12 +0800521
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800522 # check if we have run all required tests
Jon Salz304a75d2012-07-06 11:14:15 +0800523 untested = set()
Jon Salza1412922012-07-23 16:04:17 +0800524 for requirement in test.require_run:
Wei-Han Chen3ae204c2017-04-28 19:36:55 +0800525 for i in requirement.test.Walk():
Jon Salza1412922012-07-23 16:04:17 +0800526 if i == test:
Jon Salz304a75d2012-07-06 11:14:15 +0800527 # We've hit this test itself; stop checking
528 break
Wei-Han Chen3ae204c2017-04-28 19:36:55 +0800529 if ((i.GetState().status == TestState.UNTESTED) or
Wei-Han Chen3a160172017-07-11 17:31:28 +0800530 (requirement.passed and
531 i.GetState().status not in [TestState.SKIPPED,
532 TestState.PASSED])):
Jon Salz304a75d2012-07-06 11:14:15 +0800533 # Found an untested test; move on to the next
534 # element in require_run.
Jon Salza1412922012-07-23 16:04:17 +0800535 untested.add(i)
Jon Salz304a75d2012-07-06 11:14:15 +0800536 break
537
538 if untested:
539 untested_paths = ', '.join(sorted([x.path for x in untested]))
540 if self.state_instance.get_shared_data('engineering_mode',
541 optional=True):
542 # In engineering mode, we'll let it go.
Hung-Te Lin03f1fc22017-10-16 16:38:31 +0800543 session.console.warn('In engineering mode; running '
Jon Salz304a75d2012-07-06 11:14:15 +0800544 '%s even though required tests '
545 '[%s] have not completed',
546 test.path, untested_paths)
547 else:
548 # Not in engineering mode; mark it failed.
549 error_msg = ('Required tests [%s] have not been run yet'
550 % untested_paths)
Hung-Te Lin03f1fc22017-10-16 16:38:31 +0800551 session.console.error('Not running %s: %s',
Jon Salz304a75d2012-07-06 11:14:15 +0800552 test.path, error_msg)
Wei-Han Chen3ae204c2017-04-28 19:36:55 +0800553 test.UpdateState(status=TestState.FAILED,
554 error_msg=error_msg)
Jon Salz304a75d2012-07-06 11:14:15 +0800555 continue
556
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800557 # okay, let's run the test
Wei-Han Chen03113912017-09-29 15:58:25 +0800558 if (isinstance(test, test_object.ShutdownStep) and
Ricky Liangb7eb8772014-09-15 18:05:22 +0800559 self.state_instance.get_shared_data(
Wei-Han Chen29663c12017-06-27 10:28:54 +0800560 state.KEY_POST_SHUTDOWN % test.path, optional=True)):
Ricky Liang48e47f92014-02-26 19:31:51 +0800561 # Invoking post shutdown method of shutdown test. We should retain the
562 # iterations_left and retries_left of the original test state.
563 test_state = self.state_instance.get_test_state(test.path)
564 self._run_test(test, test_state.iterations_left,
565 test_state.retries_left)
566 else:
567 # Starts a new test run; reset iterations and retries.
568 self._run_test(test, test.iterations, test.retries)
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800569 return # to leave while
Jon Salz1acc8742012-07-17 17:45:55 +0800570
Peter Shih13d2ced2017-09-25 16:25:09 +0800571 def _run_test(self, test, iterations_left=None, retries_left=None,
572 set_layout=True):
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800573 """Invokes the test.
574
575 The argument `test` should be either a leaf test (no subtests) or a parallel
576 test (all subtests should be run in parallel).
577 """
Peter Shih2c2bf262018-01-19 15:31:39 +0800578 if (self.options.goofy_ui and not self._ui_initialized and
579 not test.IsNoHost()):
Vic Yanga3cecf82014-12-26 00:44:21 -0800580 self.init_ui()
Jon Salz1acc8742012-07-17 17:45:55 +0800581
Peter Shih13d2ced2017-09-25 16:25:09 +0800582 if set_layout:
583 self.event_client.post_event(
584 Event(
585 Event.Type.SET_TEST_UI_LAYOUT,
586 layout_type=test.layout_type,
587 layout_options=test.layout_options))
588
Wei-Han Chen3ae204c2017-04-28 19:36:55 +0800589 if test.IsLeaf():
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800590 invoc = TestInvocation(
591 self, test, on_completion=self.invocation_completion,
592 on_test_failure=lambda: self.test_fail(test))
Wei-Han Chen3ae204c2017-04-28 19:36:55 +0800593 new_state = test.UpdateState(
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800594 status=TestState.ACTIVE, increment_count=1, error_msg='',
595 invocation=invoc.uuid, iterations_left=iterations_left,
Peter Shihf5c048d2017-09-01 17:57:51 +0800596 retries_left=retries_left)
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800597 invoc.count = new_state.count
Peter Shih06d08212018-01-19 17:15:57 +0800598 self.invocations[invoc.uuid] = invoc
Peter Shihb2ecd552017-08-24 17:48:58 +0800599 # Send a INIT_TEST_UI event here, so the test UI are initialized in
600 # order, and the tab order would be same as test list order when there
601 # are parallel tests with UI.
602 self.event_client.post_event(
603 Event(
604 Event.Type.INIT_TEST_UI,
Peter Shihb2ecd552017-08-24 17:48:58 +0800605 test=test.path,
606 invocation=invoc.uuid))
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800607 self.check_plugins()
Peter Shih0d4f77a2018-01-19 16:15:49 +0800608 invoc.Start()
Wei-Han Chendc3e3ba2017-07-05 16:49:09 +0800609 elif test.parallel:
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800610 for subtest in test.subtests:
611 # TODO(stimim): what if the subtests *must* be run in parallel?
612 # for example, stressapptest and countdown test.
613
614 # Make sure we don't need to skip it:
Wei-Han Chenbcac7252017-04-21 19:46:51 +0800615 if not self.test_list_iterator.CheckSkip(subtest):
Peter Shih13d2ced2017-09-25 16:25:09 +0800616 self._run_test(subtest, subtest.iterations, subtest.retries,
617 set_layout=False)
Wei-Han Chendc3e3ba2017-07-05 16:49:09 +0800618 else:
619 # This should never happen, there must be something wrong.
620 # However, we can't raise an exception, otherwise goofy will be closed
621 logging.critical(
622 'Goofy should not get a non-leaf test that is not parallel: %r',
623 test)
Hung-Te Lin03f1fc22017-10-16 16:38:31 +0800624 session.console.critical(
Wei-Han Chendc3e3ba2017-07-05 16:49:09 +0800625 'Goofy should not get a non-leaf test that is not parallel: %r',
626 test)
Jon Salz5f2a0672012-05-22 17:14:06 +0800627
Hung-Te Lina452d4d2017-10-25 17:46:14 +0800628 def run(self):
629 """Runs Goofy."""
630 # Process events forever.
631 while self.run_once(True):
632 pass
633
634 def run_enqueue(self, val):
635 """Enqueues an object on the event loop.
636
637 Generally this is a function. It may also be None to indicate that the
638 run queue should shut down.
639 """
640 self.run_queue.put(val)
641
642 def run_once(self, block=False):
643 """Runs all items pending in the event loop.
644
645 Args:
646 block: If true, block until at least one event is processed.
647
648 Returns:
649 True to keep going or False to shut down.
650 """
651 events = type_utils.DrainQueue(self.run_queue)
652 while not events:
653 # Nothing on the run queue.
654 self._run_queue_idle()
655 if block:
656 # Block for at least one event...
657 try:
658 events.append(self.run_queue.get(timeout=RUN_QUEUE_TIMEOUT_SECS))
659 except Queue.Empty:
660 # Keep going (calling _run_queue_idle() again at the top of
661 # the loop)
662 continue
663 # ...and grab anything else that showed up at the same
664 # time.
665 events.extend(type_utils.DrainQueue(self.run_queue))
666 else:
667 break
668
669 for event in events:
670 if not event:
671 # Shutdown request.
672 self.run_queue.task_done()
673 return False
674
675 try:
676 event()
677 except Exception:
678 logging.exception('Error in event loop')
679 self.record_exception(traceback.format_exception_only(
680 *sys.exc_info()[:2]))
681 # But keep going
682 finally:
683 self.run_queue.task_done()
684 return True
685
686 def _run_queue_idle(self):
687 """Invoked when the run queue has no events.
688
689 This method must not raise exception.
690 """
691 now = time.time()
692 if (self.last_idle and
693 now < (self.last_idle + RUN_QUEUE_TIMEOUT_SECS - 1)):
694 # Don't run more often than once every (RUN_QUEUE_TIMEOUT_SECS -
695 # 1) seconds.
696 return
697
698 self.last_idle = now
699 self.perform_periodic_tasks()
700
701 def check_exceptions(self):
702 """Raises an error if any exceptions have occurred in
703 invocation threads.
704 """
705 if self.exceptions:
706 raise RuntimeError('Exception in invocation thread: %r' %
707 self.exceptions)
708
709 def record_exception(self, msg):
710 """Records an exception in an invocation thread.
711
712 An exception with the given message will be rethrown when
713 Goofy is destroyed.
714 """
715 self.exceptions.append(msg)
716
717 @staticmethod
718 def drain_nondaemon_threads():
719 """Wait for all non-current non-daemon threads to exit.
720
721 This is performed by the Python runtime in an atexit handler,
722 but this implementation allows us to do more detailed logging, and
723 to support control-C for abrupt shutdown.
724 """
725 cur_thread = threading.current_thread()
726 all_threads_joined = False
727 while not all_threads_joined:
728 for thread in threading.enumerate():
729 if not thread.daemon and thread.is_alive() and thread is not cur_thread:
730 logging.info("Waiting for thread '%s'...", thread.name)
731 thread.join()
732 # We break rather than continue on because the thread list
733 # may have changed while we waited
734 break
735 else:
736 # No threads remain
737 all_threads_joined = True
738 return all_threads_joined
739
740 @staticmethod
741 def run_main_and_exit():
742 """Instantiate the receiver, run its main function, and exit when done.
743
744 This static method is the "entry point" for Goofy.
745 It instantiates the receiver and invokes its main function, while
746 handling exceptions. When main() finishes (normally or via an exception),
747 it exits the process.
748 """
749 try:
750 cls = Goofy
751 goofy = cls()
752 except Exception:
753 logging.info('Failed to instantiate %s, shutting down.', cls.__name__)
754 traceback.print_exc()
755 os._exit(1) # pylint: disable=protected-access
756 sys.exit(1)
757
758 try:
759 goofy.main()
760 except SystemExit:
761 # Propagate SystemExit without logging.
762 raise
763 except KeyboardInterrupt:
764 logging.info('Interrupted, shutting down...')
765 except Exception:
766 # Log the error before trying to shut down
767 logging.exception('Error in main loop')
768 raise
769 finally:
770 try:
771 # We drain threads manually, rather than letting Python do it,
772 # so that we can report to the user which threads are stuck
773 goofy.destroy()
774 cls.drain_nondaemon_threads()
775 except (KeyboardInterrupt, Exception):
776 # We got a keyboard interrupt while attempting to shut down.
777 # The user is waiting impatiently! This can happen if threads get stuck.
778 # We need to exit via os._exit, not sys.exit, because sys.exit() will
779 # run the main thread's atexit handler, which waits for all threads to
780 # exit, which is likely how we got stuck in the first place. However, we
781 # do want to capture all logs, so we shut down logging gracefully.
782 logging.info('Graceful shutdown interrupted, shutting down abruptly')
783 logging.shutdown()
784 os._exit(1) # pylint: disable=protected-access
785 # Normal exit path
786 sys.exit(0)
787
Earl Oua3bca122016-10-21 16:00:30 +0800788 def check_plugins(self):
789 """Check plugins to be paused or resumed."""
790 exclusive_resources = set()
Peter Shih06d08212018-01-19 17:15:57 +0800791 for invoc in self.invocations.itervalues():
Earl Oua3bca122016-10-21 16:00:30 +0800792 exclusive_resources = exclusive_resources.union(
Peter Shih06d08212018-01-19 17:15:57 +0800793 invoc.test.GetExclusiveResources())
Earl Oua3bca122016-10-21 16:00:30 +0800794 self.plugin_controller.PauseAndResumePluginByResource(exclusive_resources)
795
cychiang21886742012-07-05 15:16:32 +0800796 def check_for_updates(self):
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +0800797 """Schedules an asynchronous check for updates if necessary."""
cychiang21886742012-07-05 15:16:32 +0800798 if not self.test_list.options.update_period_secs:
799 # Not enabled.
800 return
801
802 now = time.time()
803 if self.last_update_check and (
804 now - self.last_update_check <
805 self.test_list.options.update_period_secs):
806 # Not yet time for another check.
807 return
808
809 self.last_update_check = now
810
You-Cheng Syud4a24bf2017-08-21 17:56:48 +0800811 def handle_check_for_update(
Hung-Te Lind151bf32017-08-30 11:05:47 +0800812 reached_server, toolkit_version, needs_update):
813 if reached_server:
You-Cheng Syud4a24bf2017-08-21 17:56:48 +0800814 new_update_toolkit_version = toolkit_version if needs_update else None
815 if self.dut.info.update_toolkit_version != new_update_toolkit_version:
816 logging.info('Received new update TOOLKIT_VERSION: %s',
817 new_update_toolkit_version)
818 self.dut.info.Overrides('update_toolkit_version',
819 new_update_toolkit_version)
Peter Ammon1e1ec572014-06-26 17:56:32 -0700820 self.run_enqueue(self.update_system_info)
You-Cheng Syud4a24bf2017-08-21 17:56:48 +0800821 elif not self._suppress_periodic_update_messages:
822 logging.warning('Suppress error messages for periodic update checking '
823 'after the first one.')
824 self._suppress_periodic_update_messages = True
cychiang21886742012-07-05 15:16:32 +0800825
826 updater.CheckForUpdateAsync(
Hung-Te Lind151bf32017-08-30 11:05:47 +0800827 handle_check_for_update, None, self._suppress_periodic_update_messages)
cychiang21886742012-07-05 15:16:32 +0800828
Jon Salza6711d72012-07-18 14:33:03 +0800829 def cancel_pending_tests(self):
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +0800830 """Cancels any tests in the run queue."""
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800831 self.run_tests(None)
Jon Salza6711d72012-07-18 14:33:03 +0800832
Ricky Liang4bff3e32014-02-20 18:46:11 +0800833 def restore_active_run_state(self):
834 """Restores active run id and the list of scheduled tests."""
835 self.run_id = self.state_instance.get_shared_data('run_id', optional=True)
836 self.scheduled_run_tests = self.state_instance.get_shared_data(
837 'scheduled_run_tests', optional=True)
838
839 def set_active_run_state(self):
840 """Sets active run id and the list of scheduled tests."""
841 self.run_id = str(uuid.uuid4())
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800842 # try our best to predict which tests will be run.
Wei-Han Chenbcac7252017-04-21 19:46:51 +0800843 self.scheduled_run_tests = self.test_list_iterator.GetPendingTests()
Ricky Liang4bff3e32014-02-20 18:46:11 +0800844 self.state_instance.set_shared_data('run_id', self.run_id)
845 self.state_instance.set_shared_data('scheduled_run_tests',
846 self.scheduled_run_tests)
847
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800848 def run_tests(self, subtree, status_filter=None):
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +0800849 """Runs tests under subtree.
Jon Salz258a40c2012-04-19 12:34:01 +0800850
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800851 Run tests under a given subtree.
Jon Salzb1b39092012-05-03 02:05:09 +0800852
Ricky Liang6fe218c2013-12-27 15:17:17 +0800853 Args:
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800854 subtree: root of subtree to run or None to run nothing.
Chih-Yu Huang85dc63c2015-08-12 15:21:28 +0800855 status_filter: List of available test states. Only run the tests which
856 states are in the list. Set to None if all test states are available.
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +0800857 """
Hung-Te Lin793d6572017-09-04 18:17:56 +0800858 self.hooks.OnTestStart()
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800859 self.test_list_iterator = TestListIterator(
860 subtree, status_filter, self.test_list)
861 if subtree is not None:
Ricky Liang4bff3e32014-02-20 18:46:11 +0800862 self.set_active_run_state()
Jon Salz0697cbf2012-07-04 15:14:04 +0800863 self.run_next_test()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800864
Jon Salz0697cbf2012-07-04 15:14:04 +0800865 def reap_completed_tests(self):
Peter Shih0d4f77a2018-01-19 16:15:49 +0800866 """Removes completed tests from the set of active tests."""
Cheng-Yi Chiang5ac22ca2013-04-12 17:45:26 +0800867 test_completed = False
Peter Shih06d08212018-01-19 17:15:57 +0800868 # Since items are removed while iterating, make a copy using values()
869 # instead of itervalues().
870 for invoc in self.invocations.values():
871 test = invoc.test
872 if invoc.IsCompleted():
Cheng-Yi Chiang5ac22ca2013-04-12 17:45:26 +0800873 test_completed = True
Peter Shih06d08212018-01-19 17:15:57 +0800874 new_state = test.UpdateState(**invoc.update_state_on_completion)
875 del self.invocations[invoc.uuid]
Jon Salz0697cbf2012-07-04 15:14:04 +0800876
Johny Lin62ed2a32015-05-13 11:57:12 +0800877 # Stop on failure if flag is true and there is no retry chances.
Chun-Ta Lin54e17e42012-09-06 22:05:13 +0800878 if (self.test_list.options.stop_on_failure and
Johny Lin62ed2a32015-05-13 11:57:12 +0800879 new_state.retries_left < 0 and
Chun-Ta Lin54e17e42012-09-06 22:05:13 +0800880 new_state.status == TestState.FAILED):
881 # Clean all the tests to cause goofy to stop.
Hung-Te Lin03f1fc22017-10-16 16:38:31 +0800882 session.console.info('Stop on failure triggered. Empty the queue.')
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800883 self.cancel_pending_tests()
Chun-Ta Lin54e17e42012-09-06 22:05:13 +0800884
Jon Salz1acc8742012-07-17 17:45:55 +0800885 if new_state.iterations_left and new_state.status == TestState.PASSED:
886 # Play it again, Sam!
Peter Shih06d08212018-01-19 17:15:57 +0800887 self._run_test(test)
Cheng-Yi Chiangce05c002013-04-04 02:13:17 +0800888 # new_state.retries_left is obtained after update.
889 # For retries_left == 0, test can still be run for the last time.
890 elif (new_state.retries_left >= 0 and
891 new_state.status == TestState.FAILED):
892 # Still have to retry, Sam!
Peter Shih06d08212018-01-19 17:15:57 +0800893 self._run_test(test)
Jon Salz1acc8742012-07-17 17:45:55 +0800894
Cheng-Yi Chiang5ac22ca2013-04-12 17:45:26 +0800895 if test_completed:
Vic Yangf01c59f2013-04-19 17:37:56 +0800896 self.log_watcher.KickWatchThread()
Cheng-Yi Chiang5ac22ca2013-04-12 17:45:26 +0800897
Jon Salz6dc031d2013-06-19 13:06:23 +0800898 def kill_active_tests(self, abort, root=None, reason=None):
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +0800899 """Kills and waits for all active tests.
Jon Salz0697cbf2012-07-04 15:14:04 +0800900
Jon Salz85a39882012-07-05 16:45:04 +0800901 Args:
902 abort: True to change state of killed tests to FAILED, False for
Jon Salz0697cbf2012-07-04 15:14:04 +0800903 UNTESTED.
Jon Salz85a39882012-07-05 16:45:04 +0800904 root: If set, only kills tests with root as an ancestor.
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +0800905 reason: If set, the abort reason.
906 """
Jon Salz0697cbf2012-07-04 15:14:04 +0800907 self.reap_completed_tests()
Peter Shih06d08212018-01-19 17:15:57 +0800908 # Since items are removed while iterating, make a copy using values()
909 # instead of itervalues().
910 for invoc in self.invocations.values():
911 test = invoc.test
Wei-Han Chen3ae204c2017-04-28 19:36:55 +0800912 if root and not test.HasAncestor(root):
Jon Salz85a39882012-07-05 16:45:04 +0800913 continue
914
Hung-Te Lin03f1fc22017-10-16 16:38:31 +0800915 session.console.info('Killing active test %s...', test.path)
Peter Shih0d4f77a2018-01-19 16:15:49 +0800916 invoc.AbortAndJoin(reason)
Hung-Te Lin03f1fc22017-10-16 16:38:31 +0800917 session.console.info('Killed %s', test.path)
Wei-Han Chen3ae204c2017-04-28 19:36:55 +0800918 test.UpdateState(**invoc.update_state_on_completion)
Peter Shih06d08212018-01-19 17:15:57 +0800919 del self.invocations[invoc.uuid]
Jon Salz1acc8742012-07-17 17:45:55 +0800920
Jon Salz0697cbf2012-07-04 15:14:04 +0800921 if not abort:
Wei-Han Chen3ae204c2017-04-28 19:36:55 +0800922 test.UpdateState(status=TestState.UNTESTED)
Jon Salz0697cbf2012-07-04 15:14:04 +0800923 self.reap_completed_tests()
924
Jon Salz6dc031d2013-06-19 13:06:23 +0800925 def stop(self, root=None, fail=False, reason=None):
926 self.kill_active_tests(fail, root, reason)
Wei-Han Chenc17b4112016-11-22 14:56:51 +0800927
Wei-Han Chenbcac7252017-04-21 19:46:51 +0800928 self.test_list_iterator.Stop(root)
Jon Salz85a39882012-07-05 16:45:04 +0800929 self.run_next_test()
Jon Salz0697cbf2012-07-04 15:14:04 +0800930
Jon Salz4712ac72013-02-07 17:12:05 +0800931 def clear_state(self, root=None):
Jon Salzd7550792013-07-12 05:49:27 +0800932 if root is None:
933 root = self.test_list
Jon Salz6dc031d2013-06-19 13:06:23 +0800934 self.stop(root, reason='Clearing test state')
Wei-Han Chen3ae204c2017-04-28 19:36:55 +0800935 for f in root.Walk():
936 if f.IsLeaf():
937 f.UpdateState(status=TestState.UNTESTED)
Jon Salz4712ac72013-02-07 17:12:05 +0800938
Jon Salz6dc031d2013-06-19 13:06:23 +0800939 def abort_active_tests(self, reason=None):
940 self.kill_active_tests(True, reason=reason)
Jon Salz0697cbf2012-07-04 15:14:04 +0800941
942 def main(self):
Jon Salzeff94182013-06-19 15:06:28 +0800943 syslog.openlog('goofy')
944
Jon Salz0697cbf2012-07-04 15:14:04 +0800945 try:
Jon Salzd7550792013-07-12 05:49:27 +0800946 self.status = Status.INITIALIZING
Jon Salz0697cbf2012-07-04 15:14:04 +0800947 self.init()
948 self.event_log.Log('goofy_init',
Ricky Liang45c73e72015-01-15 15:00:30 +0800949 success=True)
Chun-Ta Lin53cbbd52016-06-08 21:42:19 +0800950 testlog.Log(
Joel Kitching9eb203a2016-04-21 15:36:30 +0800951 testlog.StationInit({
Hung-Te Linda8eb992017-09-28 03:27:12 +0800952 'stationDeviceId': session.GetDeviceID(),
953 'stationInstallationId': session.GetInstallationID(),
954 'count': session.GetInitCount(),
Joel Kitching9eb203a2016-04-21 15:36:30 +0800955 'success': True}))
Hung-Te Linc8174b52017-06-02 11:11:45 +0800956 except Exception:
Joel Kitching9eb203a2016-04-21 15:36:30 +0800957 try:
958 if self.event_log:
Jon Salz0697cbf2012-07-04 15:14:04 +0800959 self.event_log.Log('goofy_init',
Ricky Liang45c73e72015-01-15 15:00:30 +0800960 success=False,
961 trace=traceback.format_exc())
Chun-Ta Lin53cbbd52016-06-08 21:42:19 +0800962 if self.testlog:
963 testlog.Log(
Joel Kitching9eb203a2016-04-21 15:36:30 +0800964 testlog.StationInit({
Hung-Te Linda8eb992017-09-28 03:27:12 +0800965 'stationDeviceId': session.GetDeviceID(),
966 'stationInstallationId': session.GetInstallationID(),
967 'count': session.GetInitCount(),
Joel Kitching9eb203a2016-04-21 15:36:30 +0800968 'success': False,
969 'failureMessage': traceback.format_exc()}))
Hung-Te Linc8174b52017-06-02 11:11:45 +0800970 except Exception:
Joel Kitching9eb203a2016-04-21 15:36:30 +0800971 pass
Jon Salz0697cbf2012-07-04 15:14:04 +0800972 raise
973
Jon Salzd7550792013-07-12 05:49:27 +0800974 self.status = Status.RUNNING
Jon Salzeff94182013-06-19 15:06:28 +0800975 syslog.syslog('Goofy (factory test harness) starting')
Chun-Ta Lin5d12b592015-06-30 00:54:23 -0700976 syslog.syslog('Boot sequence = %d' % GetBootSequence())
Hung-Te Linda8eb992017-09-28 03:27:12 +0800977 syslog.syslog('Goofy init count = %d' % session.GetInitCount())
Jon Salz0697cbf2012-07-04 15:14:04 +0800978 self.run()
979
980 def update_system_info(self):
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +0800981 """Updates system info."""
Shen-En Shihce8ffe02017-08-01 18:58:09 +0800982 logging.info('Received a notify to update system info.')
983 self.dut.info.Invalidate()
984
985 # Propagate this notify to goofy components
986 try:
987 status_monitor = plugin_controller.GetPluginRPCProxy(
988 'status_monitor.status_monitor')
989 status_monitor.UpdateDeviceInfo()
990 except Exception:
991 logging.debug('Failed to update status monitor plugin.')
Jon Salz0697cbf2012-07-04 15:14:04 +0800992
Wei-Han Chen8d7fbc42017-10-18 19:20:47 +0800993 def set_force_auto_run(self):
994 self.state_instance.set_shared_data(TESTS_AFTER_SHUTDOWN, FORCE_AUTO_RUN)
995
Jon Salzeb42f0d2012-07-27 19:14:04 +0800996 def update_factory(self, auto_run_on_restart=False, post_update_hook=None):
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +0800997 """Commences updating factory software.
Jon Salzeb42f0d2012-07-27 19:14:04 +0800998
999 Args:
1000 auto_run_on_restart: Auto-run when the machine comes back up.
1001 post_update_hook: Code to call after update but immediately before
1002 restart.
1003
1004 Returns:
1005 Never if the update was successful (we just reboot).
1006 False if the update was unnecessary (no update available).
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +08001007 """
Jon Salz6dc031d2013-06-19 13:06:23 +08001008 self.kill_active_tests(False, reason='Factory software update')
Jon Salza6711d72012-07-18 14:33:03 +08001009 self.cancel_pending_tests()
Jon Salz0697cbf2012-07-04 15:14:04 +08001010
Jon Salz5c344f62012-07-13 14:31:16 +08001011 def pre_update_hook():
1012 if auto_run_on_restart:
Wei-Han Chen8d7fbc42017-10-18 19:20:47 +08001013 self.set_force_auto_run()
Jon Salz5c344f62012-07-13 14:31:16 +08001014 self.state_instance.close()
1015
Jon Salzeb42f0d2012-07-27 19:14:04 +08001016 if updater.TryUpdate(pre_update_hook=pre_update_hook):
1017 if post_update_hook:
1018 post_update_hook()
1019 self.env.shutdown('reboot')
Jon Salz0697cbf2012-07-04 15:14:04 +08001020
chuntsen9d675c62017-06-20 14:35:30 +08001021 def handle_signal(self, signum, unused_frame):
1022 names = [signame for signame in dir(signal) if signame.startswith('SIG') and
1023 getattr(signal, signame) == signum]
1024 signal_name = ', '.join(names) if names else 'UNKNOWN'
1025 logging.error('Received signal %s(%d)', signal_name, signum)
Peter Ammon1e1ec572014-06-26 17:56:32 -07001026 self.run_enqueue(None)
Peter Shihbe962972018-02-23 12:46:41 +08001027 raise KeyboardInterrupt
Jon Salz77c151e2012-08-28 07:20:37 +08001028
Jon Salz128b0932013-07-03 16:55:26 +08001029 def GetTestList(self, test_list_id):
1030 """Returns the test list with the given ID.
1031
1032 Raises:
1033 TestListError: The test list ID is not valid.
1034 """
1035 try:
1036 return self.test_lists[test_list_id]
1037 except KeyError:
Peter Shihd14623e2017-11-14 15:12:54 +08001038 raise type_utils.TestListError(
Wei-Han Chendbc0fa22017-09-28 10:29:53 +08001039 '%r is not a valid test list ID (available IDs are %r)' % (
1040 test_list_id, sorted(self.test_lists.keys())))
Jon Salz128b0932013-07-03 16:55:26 +08001041
Chih-Yu Huang1725d622017-03-24 16:08:35 +08001042 def _RecordStartError(self, error_message):
1043 """Appends the startup error message into the shared data."""
1044 KEY = 'startup_error'
1045 data = self.state_instance.get_shared_data(KEY, optional=True)
1046 new_data = '%s\n\n%s' % (data, error_message) if data else error_message
1047 self.state_instance.set_shared_data(KEY, new_data)
1048
Jon Salz128b0932013-07-03 16:55:26 +08001049 def InitTestLists(self):
Joel Kitching50a63ea2016-02-22 13:15:09 +08001050 """Reads in all test lists and sets the active test list.
1051
1052 Returns:
1053 True if the active test list could be set, False if failed.
1054 """
1055 startup_errors = []
Wei-Han Chen16cc5dd2017-04-27 17:38:53 +08001056
1057 self.test_lists, failed_files = self.test_list_manager.BuildAllTestLists()
1058
Wei-Han Chendbc0fa22017-09-28 10:29:53 +08001059 logging.info('Loaded test lists: %r', sorted(self.test_lists.keys()))
Jon Salz128b0932013-07-03 16:55:26 +08001060
Joel Kitching50a63ea2016-02-22 13:15:09 +08001061 # Check for any syntax errors in test list files.
1062 if failed_files:
1063 logging.info('Failed test list files: [%s]',
1064 ' '.join(failed_files.keys()))
1065 for f, exc_info in failed_files.iteritems():
1066 logging.error('Error in test list file: %s', f,
1067 exc_info=exc_info)
1068
1069 # Limit the stack trace to the very last entry.
1070 exc_type, exc_value, exc_traceback = exc_info
1071 while exc_traceback and exc_traceback.tb_next:
1072 exc_traceback = exc_traceback.tb_next
1073
1074 exc_string = ''.join(
1075 traceback.format_exception(
1076 exc_type, exc_value, exc_traceback)).rstrip()
1077 startup_errors.append('Error in test list file (%s):\n%s'
1078 % (f, exc_string))
1079
Jon Salz128b0932013-07-03 16:55:26 +08001080 if not self.options.test_list:
Hung-Te Linab78fc42017-09-22 00:21:57 +08001081 self.options.test_list = self.test_list_manager.GetActiveTestListId()
Jon Salz128b0932013-07-03 16:55:26 +08001082
Joel Kitching50a63ea2016-02-22 13:15:09 +08001083 # Check for a non-existent test list ID.
1084 try:
Wei-Han Chen84fee7c2016-08-26 21:56:25 +08001085 self.test_list = self.GetTestList(self.options.test_list)
Joel Kitching50a63ea2016-02-22 13:15:09 +08001086 logging.info('Active test list: %s', self.test_list.test_list_id)
Peter Shihd14623e2017-11-14 15:12:54 +08001087 except type_utils.TestListError as e:
Joel Kitching50a63ea2016-02-22 13:15:09 +08001088 logging.exception('Invalid active test list: %s',
1089 self.options.test_list)
1090 startup_errors.append(e.message)
Jon Salz128b0932013-07-03 16:55:26 +08001091
Joel Kitching50a63ea2016-02-22 13:15:09 +08001092 # Show all startup errors.
1093 if startup_errors:
Chih-Yu Huang1725d622017-03-24 16:08:35 +08001094 self._RecordStartError('\n\n'.join(startup_errors))
Joel Kitching50a63ea2016-02-22 13:15:09 +08001095
1096 # Only return False if failed to load the active test list.
1097 return bool(self.test_list)
Jon Salz128b0932013-07-03 16:55:26 +08001098
Shuo-Peng Liao268b40b2013-07-01 15:58:59 +08001099 def init_hooks(self):
1100 """Initializes hooks.
1101
1102 Must run after self.test_list ready.
1103 """
Shuo-Peng Liao52b90da2013-06-30 17:00:06 +08001104 module, cls = self.test_list.options.hooks_class.rsplit('.', 1)
1105 self.hooks = getattr(__import__(module, fromlist=[cls]), cls)()
Wei-Han Chen1a114682017-10-02 10:33:54 +08001106 assert isinstance(self.hooks, hooks.Hooks), (
Ricky Liang45c73e72015-01-15 15:00:30 +08001107 'hooks should be of type Hooks but is %r' % type(self.hooks))
Shuo-Peng Liao52b90da2013-06-30 17:00:06 +08001108 self.hooks.test_list = self.test_list
Shuo-Peng Liao268b40b2013-07-01 15:58:59 +08001109 self.hooks.OnCreatedTestList()
Shuo-Peng Liao52b90da2013-06-30 17:00:06 +08001110
Vic Yanga3cecf82014-12-26 00:44:21 -08001111 def init_ui(self):
1112 """Initialize UI."""
Shen-En Shih65619f42017-10-02 16:52:10 +08001113 logging.info('Waiting for a web socket connection')
1114 self.web_socket_manager.wait()
Vic Yanga3cecf82014-12-26 00:44:21 -08001115 self._ui_initialized = True
Vic Yanga3cecf82014-12-26 00:44:21 -08001116
Hung-Te Lin9d81ac72017-09-21 12:58:01 +08001117 @staticmethod
1118 def GetCommandLineArgsParser():
1119 """Returns a parser for Goofy command line arguments."""
Jon Salz0697cbf2012-07-04 15:14:04 +08001120 parser = OptionParser()
1121 parser.add_option('-v', '--verbose', dest='verbose',
Jon Salz8fa8e832012-07-13 19:04:09 +08001122 action='store_true',
1123 help='Enable debug logging')
Jon Salz0697cbf2012-07-04 15:14:04 +08001124 parser.add_option('--print_test_list', dest='print_test_list',
Wei-Han Chen84fee7c2016-08-26 21:56:25 +08001125 metavar='TEST_LIST_ID',
1126 help='Print the content of TEST_LIST_ID and exit')
Jon Salz0697cbf2012-07-04 15:14:04 +08001127 parser.add_option('--restart', dest='restart',
Jon Salz8fa8e832012-07-13 19:04:09 +08001128 action='store_true',
1129 help='Clear all test state')
Jon Salz0697cbf2012-07-04 15:14:04 +08001130 parser.add_option('--test_list', dest='test_list',
Wei-Han Chen84fee7c2016-08-26 21:56:25 +08001131 metavar='TEST_LIST_ID',
1132 help='Use test list whose id is TEST_LIST_ID')
Peter Shih2c2bf262018-01-19 15:31:39 +08001133 parser.add_option('--no-goofy-ui', dest='goofy_ui',
1134 action='store_false', default=True,
1135 help='start without Goofy UI')
Hung-Te Lin9d81ac72017-09-21 12:58:01 +08001136 return parser
1137
Shen-En Shih8227d5d2018-02-06 19:45:39 +08001138 def _PrepareDUTLink(self):
1139 # TODO(akahuang): Move this part into a pytest.
1140 # Prepare DUT link after the plugins start running, because the link might
1141 # need the network connection.
1142
1143 dut_options = self.test_list.options.dut_options
1144 if dut_options:
1145 logging.info('dut_options set by %s: %r', self.test_list.test_list_id,
1146 self.test_list.options.dut_options)
1147
1148 def prepare_link():
1149 try:
1150 device_utils.PrepareDUTLink(**dut_options)
1151 except Exception:
1152 logging.exception('Unable to prepare DUT link.')
1153
1154 thread = threading.Thread(target=prepare_link)
1155 thread.daemon = True
1156 thread.start()
1157
Hung-Te Lin9d81ac72017-09-21 12:58:01 +08001158 def init(self, args=None, env=None):
1159 """Initializes Goofy.
1160
1161 Args:
Shen-En Shihe05cbbc2017-10-02 16:47:30 +08001162 args: A list of command-line arguments. Uses sys.argv if args is None.
1163 env: An Environment instance to use (or None to use DUTEnvironment).
Hung-Te Lin9d81ac72017-09-21 12:58:01 +08001164 """
1165 (self.options, self.args) = self.GetCommandLineArgsParser().parse_args(args)
Jon Salz0697cbf2012-07-04 15:14:04 +08001166
Joel Kitching261e0422017-03-30 16:52:01 -07001167 signal.signal(signal.SIGINT, self.handle_signal)
1168 signal.signal(signal.SIGTERM, self.handle_signal)
Hung-Te Lina846f602014-07-04 20:32:22 +08001169 # TODO(hungte) SIGTERM does not work properly without Telemetry and should
1170 # be fixed.
Hung-Te Lina846f602014-07-04 20:32:22 +08001171
Jon Salz46b89562012-07-05 11:49:22 +08001172 # Make sure factory directories exist.
Peter Shihb4e49352017-05-25 17:35:11 +08001173 for path in [
1174 paths.DATA_LOG_DIR, paths.DATA_STATE_DIR, paths.DATA_TESTS_DIR]:
1175 file_utils.TryMakeDirs(path)
Jon Salz46b89562012-07-05 11:49:22 +08001176
Wei-Han Chen78f35f62017-03-06 20:11:20 +08001177 try:
1178 goofy_default_options = config_utils.LoadConfig(validate_schema=False)
1179 for key, value in goofy_default_options.iteritems():
1180 if getattr(self.options, key, None) is None:
1181 logging.info('self.options.%s = %r', key, value)
1182 setattr(self.options, key, value)
1183 except Exception:
1184 logging.exception('failed to load goofy overriding options')
1185
Jon Salz0f996602012-10-03 15:26:48 +08001186 if self.options.print_test_list:
Wei-Han Chen16cc5dd2017-04-27 17:38:53 +08001187 all_test_lists, unused_errors = self.test_list_manager.BuildAllTestLists()
1188 test_list = (
1189 all_test_lists[self.options.print_test_list].ToFactoryTestList())
Wei-Han Chen84fee7c2016-08-26 21:56:25 +08001190 print(test_list.__repr__(recursive=True))
Jon Salz0f996602012-10-03 15:26:48 +08001191 sys.exit(0)
1192
Jon Salzee85d522012-07-17 14:34:46 +08001193 event_log.IncrementBootSequence()
Hung-Te Linda8eb992017-09-28 03:27:12 +08001194 session.IncrementInitCount()
Chun-Ta Lin53cbbd52016-06-08 21:42:19 +08001195
Jon Salzd15bbcf2013-05-21 17:33:57 +08001196 # Don't defer logging the initial event, so we can make sure
1197 # that device_id, reimage_id, etc. are all set up.
1198 self.event_log = EventLog('goofy', defer=False)
Chun-Ta Lin53cbbd52016-06-08 21:42:19 +08001199 self.testlog = testlog.Testlog(
Peter Shihb4e49352017-05-25 17:35:11 +08001200 log_root=paths.DATA_LOG_DIR, uuid=self.uuid,
Hung-Te Linda8eb992017-09-28 03:27:12 +08001201 stationDeviceId=session.GetDeviceID(),
1202 stationInstallationId=session.GetInstallationID())
Jon Salz0697cbf2012-07-04 15:14:04 +08001203
Jon Salz0697cbf2012-07-04 15:14:04 +08001204 if env:
1205 self.env = env
Shen-En Shihe05cbbc2017-10-02 16:47:30 +08001206 else:
Ricky Liang09d66d82014-09-25 11:20:54 +08001207 self.env = test_environment.DUTEnvironment()
Jon Salz0697cbf2012-07-04 15:14:04 +08001208 self.env.goofy = self
1209
1210 if self.options.restart:
1211 state.clear_state()
1212
Peter Shihf821c6a2018-03-05 13:31:22 +08001213 self.init_goofy_server()
1214 # Both the i18n file and index.html should be registered to Goofy before we
1215 # start the Goofy server, to avoid race condition that Goofy would return
1216 # 404 not found before index.html is registered.
Peter Shih80e78b42017-03-10 17:00:56 +08001217 self.init_i18n()
Peter Shihbb771342017-10-19 16:42:28 +08001218 self.init_static_files()
Peter Shihf821c6a2018-03-05 13:31:22 +08001219
1220 logging.info('Starting goofy server')
1221 self.goofy_server_thread.start()
1222
Peter Shihbb771342017-10-19 16:42:28 +08001223 self.init_state_instance()
Jon Salz0697cbf2012-07-04 15:14:04 +08001224 self.last_shutdown_time = (
Ricky Liang45c73e72015-01-15 15:00:30 +08001225 self.state_instance.get_shared_data('shutdown_time', optional=True))
Jon Salz0697cbf2012-07-04 15:14:04 +08001226 self.state_instance.del_shared_data('shutdown_time', optional=True)
Jon Salzb19ea072013-02-07 16:35:00 +08001227 self.state_instance.del_shared_data('startup_error', optional=True)
Jon Salz0697cbf2012-07-04 15:14:04 +08001228
Joel Kitching50a63ea2016-02-22 13:15:09 +08001229 success = False
Jon Salz128b0932013-07-03 16:55:26 +08001230 try:
Joel Kitching50a63ea2016-02-22 13:15:09 +08001231 success = self.InitTestLists()
Hung-Te Linc8174b52017-06-02 11:11:45 +08001232 except Exception:
Shen-En Shih44107d12017-10-02 11:31:12 +08001233 logging.exception('Unable to initialize test lists')
1234 self._RecordStartError(
1235 'Unable to initialize test lists\n%s' % traceback.format_exc())
Joel Kitching50a63ea2016-02-22 13:15:09 +08001236
1237 if not success:
Shen-En Shih65619f42017-10-02 16:52:10 +08001238 # Create an empty test list with default options so that the rest of
1239 # startup can proceed.
1240 # A message box will pop up in UI for the error details.
Wei-Han Chena14210e2017-10-16 14:07:52 +08001241 self.test_list = manager.DummyTestList(self.test_list_manager)
1242
1243 self.test_list.state_instance = self.state_instance
Jon Salzb19ea072013-02-07 16:35:00 +08001244
Shuo-Peng Liao268b40b2013-07-01 15:58:59 +08001245 self.init_hooks()
chuntsen6780ea22017-12-12 14:53:53 +08001246 self.testlog.init_hooks(self.test_list.options.testlog_hooks)
Shuo-Peng Liao268b40b2013-07-01 15:58:59 +08001247
Jon Salz822838b2013-03-25 17:32:33 +08001248 if self.test_list.options.clear_state_on_start:
Wei-Han Chendcecbea2018-03-14 19:00:23 +08001249 # TODO(stimim): Perhaps we should check if we are running `shutdown` test?
Jon Salz822838b2013-03-25 17:32:33 +08001250 self.state_instance.clear_test_state()
1251
Jon Salz670ce062014-05-16 15:53:50 +08001252 # If the phase is invalid, this will raise a ValueError.
1253 phase.SetPersistentPhase(self.test_list.options.phase)
1254
Peter Shih3b0bb9f2017-03-21 16:23:32 +08001255 if not self.state_instance.has_shared_data('ui_locale'):
Hung-Te Lin134403c2017-08-23 17:30:17 +08001256 ui_locale = self.test_list.options.ui_locale
Peter Shih3b0bb9f2017-03-21 16:23:32 +08001257 self.state_instance.set_shared_data('ui_locale', ui_locale)
Jon Salz0697cbf2012-07-04 15:14:04 +08001258 self.state_instance.set_shared_data(
Ricky Liang45c73e72015-01-15 15:00:30 +08001259 'test_list_options',
Peter Shih90425db2017-08-02 15:53:48 +08001260 self.test_list.options.ToDict())
Jon Salz0697cbf2012-07-04 15:14:04 +08001261 self.state_instance.test_list = self.test_list
1262
1263 self.init_states()
1264 self.start_event_server()
Hung-Te Lincc41d2a2014-10-29 13:35:20 +08001265
Earl Oua3bca122016-10-21 16:00:30 +08001266 # Load and run Goofy plugins.
1267 self.plugin_controller = plugin_controller.PluginController(
1268 self.test_list.options.plugin_config_name, self)
1269 self.plugin_controller.StartAllPlugins()
1270
Chih-Yu Huang97103ae2017-03-20 18:22:54 +08001271 if success:
Shen-En Shih8227d5d2018-02-06 19:45:39 +08001272 self._PrepareDUTLink()
Chih-Yu Huang97103ae2017-03-20 18:22:54 +08001273
Jon Salz0697cbf2012-07-04 15:14:04 +08001274 # Note that we create a log watcher even if
1275 # sync_event_log_period_secs isn't set (no background
1276 # syncing), since we may use it to flush event logs as well.
1277 self.log_watcher = EventLogWatcher(
Ricky Liang45c73e72015-01-15 15:00:30 +08001278 self.test_list.options.sync_event_log_period_secs,
1279 event_log_db_file=None,
1280 handle_event_logs_callback=self.handle_event_logs)
Jon Salz0697cbf2012-07-04 15:14:04 +08001281 if self.test_list.options.sync_event_log_period_secs:
1282 self.log_watcher.StartWatchThread()
1283
Shen-En Shihf4ad32f2017-07-31 15:56:39 +08001284 self.event_client.post_event(
1285 Event(Event.Type.UPDATE_SYSTEM_INFO))
Jon Salz0697cbf2012-07-04 15:14:04 +08001286
1287 os.environ['CROS_FACTORY'] = '1'
1288 os.environ['CROS_DISABLE_SITE_SYSINFO'] = '1'
1289
Shuo-Peng Liao268b40b2013-07-01 15:58:59 +08001290 # Should not move earlier.
1291 self.hooks.OnStartup()
1292
Ricky Liang36512a32014-07-25 11:47:04 +08001293 # Only after this point the Goofy backend is ready for UI connection.
1294 self.ready_for_ui_connection = True
1295
Jon Salz0697cbf2012-07-04 15:14:04 +08001296 def state_change_callback(test, test_state):
1297 self.event_client.post_event(
Peter Shihd46c5fd2017-09-22 15:28:42 +08001298 Event(
1299 Event.Type.STATE_CHANGE,
1300 path=test.path,
1301 state=test_state.ToStruct()))
Jon Salz0697cbf2012-07-04 15:14:04 +08001302 self.test_list.state_change_callback = state_change_callback
Jon Salz73e0fd02012-04-04 11:46:38 +08001303
Vic Yange2c76a82014-10-30 12:48:19 -07001304 self.pytest_prespawner = prespawner.PytestPrespawner()
1305 self.pytest_prespawner.start()
Jon Salza6711d72012-07-18 14:33:03 +08001306
Ricky Liang48e47f92014-02-26 19:31:51 +08001307 tests_after_shutdown = self.state_instance.get_shared_data(
Wei-Han Chenc17b4112016-11-22 14:56:51 +08001308 TESTS_AFTER_SHUTDOWN, optional=True)
Jon Salz5c344f62012-07-13 14:31:16 +08001309 force_auto_run = (tests_after_shutdown == FORCE_AUTO_RUN)
Wei-Han Chenc17b4112016-11-22 14:56:51 +08001310
Jon Salz5c344f62012-07-13 14:31:16 +08001311 if not force_auto_run and tests_after_shutdown is not None:
Wei-Han Chenc17b4112016-11-22 14:56:51 +08001312 logging.info('Resuming tests after shutdown: %r', tests_after_shutdown)
1313 self.test_list_iterator = tests_after_shutdown
Wei-Han Chenbcac7252017-04-21 19:46:51 +08001314 self.test_list_iterator.SetTestList(self.test_list)
Peter Ammon1e1ec572014-06-26 17:56:32 -07001315 self.run_enqueue(self.run_next_test)
Wei-Han Chenc17b4112016-11-22 14:56:51 +08001316 elif force_auto_run or self.test_list.options.auto_run_on_start:
Peter Shih53323922018-01-02 15:02:21 +08001317 status_filter = [TestState.UNTESTED]
1318 if self.test_list.options.retry_failed_on_start:
1319 status_filter.append(TestState.FAILED)
1320 self.run_enqueue(lambda: self.run_tests(self.test_list, status_filter))
Wei-Han Chenc17b4112016-11-22 14:56:51 +08001321 self.state_instance.set_shared_data(TESTS_AFTER_SHUTDOWN, None)
Ricky Liang4bff3e32014-02-20 18:46:11 +08001322 self.restore_active_run_state()
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001323
Hung-Te Lin793d6572017-09-04 18:17:56 +08001324 self.hooks.OnTestStart()
Vic Yang08505c72015-01-06 17:01:53 -08001325
Dean Liao592e4d52013-01-10 20:06:39 +08001326 self.may_disable_cros_shortcut_keys()
1327
1328 def may_disable_cros_shortcut_keys(self):
1329 test_options = self.test_list.options
1330 if test_options.disable_cros_shortcut_keys:
1331 logging.info('Filter ChromeOS shortcut keys.')
1332 self.key_filter = KeyFilter(
1333 unmap_caps_lock=test_options.disable_caps_lock,
1334 caps_lock_keycode=test_options.caps_lock_keycode)
1335 self.key_filter.Start()
1336
Peter Ammon1e1ec572014-06-26 17:56:32 -07001337 def perform_periodic_tasks(self):
Hung-Te Lina452d4d2017-10-25 17:46:14 +08001338 """Perform any periodic work.
Vic Yang4953fc12012-07-26 16:19:53 +08001339
Peter Ammon1e1ec572014-06-26 17:56:32 -07001340 This method must not raise exceptions.
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +08001341 """
Earl Oua3bca122016-10-21 16:00:30 +08001342 self.check_plugins()
cychiang21886742012-07-05 15:16:32 +08001343 self.check_for_updates()
Jon Salz57717ca2012-04-04 16:47:25 +08001344
Cheng-Yi Chiangf5b21012015-03-17 15:37:14 +08001345 def handle_event_logs(self, chunks, periodic=False):
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +08001346 """Callback for event watcher.
Jon Salz258a40c2012-04-19 12:34:01 +08001347
Hung-Te Lind151bf32017-08-30 11:05:47 +08001348 Attempts to upload the event logs to the factory server.
Vic Yang93027612013-05-06 02:42:49 +08001349
1350 Args:
Jon Salzd15bbcf2013-05-21 17:33:57 +08001351 chunks: A list of Chunk objects.
Cheng-Yi Chiangf5b21012015-03-17 15:37:14 +08001352 periodic: This event log handling is periodic. Error messages
1353 will only be shown for the first time.
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +08001354 """
Vic Yang93027612013-05-06 02:42:49 +08001355 first_exception = None
1356 exception_count = 0
Cheng-Yi Chiangf5b21012015-03-17 15:37:14 +08001357 # Suppress error messages for periodic event syncing except for the
1358 # first time. If event syncing is not periodic, always show the error
1359 # messages.
1360 quiet = self._suppress_event_log_error_messages if periodic else False
Vic Yang93027612013-05-06 02:42:49 +08001361
Jon Salzd15bbcf2013-05-21 17:33:57 +08001362 for chunk in chunks:
Vic Yang93027612013-05-06 02:42:49 +08001363 try:
Jon Salzcddb6402013-05-23 12:56:42 +08001364 description = 'event logs (%s)' % str(chunk)
Vic Yang93027612013-05-06 02:42:49 +08001365 start_time = time.time()
Hung-Te Lind151bf32017-08-30 11:05:47 +08001366 proxy = server_proxy.GetServerProxy(quiet=quiet)
1367 proxy.UploadEvent(
1368 chunk.log_name + '.' + event_log.GetReimageId(),
1369 Binary(chunk.chunk))
Vic Yang93027612013-05-06 02:42:49 +08001370 logging.info(
Ricky Liang45c73e72015-01-15 15:00:30 +08001371 'Successfully synced %s in %.03f s',
1372 description, time.time() - start_time)
Hung-Te Linc8174b52017-06-02 11:11:45 +08001373 except Exception:
Hung-Te Linf707b242016-01-08 23:11:42 +08001374 first_exception = (first_exception or
1375 (chunk.log_name + ': ' +
1376 debug_utils.FormatExceptionOnly()))
Vic Yang93027612013-05-06 02:42:49 +08001377 exception_count += 1
1378
1379 if exception_count:
1380 if exception_count == 1:
1381 msg = 'Log upload failed: %s' % first_exception
1382 else:
1383 msg = '%d log upload failed; first is: %s' % (
1384 exception_count, first_exception)
Cheng-Yi Chiangf5b21012015-03-17 15:37:14 +08001385 # For periodic event log syncing, only show the first error messages.
1386 if periodic:
1387 if not self._suppress_event_log_error_messages:
1388 self._suppress_event_log_error_messages = True
Hung-Te Lind151bf32017-08-30 11:05:47 +08001389 logging.warning('Suppress periodic factory server error messages for '
Cheng-Yi Chiangf5b21012015-03-17 15:37:14 +08001390 'event log syncing after the first one.')
1391 raise Exception(msg)
1392 # For event log syncing by request, show the error messages.
1393 else:
1394 raise Exception(msg)
Vic Yang93027612013-05-06 02:42:49 +08001395
Wei-Han Chenc17b4112016-11-22 14:56:51 +08001396 def run_tests_with_status(self, statuses_to_run, root=None):
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +08001397 """Runs all top-level tests with a particular status.
Jon Salz0405ab52012-03-16 15:26:52 +08001398
Jon Salz0697cbf2012-07-04 15:14:04 +08001399 All active tests, plus any tests to re-run, are reset.
Jon Salz57717ca2012-04-04 16:47:25 +08001400
Jon Salz0697cbf2012-07-04 15:14:04 +08001401 Args:
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +08001402 statuses_to_run: The particular status that caller wants to run.
Jon Salz0697cbf2012-07-04 15:14:04 +08001403 starting_at: If provided, only auto-runs tests beginning with
1404 this test.
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +08001405 root: The root of tests to run. If not provided, it will be
1406 the root of all tests.
1407 """
Jon Salz0697cbf2012-07-04 15:14:04 +08001408 root = root or self.test_list
Jon Salz6dc031d2013-06-19 13:06:23 +08001409 self.abort_active_tests('Operator requested run/re-run of certain tests')
Wei-Han Chenc17b4112016-11-22 14:56:51 +08001410 self.run_tests(root, status_filter=statuses_to_run)
Jon Salz0405ab52012-03-16 15:26:52 +08001411
Jon Salz0697cbf2012-07-04 15:14:04 +08001412 def restart_tests(self, root=None):
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +08001413 """Restarts all tests."""
Jon Salz0697cbf2012-07-04 15:14:04 +08001414 root = root or self.test_list
Jon Salz0405ab52012-03-16 15:26:52 +08001415
Jon Salz6dc031d2013-06-19 13:06:23 +08001416 self.abort_active_tests('Operator requested restart of certain tests')
Wei-Han Chen3ae204c2017-04-28 19:36:55 +08001417 for test in root.Walk():
1418 test.UpdateState(status=TestState.UNTESTED)
Jon Salz0697cbf2012-07-04 15:14:04 +08001419 self.run_tests(root)
Hung-Te Lin96632362012-03-20 21:14:18 +08001420
Wei-Han Chenc17b4112016-11-22 14:56:51 +08001421 def auto_run(self, root=None):
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +08001422 """"Auto-runs" tests that have not been run yet.
Hung-Te Lin96632362012-03-20 21:14:18 +08001423
Jon Salz0697cbf2012-07-04 15:14:04 +08001424 Args:
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +08001425 root: If provided, the root of tests to run. If not provided, the root
1426 will be test_list (root of all tests).
1427 """
Jon Salz0697cbf2012-07-04 15:14:04 +08001428 root = root or self.test_list
1429 self.run_tests_with_status([TestState.UNTESTED, TestState.ACTIVE],
Ricky Liang45c73e72015-01-15 15:00:30 +08001430 root=root)
Jon Salz968e90b2012-03-18 16:12:43 +08001431
Wei-Ning Huang38b75f02015-02-25 18:25:14 +08001432 def handle_key_filter_mode(self, event):
1433 if self.key_filter:
1434 if getattr(event, 'enabled'):
1435 self.key_filter.Start()
1436 else:
1437 self.key_filter.Stop()
1438
Jon Salz0697cbf2012-07-04 15:14:04 +08001439 def wait(self):
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +08001440 """Waits for all pending invocations.
Jon Salz0697cbf2012-07-04 15:14:04 +08001441
1442 Useful for testing.
Cheng-Yi Chiang1e3e2692013-12-24 18:02:36 +08001443 """
Jon Salz1acc8742012-07-17 17:45:55 +08001444 while self.invocations:
Peter Shih06d08212018-01-19 17:15:57 +08001445 for invoc in self.invocations.itervalues():
1446 logging.info('Waiting for %s to complete...', invoc.test)
1447 invoc.thread.join()
Jon Salz1acc8742012-07-17 17:45:55 +08001448 self.reap_completed_tests()
Jon Salz0697cbf2012-07-04 15:14:04 +08001449
Claire Changd1961a22015-08-05 16:15:55 +08001450 def test_fail(self, test):
Hung-Te Lin793d6572017-09-04 18:17:56 +08001451 self.hooks.OnTestFailure(test)
Claire Changd1961a22015-08-05 16:15:55 +08001452
Wei-Han Chenced08ef2016-11-08 09:40:02 +08001453
Hung-Te Lin9d81ac72017-09-21 12:58:01 +08001454def main():
1455 # Logging should be solved first.
1456 (options, unused_args) = Goofy.GetCommandLineArgsParser().parse_args()
Hung-Te Lin8fc0d652017-09-21 13:05:44 +08001457 log_utils.InitLogging(verbose=options.verbose)
Hung-Te Lin9d81ac72017-09-21 12:58:01 +08001458
Peter Ammona3d298c2014-09-23 10:11:02 -07001459 Goofy.run_main_and_exit()
Hung-Te Lin9d81ac72017-09-21 12:58:01 +08001460
1461
1462if __name__ == '__main__':
1463 main()