Chris Masone | 859fdec | 2012-01-30 08:38:09 -0800 | [diff] [blame^] | 1 | # Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
Chris Masone | 6fed646 | 2011-10-20 16:36:43 -0700 | [diff] [blame] | 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
| 4 | |
| 5 | import common |
Chris Masone | fef2138 | 2012-01-17 11:16:32 -0800 | [diff] [blame] | 6 | import compiler, logging, os, random, re, time, urllib2 |
Chris Masone | 6fed646 | 2011-10-20 16:36:43 -0700 | [diff] [blame] | 7 | from autotest_lib.client.common_lib import control_data, error, utils |
Chris Masone | fef2138 | 2012-01-17 11:16:32 -0800 | [diff] [blame] | 8 | from autotest_lib.client.common_lib.cros import dev_server |
| 9 | |
| 10 | |
| 11 | class ControlFileNotFound(Exception): |
| 12 | """Raised when a control file cannot be found and/or read.""" |
| 13 | pass |
| 14 | |
| 15 | |
| 16 | class NoControlFileList(Exception): |
| 17 | """Raised when to indicate that a listing can't be done.""" |
| 18 | pass |
Chris Masone | 6fed646 | 2011-10-20 16:36:43 -0700 | [diff] [blame] | 19 | |
| 20 | |
| 21 | class ControlFileGetter(object): |
| 22 | """ |
| 23 | Interface for classes that can list and fetch known control files. |
Chris Masone | 6fed646 | 2011-10-20 16:36:43 -0700 | [diff] [blame] | 24 | """ |
| 25 | |
Chris Masone | 6fed646 | 2011-10-20 16:36:43 -0700 | [diff] [blame] | 26 | def __init__(self): |
| 27 | pass |
| 28 | |
| 29 | |
| 30 | def get_control_file_list(self): |
| 31 | """ |
Chris Masone | fef2138 | 2012-01-17 11:16:32 -0800 | [diff] [blame] | 32 | Gather a list of paths to control files. |
Chris Masone | 6fed646 | 2011-10-20 16:36:43 -0700 | [diff] [blame] | 33 | |
Chris Masone | fef2138 | 2012-01-17 11:16:32 -0800 | [diff] [blame] | 34 | @return A list of file paths. |
| 35 | @throws NoControlFileList if there is an error while listing. |
Chris Masone | 6fed646 | 2011-10-20 16:36:43 -0700 | [diff] [blame] | 36 | """ |
| 37 | pass |
| 38 | |
| 39 | |
| 40 | def get_control_file_contents(self, test_path): |
| 41 | """ |
| 42 | Given a path to a control file, return its contents. |
| 43 | |
Chris Masone | fef2138 | 2012-01-17 11:16:32 -0800 | [diff] [blame] | 44 | @param test_path: the path to the control file. |
Chris Masone | 6fed646 | 2011-10-20 16:36:43 -0700 | [diff] [blame] | 45 | @return the contents of the control file specified by the path. |
Chris Masone | fef2138 | 2012-01-17 11:16:32 -0800 | [diff] [blame] | 46 | @throws ControlFileNotFound if the file cannot be retrieved. |
Chris Masone | 6fed646 | 2011-10-20 16:36:43 -0700 | [diff] [blame] | 47 | """ |
| 48 | pass |
| 49 | |
| 50 | |
| 51 | def get_control_file_contents_by_name(self, test_name): |
| 52 | """ |
| 53 | Given the name of a control file, return its contents. |
| 54 | |
Chris Masone | fef2138 | 2012-01-17 11:16:32 -0800 | [diff] [blame] | 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. |
Chris Masone | 6fed646 | 2011-10-20 16:36:43 -0700 | [diff] [blame] | 58 | """ |
| 59 | pass |
| 60 | |
| 61 | |
Chris Masone | fef2138 | 2012-01-17 11:16:32 -0800 | [diff] [blame] | 62 | class CacheingControlFileGetter(ControlFileGetter): |
| 63 | """Wraps ControlFileGetter to cache the retrieved control file list.""" |
| 64 | def __init__(self): |
| 65 | super(CacheingControlFileGetter, self).__init__() |
| 66 | self._files = [] |
| 67 | |
| 68 | |
| 69 | def get_control_file_list(self): |
| 70 | """ |
| 71 | Gather a list of paths to control files. |
| 72 | |
| 73 | Gets a list of control files; populates |self._files| with that list |
| 74 | and then returns the paths to all useful files in the list. |
| 75 | |
| 76 | @return A list of file paths. |
| 77 | @throws NoControlFileList if there is an error while listing. |
| 78 | """ |
| 79 | self._files = self._get_control_file_list() |
| 80 | return self._files |
| 81 | |
| 82 | |
| 83 | def get_control_file_contents_by_name(self, test_name): |
| 84 | """ |
| 85 | Given the name of a control file, return its contents. |
| 86 | |
| 87 | Searches through previously-compiled list in |self._files| for a |
| 88 | test named |test_name| and returns the contents of the control file |
| 89 | for that test if it is found. |
| 90 | |
| 91 | @param test_name: the name of the test whose control file is desired. |
| 92 | @return the contents of the control file specified by the name. |
| 93 | @throws ControlFileNotFound if the file cannot be retrieved. |
| 94 | """ |
| 95 | if not self._files and not self.get_control_file_list(): |
| 96 | raise ControlFileNotFound('No control files found.') |
Chris Masone | 859fdec | 2012-01-30 08:38:09 -0800 | [diff] [blame^] | 97 | |
| 98 | if 'control' not in test_name: |
| 99 | regexp = re.compile(os.path.join(test_name, 'control')) |
| 100 | else: |
| 101 | regexp = re.compile(test_name) |
Chris Masone | fef2138 | 2012-01-17 11:16:32 -0800 | [diff] [blame] | 102 | candidates = filter(regexp.search, self._files) |
| 103 | if not candidates: |
| 104 | raise ControlFileNotFound('No control file for ' + test_name) |
| 105 | if len(candidates) > 1: |
| 106 | raise ControlFileNotFound(test_name + ' is not unique.') |
| 107 | return self.get_control_file_contents(candidates[0]) |
| 108 | |
| 109 | |
| 110 | class FileSystemGetter(CacheingControlFileGetter): |
| 111 | """ |
| 112 | Class that can list and fetch known control files from disk. |
| 113 | |
| 114 | @var _CONTROL_PATTERN: control file name format to match. |
| 115 | """ |
| 116 | |
| 117 | _CONTROL_PATTERN = '^control(?:\..+)?$' |
| 118 | |
| 119 | def __init__(self, paths): |
| 120 | """ |
| 121 | @param paths: base directories to start search. |
| 122 | """ |
| 123 | super(FileSystemGetter, self).__init__() |
| 124 | self._paths = paths |
| 125 | |
| 126 | |
Chris Masone | 6fed646 | 2011-10-20 16:36:43 -0700 | [diff] [blame] | 127 | def _is_useful_file(self, name): |
| 128 | return '__init__.py' not in name and '.svn' not in name |
| 129 | |
| 130 | |
Chris Masone | fef2138 | 2012-01-17 11:16:32 -0800 | [diff] [blame] | 131 | def _get_control_file_list(self): |
Chris Masone | 6fed646 | 2011-10-20 16:36:43 -0700 | [diff] [blame] | 132 | """ |
Chris Masone | fef2138 | 2012-01-17 11:16:32 -0800 | [diff] [blame] | 133 | Gather a list of paths to control files under |self._paths|. |
Chris Masone | 6fed646 | 2011-10-20 16:36:43 -0700 | [diff] [blame] | 134 | |
Chris Masone | fef2138 | 2012-01-17 11:16:32 -0800 | [diff] [blame] | 135 | Searches under |self._paths| for files that match |
| 136 | |self._CONTROL_PATTERN|. Populates |self._files| with that list |
| 137 | and then returns the paths to all useful files in the list. |
Chris Masone | 6fed646 | 2011-10-20 16:36:43 -0700 | [diff] [blame] | 138 | |
Chris Masone | fef2138 | 2012-01-17 11:16:32 -0800 | [diff] [blame] | 139 | @return A list of files that match |self._CONTROL_PATTERN|. |
| 140 | @throws NoControlFileList if we find no files. |
Chris Masone | 6fed646 | 2011-10-20 16:36:43 -0700 | [diff] [blame] | 141 | """ |
| 142 | regexp = re.compile(self._CONTROL_PATTERN) |
| 143 | directories = self._paths |
| 144 | while len(directories) > 0: |
| 145 | directory = directories.pop() |
| 146 | if not os.path.exists(directory): |
| 147 | continue |
| 148 | for name in os.listdir(directory): |
| 149 | fullpath = os.path.join(directory, name) |
| 150 | if os.path.isfile(fullpath): |
| 151 | if regexp.search(name): |
| 152 | # if we are a control file |
| 153 | self._files.append(fullpath) |
| 154 | elif os.path.isdir(fullpath): |
| 155 | directories.append(fullpath) |
Chris Masone | fef2138 | 2012-01-17 11:16:32 -0800 | [diff] [blame] | 156 | if not self._files: |
| 157 | msg = 'No control files under ' + ','.join(self._paths) |
| 158 | raise NoControlFileList(msg) |
Chris Masone | 6fed646 | 2011-10-20 16:36:43 -0700 | [diff] [blame] | 159 | return [f for f in self._files if self._is_useful_file(f)] |
| 160 | |
| 161 | |
| 162 | def get_control_file_contents(self, test_path): |
Chris Masone | fef2138 | 2012-01-17 11:16:32 -0800 | [diff] [blame] | 163 | """ |
| 164 | Get the contents of the control file at |test_path|. |
| 165 | |
| 166 | @return The contents of the aforementioned file. |
| 167 | @throws ControlFileNotFound if the file cannot be retrieved. |
| 168 | """ |
| 169 | try: |
| 170 | return utils.read_file(test_path) |
| 171 | except EnvironmentError as (errno, strerror): |
| 172 | msg = "Can't retrieve {0}: {1} ({2})".format(test_path, |
| 173 | strerror, |
| 174 | errno) |
| 175 | raise ControlFileNotFound(msg) |
Chris Masone | 6fed646 | 2011-10-20 16:36:43 -0700 | [diff] [blame] | 176 | |
| 177 | |
Chris Masone | fef2138 | 2012-01-17 11:16:32 -0800 | [diff] [blame] | 178 | class DevServerGetter(CacheingControlFileGetter): |
| 179 | def __init__(self, build, ds=None): |
| 180 | """ |
| 181 | @param build: The build from which to get control files. |
| 182 | @param ds: An existing dev_server.DevServer object to use. |
| 183 | """ |
| 184 | super(DevServerGetter, self).__init__() |
| 185 | self._dev_server = ds if ds else dev_server.DevServer() |
| 186 | self._build = build |
| 187 | |
| 188 | |
Chris Masone | 859fdec | 2012-01-30 08:38:09 -0800 | [diff] [blame^] | 189 | @staticmethod |
| 190 | def create(build, ds=None): |
| 191 | """Wraps constructor. Can be mocked for testing purposes.""" |
| 192 | return DevServerGetter(build, ds) |
| 193 | |
| 194 | |
Chris Masone | fef2138 | 2012-01-17 11:16:32 -0800 | [diff] [blame] | 195 | def _get_control_file_list(self): |
| 196 | """ |
| 197 | Gather a list of paths to control files from |self._dev_server|. |
| 198 | |
| 199 | Get a listing of all the control files for |self._build| on |
| 200 | |self._dev_server|. Populates |self._files| with that list |
| 201 | and then returns paths (under the autotest dir) to them. |
| 202 | |
| 203 | @return A list of control file paths. None on failure. |
| 204 | @throws NoControlFileList if there is an error while listing. |
| 205 | """ |
| 206 | try: |
| 207 | return self._dev_server.list_control_files(self._build) |
| 208 | except urllib2.HTTPError as e: |
| 209 | raise NoControlFileList(e) |
| 210 | |
| 211 | |
| 212 | def get_control_file_contents(self, test_path): |
| 213 | """ |
| 214 | Return the contents of |test_path| from |self._dev_server|. |
| 215 | |
| 216 | Get the contents of the control file at |test_path| for |self._build| on |
| 217 | |self._dev_server|. |
| 218 | |
| 219 | @return The contents of |test_path|. None on failure. |
| 220 | @throws ControlFileNotFound if the file cannot be retrieved. |
| 221 | """ |
| 222 | try: |
| 223 | return self._dev_server.get_control_file(self._build, test_path) |
| 224 | except urllib2.HTTPError as e: |
| 225 | raise ControlFileNotFound(e) |