1 # Copyright (C) 2009 Google Inc. All rights reserved.
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions are
7 # * Redistributions of source code must retain the above copyright
8 # notice, this list of conditions and the following disclaimer.
9 # * Redistributions in binary form must reproduce the above
10 # copyright notice, this list of conditions and the following disclaimer
11 # in the documentation and/or other materials provided with the
13 # * Neither the name of Google Inc. nor the names of its
14 # contributors may be used to endorse or promote products derived from
15 # this software without specific prior written permission.
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 from webkitpy.common.system import path
38 class MockFileSystem(object):
42 def __init__(self, files=None, dirs=None, cwd='/'):
43 """Initializes a "mock" filesystem that can be used to completely
44 stub out a filesystem.
47 files: a dict of filenames -> file contents. A file contents
48 value of None is used to indicate that the file should
51 self.files = files or {}
52 self.written_files = {}
53 self.last_tmpdir = None
54 self.current_tmpno = 0
56 self.dirs = set(dirs or [])
60 while not d in self.dirs:
64 def clear_written_files(self):
65 # This function can be used to track what is written between steps in a test.
66 self.written_files = {}
68 def _raise_not_found(self, path):
69 raise IOError(errno.ENOENT, path, os.strerror(errno.ENOENT))
71 def _split(self, path):
72 # This is not quite a full implementation of os.path.split
73 # http://docs.python.org/library/os.path.html#os.path.split
75 return path.rsplit(self.sep, 1)
78 def abspath(self, path):
79 if os.path.isabs(path):
80 return self.normpath(path)
81 return self.abspath(self.join(self.cwd, path))
83 def realpath(self, path):
84 return self.abspath(path)
86 def basename(self, path):
87 return self._split(path)[1]
89 def expanduser(self, path):
92 parts = path.split(self.sep, 1)
93 home_directory = self.sep + "Users" + self.sep + "mock"
96 return home_directory + self.sep + parts[1]
98 def path_to_module(self, module_name):
99 return "/mock-checkout/third_party/WebKit/Tools/Scripts/" + module_name.replace('.', '/') + ".py"
101 def chdir(self, path):
102 path = self.normpath(path)
103 if not self.isdir(path):
104 raise OSError(errno.ENOENT, path, os.strerror(errno.ENOENT))
107 def copyfile(self, source, destination):
108 if not self.exists(source):
109 self._raise_not_found(source)
110 if self.isdir(source):
111 raise IOError(errno.EISDIR, source, os.strerror(errno.EISDIR))
112 if self.isdir(destination):
113 raise IOError(errno.EISDIR, destination, os.strerror(errno.EISDIR))
114 if not self.exists(self.dirname(destination)):
115 raise IOError(errno.ENOENT, destination, os.strerror(errno.ENOENT))
117 self.files[destination] = self.files[source]
118 self.written_files[destination] = self.files[source]
120 def dirname(self, path):
121 return self._split(path)[0]
123 def exists(self, path):
124 return self.isfile(path) or self.isdir(path)
126 def files_under(self, path, dirs_to_skip=[], file_filter=None):
127 def filter_all(fs, dirpath, basename):
130 file_filter = file_filter or filter_all
132 if self.isfile(path):
133 if file_filter(self, self.dirname(path), self.basename(path)) and self.files[path] is not None:
137 if self.basename(path) in dirs_to_skip:
140 if not path.endswith(self.sep):
143 dir_substrings = [self.sep + d + self.sep for d in dirs_to_skip]
144 for filename in self.files:
145 if not filename.startswith(path):
148 suffix = filename[len(path) - 1:]
149 if any(dir_substring in suffix for dir_substring in dir_substrings):
152 dirpath, basename = self._split(filename)
153 if file_filter(self, dirpath, basename) and self.files[filename] is not None:
154 files.append(filename)
161 def glob(self, glob_string):
162 # FIXME: This handles '*', but not '?', '[', or ']'.
163 glob_string = re.escape(glob_string)
164 glob_string = glob_string.replace('\\*', '[^\\/]*') + '$'
165 glob_string = glob_string.replace('\\/', '/')
166 path_filter = lambda path: re.match(glob_string, path)
168 # We could use fnmatch.fnmatch, but that might not do the right thing on windows.
169 existing_files = [path for path, contents in self.files.items() if contents is not None]
170 return filter(path_filter, existing_files) + filter(path_filter, self.dirs)
172 def isabs(self, path):
173 return path.startswith(self.sep)
175 def isfile(self, path):
176 return path in self.files and self.files[path] is not None
178 def isdir(self, path):
179 return self.normpath(path) in self.dirs
181 def _slow_but_correct_join(self, *comps):
182 return re.sub(re.escape(os.path.sep), self.sep, os.path.join(*comps))
184 def join(self, *comps):
185 # This function is called a lot, so we optimize it; there are
186 # unittests to check that we match _slow_but_correct_join(), above.
198 if comps[-1] == '' and path:
200 path = path.replace(sep + sep, sep)
203 def listdir(self, path):
204 root, dirs, files = list(self.walk(path))[0]
209 if not self.isdir(top):
210 raise OSError("%s is not a directory" % top)
212 if not top.endswith(sep):
218 if self.exists(f) and f.startswith(top):
219 remaining = f[len(top):]
221 dir = remaining[:remaining.index(sep)]
225 files.append(remaining)
226 return [(top[:-1], dirs, files)]
228 def mtime(self, path):
229 if self.exists(path):
231 self._raise_not_found(path)
233 def _mktemp(self, suffix='', prefix='tmp', dir=None, **kwargs):
235 dir = self.sep + '__im_tmp'
236 curno = self.current_tmpno
237 self.current_tmpno += 1
238 self.last_tmpdir = self.join(dir, '%s_%u_%s' % (prefix, curno, suffix))
239 return self.last_tmpdir
241 def mkdtemp(self, **kwargs):
242 class TemporaryDirectory(object):
243 def __init__(self, fs, **kwargs):
244 self._kwargs = kwargs
245 self._filesystem = fs
246 self._directory_path = fs._mktemp(**kwargs)
247 fs.maybe_make_directory(self._directory_path)
250 return self._directory_path
253 return self._directory_path
255 def __exit__(self, type, value, traceback):
256 # Only self-delete if necessary.
258 # FIXME: Should we delete non-empty directories?
259 if self._filesystem.exists(self._directory_path):
260 self._filesystem.rmtree(self._directory_path)
262 return TemporaryDirectory(fs=self, **kwargs)
264 def maybe_make_directory(self, *path):
265 norm_path = self.normpath(self.join(*path))
266 while norm_path and not self.isdir(norm_path):
267 self.dirs.add(norm_path)
268 norm_path = self.dirname(norm_path)
270 def move(self, source, destination):
271 if not self.exists(source):
272 self._raise_not_found(source)
273 if self.isfile(source):
274 self.files[destination] = self.files[source]
275 self.written_files[destination] = self.files[destination]
276 self.files[source] = None
277 self.written_files[source] = None
279 self.copytree(source, destination)
282 def _slow_but_correct_normpath(self, path):
283 return re.sub(re.escape(os.path.sep), self.sep, os.path.normpath(path))
285 def normpath(self, path):
286 # This function is called a lot, so we try to optimize the common cases
287 # instead of always calling _slow_but_correct_normpath(), above.
288 if '..' in path or '/./' in path:
289 # This doesn't happen very often; don't bother trying to optimize it.
290 return self._slow_but_correct_normpath(path)
297 if path.endswith('/.'):
299 if path.endswith('/'):
303 def open_binary_tempfile(self, suffix=''):
304 path = self._mktemp(suffix)
305 return (WritableBinaryFileObject(self, path), path)
307 def open_binary_file_for_reading(self, path):
308 if self.files[path] is None:
309 self._raise_not_found(path)
310 return ReadableBinaryFileObject(self, path, self.files[path])
312 def read_binary_file(self, path):
313 # Intentionally raises KeyError if we don't recognize the path.
314 if self.files[path] is None:
315 self._raise_not_found(path)
316 return self.files[path]
318 def write_binary_file(self, path, contents):
319 # FIXME: should this assert if dirname(path) doesn't exist?
320 self.maybe_make_directory(self.dirname(path))
321 self.files[path] = contents
322 self.written_files[path] = contents
324 def open_text_file_for_reading(self, path):
325 if self.files[path] is None:
326 self._raise_not_found(path)
327 return ReadableTextFileObject(self, path, self.files[path])
329 def open_text_file_for_writing(self, path):
330 return WritableTextFileObject(self, path)
332 def read_text_file(self, path):
333 return self.read_binary_file(path).decode('utf-8')
335 def write_text_file(self, path, contents):
336 return self.write_binary_file(path, contents.encode('utf-8'))
338 def sha1(self, path):
339 contents = self.read_binary_file(path)
340 return hashlib.sha1(contents).hexdigest()
342 def relpath(self, path, start='.'):
343 # Since os.path.relpath() calls os.path.normpath()
344 # (see http://docs.python.org/library/os.path.html#os.path.abspath )
345 # it also removes trailing slashes and converts forward and backward
346 # slashes to the preferred slash os.sep.
347 start = self.abspath(start)
348 path = self.abspath(path)
352 while not common_root == '':
353 if path.startswith(common_root):
355 common_root = self.dirname(common_root)
356 dot_dot += '..' + self.sep
358 rel_path = path[len(common_root):]
363 if rel_path[0] == self.sep:
364 # It is probably sufficient to remove just the first character
365 # since os.path.normpath() collapses separators, but we use
366 # lstrip() just to be sure.
367 rel_path = rel_path.lstrip(self.sep)
368 elif not common_root == '/':
369 # We are in the case typified by the following example:
370 # path = "/tmp/foobar", start = "/tmp/foo" -> rel_path = "bar"
371 common_root = self.dirname(common_root)
372 dot_dot += '..' + self.sep
373 rel_path = path[len(common_root) + 1:]
375 return dot_dot + rel_path
377 def remove(self, path):
378 if self.files[path] is None:
379 self._raise_not_found(path)
380 self.files[path] = None
381 self.written_files[path] = None
383 def rmtree(self, path):
384 path = self.normpath(path)
387 # We need to add a trailing separator to path to avoid matching
388 # cases like path='/foo/b' and f='/foo/bar/baz'.
389 if f == path or f.startswith(path + self.sep):
392 self.dirs = set(filter(lambda d: not (d == path or d.startswith(path + self.sep)), self.dirs))
394 def copytree(self, source, destination):
395 source = self.normpath(source)
396 destination = self.normpath(destination)
398 for source_file in list(self.files):
399 if source_file.startswith(source):
400 destination_path = self.join(destination, self.relpath(source_file, source))
401 self.maybe_make_directory(self.dirname(destination_path))
402 self.files[destination_path] = self.files[source_file]
404 def split(self, path):
405 idx = path.rfind(self.sep)
408 return (path[:idx], path[(idx + 1):])
410 def splitext(self, path):
411 idx = path.rfind('.')
414 return (path[0:idx], path[idx:])
417 class WritableBinaryFileObject(object):
418 def __init__(self, fs, path):
422 self.fs.files[path] = ""
427 def __exit__(self, type, value, traceback):
433 def write(self, str):
434 self.fs.files[self.path] += str
435 self.fs.written_files[self.path] = self.fs.files[self.path]
438 class WritableTextFileObject(WritableBinaryFileObject):
439 def write(self, str):
440 WritableBinaryFileObject.write(self, str.encode('utf-8'))
443 class ReadableBinaryFileObject(object):
444 def __init__(self, fs, path, data):
454 def __exit__(self, type, value, traceback):
460 def read(self, bytes=None):
462 return self.data[self.offset:]
465 return self.data[start:self.offset]
468 class ReadableTextFileObject(ReadableBinaryFileObject):
469 def __init__(self, fs, path, data):
470 super(ReadableTextFileObject, self).__init__(fs, path, StringIO.StringIO(data.decode("utf-8")))
474 super(ReadableTextFileObject, self).close()
476 def read(self, bytes=-1):
477 return self.data.read(bytes)
479 def readline(self, length=None):
480 return self.data.readline(length)
483 return self.data.__iter__()
486 return self.data.next()
488 def seek(self, offset, whence=os.SEEK_SET):
489 self.data.seek(offset, whence)