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