| OLD | NEW |
| (Empty) |
| 1 # Copyright (c) 2006-2009 The Chromium 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 | |
| 5 """This package contains utility methods for manipulating paths and | |
| 6 filenames for test results and baselines. It also contains wrappers | |
| 7 of a few routines in platform_utils.py so that platform_utils.py can | |
| 8 be considered a 'protected' package - i.e., this file should be | |
| 9 the only file that ever includes platform_utils. This leads to | |
| 10 us including a few things that don't really have anything to do | |
| 11 with paths, unfortunately.""" | |
| 12 | |
| 13 import errno | |
| 14 import os | |
| 15 import stat | |
| 16 import sys | |
| 17 | |
| 18 import platform_utils | |
| 19 import platform_utils_win | |
| 20 import platform_utils_mac | |
| 21 import platform_utils_linux | |
| 22 | |
| 23 # Cache some values so we don't have to recalculate them. _basedir is | |
| 24 # used by PathFromBase() and caches the full (native) path to the top | |
| 25 # of the source tree (/src). _baseline_search_path is used by | |
| 26 # ExpectedBaselines() and caches the list of native paths to search | |
| 27 # for baseline results. | |
| 28 _basedir = None | |
| 29 _baseline_search_path = None | |
| 30 | |
| 31 | |
| 32 class PathNotFound(Exception): | |
| 33 pass | |
| 34 | |
| 35 | |
| 36 def LayoutTestsDir(): | |
| 37 """Returns the fully-qualified path to the directory containing the input | |
| 38 data for the specified layout test.""" | |
| 39 return PathFromBase('third_party', 'WebKit', 'LayoutTests') | |
| 40 | |
| 41 | |
| 42 def ChromiumBaselinePath(platform=None): | |
| 43 """Returns the full path to the directory containing expected | |
| 44 baseline results from chromium ports. If |platform| is None, the | |
| 45 currently executing platform is used. | |
| 46 | |
| 47 Note: although directly referencing individual platform_utils_* files is | |
| 48 usually discouraged, we allow it here so that the rebaselining tool can | |
| 49 pull baselines for platforms other than the host platform.""" | |
| 50 | |
| 51 # Normalize the platform string. | |
| 52 platform = PlatformName(platform) | |
| 53 if platform.startswith('chromium-mac'): | |
| 54 return platform_utils_mac.BaselinePath(platform) | |
| 55 elif platform.startswith('chromium-win'): | |
| 56 return platform_utils_win.BaselinePath(platform) | |
| 57 elif platform.startswith('chromium-linux'): | |
| 58 return platform_utils_linux.BaselinePath(platform) | |
| 59 | |
| 60 return platform_utils.BaselinePath() | |
| 61 | |
| 62 | |
| 63 def WebKitBaselinePath(platform): | |
| 64 """Returns the full path to the directory containing expected | |
| 65 baseline results from WebKit ports.""" | |
| 66 return PathFromBase('third_party', 'WebKit', 'LayoutTests', | |
| 67 'platform', platform) | |
| 68 | |
| 69 | |
| 70 def BaselineSearchPath(platform=None): | |
| 71 """Returns the list of directories to search for baselines/results for a | |
| 72 given platform, in order of preference. Paths are relative to the top of | |
| 73 the source tree. If parameter platform is None, returns the list for the | |
| 74 current platform that the script is running on. | |
| 75 | |
| 76 Note: although directly referencing individual platform_utils_* files is | |
| 77 usually discouraged, we allow it here so that the rebaselining tool can | |
| 78 pull baselines for platforms other than the host platform.""" | |
| 79 | |
| 80 # Normalize the platform name. | |
| 81 platform = PlatformName(platform) | |
| 82 if platform.startswith('chromium-mac'): | |
| 83 return platform_utils_mac.BaselineSearchPath(platform) | |
| 84 elif platform.startswith('chromium-win'): | |
| 85 return platform_utils_win.BaselineSearchPath(platform) | |
| 86 elif platform.startswith('chromium-linux'): | |
| 87 return platform_utils_linux.BaselineSearchPath(platform) | |
| 88 return platform_utils.BaselineSearchPath() | |
| 89 | |
| 90 | |
| 91 def ExpectedBaselines(filename, suffix, platform=None, all_baselines=False): | |
| 92 """Given a test name, finds where the baseline results are located. | |
| 93 | |
| 94 Args: | |
| 95 filename: absolute filename to test file | |
| 96 suffix: file suffix of the expected results, including dot; e.g. '.txt' | |
| 97 or '.png'. This should not be None, but may be an empty string. | |
| 98 platform: layout test platform: 'win', 'linux' or 'mac'. Defaults to | |
| 99 the current platform. | |
| 100 all_baselines: If True, return an ordered list of all baseline paths | |
| 101 for the given platform. If False, return only the first | |
| 102 one. | |
| 103 Returns | |
| 104 a list of ( platform_dir, results_filename ), where | |
| 105 platform_dir - abs path to the top of the results tree (or test tree) | |
| 106 results_filename - relative path from top of tree to the results file | |
| 107 (os.path.join of the two gives you the full path to the file, | |
| 108 unless None was returned.) | |
| 109 Return values will be in the format appropriate for the current platform | |
| 110 (e.g., "\\" for path separators on Windows). If the results file is not | |
| 111 found, then None will be returned for the directory, but the expected | |
| 112 relative pathname will still be returned. | |
| 113 """ | |
| 114 global _baseline_search_path | |
| 115 global _search_path_platform | |
| 116 testname = os.path.splitext(RelativeTestFilename(filename))[0] | |
| 117 | |
| 118 baseline_filename = testname + '-expected' + suffix | |
| 119 | |
| 120 if (_baseline_search_path is None) or (_search_path_platform != platform): | |
| 121 _baseline_search_path = BaselineSearchPath(platform) | |
| 122 _search_path_platform = platform | |
| 123 | |
| 124 baselines = [] | |
| 125 for platform_dir in _baseline_search_path: | |
| 126 if os.path.exists(os.path.join(platform_dir, baseline_filename)): | |
| 127 baselines.append((platform_dir, baseline_filename)) | |
| 128 | |
| 129 if not all_baselines and baselines: | |
| 130 return baselines | |
| 131 | |
| 132 # If it wasn't found in a platform directory, return the expected result | |
| 133 # in the test directory, even if no such file actually exists. | |
| 134 platform_dir = LayoutTestsDir() | |
| 135 if os.path.exists(os.path.join(platform_dir, baseline_filename)): | |
| 136 baselines.append((platform_dir, baseline_filename)) | |
| 137 | |
| 138 if baselines: | |
| 139 return baselines | |
| 140 | |
| 141 return [(None, baseline_filename)] | |
| 142 | |
| 143 | |
| 144 def ExpectedFilename(filename, suffix): | |
| 145 """Given a test name, returns an absolute path to its expected results. | |
| 146 | |
| 147 If no expected results are found in any of the searched directories, the | |
| 148 directory in which the test itself is located will be returned. The return | |
| 149 value is in the format appropriate for the platform (e.g., "\\" for | |
| 150 path separators on windows). | |
| 151 | |
| 152 Args: | |
| 153 filename: absolute filename to test file | |
| 154 suffix: file suffix of the expected results, including dot; e.g. '.txt' | |
| 155 or '.png'. This should not be None, but may be an empty string. | |
| 156 platform: the most-specific directory name to use to build the | |
| 157 search list of directories, e.g., 'chromium-win', or | |
| 158 'chromium-mac-leopard' (we follow the WebKit format) | |
| 159 """ | |
| 160 platform_dir, baseline_filename = ExpectedBaselines(filename, suffix)[0] | |
| 161 if platform_dir: | |
| 162 return os.path.join(platform_dir, baseline_filename) | |
| 163 return os.path.join(LayoutTestsDir(), baseline_filename) | |
| 164 | |
| 165 | |
| 166 def RelativeTestFilename(filename): | |
| 167 """Provide the filename of the test relative to the layout tests | |
| 168 directory as a unix style path (a/b/c).""" | |
| 169 return _WinPathToUnix(filename[len(LayoutTestsDir()) + 1:]) | |
| 170 | |
| 171 | |
| 172 def _WinPathToUnix(path): | |
| 173 """Convert a windows path to use unix-style path separators (a/b/c).""" | |
| 174 return path.replace('\\', '/') | |
| 175 | |
| 176 # | |
| 177 # Routines that are arguably platform-specific but have been made | |
| 178 # generic for now (they used to be in platform_utils_*) | |
| 179 # | |
| 180 | |
| 181 | |
| 182 def FilenameToUri(full_path): | |
| 183 """Convert a test file to a URI.""" | |
| 184 LAYOUTTEST_HTTP_DIR = "http/tests/" | |
| 185 LAYOUTTEST_WEBSOCKET_DIR = "websocket/tests/" | |
| 186 | |
| 187 relative_path = _WinPathToUnix(RelativeTestFilename(full_path)) | |
| 188 port = None | |
| 189 use_ssl = False | |
| 190 | |
| 191 if relative_path.startswith(LAYOUTTEST_HTTP_DIR): | |
| 192 # http/tests/ run off port 8000 and ssl/ off 8443 | |
| 193 relative_path = relative_path[len(LAYOUTTEST_HTTP_DIR):] | |
| 194 port = 8000 | |
| 195 elif relative_path.startswith(LAYOUTTEST_WEBSOCKET_DIR): | |
| 196 # websocket/tests/ run off port 8880 and 9323 | |
| 197 # Note: the root is /, not websocket/tests/ | |
| 198 port = 8880 | |
| 199 | |
| 200 # Make http/tests/local run as local files. This is to mimic the | |
| 201 # logic in run-webkit-tests. | |
| 202 # TODO(jianli): Consider extending this to "media/". | |
| 203 if port and not relative_path.startswith("local/"): | |
| 204 if relative_path.startswith("ssl/"): | |
| 205 port += 443 | |
| 206 protocol = "https" | |
| 207 else: | |
| 208 protocol = "http" | |
| 209 return "%s://127.0.0.1:%u/%s" % (protocol, port, relative_path) | |
| 210 | |
| 211 if sys.platform in ('cygwin', 'win32'): | |
| 212 return "file:///" + GetAbsolutePath(full_path) | |
| 213 return "file://" + GetAbsolutePath(full_path) | |
| 214 | |
| 215 | |
| 216 def GetAbsolutePath(path): | |
| 217 """Returns an absolute UNIX path.""" | |
| 218 return _WinPathToUnix(os.path.abspath(path)) | |
| 219 | |
| 220 | |
| 221 def MaybeMakeDirectory(*path): | |
| 222 """Creates the specified directory if it doesn't already exist.""" | |
| 223 # This is a reimplementation of google.path_utils.MaybeMakeDirectory(). | |
| 224 try: | |
| 225 os.makedirs(os.path.join(*path)) | |
| 226 except OSError, e: | |
| 227 if e.errno != errno.EEXIST: | |
| 228 raise | |
| 229 | |
| 230 | |
| 231 def PathFromBase(*comps): | |
| 232 """Returns an absolute filename from a set of components specified | |
| 233 relative to the top of the source tree. If the path does not exist, | |
| 234 the exception PathNotFound is raised.""" | |
| 235 # This is a reimplementation of google.path_utils.PathFromBase(). | |
| 236 global _basedir | |
| 237 if _basedir == None: | |
| 238 # We compute the top of the source tree by finding the absolute | |
| 239 # path of this source file, and then climbing up three directories | |
| 240 # as given in subpath. If we move this file, subpath needs to be | |
| 241 # updated. | |
| 242 path = os.path.abspath(__file__) | |
| 243 subpath = os.path.join('webkit', 'tools', 'layout_tests') | |
| 244 _basedir = path[:path.index(subpath)] | |
| 245 path = os.path.join(_basedir, *comps) | |
| 246 if not os.path.exists(path): | |
| 247 raise PathNotFound('could not find %s' % (path)) | |
| 248 return path | |
| 249 | |
| 250 | |
| 251 def RemoveDirectory(*path): | |
| 252 """Recursively removes a directory, even if it's marked read-only. | |
| 253 | |
| 254 Remove the directory located at *path, if it exists. | |
| 255 | |
| 256 shutil.rmtree() doesn't work on Windows if any of the files or directories | |
| 257 are read-only, which svn repositories and some .svn files are. We need to | |
| 258 be able to force the files to be writable (i.e., deletable) as we traverse | |
| 259 the tree. | |
| 260 | |
| 261 Even with all this, Windows still sometimes fails to delete a file, citing | |
| 262 a permission error (maybe something to do with antivirus scans or disk | |
| 263 indexing). The best suggestion any of the user forums had was to wait a | |
| 264 bit and try again, so we do that too. It's hand-waving, but sometimes it | |
| 265 works. :/ | |
| 266 """ | |
| 267 file_path = os.path.join(*path) | |
| 268 if not os.path.exists(file_path): | |
| 269 return | |
| 270 | |
| 271 win32 = False | |
| 272 if sys.platform == 'win32': | |
| 273 win32 = True | |
| 274 # Some people don't have the APIs installed. In that case we'll do | |
| 275 # without. | |
| 276 try: | |
| 277 win32api = __import__('win32api') | |
| 278 win32con = __import__('win32con') | |
| 279 except ImportError: | |
| 280 win32 = False | |
| 281 | |
| 282 def remove_with_retry(rmfunc, path): | |
| 283 os.chmod(path, stat.S_IWRITE) | |
| 284 if win32: | |
| 285 win32api.SetFileAttributes(path, | |
| 286 win32con.FILE_ATTRIBUTE_NORMAL) | |
| 287 try: | |
| 288 return rmfunc(path) | |
| 289 except EnvironmentError, e: | |
| 290 if e.errno != errno.EACCES: | |
| 291 raise | |
| 292 print 'Failed to delete %s: trying again' % repr(path) | |
| 293 time.sleep(0.1) | |
| 294 return rmfunc(path) | |
| 295 else: | |
| 296 | |
| 297 def remove_with_retry(rmfunc, path): | |
| 298 if os.path.islink(path): | |
| 299 return os.remove(path) | |
| 300 else: | |
| 301 return rmfunc(path) | |
| 302 | |
| 303 for root, dirs, files in os.walk(file_path, topdown=False): | |
| 304 # For POSIX: making the directory writable guarantees removability. | |
| 305 # Windows will ignore the non-read-only bits in the chmod value. | |
| 306 os.chmod(root, 0770) | |
| 307 for name in files: | |
| 308 remove_with_retry(os.remove, os.path.join(root, name)) | |
| 309 for name in dirs: | |
| 310 remove_with_retry(os.rmdir, os.path.join(root, name)) | |
| 311 | |
| 312 remove_with_retry(os.rmdir, file_path) | |
| 313 | |
| 314 # | |
| 315 # Wrappers around platform_utils | |
| 316 # | |
| 317 | |
| 318 | |
| 319 def PlatformName(platform=None): | |
| 320 """Returns the appropriate chromium platform name for |platform|. If | |
| 321 |platform| is None, returns the name of the chromium platform on the | |
| 322 currently running system. If |platform| is of the form 'chromium-*', | |
| 323 it is returned unchanged, otherwise 'chromium-' is prepended.""" | |
| 324 if platform == None: | |
| 325 return platform_utils.PlatformName() | |
| 326 if not platform.startswith('chromium-'): | |
| 327 platform = "chromium-" + platform | |
| 328 return platform | |
| 329 | |
| 330 | |
| 331 def PlatformVersion(): | |
| 332 return platform_utils.PlatformVersion() | |
| 333 | |
| 334 | |
| 335 def LigHTTPdExecutablePath(): | |
| 336 return platform_utils.LigHTTPdExecutablePath() | |
| 337 | |
| 338 | |
| 339 def LigHTTPdModulePath(): | |
| 340 return platform_utils.LigHTTPdModulePath() | |
| 341 | |
| 342 | |
| 343 def LigHTTPdPHPPath(): | |
| 344 return platform_utils.LigHTTPdPHPPath() | |
| 345 | |
| 346 | |
| 347 def WDiffPath(): | |
| 348 return platform_utils.WDiffPath() | |
| 349 | |
| 350 | |
| 351 def TestShellPath(target): | |
| 352 return platform_utils.TestShellPath(target) | |
| 353 | |
| 354 | |
| 355 def ImageDiffPath(target): | |
| 356 return platform_utils.ImageDiffPath(target) | |
| 357 | |
| 358 | |
| 359 def LayoutTestHelperPath(target): | |
| 360 return platform_utils.LayoutTestHelperPath(target) | |
| 361 | |
| 362 | |
| 363 def FuzzyMatchPath(): | |
| 364 return platform_utils.FuzzyMatchPath() | |
| 365 | |
| 366 | |
| 367 def ShutDownHTTPServer(server_pid): | |
| 368 return platform_utils.ShutDownHTTPServer(server_pid) | |
| 369 | |
| 370 | |
| 371 def KillAllTestShells(): | |
| 372 platform_utils.KillAllTestShells() | |
| OLD | NEW |