• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python2
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
7import os
8import signal
9import subprocess
10import sys
11
12import logging
13# Turn the logging level to INFO before importing other autotest
14# code, to avoid having failed import logging messages confuse the
15# test_that user.
16logging.basicConfig(level=logging.INFO)
17
18import common
19from autotest_lib.client.common_lib import error, logging_manager
20from autotest_lib.server import server_logging_config
21from autotest_lib.server.cros.dynamic_suite import constants
22from autotest_lib.server.hosts import factory
23from autotest_lib.site_utils import test_runner_utils
24
25
26_QUICKMERGE_SCRIPTNAME = '/mnt/host/source/chromite/bin/autotest_quickmerge'
27
28
29def _get_board_from_host(remote):
30    """Get the board of the remote host.
31
32    @param remote: string representing the IP of the remote host.
33
34    @return: A string representing the board of the remote host.
35    """
36    logging.info('Board unspecified, attempting to determine board from host.')
37    host = factory.create_host(remote)
38    try:
39        board = host.get_board().replace(constants.BOARD_PREFIX, '')
40    except error.AutoservRunError:
41        raise test_runner_utils.TestThatRunError(
42                'Cannot determine board, please specify a --board option.')
43    logging.info('Detected host board: %s', board)
44    return board
45
46
47def validate_arguments(arguments):
48    """
49    Validates parsed arguments.
50
51    @param arguments: arguments object, as parsed by ParseArguments
52    @raises: ValueError if arguments were invalid.
53    """
54    if arguments.remote == ':lab:':
55        if arguments.args:
56            raise ValueError('--args flag not supported when running against '
57                             ':lab:')
58        if arguments.pretend:
59            raise ValueError('--pretend flag not supported when running '
60                             'against :lab:')
61        if arguments.ssh_verbosity:
62            raise ValueError('--ssh_verbosity flag not supported when running '
63                             'against :lab:')
64        if not arguments.board or arguments.build == test_runner_utils.NO_BUILD:
65            raise ValueError('--board and --build are both required when '
66                             'running against :lab:')
67    else:
68        if arguments.web:
69            raise ValueError('--web flag not supported when running locally')
70
71
72def parse_arguments(argv):
73    """
74    Parse command line arguments
75
76    @param argv: argument list to parse
77    @returns:    parsed arguments
78    @raises SystemExit if arguments are malformed, or required arguments
79            are not present.
80    """
81    return _parse_arguments_internal(argv)[0]
82
83
84def _parse_arguments_internal(argv):
85    """
86    Parse command line arguments
87
88    @param argv: argument list to parse
89    @returns:    tuple of parsed arguments and argv suitable for remote runs
90    @raises SystemExit if arguments are malformed, or required arguments
91            are not present.
92    """
93    local_parser, remote_argv = parse_local_arguments(argv)
94
95    parser = argparse.ArgumentParser(description='Run remote tests.',
96                                     parents=[local_parser])
97
98    parser.add_argument('remote', metavar='REMOTE',
99                        help='hostname[:port] for remote device. Specify '
100                             ':lab: to run in test lab. When tests are run in '
101                             'the lab, test_that will use the client autotest '
102                             'package for the build specified with --build, '
103                             'and the lab server code rather than local '
104                             'changes.')
105    test_runner_utils.add_common_args(parser)
106    parser.add_argument('-b', '--board', metavar='BOARD',
107                        action='store',
108                        help='Board for which the test will run. '
109                             'Default: %(default)s')
110    parser.add_argument('-m', '--model', metavar='MODEL', default='',
111                        help='Specific model the test will run against. '
112                             'Matches the model:FAKE_MODEL label for the host.')
113    parser.add_argument('-i', '--build', metavar='BUILD',
114                        default=test_runner_utils.NO_BUILD,
115                        help='Build to test. Device will be reimaged if '
116                             'necessary. Omit flag to skip reimage and test '
117                             'against already installed DUT image. Examples: '
118                             'link-paladin/R34-5222.0.0-rc2, '
119                             'lumpy-release/R34-5205.0.0')
120    parser.add_argument('-p', '--pool', metavar='POOL', default='suites',
121                        help='Pool to use when running tests in the lab. '
122                             'Default is "suites"')
123    parser.add_argument('--autotest_dir', metavar='AUTOTEST_DIR',
124                        help='Use AUTOTEST_DIR instead of normal board sysroot '
125                             'copy of autotest, and skip the quickmerge step.')
126    parser.add_argument('--no-quickmerge', action='store_true', default=False,
127                        dest='no_quickmerge',
128                        help='Skip the quickmerge step and use the sysroot '
129                             'as it currently is. May result in un-merged '
130                             'source tree changes not being reflected in the '
131                             'run. If using --autotest_dir, this flag is '
132                             'automatically applied.')
133    parser.add_argument('--whitelist-chrome-crashes', action='store_true',
134                        default=False, dest='whitelist_chrome_crashes',
135                        help='Ignore chrome crashes when producing test '
136                             'report. This flag gets passed along to the '
137                             'report generation tool.')
138    parser.add_argument('--ssh_private_key', action='store',
139                        default=test_runner_utils.TEST_KEY_PATH,
140                        help='Path to the private ssh key.')
141    return parser.parse_args(argv), remote_argv
142
143
144def parse_local_arguments(argv):
145    """
146    Strips out arguments that are not to be passed through to runs.
147
148    Add any arguments that should not be passed to remote test_that runs here.
149
150    @param argv: argument list to parse.
151    @returns: tuple of local argument parser and remaining argv.
152    """
153    parser = argparse.ArgumentParser(add_help=False)
154    parser.add_argument('-w', '--web', dest='web', default=None,
155                        help='Address of a webserver to receive test requests.')
156    parser.add_argument('-x', '--max_runtime_mins', type=int,
157                        dest='max_runtime_mins', default=20,
158                        help='Default time allowed for the tests to complete.')
159    parser.add_argument('--no-retries', '--no-retry',
160                        dest='retry', action='store_false', default=True,
161                        help='For local runs only, ignore any retries '
162                             'specified in the control files.')
163    _, remaining_argv = parser.parse_known_args(argv)
164    return parser, remaining_argv
165
166
167def perform_bootstrap_into_autotest_root(arguments, autotest_path, argv):
168    """
169    Perfoms a bootstrap to run test_that from the |autotest_path|.
170
171    This function is to be called from test_that's main() script, when
172    test_that is executed from the source tree location. It runs
173    autotest_quickmerge to update the sysroot unless arguments.no_quickmerge
174    is set. It then executes and waits on the version of test_that.py
175    in |autotest_path|.
176
177    @param arguments: A parsed arguments object, as returned from
178                      test_that.parse_arguments(...).
179    @param autotest_path: Full absolute path to the autotest root directory.
180    @param argv: The arguments list, as passed to main(...)
181
182    @returns: The return code of the test_that script that was executed in
183              |autotest_path|.
184    """
185    logging_manager.configure_logging(
186            server_logging_config.ServerLoggingConfig(),
187            use_console=True,
188            verbose=arguments.debug)
189    if arguments.no_quickmerge:
190        logging.info('Skipping quickmerge step.')
191    else:
192        logging.info('Running autotest_quickmerge step.')
193        command = [_QUICKMERGE_SCRIPTNAME, '--board='+arguments.board]
194        s = subprocess.Popen(command,
195                             stdout=subprocess.PIPE,
196                             stderr=subprocess.STDOUT)
197        for message in iter(s.stdout.readline, b''):
198            logging.info('quickmerge| %s', message.strip())
199        return_code = s.wait()
200        if return_code:
201            raise test_runner_utils.TestThatRunError(
202                    'autotest_quickmerge failed with error code %s.' %
203                    return_code)
204
205    logging.info('Re-running test_that script in %s copy of autotest.',
206                 autotest_path)
207    script_command = os.path.join(autotest_path, 'site_utils',
208                                  'test_that.py')
209    if not os.path.exists(script_command):
210        raise test_runner_utils.TestThatRunError(
211            'Unable to bootstrap to autotest root, %s not found.' %
212            script_command)
213    proc = None
214    def resend_sig(signum, stack_frame):
215        #pylint: disable-msg=C0111
216        if proc:
217            proc.send_signal(signum)
218    signal.signal(signal.SIGINT, resend_sig)
219    signal.signal(signal.SIGTERM, resend_sig)
220
221    proc = subprocess.Popen([script_command] + argv)
222
223    return proc.wait()
224
225
226def _main_for_local_run(argv, arguments):
227    """
228    Effective entry point for local test_that runs.
229
230    @param argv: Script command line arguments.
231    @param arguments: Parsed command line arguments.
232    """
233    if not os.path.exists('/etc/cros_chroot_version'):
234        print >> sys.stderr, 'For local runs, script must be run inside chroot.'
235        return 1
236
237    results_directory = test_runner_utils.create_results_directory(
238            arguments.results_dir, arguments.board)
239    test_runner_utils.add_ssh_identity(results_directory,
240                                       arguments.ssh_private_key)
241    arguments.results_dir = results_directory
242
243    # If the board has not been specified through --board, and is not set in the
244    # default_board file, determine the board by ssh-ing into the host. Also
245    # prepend it to argv so we can re-use it when we run test_that from the
246    # sysroot.
247    if arguments.board is None:
248        arguments.board = _get_board_from_host(arguments.remote)
249        argv = ['--board=%s' % (arguments.board,)] + argv
250
251    if arguments.autotest_dir:
252        autotest_path = arguments.autotest_dir
253        arguments.no_quickmerge = True
254    else:
255        sysroot_path = os.path.join('/build', arguments.board, '')
256
257        if not os.path.exists(sysroot_path):
258            print >> sys.stderr, ('%s does not exist. Have you run '
259                                  'setup_board?' % sysroot_path)
260            return 1
261
262        path_ending = 'usr/local/build/autotest'
263        autotest_path = os.path.join(sysroot_path, path_ending)
264
265    site_utils_path = os.path.join(autotest_path, 'site_utils')
266
267    if not os.path.exists(autotest_path):
268        print >> sys.stderr, ('%s does not exist. Have you run '
269                              'build_packages? Or if you are using '
270                              '--autotest_dir, make sure it points to '
271                              'a valid autotest directory.' % autotest_path)
272        return 1
273
274    realpath = os.path.realpath(__file__)
275    site_utils_path = os.path.realpath(site_utils_path)
276
277    # If we are not running the sysroot version of script, perform
278    # a quickmerge if necessary and then re-execute
279    # the sysroot version of script with the same arguments.
280    if os.path.dirname(realpath) != site_utils_path:
281        return perform_bootstrap_into_autotest_root(
282                arguments, autotest_path, argv)
283    else:
284        return test_runner_utils.perform_run_from_autotest_root(
285                autotest_path, argv, arguments.tests, arguments.remote,
286                build=arguments.build, board=arguments.board,
287                args=arguments.args, ignore_deps=not arguments.enforce_deps,
288                results_directory=results_directory,
289                ssh_verbosity=arguments.ssh_verbosity,
290                ssh_options=arguments.ssh_options,
291                iterations=arguments.iterations,
292                fast_mode=arguments.fast_mode, debug=arguments.debug,
293                whitelist_chrome_crashes=arguments.whitelist_chrome_crashes,
294                pretend=arguments.pretend, job_retry=arguments.retry)
295
296
297def _main_for_lab_run(argv, arguments):
298    """
299    Effective entry point for lab test_that runs.
300
301    @param argv: Script command line arguments.
302    @param arguments: Parsed command line arguments.
303    """
304    autotest_path = os.path.realpath(os.path.join(
305            os.path.dirname(os.path.realpath(__file__)),
306            '..',
307    ))
308    command = [os.path.join(autotest_path, 'site_utils',
309                            'run_suite.py'),
310               '--board=%s' % (arguments.board,),
311               '--build=%s' % (arguments.build,),
312               '--model=%s' % (arguments.model,),
313               '--suite_name=%s' % 'test_that_wrapper',
314               '--pool=%s' % (arguments.pool,),
315               '--max_runtime_mins=%s' % str(arguments.max_runtime_mins),
316               '--suite_args=%s'
317               % repr({'tests': _suite_arg_tests(argv)})]
318    if arguments.web:
319        command.extend(['--web=%s' % (arguments.web,)])
320    logging.info('About to start lab suite with command %s.', command)
321    return subprocess.call(command)
322
323
324def _suite_arg_tests(argv):
325    """
326    Construct a list of tests to pass into suite_args.
327
328    This is passed in suite_args to run_suite for running a test in the
329    lab.
330
331    @param argv: Remote Script command line arguments.
332    """
333    arguments = parse_arguments(argv)
334    return arguments.tests
335
336
337def main(argv):
338    """
339    Entry point for test_that script.
340
341    @param argv: arguments list
342    """
343    arguments, remote_argv = _parse_arguments_internal(argv)
344    try:
345        validate_arguments(arguments)
346    except ValueError as err:
347        print >> sys.stderr, ('Invalid arguments. %s' % err.message)
348        return 1
349
350    if arguments.remote == ':lab:':
351        return _main_for_lab_run(remote_argv, arguments)
352    else:
353        return _main_for_local_run(argv, arguments)
354
355
356if __name__ == '__main__':
357    sys.exit(main(sys.argv[1:]))
358