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
56class CacheingAndFilteringControlFileGetter(ControlFileGetter):
57    """Wraps ControlFileGetter to cache the retrieved control file list and
58    filter out unwanted control files."""
59
60    CONTROL_FILE_FILTERS = ['src/debian/control']
61
62    def __init__(self):
63        super(CacheingAndFilteringControlFileGetter, self).__init__()
64        self._files = []
65
66
67    def get_control_file_list(self, suite_name=''):
68        """
69        Gather a list of paths to control files.
70
71        Gets a list of control files; populates |self._files| with that list
72        and then returns the paths to all useful and wanted files in the list.
73
74        @param suite_name: The name of a suite we would like control files for.
75        @return A list of file paths.
76        @throws NoControlFileList if there is an error while listing.
77        """
78        files = self._get_control_file_list(suite_name=suite_name)
79        for cf_filter in self.CONTROL_FILE_FILTERS:
80          files = filter(lambda path: not path.endswith(cf_filter), files)
81        self._files = files
82        return self._files
83
84
85    def get_control_file_contents_by_name(self, test_name):
86        """
87        Given the name of a control file, return its contents.
88
89        Searches through previously-compiled list in |self._files| for a
90        test named |test_name| and returns the contents of the control file
91        for that test if it is found.
92
93        @param test_name: the name of the test whose control file is desired.
94        @return the contents of the control file specified by the name.
95        @throws ControlFileNotFound if the file cannot be retrieved.
96        """
97        if not self._files and not self.get_control_file_list():
98            raise error.ControlFileNotFound('No control files found.')
99
100        if 'control' not in test_name:
101            regexp = re.compile(os.path.join(test_name, 'control$'))
102        else:
103            regexp = re.compile(test_name + '$')
104        candidates = filter(regexp.search, self._files)
105        if not candidates:
106            raise error.ControlFileNotFound('No control file for ' + test_name)
107        if len(candidates) > 1:
108            raise error.ControlFileNotFound(test_name + ' is not unique.')
109        return self.get_control_file_contents(candidates[0])
110
111
112class FileSystemGetter(CacheingAndFilteringControlFileGetter):
113    """
114    Class that can list and fetch known control files from disk.
115
116    @var _CONTROL_PATTERN: control file name format to match.
117    """
118
119    _CONTROL_PATTERN = '^control(?:\..+)?$'
120
121    def __init__(self, paths):
122        """
123        @param paths: base directories to start search.
124        """
125        super(FileSystemGetter, self).__init__()
126        self._paths = paths
127
128
129    def _is_useful_file(self, name):
130        return '__init__.py' not in name and '.svn' not in name
131
132
133    def _get_control_file_list(self, suite_name=''):
134        """
135        Gather a list of paths to control files under |self._paths|.
136
137        Searches under |self._paths| for files that match
138        |self._CONTROL_PATTERN|.  Populates |self._files| with that list
139        and then returns the paths to all useful files in the list.
140
141        @param suite_name: The name of a suite we would like control files for.
142        @return A list of files that match |self._CONTROL_PATTERN|.
143        @throws NoControlFileList if we find no files.
144        """
145        if suite_name:
146            logging.debug('Getting control files for a specific suite has '
147                          'not been implemented for FileSystemGetter. '
148                          'Getting all control files instead.')
149
150
151        regexp = re.compile(self._CONTROL_PATTERN)
152        directories = self._paths
153        while len(directories) > 0:
154            directory = directories.pop()
155            if not os.path.exists(directory):
156                continue
157            try:
158                for name in os.listdir(directory):
159                    fullpath = os.path.join(directory, name)
160                    if os.path.isfile(fullpath):
161                        if regexp.search(name):
162                            # if we are a control file
163                            self._files.append(fullpath)
164                    elif os.path.isdir(fullpath):
165                        directories.append(fullpath)
166            except OSError:
167                # Some directories under results/ like the Chrome Crash
168                # Reports will cause issues when attempted to be searched.
169                logging.error('Unable to search directory %d for control '
170                              'files.', directory)
171                pass
172        if not self._files:
173            msg = 'No control files under ' + ','.join(self._paths)
174            raise error.NoControlFileList(msg)
175        return [f for f in self._files if self._is_useful_file(f)]
176
177
178    def get_control_file_contents(self, test_path):
179        """
180        Get the contents of the control file at |test_path|.
181
182        @return The contents of the aforementioned file.
183        @throws ControlFileNotFound if the file cannot be retrieved.
184        """
185        try:
186            return utils.read_file(test_path)
187        except EnvironmentError as (errno, strerror):
188            msg = "Can't retrieve {0}: {1} ({2})".format(test_path,
189                                                         strerror,
190                                                         errno)
191            raise error.ControlFileNotFound(msg)
192
193
194class DevServerGetter(CacheingAndFilteringControlFileGetter):
195    """Class that can list and fetch known control files from DevServer.
196
197    @var _CONTROL_PATTERN: control file name format to match.
198    """
199    def __init__(self, build, ds):
200        """
201        @param build: The build from which to get control files.
202        @param ds: An existing dev_server.DevServer object to use.
203        """
204        super(DevServerGetter, self).__init__()
205        self._dev_server = ds
206        self._build = build
207
208
209    @staticmethod
210    def create(build, ds=None):
211        """Wraps constructor.  Can be mocked for testing purposes.
212        @param build: The build from which to get control files.
213        @param ds: An existing dev_server.DevServer object to use
214                  (default=None)
215        @returns: New DevServerGetter.
216        """
217        return DevServerGetter(build, ds)
218
219
220    def _get_control_file_list(self, suite_name=''):
221        """
222        Gather a list of paths to control files from |self._dev_server|.
223
224        Get a listing of all the control files for |self._build| on
225        |self._dev_server|.  Populates |self._files| with that list
226        and then returns paths (under the autotest dir) to them. If suite_name
227        is specified, this method populates |self._files| with the control
228        files from just the specified suite.
229
230        @param suite_name: The name of a suite we would like control files for.
231        @return A list of control file paths.
232        @throws NoControlFileList if there is an error while listing.
233        """
234        try:
235            return self._dev_server.list_control_files(self._build,
236                                                       suite_name=suite_name)
237        except dev_server.DevServerException as e:
238            raise error.NoControlFileList(e)
239
240
241    def get_control_file_contents(self, test_path):
242        """
243        Return the contents of |test_path| from |self._dev_server|.
244
245        Get the contents of the control file at |test_path| for |self._build| on
246        |self._dev_server|.
247
248        @return The contents of |test_path|.  None on failure.
249        @throws ControlFileNotFound if the file cannot be retrieved.
250        """
251        try:
252            return self._dev_server.get_control_file(self._build, test_path)
253        except dev_server.DevServerException as e:
254            raise error.ControlFileNotFound(e)
255