fd2438db2f585eac3037e8a3d2eec3f5c63c0882
[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):
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):
75         if not path.endswith('/'):
76             path += '/'
77         return [file for file in self.files if file.startswith(path)]
78
79     def glob(self, path):
80         # FIXME: This only handles a wildcard '*' at the end of the path.
81         # Maybe it should handle more?
82         if path[-1] == '*':
83             return [f for f in self.files if f.startswith(path[:-1])]
84         else:
85             return [f for f in self.files if f == path]
86
87     def isfile(self, path):
88         return path in self.files and self.files[path] is not None
89
90     def isdir(self, path):
91         if path in self.files:
92             return False
93         if not path.endswith('/'):
94             path += '/'
95         return any(f.startswith(path) for f in self.files)
96
97     def join(self, *comps):
98         return re.sub(re.escape(os.path.sep), '/', os.path.join(*comps))
99
100     def listdir(self, path):
101         if not self.isdir(path):
102             raise OSError("%s is not a directory" % path)
103
104         if not path.endswith('/'):
105             path += '/'
106
107         dirs = []
108         files = []
109         for f in self.files:
110             if self.exists(f) and f.startswith(path):
111                 remaining = f[len(path):]
112                 if '/' in remaining:
113                     dir = remaining[:remaining.index('/')]
114                     if not dir in dirs:
115                         dirs.append(dir)
116                 else:
117                     files.append(remaining)
118         return dirs + files
119
120     def _mktemp(self, suffix='', prefix='tmp', dir=None, **kwargs):
121         if dir is None:
122             dir = '/__im_tmp'
123         curno = self.current_tmpno
124         self.current_tmpno += 1
125         return self.join(dir, "%s_%u_%s" % (prefix, curno, suffix))
126
127     def mkdtemp(self, **kwargs):
128         class TemporaryDirectory(object):
129             def __init__(self, fs, **kwargs):
130                 self._kwargs = kwargs
131                 self._filesystem = fs
132                 self._directory_path = fs._mktemp(**kwargs)
133                 fs.maybe_make_directory(self._directory_path)
134
135             def __str__(self):
136                 return self._directory_path
137
138             def __enter__(self):
139                 return self._directory_path
140
141             def __exit__(self, type, value, traceback):
142                 # Only self-delete if necessary.
143
144                 # FIXME: Should we delete non-empty directories?
145                 if self._filesystem.exists(self._directory_path):
146                     self._filesystem.rmdir(self._directory_path)
147
148         return TemporaryDirectory(fs=self, **kwargs)
149
150     def maybe_make_directory(self, *path):
151         # FIXME: Implement such that subsequent calls to isdir() work?
152         pass
153
154     def move(self, src, dst):
155         if self.files[src] is None:
156             self._raise_not_found(src)
157         self.files[dst] = self.files[src]
158         self.files[src] = None
159
160     def normpath(self, path):
161         return path
162
163     def open_binary_tempfile(self, suffix):
164         path = self._mktemp(suffix)
165         return WritableFileObject(self, path), path
166
167     def read_text_file(self, path):
168         return self.read_binary_file(path)
169
170     def read_binary_file(self, path):
171         if path in self.files:
172             if self.files[path] is None:
173                 self._raise_not_found(path)
174             return self.files[path]
175
176     def remove(self, path):
177         if self.files[path] is None:
178             self._raise_not_found(path)
179         self.files[path] = None
180
181     def rmtree(self, path):
182         if not path.endswith('/'):
183             path += '/'
184
185         for f in self.files:
186             if f.startswith(path):
187                 self.files[f] = None
188
189     def splitext(self, path):
190         idx = path.rfind('.')
191         if idx == -1:
192             idx = 0
193         return (path[0:idx], path[idx:])
194
195     def write_text_file(self, path, contents):
196         return self.write_binary_file(path, contents)
197
198     def write_binary_file(self, path, contents):
199         self.files[path] = contents
200
201
202 class WritableFileObject(object):
203     def __init__(self, fs, path, append=False, encoding=None):
204         self.fs = fs
205         self.name = name
206         self.closed = False
207         if path not in self.fs.files or not append:
208             self.fs.files[path] = ""
209
210     def __enter__(self):
211         return self
212
213     def __exit__(self, type, value, traceback):
214         self.close()
215
216     def close(self):
217         self.closed = True
218
219     def write(self, str):
220         self.fs.files[self.name] += str
221         self.fs.written_files[self.name] = self.fs.files[self.name]