294 lines
11 KiB
Python
Executable File
294 lines
11 KiB
Python
Executable File
#!/usr/bin/python2 -u
|
|
# Copyright (c) 2013 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.
|
|
|
|
"""
|
|
Check an autotest control file for required variables.
|
|
|
|
This wrapper is invoked through autotest's PRESUBMIT.cfg for every commit
|
|
that edits a control file.
|
|
"""
|
|
|
|
|
|
import argparse
|
|
import fnmatch
|
|
import glob
|
|
import os
|
|
import re
|
|
import subprocess
|
|
|
|
import common
|
|
from autotest_lib.client.common_lib import control_data
|
|
from autotest_lib.server.cros.dynamic_suite import reporting_utils
|
|
|
|
|
|
DEPENDENCY_ARC = 'arc'
|
|
SUITES_NEED_RETRY = set(['bvt-arc', 'bvt-cq', 'bvt-inline'])
|
|
TESTS_NEED_ARC = 'cheets_'
|
|
BVT_ATTRS = set(
|
|
['suite:smoke', 'suite:bvt-inline', 'suite:bvt-cq', 'suite:bvt-arc'])
|
|
TAST_PSA_URL = (
|
|
'https://groups.google.com/a/chromium.org/d/topic/chromium-os-dev'
|
|
'/zH1nO7OjJ2M/discussion')
|
|
|
|
|
|
class ControlFileCheckerError(Exception):
|
|
"""Raised when a necessary condition of this checker isn't satisfied."""
|
|
|
|
|
|
def IsInChroot():
|
|
"""Return boolean indicating if we are running in the chroot."""
|
|
return os.path.exists("/etc/debian_chroot")
|
|
|
|
|
|
def CommandPrefix():
|
|
"""Return an argv list which must appear at the start of shell commands."""
|
|
if IsInChroot():
|
|
return []
|
|
else:
|
|
return ['cros_sdk', '--']
|
|
|
|
|
|
def GetOverlayPath(overlay=None):
|
|
"""
|
|
Return the path to the overlay directory.
|
|
|
|
If the overlay path is not given, the default chromiumos-overlay path
|
|
will be returned instead.
|
|
|
|
@param overlay: The overlay repository path for autotest ebuilds.
|
|
|
|
@return normalized absolutized path of the overlay repository.
|
|
"""
|
|
if not overlay:
|
|
ourpath = os.path.abspath(__file__)
|
|
overlay = os.path.join(os.path.dirname(ourpath),
|
|
"../../../../chromiumos-overlay/")
|
|
return os.path.normpath(overlay)
|
|
|
|
|
|
def GetAutotestTestPackages(overlay=None):
|
|
"""
|
|
Return a list of ebuilds which should be checked for test existance.
|
|
|
|
@param overlay: The overlay repository path for autotest ebuilds.
|
|
|
|
@return autotest packages in overlay repository.
|
|
"""
|
|
overlay = GetOverlayPath(overlay)
|
|
packages = glob.glob(os.path.join(overlay, "chromeos-base/autotest-*"))
|
|
# Return the packages list with the leading overlay path removed.
|
|
return [x[(len(overlay) + 1):] for x in packages]
|
|
|
|
|
|
def GetEqueryWrappers():
|
|
"""Return a list of all the equery variants that should be consulted."""
|
|
# Note that we can't just glob.glob('/usr/local/bin/equery-*'), because
|
|
# we might be running outside the chroot.
|
|
pattern = '/usr/local/bin/equery-*'
|
|
cmd = CommandPrefix() + ['sh', '-c', 'echo %s' % pattern]
|
|
wrappers = subprocess.check_output(cmd).split()
|
|
# If there was no match, we get the literal pattern string echoed back.
|
|
if wrappers and wrappers[0] == pattern:
|
|
wrappers = []
|
|
return ['equery'] + wrappers
|
|
|
|
|
|
def GetUseFlags(overlay=None):
|
|
"""Get the set of all use flags from autotest packages.
|
|
|
|
@param overlay: The overlay repository path for autotest ebuilds.
|
|
|
|
@returns: useflags
|
|
"""
|
|
useflags = set()
|
|
for equery in GetEqueryWrappers():
|
|
cmd_args = (CommandPrefix() + [equery, '-qC', 'uses'] +
|
|
GetAutotestTestPackages(overlay))
|
|
child = subprocess.Popen(cmd_args, stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE)
|
|
new_useflags = child.communicate()[0].splitlines()
|
|
if child.returncode == 0:
|
|
useflags = useflags.union(new_useflags)
|
|
return useflags
|
|
|
|
|
|
def CheckSuites(ctrl_data, test_name, useflags):
|
|
"""
|
|
Check that any test in a SUITE is also in an ebuild.
|
|
|
|
Throws a ControlFileCheckerError if a test within a SUITE
|
|
does not appear in an ebuild. For purposes of this check,
|
|
the psuedo-suite "manual" does not require a test to be
|
|
in an ebuild.
|
|
|
|
@param ctrl_data: The control_data object for a test.
|
|
@param test_name: A string with the name of the test.
|
|
@param useflags: Set of all use flags from autotest packages.
|
|
|
|
@returns: None
|
|
"""
|
|
if (hasattr(ctrl_data, 'suite') and ctrl_data.suite and
|
|
ctrl_data.suite != 'manual'):
|
|
# To handle the case where a developer has cros_workon'd
|
|
# e.g. autotest-tests on one particular board, and has the
|
|
# test listed only in the -9999 ebuild, we have to query all
|
|
# the equery-* board-wrappers until we find one. We ALSO have
|
|
# to check plain 'equery', to handle the case where e.g. a
|
|
# developer who has never run setup_board, and has no
|
|
# wrappers, is making a quick edit to some existing control
|
|
# file already enabled in the stable ebuild.
|
|
for flag in useflags:
|
|
if flag.startswith('-') or flag.startswith('+'):
|
|
flag = flag[1:]
|
|
if flag == 'tests_%s' % test_name:
|
|
return
|
|
raise ControlFileCheckerError(
|
|
'No ebuild entry for %s. To fix, please do the following: 1. '
|
|
'Add your new test to one of the ebuilds referenced by '
|
|
'autotest-all. 2. cros_workon --board=<board> start '
|
|
'<your_ebuild>. 3. emerge-<board> <your_ebuild>' % test_name)
|
|
|
|
|
|
def CheckValidAttr(ctrl_data, attr_allowlist, bvt_allowlist, test_name):
|
|
"""
|
|
Check whether ATTRIBUTES are in the allowlist.
|
|
|
|
Throw a ControlFileCheckerError if tags in ATTRIBUTES don't exist in the
|
|
allowlist.
|
|
|
|
@param ctrl_data: The control_data object for a test.
|
|
@param attr_allowlist: allowlist set parsed from the attribute_allowlist.
|
|
@param bvt_allowlist: allowlist set parsed from the bvt_allowlist.
|
|
@param test_name: A string with the name of the test.
|
|
|
|
@returns: None
|
|
"""
|
|
if not (attr_allowlist >= ctrl_data.attributes):
|
|
attribute_diff = ctrl_data.attributes - attr_allowlist
|
|
raise ControlFileCheckerError(
|
|
'Attribute(s): %s not in the allowlist in control file for test '
|
|
'named %s. If this is a new attribute, please add it into '
|
|
'AUTOTEST_DIR/site_utils/attribute_allowlist.txt file' %
|
|
(attribute_diff, test_name))
|
|
if ctrl_data.attributes & BVT_ATTRS:
|
|
for pattern in bvt_allowlist:
|
|
if fnmatch.fnmatch(test_name, pattern):
|
|
break
|
|
else:
|
|
raise ControlFileCheckerError(
|
|
'%s not in the BVT allowlist. New BVT tests should be written '
|
|
'in Tast, not in Autotest. See: %s' %
|
|
(test_name, TAST_PSA_URL))
|
|
|
|
|
|
def CheckSuiteLineRemoved(ctrl_file_path):
|
|
"""
|
|
Check whether the SUITE line has been removed since it is obsolete.
|
|
|
|
@param ctrl_file_path: The path to the control file.
|
|
|
|
@raises: ControlFileCheckerError if check fails.
|
|
"""
|
|
with open(ctrl_file_path, 'r') as f:
|
|
for line in f.readlines():
|
|
if line.startswith('SUITE'):
|
|
raise ControlFileCheckerError(
|
|
'SUITE is an obsolete variable, please remove it from %s. '
|
|
'Instead, add suite:<your_suite> to the ATTRIBUTES field.'
|
|
% ctrl_file_path)
|
|
|
|
|
|
def CheckRetry(ctrl_data, test_name):
|
|
"""
|
|
Check that any test in SUITES_NEED_RETRY has turned on retry.
|
|
|
|
@param ctrl_data: The control_data object for a test.
|
|
@param test_name: A string with the name of the test.
|
|
|
|
@raises: ControlFileCheckerError if check fails.
|
|
"""
|
|
if hasattr(ctrl_data, 'suite') and ctrl_data.suite:
|
|
suites = set(x.strip() for x in ctrl_data.suite.split(',') if x.strip())
|
|
if ctrl_data.job_retries < 2 and SUITES_NEED_RETRY.intersection(suites):
|
|
raise ControlFileCheckerError(
|
|
'Setting JOB_RETRIES to 2 or greater for test in '
|
|
'%s is recommended. Please set it in the control '
|
|
'file for %s.' % (' or '.join(SUITES_NEED_RETRY), test_name))
|
|
|
|
|
|
def CheckDependencies(ctrl_data, test_name):
|
|
"""
|
|
Check if any dependencies of a test is required
|
|
|
|
@param ctrl_data: The control_data object for a test.
|
|
@param test_name: A string with the name of the test.
|
|
|
|
@raises: ControlFileCheckerError if check fails.
|
|
"""
|
|
if test_name.startswith(TESTS_NEED_ARC):
|
|
if not DEPENDENCY_ARC in ctrl_data.dependencies:
|
|
raise ControlFileCheckerError(
|
|
'DEPENDENCIES = \'arc\' for %s is needed' % test_name)
|
|
|
|
|
|
def main():
|
|
"""
|
|
Checks if all control files that are a part of this commit conform to the
|
|
ChromeOS autotest guidelines.
|
|
"""
|
|
parser = argparse.ArgumentParser(description='Process overlay arguments.')
|
|
parser.add_argument('--overlay', default=None, help='the overlay directory path')
|
|
args = parser.parse_args()
|
|
file_list = os.environ.get('PRESUBMIT_FILES')
|
|
if file_list is None:
|
|
raise ControlFileCheckerError('Expected a list of presubmit files in '
|
|
'the PRESUBMIT_FILES environment variable.')
|
|
|
|
# Parse the allowlist set from file, hardcode the filepath to the allowlist.
|
|
path_attr_allowlist = os.path.join(common.autotest_dir,
|
|
'site_utils/attribute_allowlist.txt')
|
|
with open(path_attr_allowlist, 'r') as f:
|
|
attr_allowlist = {
|
|
line.strip()
|
|
for line in f.readlines() if line.strip()
|
|
}
|
|
|
|
path_bvt_allowlist = os.path.join(common.autotest_dir,
|
|
'site_utils/bvt_allowlist.txt')
|
|
with open(path_bvt_allowlist, 'r') as f:
|
|
bvt_allowlist = {
|
|
line.strip()
|
|
for line in f.readlines() if line.strip()
|
|
}
|
|
|
|
# Delay getting the useflags. The call takes long time, so init useflags
|
|
# only when needed, i.e., the script needs to check any control file.
|
|
useflags = None
|
|
for file_path in file_list.split('\n'):
|
|
control_file = re.search(r'.*/control(?:\..+)?$', file_path)
|
|
if control_file:
|
|
ctrl_file_path = control_file.group(0)
|
|
CheckSuiteLineRemoved(ctrl_file_path)
|
|
ctrl_data = control_data.parse_control(ctrl_file_path,
|
|
raise_warnings=True)
|
|
test_name = os.path.basename(os.path.split(file_path)[0])
|
|
try:
|
|
reporting_utils.BugTemplate.validate_bug_template(
|
|
ctrl_data.bug_template)
|
|
except AttributeError:
|
|
# The control file may not have bug template defined.
|
|
pass
|
|
|
|
if not useflags:
|
|
useflags = GetUseFlags(args.overlay)
|
|
CheckSuites(ctrl_data, test_name, useflags)
|
|
CheckValidAttr(ctrl_data, attr_allowlist, bvt_allowlist, test_name)
|
|
CheckRetry(ctrl_data, test_name)
|
|
CheckDependencies(ctrl_data, test_name)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|