Home | History | Annotate | Download | only in system
      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 re
     32 
     33 from webkitpy.common.system import path
     34 from webkitpy.common.system import ospath
     35 
     36 
     37 class MockFileSystem(object):
     38     def __init__(self, files=None, cwd='/'):
     39         """Initializes a "mock" filesystem that can be used to completely
     40         stub out a filesystem.
     41 
     42         Args:
     43             files: a dict of filenames -> file contents. A file contents
     44                 value of None is used to indicate that the file should
     45                 not exist.
     46         """
     47         self.files = files or {}
     48         self.written_files = {}
     49         self._sep = '/'
     50         self.current_tmpno = 0
     51         self.cwd = cwd
     52         self.dirs = {}
     53 
     54     def _get_sep(self):
     55         return self._sep
     56 
     57     sep = property(_get_sep, doc="pathname separator")
     58 
     59     def _raise_not_found(self, path):
     60         raise IOError(errno.ENOENT, path, os.strerror(errno.ENOENT))
     61 
     62     def _split(self, path):
     63         return path.rsplit(self.sep, 1)
     64 
     65     def abspath(self, path):
     66         if os.path.isabs(path):
     67             return self.normpath(path)
     68         return self.abspath(self.join(self.cwd, path))
     69 
     70     def basename(self, path):
     71         return self._split(path)[1]
     72 
     73     def chdir(self, path):
     74         path = self.normpath(path)
     75         if not self.isdir(path):
     76             raise OSError(errno.ENOENT, path, os.strerror(errno.ENOENT))
     77         self.cwd = path
     78 
     79     def copyfile(self, source, destination):
     80         if not self.exists(source):
     81             self._raise_not_found(source)
     82         if self.isdir(source):
     83             raise IOError(errno.EISDIR, source, os.strerror(errno.ISDIR))
     84         if self.isdir(destination):
     85             raise IOError(errno.EISDIR, destination, os.strerror(errno.ISDIR))
     86 
     87         self.files[destination] = self.files[source]
     88         self.written_files[destination] = self.files[source]
     89 
     90     def dirname(self, path):
     91         return self._split(path)[0]
     92 
     93     def exists(self, path):
     94         return self.isfile(path) or self.isdir(path)
     95 
     96     def files_under(self, path, dirs_to_skip=[], file_filter=None):
     97         def filter_all(fs, dirpath, basename):
     98             return True
     99 
    100         file_filter = file_filter or filter_all
    101         files = []
    102         if self.isfile(path):
    103             if file_filter(self, self.dirname(path), self.basename(path)):
    104                 files.append(path)
    105             return files
    106 
    107         if self.basename(path) in dirs_to_skip:
    108             return []
    109 
    110         if not path.endswith(self.sep):
    111             path += self.sep
    112 
    113         dir_substrings = [self.sep + d + self.sep for d in dirs_to_skip]
    114         for filename in self.files:
    115             if not filename.startswith(path):
    116                 continue
    117 
    118             suffix = filename[len(path) - 1:]
    119             if any(dir_substring in suffix for dir_substring in dir_substrings):
    120                 continue
    121 
    122             dirpath, basename = self._split(filename)
    123             if file_filter(self, dirpath, basename):
    124                 files.append(filename)
    125 
    126         return files
    127 
    128     def getcwd(self, path):
    129         return self.cwd
    130 
    131     def glob(self, path):
    132         # FIXME: This only handles a wildcard '*' at the end of the path.
    133         # Maybe it should handle more?
    134         if path[-1] == '*':
    135             return [f for f in self.files if f.startswith(path[:-1])]
    136         else:
    137             return [f for f in self.files if f == path]
    138 
    139     def isabs(self, path):
    140         return path.startswith(self.sep)
    141 
    142     def isfile(self, path):
    143         return path in self.files and self.files[path] is not None
    144 
    145     def isdir(self, path):
    146         if path in self.files:
    147             return False
    148         path = self.normpath(path)
    149         if path in self.dirs:
    150             return True
    151 
    152         # We need to use a copy of the keys here in order to avoid switching
    153         # to a different thread and potentially modifying the dict in
    154         # mid-iteration.
    155         files = self.files.keys()[:]
    156         result = any(f.startswith(path) for f in files)
    157         if result:
    158             self.dirs[path] = True
    159         return result
    160 
    161     def join(self, *comps):
    162         # FIXME: might want tests for this and/or a better comment about how
    163         # it works.
    164         return re.sub(re.escape(os.path.sep), self.sep, os.path.join(*comps))
    165 
    166     def listdir(self, path):
    167         if not self.isdir(path):
    168             raise OSError("%s is not a directory" % path)
    169 
    170         if not path.endswith(self.sep):
    171             path += self.sep
    172 
    173         dirs = []
    174         files = []
    175         for f in self.files:
    176             if self.exists(f) and f.startswith(path):
    177                 remaining = f[len(path):]
    178                 if self.sep in remaining:
    179                     dir = remaining[:remaining.index(self.sep)]
    180                     if not dir in dirs:
    181                         dirs.append(dir)
    182                 else:
    183                     files.append(remaining)
    184         return dirs + files
    185 
    186     def mtime(self, path):
    187         if self.exists(path):
    188             return 0
    189         self._raise_not_found(path)
    190 
    191     def _mktemp(self, suffix='', prefix='tmp', dir=None, **kwargs):
    192         if dir is None:
    193             dir = self.sep + '__im_tmp'
    194         curno = self.current_tmpno
    195         self.current_tmpno += 1
    196         return self.join(dir, "%s_%u_%s" % (prefix, curno, suffix))
    197 
    198     def mkdtemp(self, **kwargs):
    199         class TemporaryDirectory(object):
    200             def __init__(self, fs, **kwargs):
    201                 self._kwargs = kwargs
    202                 self._filesystem = fs
    203                 self._directory_path = fs._mktemp(**kwargs)
    204                 fs.maybe_make_directory(self._directory_path)
    205 
    206             def __str__(self):
    207                 return self._directory_path
    208 
    209             def __enter__(self):
    210                 return self._directory_path
    211 
    212             def __exit__(self, type, value, traceback):
    213                 # Only self-delete if necessary.
    214 
    215                 # FIXME: Should we delete non-empty directories?
    216                 if self._filesystem.exists(self._directory_path):
    217                     self._filesystem.rmtree(self._directory_path)
    218 
    219         return TemporaryDirectory(fs=self, **kwargs)
    220 
    221     def maybe_make_directory(self, *path):
    222         norm_path = self.normpath(self.join(*path))
    223         if not self.isdir(norm_path):
    224             self.dirs[norm_path] = True
    225 
    226     def move(self, source, destination):
    227         if self.files[source] is None:
    228             self._raise_not_found(source)
    229         self.files[destination] = self.files[source]
    230         self.written_files[destination] = self.files[destination]
    231         self.files[source] = None
    232         self.written_files[source] = None
    233 
    234     def normpath(self, path):
    235         # Like join(), relies on os.path functionality but normalizes the
    236         # path separator to the mock one.
    237         return re.sub(re.escape(os.path.sep), self.sep, os.path.normpath(path))
    238 
    239     def open_binary_tempfile(self, suffix=''):
    240         path = self._mktemp(suffix)
    241         return (WritableFileObject(self, path), path)
    242 
    243     def open_text_file_for_writing(self, path, append=False):
    244         return WritableFileObject(self, path, append)
    245 
    246     def read_text_file(self, path):
    247         return self.read_binary_file(path).decode('utf-8')
    248 
    249     def open_binary_file_for_reading(self, path):
    250         if self.files[path] is None:
    251             self._raise_not_found(path)
    252         return ReadableFileObject(self, path, self.files[path])
    253 
    254     def read_binary_file(self, path):
    255         # Intentionally raises KeyError if we don't recognize the path.
    256         if self.files[path] is None:
    257             self._raise_not_found(path)
    258         return self.files[path]
    259 
    260     def relpath(self, path, start='.'):
    261         return ospath.relpath(path, start, self.abspath, self.sep)
    262 
    263     def remove(self, path):
    264         if self.files[path] is None:
    265             self._raise_not_found(path)
    266         self.files[path] = None
    267         self.written_files[path] = None
    268 
    269     def rmtree(self, path):
    270         if not path.endswith(self.sep):
    271             path += self.sep
    272 
    273         for f in self.files:
    274             if f.startswith(path):
    275                 self.files[f] = None
    276 
    277     def splitext(self, path):
    278         idx = path.rfind('.')
    279         if idx == -1:
    280             idx = 0
    281         return (path[0:idx], path[idx:])
    282 
    283     def write_text_file(self, path, contents):
    284         return self.write_binary_file(path, contents.encode('utf-8'))
    285 
    286     def write_binary_file(self, path, contents):
    287         self.files[path] = contents
    288         self.written_files[path] = contents
    289 
    290 
    291 class WritableFileObject(object):
    292     def __init__(self, fs, path, append=False, encoding=None):
    293         self.fs = fs
    294         self.path = path
    295         self.closed = False
    296         if path not in self.fs.files or not append:
    297             self.fs.files[path] = ""
    298 
    299     def __enter__(self):
    300         return self
    301 
    302     def __exit__(self, type, value, traceback):
    303         self.close()
    304 
    305     def close(self):
    306         self.closed = True
    307 
    308     def write(self, str):
    309         self.fs.files[self.path] += str
    310         self.fs.written_files[self.path] = self.fs.files[self.path]
    311 
    312 
    313 class ReadableFileObject(object):
    314     def __init__(self, fs, path, data=""):
    315         self.fs = fs
    316         self.path = path
    317         self.closed = False
    318         self.data = data
    319         self.offset = 0
    320 
    321     def __enter__(self):
    322         return self
    323 
    324     def __exit__(self, type, value, traceback):
    325         self.close()
    326 
    327     def close(self):
    328         self.closed = True
    329 
    330     def read(self, bytes=None):
    331         if not bytes:
    332             return self.data[self.offset:]
    333         start = self.offset
    334         self.offset += bytes
    335         return self.data[start:self.offset]
    336