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