• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python
2# Copyright 2015 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 logging
7import os
8import sys
9import tempfile
10import time
11
12import common
13try:
14    # Ensure the chromite site-package is installed.
15    from chromite.lib import *
16except ImportError:
17    import subprocess
18    build_externals_path = os.path.join(
19            os.path.dirname(os.path.dirname(os.path.realpath(__file__))),
20            'utils', 'build_externals.py')
21    subprocess.check_call([build_externals_path, 'chromiterepo'])
22    # Restart the script so python now finds the autotest site-packages.
23    sys.exit(os.execv(__file__, sys.argv))
24from autotest_lib.client.common_lib import control_data
25from autotest_lib.server import utils
26from autotest_lib.server.cros.dynamic_suite import control_file_getter
27from autotest_lib.server.cros.dynamic_suite import tools
28from autotest_lib.server.hosts import moblab_host
29from autotest_lib.site_utils import brillo_common
30from autotest_lib.site_utils import run_suite
31
32
33_AFE_JOB_PAGE_TEMPLATE = ('http://%(moblab)s/afe/#tab_id=view_job&'
34                          'object_id=%(job_id)s')
35_AFE_HOST_PAGE_TEMPLATE = ('http://%(moblab)s/afe/#tab_id=view_host&'
36                           'object_id=%(host_id)s')
37_QUICKMERGE_LIST = ('client/',
38                    'global_config.ini',
39                    'server/',
40                    'site_utils/',
41                    'test_suites/',
42                    'utils/')
43
44
45class BrilloTestExecutionError(brillo_common.BrilloTestError):
46    """An error while launching and running a test."""
47
48
49def setup_parser(parser):
50    """Add parser options.
51
52    @param parser: argparse.ArgumentParser of the script.
53    """
54    parser.add_argument('-t', '--test_name',
55                        help="Name of the test to run. This is either the "
56                             "name in the test's default control file e.g. "
57                             "brillo_Gtests or a specific control file's "
58                             "filename e.g. control.brillo_GtestsWhitelist.")
59    parser.add_argument('-A', '--test_arg', metavar='NAME=VAL',
60                        dest='test_args', default=[], action='append',
61                        help='An argument to pass to the test.')
62
63
64def quickmerge(moblab):
65    """Transfer over a subset of Autotest directories.
66
67    Quickmerge allows developers to do basic editting of tests and test
68    libraries on their workstation without requiring them to emerge and cros
69    deploy the autotest-server package.
70
71    @param moblab: MoblabHost representing the MobLab being used to launch the
72                   testing.
73    """
74    autotest_rootdir = os.path.dirname(
75            os.path.dirname(os.path.realpath(__file__)))
76    # We use rsync -R to copy a bunch of sources in a single run, adding a dot
77    # to pinpoint the relative path root.
78    rsync_cmd = ['rsync', '-aR', '--exclude', '*.pyc']
79    ssh_cmd = 'ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'
80    if int(moblab.port) != 22:
81        ssh_cmd += ' -p %s' % moblab.port
82    rsync_cmd += ['-e', ssh_cmd]
83    rsync_cmd += [os.path.join(autotest_rootdir, '.', path)
84                  for path in _QUICKMERGE_LIST]
85    rsync_cmd.append('moblab@%s:%s' %
86                     (moblab.hostname, moblab_host.AUTOTEST_INSTALL_DIR))
87    utils.run(rsync_cmd, timeout=240)
88
89
90def add_adb_host(moblab, adb_hostname):
91    """Add the ADB host to the MobLab's host list.
92
93    @param moblab: MoblabHost representing the MobLab being used to launch the
94                   tests.
95    @param adb_hostname: Hostname of the ADB Host.
96
97    @returns The adb host to use for launching tests.
98    """
99    if not adb_hostname:
100        adb_hostname = 'localhost'
101        moblab.enable_adb_testing()
102    if all([host.hostname != adb_hostname for host in moblab.afe.get_hosts()]):
103        moblab.add_dut(adb_hostname)
104    return adb_hostname
105
106
107def schedule_test(moblab, host, test, test_args):
108    """Schedule a Brillo test.
109
110    @param moblab: MoblabHost representing the MobLab being used for testing.
111    @param host: Hostname of the DUT.
112    @param test: Test name.
113    @param test_args: Iterable of 'NAME=VAL' (strings) encoding argument
114                      assignments for the test.
115
116    @returns autotest_lib.server.frontend.Job object representing the scheduled
117             job.
118    """
119    getter = control_file_getter.FileSystemGetter(
120            [os.path.dirname(os.path.dirname(os.path.realpath(__file__)))])
121    controlfile_conts = getter.get_control_file_contents_by_name(test)
122
123    # TODO(garnold) This should be removed and arguments injected by feeding
124    # args=test_args to create_jobs() directly once crbug.com/545572 is fixed.
125    if test_args:
126        controlfile_conts = tools.inject_vars({'args': test_args},
127                                              controlfile_conts)
128
129    job = moblab.afe.create_job(
130            controlfile_conts, name=test,
131            control_type=control_data.CONTROL_TYPE_NAMES.SERVER,
132            hosts=[host], require_ssp=False)
133    logging.info('Tests Scheduled. Please wait for results.')
134    job_page = _AFE_JOB_PAGE_TEMPLATE % dict(moblab=moblab.web_address,
135                                             job_id=job.id)
136    logging.info('Progress can be monitored at %s', job_page)
137    logging.info('Please note tests that launch other tests (e.g. sequences) '
138                 'might complete quickly, but links to child jobs will appear '
139                 'shortly at the bottom on the page (Hit Refresh).')
140    return job
141
142
143def get_all_jobs(moblab, parent_job):
144    """Generate a list of the parent_job and it's subjobs.
145
146    @param moblab: MoblabHost representing the MobLab being used for testing.
147    @param host: Hostname of the DUT.
148    @param parent_job: autotest_lib.server.frontend.Job object representing the
149                       parent job.
150
151    @returns list of autotest_lib.server.frontend.Job objects.
152    """
153    jobs_list = moblab.afe.get_jobs(id=parent_job.id)
154    jobs_list.extend(moblab.afe.get_jobs(parent_job=parent_job.id))
155    return jobs_list
156
157
158def wait_for_test_completion(moblab, host, parent_job):
159    """Wait for the parent job and it's subjobs to complete.
160
161    @param moblab: MoblabHost representing the MobLab being used for testing.
162    @param host: Hostname of the DUT.
163    @param parent_job: autotest_lib.server.frontend.Job object representing the
164                       test job.
165    """
166    # Wait for the sequence job and it's sub-jobs to finish, while monitoring
167    # the DUT state. As long as the DUT does not go into 'Repair Failed' the
168    # tests will complete.
169    while (moblab.afe.get_jobs(id=parent_job.id, not_yet_run=True,
170                               running=True)
171           or moblab.afe.get_jobs(parent_job=parent_job.id, not_yet_run=True,
172                                  running=True)):
173        afe_host = moblab.afe.get_hosts(hostnames=(host,))[0]
174        if afe_host.status == 'Repair Failed':
175            moblab.afe.abort_jobs(
176                [j.id for j in get_all_jobs(moblab, parent_job)])
177            host_page = _AFE_HOST_PAGE_TEMPLATE % dict(
178                    moblab=moblab.web_address, host_id=afe_host.id)
179            raise BrilloTestExecutionError(
180                    'ADB dut %s has become Repair Failed. More information '
181                    'can be found at %s' % (host, host_page))
182        time.sleep(10)
183
184
185def copy_results(moblab, parent_job):
186    """Copy job results locally.
187
188    @param moblab: MoblabHost representing the MobLab being used for testing.
189    @param parent_job: autotest_lib.server.frontend.Job object representing the
190                       parent job.
191
192    @returns Temporary directory path.
193    """
194    tempdir = tempfile.mkdtemp(prefix='brillo_test_results')
195    for job in get_all_jobs(moblab, parent_job):
196        moblab.get_file('/usr/local/autotest/results/%d-moblab' % job.id,
197                        tempdir)
198    return tempdir
199
200
201def output_results(moblab, parent_job):
202    """Output the Brillo PTS and it's subjobs results.
203
204    @param moblab: MoblabHost representing the MobLab being used for testing.
205    @param parent_job: autotest_lib.server.frontend.Job object representing the
206                       test job.
207    """
208    solo_test_run = len(moblab.afe.get_jobs(parent_job=parent_job.id)) == 0
209    rc = run_suite.ResultCollector(moblab.web_address, moblab.afe, moblab.tko,
210                                   None, None, parent_job.name, parent_job.id,
211                                   user='moblab', solo_test_run=solo_test_run)
212    rc.run()
213    rc.output_results()
214
215
216def main(args):
217    """The main function."""
218    args = brillo_common.parse_args('Launch a Brillo test using Moblab.',
219                                    setup_parser=setup_parser)
220    moblab, _ = brillo_common.get_moblab_and_devserver_port(args.moblab_host)
221
222    if args.quickmerge:
223        quickmerge(moblab)
224
225    # Add the adb host object to the MobLab.
226    adb_host = add_adb_host(moblab, args.adb_host)
227
228    # Schedule the test job.
229    test_job = schedule_test(moblab, adb_host, args.test_name, args.test_args)
230    wait_for_test_completion(moblab, adb_host, test_job)
231
232    # Gather and report the test results.
233    local_results_folder = copy_results(moblab, test_job)
234    output_results(moblab, test_job)
235    logging.info('Results have also been copied locally to %s',
236                 local_results_folder)
237
238
239if __name__ == '__main__':
240    try:
241        main(sys.argv)
242        sys.exit(0)
243    except brillo_common.BrilloTestError as e:
244        logging.error('Error: %s', e)
245
246    sys.exit(1)
247