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