blob: d5e3d343ebfc017dda7f9b5830681b4f830db507 [file] [log] [blame]
Aviv Keshet021c19f2013-02-22 13:19:43 -08001#!/usr/bin/python
2# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import argparse
Aviv Keshet0e5d5252013-04-26 16:01:36 -07007import logging
8import os
Aviv Keshetd4a04302013-04-30 15:48:30 -07009import re
Aviv Keshet1d991ea2013-06-12 17:24:23 -070010import signal
Aviv Keshetc8824402013-06-29 20:37:30 -070011import stat
Aviv Keshet0e5d5252013-04-26 16:01:36 -070012import subprocess
Aviv Keshet021c19f2013-02-22 13:19:43 -080013import sys
Aviv Keshetd4a04302013-04-30 15:48:30 -070014import tempfile
Aviv Keshet1d991ea2013-06-12 17:24:23 -070015import threading
Aviv Keshet021c19f2013-02-22 13:19:43 -080016
17import common
Aviv Keshet1d991ea2013-06-12 17:24:23 -070018from autotest_lib.client.common_lib.cros import dev_server, retry
Aviv Keshetd4a04302013-04-30 15:48:30 -070019from autotest_lib.server.cros.dynamic_suite import suite
20from autotest_lib.server.cros.dynamic_suite import constants
21from autotest_lib.server import autoserv_utils
22
Aviv Keshet0e5d5252013-04-26 16:01:36 -070023try:
24 from chromite.lib import cros_build_lib
25except ImportError:
26 print 'Unable to import chromite.'
27 print 'This script must be either:'
28 print ' - Be run in the chroot.'
29 print ' - (not yet supported) be run after running '
30 print ' ../utils/build_externals.py'
Aviv Keshet021c19f2013-02-22 13:19:43 -080031
Aviv Keshet1d991ea2013-06-12 17:24:23 -070032_autoserv_proc = None
33_sigint_handler_lock = threading.Lock()
34
35_AUTOSERV_SIGINT_TIMEOUT_SECONDS = 5
Alex Millera0913072013-06-12 10:01:51 -070036_NO_BOARD = 'ad_hoc_board'
Aviv Keshet10711962013-06-24 12:20:33 -070037_NO_BUILD = 'ad_hoc_build'
Aviv Keshet1d991ea2013-06-12 17:24:23 -070038
Aviv Keshet2dc98932013-06-17 15:22:10 -070039_QUICKMERGE_SCRIPTNAME = '/mnt/host/source/chromite/bin/autotest_quickmerge'
40
Aviv Keshetba4992a2013-07-02 14:09:23 -070041_TEST_REPORT_SCRIPTNAME = '/usr/bin/generate_test_report'
42
Aviv Keshetd4a04302013-04-30 15:48:30 -070043
Aviv Keshet10711962013-06-24 12:20:33 -070044def schedule_local_suite(autotest_path, suite_name, afe, build=_NO_BUILD,
Aviv Keshetba4992a2013-07-02 14:09:23 -070045 board=_NO_BOARD, results_directory=None):
Aviv Keshetd4a04302013-04-30 15:48:30 -070046 """
47 Schedule a suite against a mock afe object, for a local suite run.
48 @param autotest_path: Absolute path to autotest (in sysroot).
49 @param suite_name: Name of suite to schedule.
50 @param afe: afe object to schedule against (typically a directAFE)
51 @param build: Build to schedule suite for.
Aviv Keshet10711962013-06-24 12:20:33 -070052 @param board: Board to schedule suite for.
Aviv Keshetba4992a2013-07-02 14:09:23 -070053 @param results_directory: Absolute path of directory to store results in.
54 (results will be stored in subdirectory of this).
Aviv Keshet69ebb6c2013-06-11 13:58:44 -070055 @returns: The number of tests scheduled.
Aviv Keshetd4a04302013-04-30 15:48:30 -070056 """
57 fs_getter = suite.Suite.create_fs_getter(autotest_path)
58 devserver = dev_server.ImageServer('')
Aviv Keshet10711962013-06-24 12:20:33 -070059 my_suite = suite.Suite.create_from_name(suite_name, build, board,
Aviv Keshetba4992a2013-07-02 14:09:23 -070060 devserver, fs_getter, afe=afe, ignore_deps=True,
61 results_dir=results_directory)
Aviv Keshetd4a04302013-04-30 15:48:30 -070062 if len(my_suite.tests) == 0:
63 raise ValueError('Suite named %s does not exist, or contains no '
64 'tests.' % suite_name)
65 my_suite.schedule(lambda x: None) # Schedule tests, discard record calls.
Aviv Keshet69ebb6c2013-06-11 13:58:44 -070066 return len(my_suite.tests)
Aviv Keshet021c19f2013-02-22 13:19:43 -080067
68
Aviv Keshet10711962013-06-24 12:20:33 -070069def schedule_local_test(autotest_path, test_name, afe, build=_NO_BUILD,
Aviv Keshetba4992a2013-07-02 14:09:23 -070070 board=_NO_BOARD, results_directory=None):
Aviv Keshetd4a04302013-04-30 15:48:30 -070071 #temporarily disabling pylint
72 #pylint: disable-msg=C0111
73 """
74 Schedule an individual test against a mock afe object, for a local run.
Aviv Keshet69ebb6c2013-06-11 13:58:44 -070075 @param autotest_path: Absolute path to autotest (in sysroot).
76 @param test_name: Name of test to schedule.
77 @param afe: afe object to schedule against (typically a directAFE)
78 @param build: Build to schedule suite for.
Aviv Keshet10711962013-06-24 12:20:33 -070079 @param board: Board to schedule suite for.
Aviv Keshetba4992a2013-07-02 14:09:23 -070080 @param results_directory: Absolute path of directory to store results in.
81 (results will be stored in subdirectory of this).
Aviv Keshet69ebb6c2013-06-11 13:58:44 -070082 @returns: The number of tests scheduled (may be >1 if there are
83 multiple tests with the same name).
Aviv Keshetd4a04302013-04-30 15:48:30 -070084 """
Aviv Keshet69ebb6c2013-06-11 13:58:44 -070085 fs_getter = suite.Suite.create_fs_getter(autotest_path)
86 devserver = dev_server.ImageServer('')
87 predicates = [suite.Suite.test_name_equals_predicate(test_name)]
88 suite_name = 'suite_' + test_name
Aviv Keshet10711962013-06-24 12:20:33 -070089 my_suite = suite.Suite.create_from_predicates(predicates, build, board,
Aviv Keshetba4992a2013-07-02 14:09:23 -070090 devserver, fs_getter, afe=afe, name=suite_name, ignore_deps=True,
91 results_dir=results_directory)
Aviv Keshet69ebb6c2013-06-11 13:58:44 -070092 if len(my_suite.tests) == 0:
93 raise ValueError('No tests named %s.' % test_name)
94 my_suite.schedule(lambda x: None) # Schedule tests, discard record calls.
95 return len(my_suite.tests)
Aviv Keshetd4a04302013-04-30 15:48:30 -070096
97
Aviv Keshetc8824402013-06-29 20:37:30 -070098def run_job(job, host, sysroot_autotest_path, results_directory):
Aviv Keshetd4a04302013-04-30 15:48:30 -070099 """
100 Shell out to autoserv to run an individual test job.
101
102 @param job: A Job object containing the control file contents and other
103 relevent metadata for this test.
104 @param host: Hostname of DUT to run test against.
105 @param sysroot_autotest_path: Absolute path of autotest directory.
Aviv Keshetc8824402013-06-29 20:37:30 -0700106 @param results_directory: Absolute path of directory to store results in.
107 (results will be stored in subdirectory of this).
108 @returns: Absolute path of directory where results were stored.
Aviv Keshetd4a04302013-04-30 15:48:30 -0700109 """
110 with tempfile.NamedTemporaryFile() as temp_file:
111 temp_file.write(job.control_file)
112 temp_file.flush()
113
Aviv Keshetc8824402013-06-29 20:37:30 -0700114 results_directory = os.path.join(results_directory,
115 'results-%s' % job.id)
116
Aviv Keshetd4a04302013-04-30 15:48:30 -0700117 command = autoserv_utils.autoserv_run_job_command(
118 os.path.join(sysroot_autotest_path, 'server'),
119 machines=host, job=job, verbose=False,
Aviv Keshetc8824402013-06-29 20:37:30 -0700120 results_directory=results_directory,
Aviv Keshetd4a04302013-04-30 15:48:30 -0700121 extra_args=[temp_file.name])
Aviv Keshet1d991ea2013-06-12 17:24:23 -0700122 global _autoserv_proc
123 _autoserv_proc = subprocess.Popen(command)
124 _autoserv_proc.wait()
125 _autoserv_proc = None
Aviv Keshetc8824402013-06-29 20:37:30 -0700126 return results_directory
Aviv Keshetd4a04302013-04-30 15:48:30 -0700127
128
129def setup_local_afe():
130 """
131 Setup a local afe database and return a direct_afe object to access it.
132
133 @returns: A autotest_lib.frontend.afe.direct_afe instance.
134 """
135 # This import statement is delayed until now rather than running at
136 # module load time, because it kicks off a local sqlite :memory: backed
137 # database, and we don't need that unless we are doing a local run.
138 from autotest_lib.frontend import setup_django_lite_environment
139 from autotest_lib.frontend.afe import direct_afe
140 return direct_afe.directAFE()
141
142
Aviv Keshet10711962013-06-24 12:20:33 -0700143def perform_local_run(afe, autotest_path, tests, remote, build=_NO_BUILD,
144 board=_NO_BOARD):
Aviv Keshetd4a04302013-04-30 15:48:30 -0700145 """
146 @param afe: A direct_afe object used to interact with local afe database.
147 @param autotest_path: Absolute path of sysroot installed autotest.
148 @param tests: List of strings naming tests and suites to run. Suite strings
149 should be formed like "suite:smoke".
150 @param remote: Remote hostname.
151 @param build: String specifying build for local run.
Aviv Keshet10711962013-06-24 12:20:33 -0700152 @param board: String specifyinb board for local run.
Aviv Keshetba4992a2013-07-02 14:09:23 -0700153
154 @returns: directory in which results are stored.
Aviv Keshetd4a04302013-04-30 15:48:30 -0700155 """
156 afe.create_label(constants.VERSION_PREFIX + build)
Aviv Keshet10711962013-06-24 12:20:33 -0700157 afe.create_label(board)
Aviv Keshetd4a04302013-04-30 15:48:30 -0700158 afe.create_host(remote)
159
Aviv Keshetba4992a2013-07-02 14:09:23 -0700160 results_directory = tempfile.mkdtemp(prefix='test_that_results_')
161 os.chmod(results_directory, stat.S_IWOTH | stat.S_IROTH | stat.S_IXOTH)
162 logging.info('Running jobs. Results will be placed in %s',
163 results_directory)
Aviv Keshetd4a04302013-04-30 15:48:30 -0700164 # Schedule tests / suites in local afe
165 for test in tests:
166 suitematch = re.match(r'suite:(.*)', test)
167 if suitematch:
168 suitename = suitematch.group(1)
Aviv Keshet69ebb6c2013-06-11 13:58:44 -0700169 logging.info('Scheduling suite %s...', suitename)
Aviv Keshet10711962013-06-24 12:20:33 -0700170 ntests = schedule_local_suite(autotest_path, suitename, afe,
Aviv Keshetba4992a2013-07-02 14:09:23 -0700171 build=build, board=board,
172 results_directory=results_directory)
Aviv Keshetd4a04302013-04-30 15:48:30 -0700173 else:
Aviv Keshet69ebb6c2013-06-11 13:58:44 -0700174 logging.info('Scheduling test %s...', test)
Aviv Keshet10711962013-06-24 12:20:33 -0700175 ntests = schedule_local_test(autotest_path, test, afe,
Aviv Keshetba4992a2013-07-02 14:09:23 -0700176 build=build, board=board,
177 results_directory=results_directory)
Aviv Keshet69ebb6c2013-06-11 13:58:44 -0700178 logging.info('... scheduled %s tests.', ntests)
Aviv Keshetd4a04302013-04-30 15:48:30 -0700179
180 for job in afe.get_jobs():
Aviv Keshetc8824402013-06-29 20:37:30 -0700181 run_job(job, remote, autotest_path, results_directory)
Aviv Keshetd4a04302013-04-30 15:48:30 -0700182
Aviv Keshetba4992a2013-07-02 14:09:23 -0700183 return results_directory
Aviv Keshetd4a04302013-04-30 15:48:30 -0700184
185
186def validate_arguments(arguments):
Aviv Keshet0e5d5252013-04-26 16:01:36 -0700187 """
188 Validates parsed arguments.
189
190 @param arguments: arguments object, as parsed by ParseArguments
191 @raises: ValueError if arguments were invalid.
192 """
193 if arguments.args:
194 raise ValueError('--args flag not yet supported.')
195
196 if not arguments.board:
197 raise ValueError('Board autodetection not yet supported. '
198 '--board required.')
199
Aviv Keshetd4a04302013-04-30 15:48:30 -0700200 if arguments.remote == ':lab:':
201 raise ValueError('Running tests in test lab not yet supported.')
Aviv Keshet0e5d5252013-04-26 16:01:36 -0700202
Aviv Keshetd4a04302013-04-30 15:48:30 -0700203
204def parse_arguments(argv):
Aviv Keshet021c19f2013-02-22 13:19:43 -0800205 """
206 Parse command line arguments
207
208 @param argv: argument list to parse
209 @returns: parsed arguments.
210 """
211 parser = argparse.ArgumentParser(description='Run remote tests.')
212
Aviv Keshet0e5d5252013-04-26 16:01:36 -0700213 parser.add_argument('remote', metavar='REMOTE',
Aviv Keshet021c19f2013-02-22 13:19:43 -0800214 help='hostname[:port] for remote device. Specify '
215 ':lab: to run in test lab, or :vm:PORT_NUMBER to '
216 'run in vm.')
217 parser.add_argument('tests', nargs='+', metavar='TEST',
218 help='Run given test(s). Use suite:SUITE to specify '
219 'test suite.')
Aviv Keshet0e5d5252013-04-26 16:01:36 -0700220 parser.add_argument('-b', '--board', metavar='BOARD',
221 action='store',
Aviv Keshet021c19f2013-02-22 13:19:43 -0800222 help='Board for which the test will run.')
Aviv Keshetd4a04302013-04-30 15:48:30 -0700223 parser.add_argument('-i', '--build', metavar='BUILD',
Aviv Keshet021c19f2013-02-22 13:19:43 -0800224 help='Build to test. Device will be reimaged if '
225 'necessary. Omit flag to skip reimage and test '
226 'against already installed DUT image.')
Aviv Keshetd4a04302013-04-30 15:48:30 -0700227 parser.add_argument('--args', metavar='ARGS',
Aviv Keshet021c19f2013-02-22 13:19:43 -0800228 help='Argument string to pass through to test.')
229
230 return parser.parse_args(argv)
231
232
Aviv Keshet1d991ea2013-06-12 17:24:23 -0700233def sigint_handler(signum, stack_frame):
234 #pylint: disable-msg=C0111
235 """Handle SIGINT or SIGTERM to a local test_that run.
236
237 This handler sends a SIGINT to the running autoserv process,
238 if one is running, giving it up to 5 seconds to clean up and exit. After
239 the timeout elapses, autoserv is killed. In either case, after autoserv
240 exits then this process exits with status 1.
241 """
242 # If multiple signals arrive before handler is unset, ignore duplicates
243 if not _sigint_handler_lock.acquire(False):
244 return
245 try:
246 # Ignore future signals by unsetting handler.
247 signal.signal(signal.SIGINT, signal.SIG_IGN)
248 signal.signal(signal.SIGTERM, signal.SIG_IGN)
249
250 logging.warning('Received SIGINT or SIGTERM. Cleaning up and exiting.')
251 if _autoserv_proc:
252 logging.warning('Sending SIGINT to autoserv process. Waiting up '
253 'to %s seconds for cleanup.',
254 _AUTOSERV_SIGINT_TIMEOUT_SECONDS)
255 _autoserv_proc.send_signal(signal.SIGINT)
256 timed_out, _ = retry.timeout(_autoserv_proc.wait,
257 timeout_sec=_AUTOSERV_SIGINT_TIMEOUT_SECONDS)
258 if timed_out:
259 _autoserv_proc.kill()
260 logging.warning('Timed out waiting for autoserv to handle '
261 'SIGINT. Killed autoserv.')
262 finally:
263 _sigint_handler_lock.release() # this is not really necessary?
264 sys.exit(1)
265
266
Aviv Keshet021c19f2013-02-22 13:19:43 -0800267def main(argv):
268 """
269 Entry point for test_that script.
270 @param argv: arguments list
271 """
Aviv Keshet0e5d5252013-04-26 16:01:36 -0700272 if not cros_build_lib.IsInsideChroot():
273 logging.error('Script must be invoked inside the chroot.')
274 return 1
275
Aviv Keshet712128f2013-06-11 14:41:08 -0700276 logging.getLogger('').setLevel(logging.INFO)
277
Aviv Keshetd4a04302013-04-30 15:48:30 -0700278 arguments = parse_arguments(argv)
Aviv Keshet0e5d5252013-04-26 16:01:36 -0700279 try:
Aviv Keshetd4a04302013-04-30 15:48:30 -0700280 validate_arguments(arguments)
Aviv Keshet0e5d5252013-04-26 16:01:36 -0700281 except ValueError as err:
282 logging.error('Invalid arguments. %s', err.message)
283 return 1
284
285 # TODO: Determine the following string programatically.
286 # (same TODO applied to autotest_quickmerge)
287 sysroot_path = os.path.join('/build', arguments.board, '')
288 sysroot_autotest_path = os.path.join(sysroot_path, 'usr', 'local',
289 'autotest', '')
290 sysroot_site_utils_path = os.path.join(sysroot_autotest_path,
291 'site_utils')
292
293 if not os.path.exists(sysroot_path):
294 logging.error('%s does not exist. Have you run setup_board?',
295 sysroot_path)
296 return 1
297 if not os.path.exists(sysroot_autotest_path):
298 logging.error('%s does not exist. Have you run build_packages?',
299 sysroot_autotest_path)
300 return 1
301
Aviv Keshet2dc98932013-06-17 15:22:10 -0700302 # If we are not running the sysroot version of script, perform
303 # a quickmerge if necessary and then re-execute
304 # the sysroot version of script with the same arguments.
Aviv Keshet0e5d5252013-04-26 16:01:36 -0700305 realpath = os.path.realpath(__file__)
306 if os.path.dirname(realpath) != sysroot_site_utils_path:
Aviv Keshet2dc98932013-06-17 15:22:10 -0700307 subprocess.call([_QUICKMERGE_SCRIPTNAME, '--board='+arguments.board])
308
Aviv Keshet0e5d5252013-04-26 16:01:36 -0700309 script_command = os.path.join(sysroot_site_utils_path,
310 os.path.basename(realpath))
Aviv Keshet1d991ea2013-06-12 17:24:23 -0700311 proc = None
312 def resend_sig(signum, stack_frame):
313 #pylint: disable-msg=C0111
314 if proc:
315 proc.send_signal(signum)
316 signal.signal(signal.SIGINT, resend_sig)
317 signal.signal(signal.SIGTERM, resend_sig)
318
319 proc = subprocess.Popen([script_command] + argv)
320
321 return proc.wait()
Aviv Keshet0e5d5252013-04-26 16:01:36 -0700322
Aviv Keshetd4a04302013-04-30 15:48:30 -0700323 # Hard coded to True temporarily. This will eventually be parsed to false
324 # if we are doing a run in the test lab.
325 local_run = True
Aviv Keshet021c19f2013-02-22 13:19:43 -0800326
Aviv Keshet1d991ea2013-06-12 17:24:23 -0700327 signal.signal(signal.SIGINT, sigint_handler)
328 signal.signal(signal.SIGTERM, sigint_handler)
329
Aviv Keshetd4a04302013-04-30 15:48:30 -0700330 if local_run:
331 afe = setup_local_afe()
Aviv Keshetba4992a2013-07-02 14:09:23 -0700332 res_dir= perform_local_run(afe, sysroot_autotest_path, arguments.tests,
333 arguments.remote)
334 return subprocess.call([_TEST_REPORT_SCRIPTNAME, res_dir])
335
Aviv Keshet021c19f2013-02-22 13:19:43 -0800336
337if __name__ == '__main__':
Aviv Keshetd4a04302013-04-30 15:48:30 -0700338 sys.exit(main(sys.argv[1:]))