You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
660 lines
28 KiB
660 lines
28 KiB
# Lint as: python2, python3
|
|
# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
from __future__ import print_function
|
|
from __future__ import division
|
|
from __future__ import absolute_import
|
|
|
|
import datetime
|
|
import logging
|
|
import time
|
|
import six
|
|
import warnings
|
|
|
|
import common
|
|
|
|
from autotest_lib.client.common_lib import base_job
|
|
from autotest_lib.client.common_lib import error
|
|
from autotest_lib.client.common_lib import priorities
|
|
from autotest_lib.client.common_lib import time_utils
|
|
from autotest_lib.client.common_lib import utils
|
|
from autotest_lib.client.common_lib.cros import dev_server
|
|
from autotest_lib.server.cros import provision
|
|
from autotest_lib.server.cros.dynamic_suite import constants
|
|
from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
|
|
from autotest_lib.server.cros.dynamic_suite.suite import ProvisionSuite
|
|
from autotest_lib.server.cros.dynamic_suite.suite import Suite
|
|
from autotest_lib.tko import utils as tko_utils
|
|
|
|
|
|
"""CrOS dynamic test suite generation and execution module.
|
|
|
|
This module implements runtime-generated test suites for CrOS.
|
|
Design doc: http://goto.google.com/suitesv2
|
|
|
|
Individual tests can declare themselves as a part of one or more
|
|
suites, and the code here enables control files to be written
|
|
that can refer to these "dynamic suites" by name. We also provide
|
|
support for reimaging devices with a given build and running a
|
|
dynamic suite across all reimaged devices.
|
|
|
|
The public API for defining a suite includes one method: reimage_and_run().
|
|
A suite control file can be written by importing this module and making
|
|
an appropriate call to this single method. In normal usage, this control
|
|
file will be run in a 'hostless' server-side autotest job, scheduling
|
|
sub-jobs to do the needed reimaging and test running.
|
|
|
|
Example control file:
|
|
|
|
import common
|
|
from autotest_lib.server.cros import provision
|
|
from autotest_lib.server.cros.dynamic_suite import dynamic_suite
|
|
|
|
dynamic_suite.reimage_and_run(
|
|
builds={provision.CROS_VERSION_PREFIX: build}, board=board, name='bvt',
|
|
job=job, pool=pool, check_hosts=check_hosts, add_experimental=True,
|
|
devserver_url=devserver_url)
|
|
|
|
This will -- at runtime -- find all control files that contain "bvt" in their
|
|
"SUITE=" clause, schedule jobs to reimage devices in the
|
|
specified pool of the specified board with the specified build and, upon
|
|
completion of those jobs, schedule and wait for jobs that run all the tests it
|
|
discovered.
|
|
|
|
Suites can be run by using the atest command-line tool:
|
|
atest suite create -b <board> -i <build/name> <suite>
|
|
e.g.
|
|
atest suite create -b x86-mario -i x86-mario/R20-2203.0.0 bvt
|
|
|
|
-------------------------------------------------------------------------
|
|
Implementation details
|
|
|
|
A Suite instance represents a single test suite, defined by some predicate
|
|
run over all known control files. The simplest example is creating a Suite
|
|
by 'name'.
|
|
|
|
create_suite_job() takes the parameters needed to define a suite run (board,
|
|
build to test, machine pool, and which suite to run), ensures important
|
|
preconditions are met, finds the appropraite suite control file, and then
|
|
schedules the hostless job that will do the rest of the work.
|
|
|
|
Note that we have more than one Dev server in our test lab architecture.
|
|
We currently load balance per-build being tested, so one and only one dev
|
|
server is used by any given run through the reimaging/testing flow.
|
|
|
|
- create_suite_job()
|
|
The primary role of create_suite_job() is to ensure that the required
|
|
artifacts for the build to be tested are staged on the dev server. This
|
|
includes payloads required to autoupdate machines to the desired build, as
|
|
well as the autotest control files appropriate for that build. Then, the
|
|
RPC pulls the control file for the suite to be run from the dev server and
|
|
uses it to create the suite job with the autotest frontend.
|
|
|
|
+----------------+
|
|
| Google Storage | Client
|
|
+----------------+ |
|
|
| ^ | create_suite_job()
|
|
payloads/ | | |
|
|
control files | | request |
|
|
V | V
|
|
+-------------+ download request +--------------------------+
|
|
| |<----------------------| |
|
|
| Dev Server | | Autotest Frontend (AFE) |
|
|
| |---------------------->| |
|
|
+-------------+ suite control file +--------------------------+
|
|
|
|
|
V
|
|
Suite Job (hostless)
|
|
|
|
- Reimage and Run
|
|
The overall process is to schedule all the tests, and then wait for the tests
|
|
to complete.
|
|
|
|
- The Reimaging Process
|
|
|
|
As an artifact of an old implementation, the number of machines to use
|
|
is called the 'sharding_factor', and the default is defined in the [CROS]
|
|
section of global_config.ini.
|
|
|
|
There used to be a 'num' parameter to control the maximum number of
|
|
machines, but it does not do anything any more.
|
|
|
|
A test control file can specify a list of DEPENDENCIES, which are really just
|
|
the set of labels a host needs to have in order for that test to be scheduled
|
|
on it. In the case of a dynamic_suite, many tests in the suite may have
|
|
DEPENDENCIES specified. All tests are scheduled with the DEPENDENCIES that
|
|
they specify, along with any suite dependencies that were specified, and the
|
|
scheduler will find and provision a host capable of running the test.
|
|
|
|
- Scheduling Suites
|
|
A Suite instance uses the labels specified in the suite dependencies to
|
|
schedule tests across all the hosts in the pool. It then waits for all these
|
|
jobs. As an optimization, the Dev server stages the payloads necessary to
|
|
run a suite in the background _after_ it has completed all the things
|
|
necessary for reimaging. Before running a suite, reimage_and_run() calls out
|
|
to the Dev server and blocks until it's completed staging all build artifacts
|
|
needed to run test suites.
|
|
|
|
Step by step:
|
|
0) At instantiation time, find all appropriate control files for this suite
|
|
that were included in the build to be tested. To do this, we consult the
|
|
Dev Server, where all these control files are staged.
|
|
|
|
+------------+ control files? +--------------------------+
|
|
| |<----------------------| |
|
|
| Dev Server | | Autotest Frontend (AFE) |
|
|
| |---------------------->| [Suite Job] |
|
|
+------------+ control files! +--------------------------+
|
|
|
|
1) Now that the Suite instance exists, it schedules jobs for every control
|
|
file it deemed appropriate, to be run on the hosts that were labeled
|
|
by the provisioning. We stuff keyvals into these jobs, indicating what
|
|
build they were testing and which suite they were for.
|
|
|
|
+--------------------------+ Job for VersLabel +--------+
|
|
| |------------------------>| Host 1 | VersLabel
|
|
| Autotest Frontend (AFE) | +--------+ +--------+
|
|
| [Suite Job] |----------->| Host 2 |
|
|
+--------------------------+ Job for +--------+
|
|
| ^ VersLabel VersLabel
|
|
| |
|
|
+----------------+
|
|
One job per test
|
|
{'build': build/name,
|
|
'suite': suite_name}
|
|
|
|
2) Now that all jobs are scheduled, they'll be doled out as labeled hosts
|
|
finish their assigned work and become available again.
|
|
|
|
- Waiting on Suites
|
|
0) As we clean up each test job, we check to see if any crashes occurred. If
|
|
they did, we look at the 'build' keyval in the job to see which build's debug
|
|
symbols we'll need to symbolicate the crash dump we just found.
|
|
|
|
1) Using this info, we tell a special Crash Server to stage the required debug
|
|
symbols. Once that's done, we ask the Crash Server to use those symbols to
|
|
symbolicate the crash dump in question.
|
|
|
|
+----------------+
|
|
| Google Storage |
|
|
+----------------+
|
|
| ^
|
|
symbols! | | symbols?
|
|
V |
|
|
+------------+ stage symbols for build +--------------------------+
|
|
| |<--------------------------| |
|
|
| Crash | | |
|
|
| Server | dump to symbolicate | Autotest Frontend (AFE) |
|
|
| |<--------------------------| [Suite Job] |
|
|
| |-------------------------->| |
|
|
+------------+ symbolicated dump +--------------------------+
|
|
|
|
2) As jobs finish, we record their success or failure in the status of the suite
|
|
job. We also record a 'job keyval' in the suite job for each test, noting
|
|
the job ID and job owner. This can be used to refer to test logs later.
|
|
3) Once all jobs are complete, status is recorded for the suite job, and the
|
|
job_repo_url host attribute is removed from all hosts used by the suite.
|
|
|
|
"""
|
|
|
|
|
|
# Relevant CrosDynamicSuiteExceptions are defined in client/common_lib/error.py.
|
|
|
|
class _SuiteSpec(object):
|
|
"""This class contains the info that defines a suite run."""
|
|
|
|
_REQUIRED_KEYWORDS = {
|
|
'board': str,
|
|
'builds': dict,
|
|
'name': str,
|
|
'job': base_job.base_job,
|
|
'devserver_url': str,
|
|
}
|
|
|
|
_VERSION_PREFIXES = frozenset((
|
|
provision.CROS_VERSION_PREFIX,
|
|
provision.CROS_ANDROID_VERSION_PREFIX,
|
|
))
|
|
|
|
def __init__(
|
|
self,
|
|
builds=None,
|
|
board=None,
|
|
name=None,
|
|
job=None,
|
|
devserver_url=None,
|
|
pool=None,
|
|
check_hosts=True,
|
|
add_experimental=True,
|
|
file_bugs=False,
|
|
max_runtime_mins=24*60,
|
|
timeout_mins=24*60,
|
|
suite_dependencies=None,
|
|
bug_template=None,
|
|
priority=priorities.Priority.DEFAULT,
|
|
predicate=None,
|
|
wait_for_results=True,
|
|
job_retry=False,
|
|
max_retries=None,
|
|
offload_failures_only=False,
|
|
test_source_build=None,
|
|
run_prod_code=False,
|
|
delay_minutes=0,
|
|
job_keyvals=None,
|
|
test_args=None,
|
|
child_dependencies=(),
|
|
test_names=None,
|
|
**dargs):
|
|
"""
|
|
Vets arguments for reimage_and_run() and populates self with supplied
|
|
values.
|
|
|
|
Currently required args:
|
|
@param builds: the builds to install e.g.
|
|
{'cros-version:': 'x86-alex-release/R18-1655.0.0',
|
|
'fwrw-version:': 'x86-alex-firmware/R36-5771.50.0'}
|
|
@param board: which kind of devices to reimage.
|
|
@param name: a value of the SUITE control file variable to search for.
|
|
@param job: an instance of client.common_lib.base_job representing the
|
|
currently running suite job.
|
|
@param devserver_url: url to the selected devserver.
|
|
|
|
Currently supported optional args:
|
|
@param pool: the pool of machines to use for scheduling purposes.
|
|
@param check_hosts: require appropriate hosts to be available now.
|
|
@param add_experimental: schedule experimental tests as well, or not.
|
|
@param file_bugs: File bugs when tests in this suite fail.
|
|
@param max_runtime_mins: Max runtime in mins for each of the sub-jobs
|
|
this suite will run.
|
|
@param timeout_mins: Max lifetime in minutes for each of the sub-jobs
|
|
that this suite runs.
|
|
@param suite_dependencies: A list of strings of suite level
|
|
dependencies, which act just like test
|
|
dependencies and are appended to each test's
|
|
set of dependencies at job creation time.
|
|
A string of comma seperated labels is
|
|
accepted for backwards compatibility.
|
|
@param bug_template: A template dictionary specifying the default bug
|
|
filing options for failures in this suite.
|
|
@param priority: Integer priority level. Higher is more important.
|
|
@param predicate: Optional argument. If present, should be a function
|
|
mapping ControlData objects to True if they should be
|
|
included in suite. If argument is absent, suite
|
|
behavior will default to creating a suite of based
|
|
on the SUITE field of control files.
|
|
@param wait_for_results: Set to False to run the suite job without
|
|
waiting for test jobs to finish.
|
|
@param job_retry: Set to True to enable job-level retry.
|
|
@param max_retries: Maximum retry limit at suite level if not None.
|
|
Regardless how many times each individual test
|
|
has been retried, the total number of retries
|
|
happening in the suite can't exceed max_retries.
|
|
@param offload_failures_only: Only enable gs_offloading for failed
|
|
jobs.
|
|
@param test_source_build: Build that contains the server-side test code,
|
|
e.g., it can be the value of builds['cros-version:'] or
|
|
builds['fw-version:']. None uses the server-side test code from
|
|
builds['cros-version:'].
|
|
@param run_prod_code: If true, the suite will run the test code that
|
|
lives in prod aka the test code currently on the
|
|
lab servers.
|
|
@param delay_minutes: Delay the creation of test jobs for a given number
|
|
of minutes.
|
|
@param job_keyvals: General job keyvals to be inserted into keyval file
|
|
@param test_args: A dict of args passed all the way to each individual
|
|
test that will be actually ran.
|
|
@param child_dependencies: (optional) list of dependency strings
|
|
to be added as dependencies to child jobs.
|
|
@param test_names: (optional) if provided, Suite will consist of the
|
|
tests named in this list.
|
|
@param **dargs: these arguments will be ignored. This allows us to
|
|
deprecate and remove arguments in ToT while not
|
|
breaking branch builds.
|
|
"""
|
|
self._check_init_params(
|
|
board=board,
|
|
builds=builds,
|
|
name=name,
|
|
job=job,
|
|
devserver_url=devserver_url)
|
|
|
|
self.board = 'board:%s' % board
|
|
self.builds = builds
|
|
self.name = name
|
|
self.job = job
|
|
self.pool = ('pool:%s' % pool) if pool else pool
|
|
self.check_hosts = check_hosts
|
|
self.add_experimental = add_experimental
|
|
self.file_bugs = file_bugs
|
|
self.dependencies = {'': []}
|
|
self.max_runtime_mins = max_runtime_mins
|
|
self.timeout_mins = timeout_mins
|
|
self.bug_template = {} if bug_template is None else bug_template
|
|
self.priority = priority
|
|
self.wait_for_results = wait_for_results
|
|
self.job_retry = job_retry
|
|
self.max_retries = max_retries
|
|
self.offload_failures_only = offload_failures_only
|
|
self.run_prod_code = run_prod_code
|
|
self.delay_minutes = delay_minutes
|
|
self.job_keyvals = job_keyvals
|
|
self.test_args = test_args
|
|
self.child_dependencies = child_dependencies
|
|
|
|
self._init_predicate(predicate, test_names)
|
|
self._init_suite_dependencies(suite_dependencies)
|
|
self._init_devserver(devserver_url)
|
|
self._init_test_source_build(test_source_build)
|
|
self._translate_builds()
|
|
self._add_builds_to_suite_deps()
|
|
|
|
for key, value in six.iteritems(dargs):
|
|
warnings.warn('Ignored key %r was passed to suite with value %r'
|
|
% (key, value))
|
|
|
|
def _check_init_params(self, **kwargs):
|
|
for key, expected_type in six.iteritems(self._REQUIRED_KEYWORDS):
|
|
value = kwargs.get(key)
|
|
# TODO(ayatane): `not value` includes both the cases where value is
|
|
# None and where value is the correct type, but empty (e.g., empty
|
|
# dict). It looks like this is NOT the intended behavior, but I'm
|
|
# hesitant to remove it in case something is actually relying on
|
|
# this behavior.
|
|
if not value or not isinstance(value, expected_type):
|
|
raise error.SuiteArgumentException(
|
|
'reimage_and_run() needs %s=<%r>'
|
|
% (key, expected_type))
|
|
|
|
def _init_predicate(self, predicate, test_names):
|
|
"""Initialize predicate attribute."""
|
|
if test_names:
|
|
self.predicate = Suite.test_name_in_list_predicate(test_names)
|
|
return
|
|
|
|
if predicate:
|
|
self.predicate = predicate
|
|
return
|
|
|
|
self.predicate = Suite.name_in_tag_predicate(self.name)
|
|
|
|
def _init_suite_dependencies(self, suite_dependencies):
|
|
"""Initialize suite dependencies attribute."""
|
|
if suite_dependencies is None:
|
|
self.suite_dependencies = []
|
|
elif isinstance(suite_dependencies, str):
|
|
self.suite_dependencies = [dep.strip(' ') for dep
|
|
in suite_dependencies.split(',')]
|
|
else:
|
|
self.suite_dependencies = suite_dependencies
|
|
|
|
def _init_devserver(self, devserver_url):
|
|
"""Initialize devserver attribute."""
|
|
self.devserver = dev_server.ImageServer(devserver_url)
|
|
|
|
def _init_test_source_build(self, test_source_build):
|
|
"""Initialize test_source_build attribute."""
|
|
if test_source_build:
|
|
test_source_build = self.devserver.translate(test_source_build)
|
|
|
|
self.test_source_build = Suite.get_test_source_build(
|
|
self.builds, test_source_build=test_source_build)
|
|
|
|
def _translate_builds(self):
|
|
"""Translate build names if they are in LATEST format."""
|
|
for prefix in self._VERSION_PREFIXES:
|
|
if prefix in self.builds:
|
|
translated_build = self.devserver.translate(
|
|
self.builds[prefix])
|
|
self.builds[prefix] = translated_build
|
|
|
|
def _add_builds_to_suite_deps(self):
|
|
"""Add builds to suite_dependencies.
|
|
|
|
To support provision both CrOS and firmware, option builds are added to
|
|
_SuiteSpec, e.g.,
|
|
|
|
builds = {'cros-version:': 'x86-alex-release/R18-1655.0.0',
|
|
'fwrw-version:': 'x86-alex-firmware/R36-5771.50.0'}
|
|
|
|
version_prefix+build should make it into each test as a DEPENDENCY.
|
|
The easiest way to do this is to tack it onto the suite_dependencies.
|
|
"""
|
|
self.suite_dependencies.extend(
|
|
provision.join(version_prefix, build)
|
|
for version_prefix, build in six.iteritems(self.builds)
|
|
)
|
|
|
|
|
|
class _ProvisionSuiteSpec(_SuiteSpec):
|
|
|
|
def __init__(self, num_required, **kwargs):
|
|
self.num_required = num_required
|
|
super(_ProvisionSuiteSpec, self).__init__(**kwargs)
|
|
|
|
|
|
def run_provision_suite(**dargs):
|
|
"""
|
|
Run a provision suite.
|
|
|
|
Will re-image a number of devices (of the specified board) with the
|
|
provided builds by scheduling dummy_Pass.
|
|
|
|
@param job: an instance of client.common_lib.base_job representing the
|
|
currently running suite job.
|
|
|
|
@raises AsynchronousBuildFailure: if there was an issue finishing staging
|
|
from the devserver.
|
|
@raises MalformedDependenciesException: if the dependency_info file for
|
|
the required build fails to parse.
|
|
"""
|
|
spec = _ProvisionSuiteSpec(**dargs)
|
|
|
|
afe = frontend_wrappers.RetryingAFE(timeout_min=30, delay_sec=10,
|
|
user=spec.job.user, debug=False)
|
|
tko = frontend_wrappers.RetryingTKO(timeout_min=30, delay_sec=10,
|
|
user=spec.job.user, debug=False)
|
|
|
|
try:
|
|
my_job_id = int(tko_utils.get_afe_job_id(spec.job.tag))
|
|
logging.debug('Determined own job id: %d', my_job_id)
|
|
except (TypeError, ValueError):
|
|
my_job_id = None
|
|
logging.warning('Could not determine own job id.')
|
|
|
|
suite = ProvisionSuite(
|
|
tag=spec.name,
|
|
builds=spec.builds,
|
|
board=spec.board,
|
|
devserver=spec.devserver,
|
|
num_required=spec.num_required,
|
|
afe=afe,
|
|
tko=tko,
|
|
pool=spec.pool,
|
|
results_dir=spec.job.resultdir,
|
|
max_runtime_mins=spec.max_runtime_mins,
|
|
timeout_mins=spec.timeout_mins,
|
|
file_bugs=spec.file_bugs,
|
|
suite_job_id=my_job_id,
|
|
extra_deps=spec.suite_dependencies,
|
|
priority=spec.priority,
|
|
wait_for_results=spec.wait_for_results,
|
|
job_retry=spec.job_retry,
|
|
max_retries=spec.max_retries,
|
|
offload_failures_only=spec.offload_failures_only,
|
|
test_source_build=spec.test_source_build,
|
|
run_prod_code=spec.run_prod_code,
|
|
job_keyvals=spec.job_keyvals,
|
|
test_args=spec.test_args,
|
|
child_dependencies=spec.child_dependencies,
|
|
)
|
|
|
|
_run_suite_with_spec(suite, spec)
|
|
|
|
logging.debug('Returning from dynamic_suite.run_provision_suite')
|
|
|
|
|
|
def reimage_and_run(**dargs):
|
|
"""
|
|
Backward-compatible API for dynamic_suite.
|
|
|
|
Will re-image a number of devices (of the specified board) with the
|
|
provided builds, and then run the indicated test suite on them.
|
|
Guaranteed to be compatible with any build from stable to dev.
|
|
|
|
@param dargs: Dictionary containing the arguments passed to _SuiteSpec().
|
|
@raises AsynchronousBuildFailure: if there was an issue finishing staging
|
|
from the devserver.
|
|
@raises MalformedDependenciesException: if the dependency_info file for
|
|
the required build fails to parse.
|
|
"""
|
|
suite_spec = _SuiteSpec(**dargs)
|
|
|
|
afe = frontend_wrappers.RetryingAFE(timeout_min=30, delay_sec=10,
|
|
user=suite_spec.job.user, debug=False)
|
|
tko = frontend_wrappers.RetryingTKO(timeout_min=30, delay_sec=10,
|
|
user=suite_spec.job.user, debug=False)
|
|
|
|
try:
|
|
my_job_id = int(tko_utils.get_afe_job_id(dargs['job'].tag))
|
|
logging.debug('Determined own job id: %d', my_job_id)
|
|
except (TypeError, ValueError):
|
|
my_job_id = None
|
|
logging.warning('Could not determine own job id.')
|
|
|
|
_perform_reimage_and_run(suite_spec, afe, tko, suite_job_id=my_job_id)
|
|
|
|
logging.debug('Returning from dynamic_suite.reimage_and_run.')
|
|
|
|
|
|
def _perform_reimage_and_run(spec, afe, tko, suite_job_id=None):
|
|
"""
|
|
Do the work of reimaging hosts and running tests.
|
|
|
|
@param spec: a populated _SuiteSpec object.
|
|
@param afe: an instance of AFE as defined in server/frontend.py.
|
|
@param tko: an instance of TKO as defined in server/frontend.py.
|
|
@param suite_job_id: Job id that will act as parent id to all sub jobs.
|
|
Default: None
|
|
"""
|
|
# We can't create the suite until the devserver has finished downloading
|
|
# control_files and test_suites packages so that we can get the control
|
|
# files to schedule.
|
|
if not spec.run_prod_code:
|
|
_stage_artifacts_for_build(spec.devserver, spec.test_source_build)
|
|
suite = Suite.create_from_predicates(
|
|
predicates=[spec.predicate],
|
|
name=spec.name,
|
|
builds=spec.builds,
|
|
board=spec.board,
|
|
devserver=spec.devserver,
|
|
afe=afe,
|
|
tko=tko,
|
|
pool=spec.pool,
|
|
results_dir=spec.job.resultdir,
|
|
max_runtime_mins=spec.max_runtime_mins,
|
|
timeout_mins=spec.timeout_mins,
|
|
file_bugs=spec.file_bugs,
|
|
suite_job_id=suite_job_id,
|
|
extra_deps=spec.suite_dependencies,
|
|
priority=spec.priority,
|
|
wait_for_results=spec.wait_for_results,
|
|
job_retry=spec.job_retry,
|
|
max_retries=spec.max_retries,
|
|
offload_failures_only=spec.offload_failures_only,
|
|
test_source_build=spec.test_source_build,
|
|
run_prod_code=spec.run_prod_code,
|
|
job_keyvals=spec.job_keyvals,
|
|
test_args=spec.test_args,
|
|
child_dependencies=spec.child_dependencies,
|
|
)
|
|
_run_suite_with_spec(suite, spec)
|
|
|
|
|
|
def _run_suite_with_spec(suite, spec):
|
|
"""
|
|
Do the work of reimaging hosts and running tests.
|
|
|
|
@param suite: _BaseSuite instance to run.
|
|
@param spec: a populated _SuiteSpec object.
|
|
"""
|
|
_run_suite(
|
|
suite=suite,
|
|
job=spec.job,
|
|
delay_minutes=spec.delay_minutes,
|
|
bug_template=spec.bug_template)
|
|
|
|
|
|
def _run_suite(
|
|
suite,
|
|
job,
|
|
delay_minutes,
|
|
bug_template):
|
|
"""
|
|
Run a suite.
|
|
|
|
@param suite: _BaseSuite instance.
|
|
@param job: an instance of client.common_lib.base_job representing the
|
|
currently running suite job.
|
|
@param delay_minutes: Delay the creation of test jobs for a given number
|
|
of minutes.
|
|
@param bug_template: A template dictionary specifying the default bug
|
|
filing options for failures in this suite.
|
|
"""
|
|
timestamp = datetime.datetime.now().strftime(time_utils.TIME_FMT)
|
|
utils.write_keyval(
|
|
job.resultdir,
|
|
{constants.ARTIFACT_FINISHED_TIME: timestamp})
|
|
|
|
if delay_minutes:
|
|
logging.debug('delay_minutes is set. Sleeping %d minutes before '
|
|
'creating test jobs.', delay_minutes)
|
|
time.sleep(delay_minutes*60)
|
|
logging.debug('Finished waiting for %d minutes before creating test '
|
|
'jobs.', delay_minutes)
|
|
|
|
# Now we get to asychronously schedule tests.
|
|
suite.schedule(job.record_entry)
|
|
|
|
if suite.wait_for_results:
|
|
logging.debug('Waiting on suite.')
|
|
suite.wait(job.record_entry)
|
|
logging.debug('Finished waiting on suite. '
|
|
'Returning from _perform_reimage_and_run.')
|
|
else:
|
|
logging.info('wait_for_results is set to False, suite job will exit '
|
|
'without waiting for test jobs to finish.')
|
|
|
|
|
|
def _stage_artifacts_for_build(devserver, build):
|
|
"""Stage artifacts for a suite job.
|
|
|
|
@param devserver: devserver to stage artifacts with.
|
|
@param build: image to stage artifacts for.
|
|
"""
|
|
try:
|
|
devserver.stage_artifacts(
|
|
image=build,
|
|
artifacts=['control_files', 'test_suites'])
|
|
except dev_server.DevServerException as e:
|
|
# If we can't get the control files, there's nothing to run.
|
|
raise error.AsynchronousBuildFailure(e)
|
|
|
|
|
|
# This function is used by the cros_test_platform suite, to unwrap json-decoded
|
|
# arguments from the cros_test_platform recipe and convert them to byte string.
|
|
#
|
|
# It should not be used for other purposes. It exists in this module simply
|
|
# to limit the number of necessary module imports in cros_test_platform.
|
|
def byteify(input_):
|
|
"""Walk a json object, turning unicode strings into byte strings."""
|
|
if isinstance(input_, dict):
|
|
return {byteify(key): byteify(value)
|
|
for key, value in six.iteritems(input_)}
|
|
elif isinstance(input_, list):
|
|
return [byteify(element) for element in input_]
|
|
elif isinstance(input_, six.text_type):
|
|
return six.ensure_binary(input_, 'utf-8')
|
|
else:
|
|
return input_ |