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