• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python -u
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
6"""
7Check an autotest control file for required variables.
8
9This wrapper is invoked through autotest's PRESUBMIT.cfg for every commit
10that edits a control file.
11"""
12
13
14import argparse
15import glob
16import os
17import re
18import subprocess
19
20import common
21from autotest_lib.client.common_lib import control_data
22from autotest_lib.server.cros.dynamic_suite import reporting_utils
23
24
25DEPENDENCY_ARC = 'arc'
26SUITES_NEED_RETRY = set(['bvt-arc', 'bvt-cq', 'bvt-inline'])
27TESTS_NEED_ARC = 'cheets_'
28
29
30class ControlFileCheckerError(Exception):
31    """Raised when a necessary condition of this checker isn't satisfied."""
32
33
34def IsInChroot():
35    """Return boolean indicating if we are running in the chroot."""
36    return os.path.exists("/etc/debian_chroot")
37
38
39def CommandPrefix():
40    """Return an argv list which must appear at the start of shell commands."""
41    if IsInChroot():
42        return []
43    else:
44        return ['cros_sdk', '--']
45
46
47def GetOverlayPath(overlay=None):
48    """
49    Return the path to the overlay directory.
50
51    If the overlay path is not given, the default chromiumos-overlay path
52    will be returned instead.
53
54    @param overlay: The overlay repository path for autotest ebuilds.
55
56    @return normalized absolutized path of the overlay repository.
57    """
58    if not overlay:
59        ourpath = os.path.abspath(__file__)
60        overlay = os.path.join(os.path.dirname(ourpath),
61                               "../../../../chromiumos-overlay/")
62    return os.path.normpath(overlay)
63
64
65def GetAutotestTestPackages(overlay=None):
66    """
67    Return a list of ebuilds which should be checked for test existance.
68
69    @param overlay: The overlay repository path for autotest ebuilds.
70
71    @return autotest packages in overlay repository.
72    """
73    overlay = GetOverlayPath(overlay)
74    packages = glob.glob(os.path.join(overlay, "chromeos-base/autotest-*"))
75    # Return the packages list with the leading overlay path removed.
76    return [x[(len(overlay) + 1):] for x in packages]
77
78
79def GetEqueryWrappers():
80    """Return a list of all the equery variants that should be consulted."""
81    # Note that we can't just glob.glob('/usr/local/bin/equery-*'), because
82    # we might be running outside the chroot.
83    pattern = '/usr/local/bin/equery-*'
84    cmd = CommandPrefix() + ['sh', '-c', 'echo %s' % pattern]
85    wrappers = subprocess.check_output(cmd).split()
86    # If there was no match, we get the literal pattern string echoed back.
87    if wrappers and wrappers[0] == pattern:
88        wrappers = []
89    return ['equery'] + wrappers
90
91
92def GetUseFlags(overlay=None):
93    """Get the set of all use flags from autotest packages.
94
95    @param overlay: The overlay repository path for autotest ebuilds.
96
97    @returns: useflags
98    """
99    useflags = set()
100    for equery in GetEqueryWrappers():
101        cmd_args = (CommandPrefix() + [equery, '-qC', 'uses'] +
102                    GetAutotestTestPackages(overlay))
103        child = subprocess.Popen(cmd_args, stdout=subprocess.PIPE,
104                                 stderr=subprocess.PIPE)
105        new_useflags = child.communicate()[0].splitlines()
106        if child.returncode == 0:
107            useflags = useflags.union(new_useflags)
108    return useflags
109
110
111def CheckSuites(ctrl_data, test_name, useflags):
112    """
113    Check that any test in a SUITE is also in an ebuild.
114
115    Throws a ControlFileCheckerError if a test within a SUITE
116    does not appear in an ebuild. For purposes of this check,
117    the psuedo-suite "manual" does not require a test to be
118    in an ebuild.
119
120    @param ctrl_data: The control_data object for a test.
121    @param test_name: A string with the name of the test.
122    @param useflags: Set of all use flags from autotest packages.
123
124    @returns: None
125    """
126    if (hasattr(ctrl_data, 'suite') and ctrl_data.suite and
127        ctrl_data.suite != 'manual'):
128        # To handle the case where a developer has cros_workon'd
129        # e.g. autotest-tests on one particular board, and has the
130        # test listed only in the -9999 ebuild, we have to query all
131        # the equery-* board-wrappers until we find one. We ALSO have
132        # to check plain 'equery', to handle the case where e.g. a
133        # developer who has never run setup_board, and has no
134        # wrappers, is making a quick edit to some existing control
135        # file already enabled in the stable ebuild.
136        for flag in useflags:
137            if flag.startswith('-') or flag.startswith('+'):
138                flag = flag[1:]
139            if flag == 'tests_%s' % test_name:
140                return
141        raise ControlFileCheckerError(
142                'No ebuild entry for %s. To fix, please do the following: 1. '
143                'Add your new test to one of the ebuilds referenced by '
144                'autotest-all. 2. cros_workon start --board=<board> '
145                '<your_ebuild>. 3. emerge-<board> <your_ebuild>' % test_name)
146
147
148def CheckValidAttr(ctrl_data, whitelist, test_name):
149    """
150    Check whether ATTRIBUTES are in the whitelist.
151
152    Throw a ControlFileCheckerError if tags in ATTRIBUTES don't exist in the
153    whitelist.
154
155    @param ctrl_data: The control_data object for a test.
156    @param whitelist: whitelist set parsed from the attribute_whitelist file.
157    @param test_name: A string with the name of the test.
158
159    @returns: None
160    """
161    if not (whitelist >= ctrl_data.attributes):
162        attribute_diff = ctrl_data.attributes - whitelist
163        raise ControlFileCheckerError(
164            'Attribute(s): %s not in the whitelist in control file for test '
165            'named %s. If this is a new attribute, please add it into '
166            'AUTOTEST_DIR/site_utils/attribute_whitelist.txt file'
167            % (attribute_diff, test_name))
168
169
170def CheckSuiteLineRemoved(ctrl_file_path):
171    """
172    Check whether the SUITE line has been removed since it is obsolete.
173
174    @param ctrl_file_path: The path to the control file.
175
176    @raises: ControlFileCheckerError if check fails.
177    """
178    with open(ctrl_file_path, 'r') as f:
179        for line in f.readlines():
180            if line.startswith('SUITE'):
181                raise ControlFileCheckerError(
182                    'SUITE is an obsolete variable, please remove it from %s. '
183                    'Instead, add suite:<your_suite> to the ATTRIBUTES field.'
184                    % ctrl_file_path)
185
186
187def CheckRetry(ctrl_data, test_name):
188    """
189    Check that any test in SUITES_NEED_RETRY has turned on retry.
190
191    @param ctrl_data: The control_data object for a test.
192    @param test_name: A string with the name of the test.
193
194    @raises: ControlFileCheckerError if check fails.
195    """
196    if hasattr(ctrl_data, 'suite') and ctrl_data.suite:
197        suites = set(x.strip() for x in ctrl_data.suite.split(',') if x.strip())
198        if ctrl_data.job_retries < 2 and SUITES_NEED_RETRY.intersection(suites):
199            raise ControlFileCheckerError(
200                'Setting JOB_RETRIES to 2 or greater for test in '
201                '%s is recommended. Please set it in the control '
202                'file for %s.' % (' or '.join(SUITES_NEED_RETRY), test_name))
203
204
205def CheckDependencies(ctrl_data, test_name):
206    """
207    Check if any dependencies of a test is required
208
209    @param ctrl_data: The control_data object for a test.
210    @param test_name: A string with the name of the test.
211
212    @raises: ControlFileCheckerError if check fails.
213    """
214    if test_name.startswith(TESTS_NEED_ARC):
215        if not DEPENDENCY_ARC in ctrl_data.dependencies:
216            raise ControlFileCheckerError(
217                    'DEPENDENCIES = \'arc\' for %s is needed' % test_name)
218
219
220def main():
221    """
222    Checks if all control files that are a part of this commit conform to the
223    ChromeOS autotest guidelines.
224    """
225    parser = argparse.ArgumentParser(description='Process overlay arguments.')
226    parser.add_argument('--overlay', default=None, help='the overlay directory path')
227    args = parser.parse_args()
228    file_list = os.environ.get('PRESUBMIT_FILES')
229    if file_list is None:
230        raise ControlFileCheckerError('Expected a list of presubmit files in '
231            'the PRESUBMIT_FILES environment variable.')
232
233    # Parse the whitelist set from file, hardcode the filepath to the whitelist.
234    path_whitelist = os.path.join(common.autotest_dir,
235                                  'site_utils/attribute_whitelist.txt')
236    with open(path_whitelist, 'r') as f:
237        whitelist = {line.strip() for line in f.readlines() if line.strip()}
238
239    # Delay getting the useflags. The call takes long time, so init useflags
240    # only when needed, i.e., the script needs to check any control file.
241    useflags = None
242    for file_path in file_list.split('\n'):
243        control_file = re.search(r'.*/control(?:\.\w+)?$', file_path)
244        if control_file:
245            ctrl_file_path = control_file.group(0)
246            CheckSuiteLineRemoved(ctrl_file_path)
247            ctrl_data = control_data.parse_control(ctrl_file_path,
248                                                   raise_warnings=True)
249            test_name = os.path.basename(os.path.split(file_path)[0])
250            try:
251                reporting_utils.BugTemplate.validate_bug_template(
252                        ctrl_data.bug_template)
253            except AttributeError:
254                # The control file may not have bug template defined.
255                pass
256
257            if not useflags:
258                useflags = GetUseFlags(args.overlay)
259            CheckSuites(ctrl_data, test_name, useflags)
260            CheckValidAttr(ctrl_data, whitelist, test_name)
261            CheckRetry(ctrl_data, test_name)
262            CheckDependencies(ctrl_data, test_name)
263
264
265if __name__ == '__main__':
266    main()
267