88c18e8ca58643c14dec090afcbe8a7adf6936ca
[WebKit.git] / Tools / Scripts / webkitpy / common / system / filesystem_mock.py
1 # Copyright (C) 2009 Google Inc. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions are
5 # met:
6 #
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
12 # distribution.
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.
16 #
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.
28
29 import errno
30 import os
31 import path
32 import re
33
34
35 class MockFileSystem(object):
36     def __init__(self, files=None):
37         """Initializes a "mock" filesystem that can be used to completely
38         stub out a filesystem.
39
40         Args:
41             files: a dict of filenames -> file contents. A file contents
42                 value of None is used to indicate that the file should
43                 not exist.
44         """
45         self.files = files or {}
46         self._current_tmpno = 0
47
48     def _raise_not_found(self, path):
49         raise IOError(errno.ENOENT, path, os.strerror(errno.ENOENT))
50
51     def _split(self, path):
52         idx = path.rfind('/')
53         return (path[0:idx], path[idx + 1:])
54
55     def basename(self, path):
56         return self._split(path)[1]
57
58     def copyfile(self, source, destination):
59         if not self.exists(source):
60             self._raise_not_found(source)
61         if self.isdir(source):
62             raise IOError(errno.EISDIR, source, os.strerror(errno.ISDIR))
63         if self.isdir(destination):
64             raise IOError(errno.EISDIR, destination, os.strerror(errno.ISDIR))
65
66         self.files[destination] = self.files[source]
67
68     def dirname(self, path):
69         return self._split(path)[0]
70
71     def exists(self, path):
72         return self.isfile(path) or self.isdir(path)
73
74     def files_under(self, path, dirs_to_skip=[], file_filter=None):
75         def filter_all(fs, dirpath, basename):
76             return True
77
78         file_filter = file_filter or filter_all
79         files = []
80         if self.isfile(path):
81             if file_filter(self, self.dirname(path), self.basename(path)):
82                 files.append(path)
83             return files
84
85         if self.basename(path) in dirs_to_skip:
86             return []
87
88         if not path.endswith('/'):
89             path += '/'
90
91         dir_substrings = ['/' + d + '/' for d in dirs_to_skip]
92         for filename in self.files:
93             if not filename.startswith(path):
94                 continue
95
96             suffix = filename[len(path) - 1:]
97             if any(dir_substring in suffix for dir_substring in dir_substrings):
98                 continue
99
100             dirpath, basename = self._split(filename)
101             if file_filter(self, dirpath, basename):
102                 files.append(filename)
103
104         return files
105
106     def glob(self, path):
107         # FIXME: This only handles a wildcard '*' at the end of the path.
108         # Maybe it should handle more?
109         if path[-1] == '*':
110             return [f for f in self.files if f.startswith(path[:-1])]
111         else:
112             return [f for f in self.files if f == path]
113
114     def isfile(self, path):
115         return path in self.files and self.files[path] is not None
116
117     def isdir(self, path):
118         if path in self.files:
119             return False
120         if not path.endswith('/'):
121             path += '/'
122         return any(f.startswith(path) for f in self.files)
123
124     def join(self, *comps):
125         return re.sub(re.escape(os.path.sep), '/', os.path.join(*comps))
126
127     def listdir(self, path):
128         if not self.isdir(path):
129             raise OSError("%s is not a directory" % path)
130
131         if not path.endswith('/'):
132             path += '/'
133
134         dirs = []
135         files = []
136         for f in self.files:
137             if self.exists(f) and f.startswith(path):
138                 remaining = f[len(path):]
139                 if '/' in remaining:
140                     dir = remaining[:remaining.index('/')]
141                     if not dir in dirs:
142                         dirs.append(dir)
143                 else:
144                     files.append(remaining)
145         return dirs + files
146
147     def _mktemp(self, suffix='', prefix='tmp', dir=None, **kwargs):
148         if dir is None:
149             dir = '/__im_tmp'
150         curno = self.current_tmpno
151         self.current_tmpno += 1
152         return self.join(dir, "%s_%u_%s" % (prefix, curno, suffix))
153
154     def mkdtemp(self, **kwargs):
155         class TemporaryDirectory(object):
156             def __init__(self, fs, **kwargs):
157                 self._kwargs = kwargs
158                 self._filesystem = fs
159                 self._directory_path = fs._mktemp(**kwargs)
160                 fs.maybe_make_directory(self._directory_path)
161
162             def __str__(self):
163                 return self._directory_path
164
165             def __enter__(self):
166                 return self._directory_path
167
168             def __exit__(self, type, value, traceback):
169                 # Only self-delete if necessary.
170
171                 # FIXME: Should we delete non-empty directories?
172                 if self._filesystem.exists(self._directory_path):
173                     self._filesystem.rmdir(self._directory_path)
174
175         return TemporaryDirectory(fs=self, **kwargs)
176
177     def maybe_make_directory(self, *path):
178         # FIXME: Implement such that subsequent calls to isdir() work?
179         pass
180
181     def move(self, src, dst):
182         if self.files[src] is None:
183             self._raise_not_found(src)
184         self.files[dst] = self.files[src]
185         self.files[src] = None
186
187     def normpath(self, path):
188         return path
189
190     def open_binary_tempfile(self, suffix):
191         path = self._mktemp(suffix)
192         return WritableFileObject(self, path), path
193
194     def read_text_file(self, path):
195         return self.read_binary_file(path)
196
197     def read_binary_file(self, path):
198         if path in self.files:
199             if self.files[path] is None:
200                 self._raise_not_found(path)
201             return self.files[path]
202
203     def remove(self, path):
204         if self.files[path] is None:
205             self._raise_not_found(path)
206         self.files[path] = None
207
208     def rmtree(self, path):
209         if not path.endswith('/'):
210             path += '/'
211
212         for f in self.files:
213             if f.startswith(path):
214                 self.files[f] = None
215
216     def splitext(self, path):
217         idx = path.rfind('.')
218         if idx == -1:
219             idx = 0
220         return (path[0:idx], path[idx:])
221
222     def write_text_file(self, path, contents):
223         return self.write_binary_file(path, contents)
224
225     def write_binary_file(self, path, contents):
226         self.files[path] = contents
227
228
229 class WritableFileObject(object):
230     def __init__(self, fs, path, append=False, encoding=None):
231         self.fs = fs
232         self.name = name
233         self.closed = False
234         if path not in self.fs.files or not append:
235             self.fs.files[path] = ""
236
237     def __enter__(self):
238         return self
239
240     def __exit__(self, type, value, traceback):
241         self.close()
242
243     def close(self):
244         self.closed = True
245
246     def write(self, str):
247         self.fs.files[self.name] += str
248         self.fs.written_files[self.name] = self.fs.files[self.name]