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 |