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 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.
41 files: a dict of filenames -> file contents. A file contents
42 value of None is used to indicate that the file should
45 self.files = files or {}
46 self._current_tmpno = 0
48 def _raise_not_found(self, path):
49 raise IOError(errno.ENOENT, path, os.strerror(errno.ENOENT))
51 def _split(self, path):
53 return (path[0:idx], path[idx + 1:])
55 def basename(self, path):
56 return self._split(path)[1]
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))
66 self.files[destination] = self.files[source]
68 def dirname(self, path):
69 return self._split(path)[0]
71 def exists(self, path):
72 return self.isfile(path) or self.isdir(path)
74 def files_under(self, path, dirs_to_skip=[], file_filter=None):
75 def filter_all(fs, dirpath, basename):
78 file_filter = file_filter or filter_all
81 if file_filter(self, self.dirname(path), self.basename(path)):
85 if self.basename(path) in dirs_to_skip:
88 if not path.endswith('/'):
91 dir_substrings = ['/' + d + '/' for d in dirs_to_skip]
92 for filename in self.files:
93 if not filename.startswith(path):
96 suffix = filename[len(path) - 1:]
97 if any(dir_substring in suffix for dir_substring in dir_substrings):
100 dirpath, basename = self._split(filename)
101 if file_filter(self, dirpath, basename):
102 files.append(filename)
106 def glob(self, path):
107 # FIXME: This only handles a wildcard '*' at the end of the path.
108 # Maybe it should handle more?
110 return [f for f in self.files if f.startswith(path[:-1])]
112 return [f for f in self.files if f == path]
114 def isfile(self, path):
115 return path in self.files and self.files[path] is not None
117 def isdir(self, path):
118 if path in self.files:
120 if not path.endswith('/'):
122 return any(f.startswith(path) for f in self.files)
124 def join(self, *comps):
125 return re.sub(re.escape(os.path.sep), '/', os.path.join(*comps))
127 def listdir(self, path):
128 if not self.isdir(path):
129 raise OSError("%s is not a directory" % path)
131 if not path.endswith('/'):
137 if self.exists(f) and f.startswith(path):
138 remaining = f[len(path):]
140 dir = remaining[:remaining.index('/')]
144 files.append(remaining)
147 def _mktemp(self, suffix='', prefix='tmp', dir=None, **kwargs):
150 curno = self.current_tmpno
151 self.current_tmpno += 1
152 return self.join(dir, "%s_%u_%s" % (prefix, curno, suffix))
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)
163 return self._directory_path
166 return self._directory_path
168 def __exit__(self, type, value, traceback):
169 # Only self-delete if necessary.
171 # FIXME: Should we delete non-empty directories?
172 if self._filesystem.exists(self._directory_path):
173 self._filesystem.rmdir(self._directory_path)
175 return TemporaryDirectory(fs=self, **kwargs)
177 def maybe_make_directory(self, *path):
178 # FIXME: Implement such that subsequent calls to isdir() work?
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
187 def normpath(self, path):
190 def open_binary_tempfile(self, suffix):
191 path = self._mktemp(suffix)
192 return WritableFileObject(self, path), path
194 def read_text_file(self, path):
195 return self.read_binary_file(path)
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]
203 def remove(self, path):
204 if self.files[path] is None:
205 self._raise_not_found(path)
206 self.files[path] = None
208 def rmtree(self, path):
209 if not path.endswith('/'):
213 if f.startswith(path):
216 def splitext(self, path):
217 idx = path.rfind('.')
220 return (path[0:idx], path[idx:])
222 def write_text_file(self, path, contents):
223 return self.write_binary_file(path, contents)
225 def write_binary_file(self, path, contents):
226 self.files[path] = contents
229 class WritableFileObject(object):
230 def __init__(self, fs, path, append=False, encoding=None):
234 if path not in self.fs.files or not append:
235 self.fs.files[path] = ""
240 def __exit__(self, type, value, traceback):
246 def write(self, str):
247 self.fs.files[self.name] += str
248 self.fs.written_files[self.name] = self.fs.files[self.name]