• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Lint as: python2, python3
2# Copyright (c) 2012 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
6from __future__ import absolute_import
7from __future__ import division
8from __future__ import print_function
9
10import abc
11import logging
12import os
13import re
14
15import common
16from autotest_lib.client.common_lib import error, utils
17from autotest_lib.client.common_lib.cros import dev_server
18import six
19from six.moves import filter
20
21
22# Relevant CrosDynamicSuiteExceptions are defined in client/common_lib/error.py.
23
24
25class ControlFileGetter(six.with_metaclass(abc.ABCMeta, object)):
26    """
27    Interface for classes that can list and fetch known control files.
28    """
29
30
31    @abc.abstractmethod
32    def get_control_file_list(self, suite_name=''):
33        """
34        Gather a list of paths to control files.
35
36        @param suite_name: The name of a suite we would like control files for.
37        @return A list of file paths.
38        @throws NoControlFileList if there is an error while listing.
39        """
40        pass
41
42
43    @abc.abstractmethod
44    def get_control_file_contents(self, test_path):
45        """
46        Given a path to a control file, return its contents.
47
48        @param test_path: the path to the control file.
49        @return the contents of the control file specified by the path.
50        @throws ControlFileNotFound if the file cannot be retrieved.
51        """
52        pass
53
54
55    @abc.abstractmethod
56    def get_control_file_contents_by_name(self, test_name):
57        """
58        Given the name of a control file, return its contents.
59
60        @param test_name: the name of the test whose control file is desired.
61        @return the contents of the control file specified by the name.
62        @throws ControlFileNotFound if the file cannot be retrieved.
63        """
64        pass
65
66
67class SuiteControlFileGetter(ControlFileGetter):
68    """Interface that additionally supports getting by suite."""
69
70
71    @abc.abstractmethod
72    def get_suite_info(self, suite_name=''):
73        """
74        Gather the control paths and contents of all the control files.
75
76        @param suite_name: The name of a suite we would like control files for.
77        @return the control paths and contents of all the control files
78        specified by the name.
79        @throws SuiteControlFileException if the info cannot be retrieved.
80        """
81        pass
82
83
84class CacheingAndFilteringControlFileGetter(ControlFileGetter):
85    """Wraps ControlFileGetter to cache the retrieved control file list and
86    filter out unwanted control files."""
87
88    CONTROL_FILE_FILTERS = ['src/debian/control']
89
90    def __init__(self):
91        super(CacheingAndFilteringControlFileGetter, self).__init__()
92        self._files = []
93
94
95    def get_control_file_list(self, suite_name=''):
96        """
97        Gather a list of paths to control files.
98
99        Gets a list of control files; populates |self._files| with that list
100        and then returns the paths to all useful and wanted files in the list.
101
102        @param suite_name: The name of a suite we would like control files for.
103        @return A list of file paths.
104        @throws NoControlFileList if there is an error while listing.
105        """
106        files = self._get_control_file_list(suite_name=suite_name)
107        for cf_filter in self.CONTROL_FILE_FILTERS:
108            files = [path for path in files if not path.endswith(cf_filter)]
109        self._files = files
110        return self._files
111
112
113    @abc.abstractmethod
114    def _get_control_file_list(self, suite_name=''):
115        pass
116
117
118    def get_control_file_path(self, test_name):
119        """
120        Given the name of a control file, return its path.
121
122        Searches through previously-compiled list in |self._files| for a
123        test named |test_name| and returns the contents of the control file
124        for that test if it is found.
125
126        @param test_name: the name of the test whose control file is desired.
127        @return control file path
128        @throws ControlFileNotFound if the file cannot be retrieved.
129        """
130        if not self._files and not self.get_control_file_list():
131            raise error.ControlFileNotFound('No control files found.')
132
133        if 'control' not in test_name:
134            regexp = re.compile(os.path.join(test_name, 'control$'))
135        else:
136            regexp = re.compile(test_name + '$')
137        candidates = list(filter(regexp.search, self._files))
138        if not candidates:
139            logging.debug('Cannot find %s in %r', regexp.pattern, self._files)
140            raise error.ControlFileNotFound('No control file for ' + test_name)
141        if len(candidates) > 1:
142            raise error.ControlFileNotFound(test_name + ' is not unique.')
143        return candidates[0]
144
145
146    def get_control_file_contents_by_name(self, test_name):
147        """
148        Given the name of a control file, return its contents.
149
150        Searches through previously-compiled list in |self._files| for a
151        test named |test_name| and returns the contents of the control file
152        for that test if it is found.
153
154        @param test_name: the name of the test whose control file is desired.
155        @return the contents of the control file specified by the name.
156        @throws ControlFileNotFound if the file cannot be retrieved.
157        """
158        path = self.get_control_file_path(test_name)
159        return self.get_control_file_contents(path)
160
161
162class FileSystemGetter(CacheingAndFilteringControlFileGetter):
163    """
164    Class that can list and fetch known control files from disk.
165
166    @var _CONTROL_PATTERN: control file name format to match.
167    """
168
169    _CONTROL_PATTERN = '^control(?:\..+)?$'
170
171    def __init__(self, paths):
172        """
173        @param paths: base directories to start search.
174        """
175        super(FileSystemGetter, self).__init__()
176        self._paths = paths
177
178
179    def _is_useful_file(self, name):
180        return '__init__.py' not in name and '.svn' not in name
181
182
183    def _get_control_file_list(self, suite_name=''):
184        """
185        Gather a list of paths to control files under |self._paths|.
186
187        Searches under |self._paths| for files that match
188        |self._CONTROL_PATTERN|.  Populates |self._files| with that list
189        and then returns the paths to all useful files in the list.
190
191        @param suite_name: The name of a suite we would like control files for.
192        @return A list of files that match |self._CONTROL_PATTERN|.
193        @throws NoControlFileList if we find no files.
194        """
195        if suite_name:
196            logging.debug('Getting control files for a specific suite has '
197                          'not been implemented for FileSystemGetter. '
198                          'Getting all control files instead.')
199
200
201        regexp = re.compile(self._CONTROL_PATTERN)
202        directories = self._paths
203        # Some of our callers are ill-considered and request that we
204        # search all of /usr/local/autotest (crbug.com/771823).
205        # Fixing the callers immediately is somewhere between a
206        # nuisance and hard.  So, we have a blocklist, hoping two
207        # wrongs will somehow make it right.
208        blocklist = {
209                'site-packages',
210                'venv',
211                'results',
212                'logs',
213                'containers',
214        }
215        while len(directories) > 0:
216            directory = directories.pop()
217            if not os.path.exists(directory):
218                continue
219            try:
220                for name in os.listdir(directory):
221                    if name in blocklist:
222                        continue
223                    fullpath = os.path.join(directory, name)
224                    if os.path.isfile(fullpath):
225                        if regexp.search(name):
226                            # if we are a control file
227                            self._files.append(fullpath)
228                    elif (not os.path.islink(fullpath)
229                          and os.path.isdir(fullpath)):
230                        directories.append(fullpath)
231            except OSError:
232                # Some directories under results/ like the Chrome Crash
233                # Reports will cause issues when attempted to be searched.
234                logging.error('Unable to search directory %s for control '
235                              'files.', directory)
236                pass
237        if not self._files:
238            msg = 'No control files under ' + ','.join(self._paths)
239            raise error.NoControlFileList(msg)
240        return [f for f in self._files if self._is_useful_file(f)]
241
242
243    def get_control_file_contents(self, test_path):
244        """
245        Get the contents of the control file at |test_path|.
246
247        @return The contents of the aforementioned file.
248        @throws ControlFileNotFound if the file cannot be retrieved.
249        """
250        try:
251            return utils.read_file(test_path)
252        except EnvironmentError as errs:
253            (errno, strerror) = errs.args
254            msg = "Can't retrieve {0}: {1} ({2})".format(test_path,
255                                                         strerror,
256                                                         errno)
257            raise error.ControlFileNotFound(msg)
258
259
260class DevServerGetter(CacheingAndFilteringControlFileGetter,
261                      SuiteControlFileGetter):
262    """Class that can list and fetch known control files from DevServer.
263
264    @var _CONTROL_PATTERN: control file name format to match.
265    """
266    def __init__(self, build, ds):
267        """
268        @param build: The build from which to get control files.
269        @param ds: An existing dev_server.DevServer object to use.
270        """
271        super(DevServerGetter, self).__init__()
272        self._dev_server = ds
273        self._build = build
274
275
276    @staticmethod
277    def create(build, ds=None):
278        """Wraps constructor.  Can be mocked for testing purposes.
279        @param build: The build from which to get control files.
280        @param ds: An existing dev_server.DevServer object to use
281                  (default=None)
282        @returns: New DevServerGetter.
283        """
284        return DevServerGetter(build, ds)
285
286
287    def _get_control_file_list(self, suite_name=''):
288        """
289        Gather a list of paths to control files from |self._dev_server|.
290
291        Get a listing of all the control files for |self._build| on
292        |self._dev_server|.  Populates |self._files| with that list
293        and then returns paths (under the autotest dir) to them. If suite_name
294        is specified, this method populates |self._files| with the control
295        files from just the specified suite.
296
297        @param suite_name: The name of a suite we would like control files for.
298        @return A list of control file paths.
299        @throws NoControlFileList if there is an error while listing.
300        """
301        try:
302            return self._dev_server.list_control_files(self._build,
303                                                       suite_name=suite_name)
304        except dev_server.DevServerException as e:
305            raise error.NoControlFileList(e)
306
307
308    def get_control_file_contents(self, test_path):
309        """
310        Return the contents of |test_path| from |self._dev_server|.
311
312        Get the contents of the control file at |test_path| for |self._build| on
313        |self._dev_server|.
314
315        @return The contents of |test_path|.  None on failure.
316        @throws ControlFileNotFound if the file cannot be retrieved.
317        """
318        try:
319            return self._dev_server.get_control_file(self._build, test_path)
320        except dev_server.DevServerException as e:
321            raise error.ControlFileNotFound(e)
322
323
324    def _list_suite_controls(self, suite_name=''):
325        """
326        Gather a dict {path:content} of all control files from
327        |self._dev_server|.
328
329        Get a dict of contents of all the control files for |self._build| on
330        |self._dev_server|: path is the key, and the control file content is
331        the value.
332
333        @param suite_name: The name of a suite we would like control files for.
334        @return A dict of paths and contents of all control files.
335        @throws NoControlFileList if there is an error while listing.
336        """
337        try:
338            return self._dev_server.list_suite_controls(self._build,
339                                                        suite_name=suite_name)
340        except dev_server.DevServerException as e:
341            raise error.SuiteControlFileException(e)
342
343
344    def get_suite_info(self, suite_name=''):
345        """
346        Gather info of a list of control files from |self._dev_server|.
347
348        The info is a dict: {control_path: control_file_content} for
349        |self._build| on |self._dev_server|.
350
351        @param suite_name: The name of a suite we would like control files for.
352        @return A dict of paths and contents of all control files:
353            {path1: content1, path2: content2, ..., pathX: contentX}
354        """
355        file_contents = self._list_suite_controls(suite_name=suite_name)
356        files = list(file_contents.keys())
357        for cf_filter in self.CONTROL_FILE_FILTERS:
358            files = [path for path in files if not path.endswith(cf_filter)]
359        self._files = files
360        return {f: file_contents[f] for f in files}
361