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