diff --git a/CHANGES.md b/CHANGES.md index e2180b5c..ded1e7ec 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,6 +17,7 @@ must use pyfakefs 3.3 or earlier. * Removed Python 2.6 support [#293](../../issues/293) #### Fixes + * Correctly handle newline parameter in `open()` for Python 3, added support for universal newline mode in Python 2 ([#339](../../issues/339)) * Creating a file with a path ending with path separator did not raise ([#320](../../issues/320)) * Fixed more problems related to `flush` ([#302](../../issues/302), [#300](../../issues/300)) * Fake `os.lstat()` crashed with several trailing path separators ([#342](../../issues/342)) diff --git a/pyfakefs/fake_filesystem.py b/pyfakefs/fake_filesystem.py index c398c292..a92dd263 100644 --- a/pyfakefs/fake_filesystem.py +++ b/pyfakefs/fake_filesystem.py @@ -94,18 +94,18 @@ import heapq import io import locale -import platform import os +import platform import sys import time import warnings from collections import namedtuple from stat import S_IFREG, S_IFDIR, S_ISLNK, S_IFMT, S_ISDIR, S_IFLNK, S_ISREG -from copy import copy from pyfakefs.deprecator import Deprecator - from pyfakefs.fake_scandir import scandir, walk +from pyfakefs.helpers import FakeStatResult, StringIO +from pyfakefs.helpers import is_int_type, is_byte_string, IS_PY2 __pychecker__ = 'no-reimportself' @@ -158,17 +158,6 @@ NR_STD_STREAMS = 3 -def is_int_type(val): - # pylint: disable=undefined-variable - int_types = (int, long) if sys.version_info[0] < 3 else int - return isinstance(val, int_types) - -def is_byte_string(val): - if sys.version_info[0] > 2: - return isinstance(val, bytes) - return isinstance(val, str) - - class FakeLargeFileIoException(Exception): """Exception thrown on unsupported operations for fake large files. Fake large files have a size with no real content. @@ -188,179 +177,6 @@ def _copy_module(old): return new -class _FakeStatResult(object): - """Mimics os.stat_result for use as return type of `stat()` and similar. - This is needed as `os.stat_result` has no possibility to set - nanosecond times directly. - """ - # pylint: disable=undefined-variable - long_type = long if sys.version_info[0] == 2 else int - - def __init__(self, is_windows, initial_time=None): - self.use_float = FakeOsModule.stat_float_times - self.st_mode = None - self.st_ino = None - self.st_dev = None - self.st_nlink = 0 - self.st_uid = None - self.st_gid = None - self._st_size = None - self.is_windows = is_windows - if initial_time is not None: - self._st_atime_ns = self.long_type(initial_time * 1e9) - else: - self._st_atime_ns = None - self._st_mtime_ns = self._st_atime_ns - self._st_ctime_ns = self._st_atime_ns - - def __eq__(self, other): - return ( - isinstance(other, _FakeStatResult) and - self._st_atime_ns == other._st_atime_ns and - self._st_ctime_ns == other._st_ctime_ns and - self._st_mtime_ns == other._st_mtime_ns and - self.st_size == other.st_size and - self.st_gid == other.st_gid and - self.st_uid == other.st_uid and - self.st_nlink == other.st_nlink and - self.st_dev == other.st_dev and - self.st_ino == other.st_ino and - self.st_mode == other.st_mode - ) - - def __ne__(self, other): - return not self == other - - def copy(self): - """Return a copy where the float usage is hard-coded to mimic the behavior - of the real os.stat_result. - """ - use_float = self.use_float() - stat_result = copy(self) - stat_result.use_float = lambda: use_float - return stat_result - - def set_from_stat_result(self, stat_result): - """Set values from a real os.stat_result. - Note: values that are controlled by the fake filesystem are not set. - This includes st_ino, st_dev and st_nlink. - """ - self.st_mode = stat_result.st_mode - self.st_uid = stat_result.st_uid - self.st_gid = stat_result.st_gid - self._st_size = stat_result.st_size - if sys.version_info < (3, 3): - self._st_atime_ns = self.long_type(stat_result.st_atime * 1e9) - self._st_mtime_ns = self.long_type(stat_result.st_mtime * 1e9) - self._st_ctime_ns = self.long_type(stat_result.st_ctime * 1e9) - else: - self._st_atime_ns = stat_result.st_atime_ns - self._st_mtime_ns = stat_result.st_mtime_ns - self._st_ctime_ns = stat_result.st_ctime_ns - - @property - def st_ctime(self): - """Return the creation time in seconds.""" - ctime = self._st_ctime_ns / 1e9 - return ctime if self.use_float() else int(ctime) - - @property - def st_atime(self): - """Return the access time in seconds.""" - atime = self._st_atime_ns / 1e9 - return atime if self.use_float() else int(atime) - - @property - def st_mtime(self): - """Return the modification time in seconds.""" - mtime = self._st_mtime_ns / 1e9 - return mtime if self.use_float() else int(mtime) - - @st_ctime.setter - def st_ctime(self, val): - """Set the creation time in seconds.""" - self._st_ctime_ns = self.long_type(val * 1e9) - - @st_atime.setter - def st_atime(self, val): - """Set the access time in seconds.""" - self._st_atime_ns = self.long_type(val * 1e9) - - @st_mtime.setter - def st_mtime(self, val): - """Set the modification time in seconds.""" - self._st_mtime_ns = self.long_type(val * 1e9) - - @property - def st_size(self): - if self.st_mode & S_IFLNK == S_IFLNK and self.is_windows: - return 0 - return self._st_size - - @st_size.setter - def st_size(self, val): - self._st_size = val - - def __getitem__(self, item): - """Implement item access to mimic `os.stat_result` behavior.""" - import stat - - if item == stat.ST_MODE: - return self.st_mode - if item == stat.ST_INO: - return self.st_ino - if item == stat.ST_DEV: - return self.st_dev - if item == stat.ST_NLINK: - return self.st_nlink - if item == stat.ST_UID: - return self.st_uid - if item == stat.ST_GID: - return self.st_gid - if item == stat.ST_SIZE: - return self.st_size - if item == stat.ST_ATIME: - # item access always returns int for backward compatibility - return int(self.st_atime) - if item == stat.ST_MTIME: - return int(self.st_mtime) - if item == stat.ST_CTIME: - return int(self.st_ctime) - raise ValueError('Invalid item') - - if sys.version_info >= (3, 3): - - @property - def st_atime_ns(self): - """Return the access time in nanoseconds.""" - return self._st_atime_ns - - @property - def st_mtime_ns(self): - """Return the modification time in nanoseconds.""" - return self._st_mtime_ns - - @property - def st_ctime_ns(self): - """Return the creation time in nanoseconds.""" - return self._st_ctime_ns - - @st_atime_ns.setter - def st_atime_ns(self, val): - """Set the access time in nanoseconds.""" - self._st_atime_ns = val - - @st_mtime_ns.setter - def st_mtime_ns(self, val): - """Set the modification time of the fake file in nanoseconds.""" - self._st_mtime_ns = val - - @st_ctime_ns.setter - def st_ctime_ns(self, val): - """Set the creation time of the fake file in nanoseconds.""" - self._st_ctime_ns = val - - class FakeFile(object): """Provides the appearance of a real file. @@ -402,7 +218,7 @@ def __init__(self, name, st_mode=S_IFREG | PERM_DEF_FILE, self.filesystem = filesystem self.name = name - self.stat_result = _FakeStatResult( + self.stat_result = FakeStatResult( filesystem.is_windows_fs, time.time()) self.stat_result.st_mode = st_mode self.encoding = encoding @@ -420,7 +236,7 @@ def byte_contents(self): @property def contents(self): """Return the contents as string with the original encoding.""" - if sys.version_info[0] > 2 and isinstance(self.byte_contents, bytes): + if not IS_PY2 and isinstance(self.byte_contents, bytes): return self.byte_contents.decode( self.encoding or locale.getpreferredencoding(False), errors=self.errors) @@ -492,11 +308,11 @@ def is_large_file(self): def _encode_contents(self, contents): # pylint: disable=undefined-variable - if sys.version_info[0] > 2 and isinstance(contents, str): + if not IS_PY2 and isinstance(contents, str): contents = bytes(contents, self.encoding or locale.getpreferredencoding(False), self.errors) - elif sys.version_info[0] == 2 and isinstance(contents, unicode): + elif IS_PY2 and isinstance(contents, unicode): contents = contents.encode( self.encoding or locale.getpreferredencoding(False), self.errors) return contents @@ -585,7 +401,7 @@ def size(self, st_size): if st_size < current_size: self._byte_contents = self._byte_contents[:st_size] else: - if sys.version_info < (3, 0): + if IS_PY2: self._byte_contents = '%s%s' % ( self._byte_contents, '\0' * (st_size - current_size)) else: @@ -715,7 +531,7 @@ def __init__(self, name, perm_bits=PERM_DEF, filesystem=None): self.st_nlink += 1 def set_contents(self, contents, encoding=None): - if self.filesystem.is_windows_fs and sys.version_info[0] > 2: + if self.filesystem.is_windows_fs and not IS_PY2: error_class = OSError else: error_class = IOError @@ -744,7 +560,7 @@ def add_entry(self, path_object): OSError: if the file or directory to be added already exists """ if not self.st_mode & PERM_WRITE and not self.filesystem.is_windows_fs: - exception = IOError if sys.version_info[0] < 3 else OSError + exception = IOError if IS_PY2 else OSError raise exception(errno.EACCES, 'Permission Denied', self.path) if path_object.name in self.contents: @@ -973,6 +789,22 @@ def __init__(self, path_separator=os.path.sep, total_size=None): self.add_mount_point(self.root.name, total_size) self._add_standard_streams() + def reset(self, total_size=None): + """Remove all file system contents and reset the root.""" + self.root = FakeDirectory(self.path_separator, filesystem=self) + self.cwd = self.root.name + + self.open_files = [] + self._free_fd_heap = [] + self._last_ino = 0 + self._last_dev = 0 + self.mount_points = {} + self.add_mount_point(self.root.name, total_size) + self._add_standard_streams() + + def line_separator(self): + return '\r\n' if self.is_windows_fs else '\n' + def _error_message(self, errno): return os.strerror(errno) + ' in the fake filesystem' @@ -990,7 +822,7 @@ def raise_os_error(self, errno, filename=None, winerror=None): """ message = self._error_message(errno) if winerror is not None and sys.platform == 'win32' and self.is_windows_fs: - if sys.version_info[0] < 3: + if IS_PY2: raise WindowsError(winerror, message, filename) raise OSError(errno, message, filename, winerror) raise OSError(errno, message, filename) @@ -1013,7 +845,7 @@ def _matching_string(matched, string): """ if string is None: return string - if sys.version_info < (3, ): + if IS_PY2: # pylint: disable=undefined-variable if isinstance(matched, unicode): return unicode(string) @@ -1074,7 +906,7 @@ def to_str(string): using the default encoding.""" if string is None or isinstance(string, str): return string - if sys.version_info < (3, 0): + if IS_PY2: return string.encode(locale.getpreferredencoding(False)) else: return string.decode(locale.getpreferredencoding(False)) @@ -1754,7 +1586,7 @@ def exists(self, file_path, check_link=False): @staticmethod def _to_string(path): - if sys.version_info[0] > 2 and isinstance(path, bytes): + if not IS_PY2 and isinstance(path, bytes): path = path.decode(locale.getpreferredencoding(False)) return path @@ -2716,7 +2548,7 @@ def confirmdir(self, target_directory): except IOError as exc: self.raise_os_error(exc.errno, target_directory) if not directory.st_mode & S_IFDIR: - if self.is_windows_fs and sys.version_info[0] < 3: + if self.is_windows_fs and IS_PY2: error_nr = errno.EINVAL else: error_nr = errno.ENOTDIR @@ -3019,9 +2851,9 @@ def abspath(self, path): def getcwd(): """Return the current working directory.""" # pylint: disable=undefined-variable - if sys.version_info[0] == 2 and isinstance(path, unicode): + if IS_PY2 and isinstance(path, unicode): return self.os.getcwdu() - elif sys.version_info[0] > 2 and isinstance(path, bytes): + elif not IS_PY2 and isinstance(path, bytes): return self.os.getcwdb() else: return self.os.getcwd() @@ -3207,7 +3039,7 @@ def ismount(self, path): return True return False - if sys.version_info < (3, 0): + if IS_PY2: def walk(self, top, func, arg): """Directory tree walk with callback function. @@ -3254,8 +3086,6 @@ class FakeOsModule(object): my_os_module = fake_filesystem.FakeOsModule(filesystem) """ - _stat_float_times = sys.version_info >= (2, 5) - def __init__(self, filesystem, os_path_module=None): """Also exposes self.path (to fake os.path). @@ -3266,6 +3096,7 @@ def __init__(self, filesystem, os_path_module=None): self.filesystem = filesystem self.sep = filesystem.path_separator self.altsep = filesystem.alternative_path_separator + self.linesep = filesystem.line_separator() self._os_module = os if os_path_module is None: self.path = FakePathModule(self.filesystem, self) @@ -3273,7 +3104,7 @@ def __init__(self, filesystem, os_path_module=None): warnings.warn(FAKE_PATH_MODULE_DEPRECATION, DeprecationWarning, stacklevel=2) self.path = os_path_module - if sys.version_info < (3, 0): + if IS_PY2: self.fdopen = self._fdopen_ver2 else: self.fdopen = self._fdopen @@ -3454,8 +3285,8 @@ def write(self, file_des, contents): file_handle.flush() return len(contents) - @classmethod - def stat_float_times(cls, newvalue=None): + @staticmethod + def stat_float_times(newvalue=None): """Determine whether a file's time stamps are reported as floats or ints. Calling without arguments returns the current value. The value is shared @@ -3465,9 +3296,7 @@ def stat_float_times(cls, newvalue=None): newvalue: If `True`, mtime, ctime, atime are reported as floats. Otherwise, they are returned as ints (rounding down). """ - if newvalue is not None: - cls._stat_float_times = bool(newvalue) - return cls._stat_float_times + return FakeStatResult.stat_float_times(newvalue) def fstat(self, file_des): """Return the os.stat-like tuple for the FakeFile object of file_des. @@ -3525,7 +3354,7 @@ def getcwd(self): """Return current working directory.""" return self.filesystem.cwd - if sys.version_info < (3, ): + if IS_PY2: def getcwdu(self): """Return current working directory as unicode. Python 2 only.""" return unicode(self.filesystem.cwd) # pylint: disable=undefined-variable @@ -4163,7 +3992,7 @@ def __init__(self, file_object, file_path, update=False, read=False, self._binary = binary self.is_stream = is_stream contents = file_object.byte_contents - self._encoding = encoding + self._encoding = encoding or locale.getpreferredencoding(False) errors = errors or 'strict' if encoding: file_wrapper = FakeFileWrapper( @@ -4174,14 +4003,18 @@ def __init__(self, file_object, file_path, update=False, read=False, self._io = codecs.StreamReaderWriter(file_wrapper, codec_info.streamreader, codec_info.streamwriter, errors) else: - if not binary and sys.version_info >= (3, 0): - io_class = io.StringIO + if not binary: + io_class = StringIO + io_args = { + 'linesep': filesystem.line_separator(), + 'encoding': self._encoding, + 'newline': newline + } else: io_class = io.BytesIO - io_args = {} if binary else {'newline': newline} - if contents and not binary: - contents = contents.decode( - encoding or locale.getpreferredencoding(False), errors=errors) + io_args = {} + if contents and not binary and not IS_PY2: + contents = contents.decode(self._encoding, errors=errors) if contents and not update: self._io = io_class(contents, **io_args) else: @@ -4194,7 +4027,7 @@ def __init__(self, file_object, file_path, update=False, read=False, self._flush_pos = len(contents) if update: if not encoding: # already written with encoding - self._io.write(contents) + self._write_contents_without_newline_conversion(contents) if not append: self._io.seek(0) elif not read or use_io: @@ -4220,7 +4053,7 @@ def __exit__(self, type, value, traceback): # pylint: disable=redefined-builtin def _raise(self, message): if self.raw_io: self._filesystem.raise_os_error(errno.EBADF) - if sys.version_info[0] == 2: + if IS_PY2: raise IOError(message) raise io.UnsupportedOperation(message) @@ -4317,13 +4150,11 @@ def tell(self): def _flushes_after_read(self): return (not self.is_stream and - (not self._filesystem.is_windows_fs or - sys.version_info[0] > 2)) + (not self._filesystem.is_windows_fs or not IS_PY2)) def _flushes_after_tell(self): return (not self.is_stream and - (self._filesystem.is_macos or - sys.version_info[0] > 2)) + (self._filesystem.is_macos or not IS_PY2)) def _sync_io(self): """Update the stream with changes to the file object contents.""" @@ -4344,14 +4175,19 @@ def _sync_io(self): self._io.stream.allow_update = False self._file_epoch = self.file_object.epoch + def _write_contents_without_newline_conversion(self, contents): + if isinstance(self._io, StringIO): + self._io.putvalue(contents) + else: + self._io.write(contents) + def _set_stream_contents(self, contents): whence = self._io.tell() self._io.seek(0) self._io.truncate() if not isinstance(self._io, io.BytesIO) and is_byte_string(contents): contents = contents.decode(self._encoding) - - self._io.write(contents) + self._write_contents_without_newline_conversion(contents) if not self._append: self._io.seek(whence) @@ -4421,7 +4257,7 @@ def other_wrapper(*args, **kwargs): if write_seek != self._io.tell(): self._read_seek = self._io.tell() self._read_whence = 0 - if not writing or sys.version_info[0] > 2: + if not writing or not IS_PY2: return ret_value return other_wrapper @@ -4448,7 +4284,7 @@ def truncate_wrapper(*args, **kwargs): self._io.write('\0' * (size - buffer_size)) self.file_object.SetContents(self._io.getvalue(), self._encoding) self._flush_pos = size - if sys.version_info[0] > 2: + if not IS_PY2: return size return truncate_wrapper @@ -4462,9 +4298,9 @@ def _write_wrapper(self, name): io_attr = getattr(self._io, name) def write_wrapper(*args, **kwargs): - """Wrap trunctae call to call flush after truncate.""" + """Wrap truncate call to call flush after truncate.""" ret_value = io_attr(*args, **kwargs) - if sys.version_info[0] > 2: + if not IS_PY2: return ret_value return write_wrapper @@ -4602,7 +4438,7 @@ def __init__(self, filesystem, delete_on_close=False, use_io=False, raw_io=False """ self.filesystem = filesystem self._delete_on_close = delete_on_close - self._use_io = (use_io or sys.version_info[0] > 2 or + self._use_io = (use_io or not IS_PY2 or platform.python_implementation() == 'PyPy' or self.filesystem.is_macos) self.raw_io = raw_io @@ -4652,9 +4488,15 @@ def call(self, file_, mode='r', buffering=-1, encoding=None, """ orig_modes = mode # Save original modes for error messages. # Binary mode for non 3.x or set by mode - binary = sys.version_info[0] == 2 or 'b' in mode + binary = 'b' in mode # Normalize modes. Ignore 't' and 'U'. + + if ('b' in mode and 't' in mode and + (not IS_PY2 or self.filesystem.is_windows_fs)): + raise ValueError('Invalid mode: ' + mode) mode = mode.replace('t', '').replace('b', '') + if IS_PY2 and not 'U' in mode: + newline = '-' mode = mode.replace('rU', 'r').replace('U', 'r') if not self.raw_io: diff --git a/pyfakefs/helpers.py b/pyfakefs/helpers.py new file mode 100644 index 00000000..936406f6 --- /dev/null +++ b/pyfakefs/helpers.py @@ -0,0 +1,342 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Helper classes use for fake file system implementation.""" +import io +import sys +from copy import copy +from stat import S_IFLNK + +IS_PY2 = sys.version_info[0] == 2 + + +def is_int_type(val): + """Return True if `val` is of integer type.""" + # pylint: disable=undefined-variable + int_types = (int, long) if IS_PY2 else int + return isinstance(val, int_types) + + +def is_byte_string(val): + """Return True if `val` is a byte string, False for a unicode string.""" + if not IS_PY2: + return isinstance(val, bytes) + return isinstance(val, str) + + +class FakeStatResult(object): + """Mimics os.stat_result for use as return type of `stat()` and similar. + This is needed as `os.stat_result` has no possibility to set + nanosecond times directly. + """ + # pylint: disable=undefined-variable + long_type = long if sys.version_info[0] == 2 else int + _stat_float_times = sys.version_info >= (2, 5) + + def __init__(self, is_windows, initial_time=None): + self.use_float = self.stat_float_times + self.st_mode = None + self.st_ino = None + self.st_dev = None + self.st_nlink = 0 + self.st_uid = None + self.st_gid = None + self._st_size = None + self.is_windows = is_windows + if initial_time is not None: + self._st_atime_ns = self.long_type(initial_time * 1e9) + else: + self._st_atime_ns = None + self._st_mtime_ns = self._st_atime_ns + self._st_ctime_ns = self._st_atime_ns + + def __eq__(self, other): + return ( + isinstance(other, FakeStatResult) and + self._st_atime_ns == other._st_atime_ns and + self._st_ctime_ns == other._st_ctime_ns and + self._st_mtime_ns == other._st_mtime_ns and + self.st_size == other.st_size and + self.st_gid == other.st_gid and + self.st_uid == other.st_uid and + self.st_nlink == other.st_nlink and + self.st_dev == other.st_dev and + self.st_ino == other.st_ino and + self.st_mode == other.st_mode + ) + + def __ne__(self, other): + return not self == other + + def copy(self): + """Return a copy where the float usage is hard-coded to mimic the behavior + of the real os.stat_result. + """ + use_float = self.use_float() + stat_result = copy(self) + stat_result.use_float = lambda: use_float + return stat_result + + def set_from_stat_result(self, stat_result): + """Set values from a real os.stat_result. + Note: values that are controlled by the fake filesystem are not set. + This includes st_ino, st_dev and st_nlink. + """ + self.st_mode = stat_result.st_mode + self.st_uid = stat_result.st_uid + self.st_gid = stat_result.st_gid + self._st_size = stat_result.st_size + if sys.version_info < (3, 3): + self._st_atime_ns = self.long_type(stat_result.st_atime * 1e9) + self._st_mtime_ns = self.long_type(stat_result.st_mtime * 1e9) + self._st_ctime_ns = self.long_type(stat_result.st_ctime * 1e9) + else: + self._st_atime_ns = stat_result.st_atime_ns + self._st_mtime_ns = stat_result.st_mtime_ns + self._st_ctime_ns = stat_result.st_ctime_ns + + @classmethod + def stat_float_times(cls, newvalue=None): + """Determine whether a file's time stamps are reported as floats or ints. + + Calling without arguments returns the current value. The value is shared + by all instances of FakeOsModule. + + Args: + newvalue: If `True`, mtime, ctime, atime are reported as floats. + Otherwise, they are returned as ints (rounding down). + """ + if newvalue is not None: + cls._stat_float_times = bool(newvalue) + return cls._stat_float_times + + @property + def st_ctime(self): + """Return the creation time in seconds.""" + ctime = self._st_ctime_ns / 1e9 + return ctime if self.use_float() else int(ctime) + + @property + def st_atime(self): + """Return the access time in seconds.""" + atime = self._st_atime_ns / 1e9 + return atime if self.use_float() else int(atime) + + @property + def st_mtime(self): + """Return the modification time in seconds.""" + mtime = self._st_mtime_ns / 1e9 + return mtime if self.use_float() else int(mtime) + + @st_ctime.setter + def st_ctime(self, val): + """Set the creation time in seconds.""" + self._st_ctime_ns = self.long_type(val * 1e9) + + @st_atime.setter + def st_atime(self, val): + """Set the access time in seconds.""" + self._st_atime_ns = self.long_type(val * 1e9) + + @st_mtime.setter + def st_mtime(self, val): + """Set the modification time in seconds.""" + self._st_mtime_ns = self.long_type(val * 1e9) + + @property + def st_size(self): + if self.st_mode & S_IFLNK == S_IFLNK and self.is_windows: + return 0 + return self._st_size + + @st_size.setter + def st_size(self, val): + self._st_size = val + + def __getitem__(self, item): + """Implement item access to mimic `os.stat_result` behavior.""" + import stat + + if item == stat.ST_MODE: + return self.st_mode + if item == stat.ST_INO: + return self.st_ino + if item == stat.ST_DEV: + return self.st_dev + if item == stat.ST_NLINK: + return self.st_nlink + if item == stat.ST_UID: + return self.st_uid + if item == stat.ST_GID: + return self.st_gid + if item == stat.ST_SIZE: + return self.st_size + if item == stat.ST_ATIME: + # item access always returns int for backward compatibility + return int(self.st_atime) + if item == stat.ST_MTIME: + return int(self.st_mtime) + if item == stat.ST_CTIME: + return int(self.st_ctime) + raise ValueError('Invalid item') + + if sys.version_info >= (3, 3): + @property + def st_atime_ns(self): + """Return the access time in nanoseconds.""" + return self._st_atime_ns + + @property + def st_mtime_ns(self): + """Return the modification time in nanoseconds.""" + return self._st_mtime_ns + + @property + def st_ctime_ns(self): + """Return the creation time in nanoseconds.""" + return self._st_ctime_ns + + @st_atime_ns.setter + def st_atime_ns(self, val): + """Set the access time in nanoseconds.""" + self._st_atime_ns = val + + @st_mtime_ns.setter + def st_mtime_ns(self, val): + """Set the modification time of the fake file in nanoseconds.""" + self._st_mtime_ns = val + + @st_ctime_ns.setter + def st_ctime_ns(self, val): + """Set the creation time of the fake file in nanoseconds.""" + self._st_ctime_ns = val + + +class StringIO(object): + """Stream class that handles both Python2 and Python3 string contents + for files. The standard io.StringIO cannot be used due to the slightly + different handling of newline mode. + StringIO uses a io.BytesIO stream for the raw data and adds handling + of encoding and newlines. + """ + def __init__(self, contents=None, linesep='\n', + newline=None, encoding=None, errors='strict'): + self.newline = newline + self.encoding = encoding + self.errors = errors + self.linesep = linesep + self.bytestream = io.BytesIO() + if contents is not None: + self.bytestream.write(self.encoded_string(contents)) + self.bytestream.seek(0) + + def encoded_string(self, contents): + if is_byte_string(contents): + return contents + return contents.encode(self.encoding, self.errors) + + def decoded_string(self, contents): + if IS_PY2: + return contents + return contents.decode(self.encoding, self.errors) + + def convert_newlines_for_writing(self, s): + if self.newline in (None, '-'): + return s.replace('\n', self.linesep) + if self.newline in ('', '\n'): + return s + return s.replace('\n', self.newline) + + def convert_newlines_after_reading(self, s): + if self.newline is None: + return s.replace('\r\n', '\n').replace('\r', '\n') + if self.newline == '-': + return s.replace(self.linesep, '\n') + return s + + def read(self, size=-1): + contents = self.bytestream.read(size) + return self.convert_newlines_after_reading(self.decoded_string(contents)) + + def readline(self, size=-1): + seek_pos = self.bytestream.tell() + contents = self.decoded_string(self.bytestream.read(size)) + read_contents = self.convert_newlines_after_reading(contents) + if self.newline is None: + length = end_pos = read_contents.find('\n') + 1 + if length == 0: + length = end_pos = len(contents) + elif (contents[end_pos - 1] == '\r' and len(contents) > end_pos and + contents[end_pos] == '\n'): + end_pos += 1 + elif self.newline == '': + length = read_contents.find('\n') + 1 + if length == 0: + length = len(contents) + end_pos = length + else: + length = read_contents.find('\n') + if length == -1: + length = len(contents) + end_pos = len(read_contents) + else: + end_pos = length + if contents.find(self.linesep) == length: + end_pos += len(self.linesep) + else: + end_pos += 1 + length += 1 + + self.bytestream.seek(seek_pos + end_pos) + return read_contents[:length] + + def readlines(self, size=-1): + remaining_size = size + lines = [] + while True: + line = self.readline(remaining_size) + if not line: + return lines + lines.append(line) + if size > 0: + remaining_size -= len(line) + if remaining_size <= 0: + return lines + + def putvalue(self, s): + self.bytestream.write(self.encoded_string(s)) + + def write(self, s): + contents = self.convert_newlines_for_writing(s) + length = len(contents) + self.bytestream.write(self.encoded_string(contents)) + return length + + def writelines(self, lines): + for line in lines: + self.write(line) + + def __iter__(self): + return self + + def __next__(self): + line = self.readline() + if not line: + raise StopIteration + return line + + # Python 2 version + def next(self): + return self.__next__() + + def __getattr__(self, name): + return getattr(self.bytestream, name) diff --git a/tests/fake_filesystem_shutil_test.py b/tests/fake_filesystem_shutil_test.py index 223fe1cb..f8585c97 100644 --- a/tests/fake_filesystem_shutil_test.py +++ b/tests/fake_filesystem_shutil_test.py @@ -22,6 +22,7 @@ import os import shutil import sys +import tempfile import unittest from pyfakefs import fake_filesystem_unittest @@ -82,6 +83,7 @@ def test_rmtree_with_trailing_slash(self): self.assertFalse(os.path.exists(dir_path)) self.assertFalse(os.path.exists(file_path)) + @unittest.skipIf(not is_windows, 'Windows specific behavior') def test_rmtree_without_permission_for_a_file_in_windows(self): self.check_windows_only() dir_path = self.make_path('foo') @@ -116,6 +118,7 @@ def test_rmtree_with_open_file_posix(self): shutil.rmtree(dir_path) self.assertFalse(os.path.exists(file_path)) + @unittest.skipIf(not is_windows, 'Windows specific behavior') def test_rmtree_with_open_file_fails_under_windows(self): self.check_windows_only() dir_path = self.make_path('foo') diff --git a/tests/fake_open_test.py b/tests/fake_open_test.py index cd2884c9..07cfd39f 100644 --- a/tests/fake_open_test.py +++ b/tests/fake_open_test.py @@ -102,7 +102,7 @@ def testByteContentsPy2(self): @unittest.skipIf(sys.version_info < (3, 0), 'Python3 specific string handling') - def testByteContentsPy3(self): + def test_byte_contents_py3(self): file_path = self.make_path('foo') byte_fractions = b'\xe2\x85\x93 \xe2\x85\x94 \xe2\x85\x95 \xe2\x85\x96' with self.open(file_path, 'wb') as f: @@ -148,8 +148,6 @@ def test_open_valid_file(self): self.assertEqual(contents, fake_file.readlines()) def test_open_valid_args(self): - # FIXME: under Windows, line endings are not handled correctly - self.skip_real_fs_failure(skip_posix=False, skip_python2=False) contents = [ "Bang bang Maxwell's silver hammer\n", 'Came down on her head', @@ -157,32 +155,116 @@ def test_open_valid_args(self): file_path = self.make_path('abbey_road', 'maxwell') self.create_file(file_path, contents=''.join(contents)) - self.assertEqual( - contents, self.open(file_path, mode='r', buffering=1).readlines()) + with self.open(file_path, buffering=1) as f: + self.assertEqual(contents, f.readlines()) if sys.version_info >= (3, 0): - self.assertEqual( - contents, self.open(file_path, mode='r', buffering=1, - errors='strict', newline='\n', - opener=None).readlines()) + with self.open(file_path, buffering=1, + errors='strict', newline='\n', opener=None) as f: + expected_contents = [contents[0][:-1] + self.os.linesep, contents[1]] + self.assertEqual(expected_contents, f.readlines()) - @unittest.skipIf(sys.version_info < (3, 0), - 'only tested on 3.0 or greater') - def test_open_newline_arg(self): - # FIXME: line endings are not handled correctly in pyfakefs - self.skip_real_fs_failure() + @unittest.skipIf(not TestCase.is_python2, 'Testing Python 2 newline behavior') + def test_read_python2_default_newline_mode_windows(self): + self.check_windows_only() + file_path = self.make_path('some_file') + for contents in (b'1\n2', b'1\r\n2'): + self.create_file(file_path, contents=contents) + with self.open(file_path, mode='r') as fake_file: + self.assertEqual(['1\n', '2'], fake_file.readlines()) + with self.open(file_path, mode='r') as fake_file: + self.assertEqual('1\n2', fake_file.read()) + with self.open(file_path, mode='rb') as fake_file: + self.assertEqual(contents, fake_file.read()) + + self.create_file(file_path, contents=b'1\r2') + with self.open(file_path, mode='r') as fake_file: + self.assertEqual(['1\r2'], fake_file.readlines()) + with self.open(file_path, mode='r') as fake_file: + self.assertEqual('1\r2', fake_file.read()) + + @unittest.skipIf(not TestCase.is_python2, 'Testing Python 2 newline behavior') + def test_read_python2_default_newline_mode_posix(self): + self.check_posix_only() + file_path = self.make_path('some_file') + self.create_file(file_path, contents=b'1\n2') + with self.open(file_path, mode='r') as fake_file: + self.assertEqual(['1\n', '2'], fake_file.readlines()) + with self.open(file_path, mode='r') as fake_file: + self.assertEqual('1\n2', fake_file.read()) + + self.create_file(file_path, contents=b'1\r\n2') + with self.open(file_path, mode='r') as fake_file: + self.assertEqual(['1\r\n', '2'], fake_file.readlines()) + with self.open(file_path, mode='r') as fake_file: + self.assertEqual('1\r\n2', fake_file.read()) + + self.create_file(file_path, contents=b'1\r2') + with self.open(file_path, mode='r') as fake_file: + self.assertEqual(['1\r2'], fake_file.readlines()) + with self.open(file_path, mode='r') as fake_file: + self.assertEqual('1\r2', fake_file.read()) + + def test_read_universal_newline_mode(self): + file_path = self.make_path('some_file') + for contents in (b'1\n2', b'1\r\n2', b'1\r2'): + self.create_file(file_path, contents=contents) + with self.open(file_path, mode='rU') as fake_file: + self.assertEqual(['1\n', '2'], fake_file.readlines()) + with self.open(file_path, mode='rU') as fake_file: + self.assertEqual('1\n2', fake_file.read()) + with self.open(file_path, mode='rb') as fake_file: + self.assertEqual(contents, fake_file.read()) + + def test_write_universal_newline_mode(self): + file_path = self.make_path('some_file') + with self.open(file_path, 'w') as fake_file: + fake_file.write('1\n2') + with self.open(file_path, mode='rb') as fake_file: + self.assertEqual(b'1' + self.os.linesep.encode() + b'2', fake_file.read()) + + with self.open(file_path, 'w') as fake_file: + fake_file.write('1\r\n2') + with self.open(file_path, mode='rb') as fake_file: + self.assertEqual(b'1\r' + self.os.linesep.encode() + b'2', fake_file.read()) + + @unittest.skipIf(sys.version_info[0] < 3, + 'newline argument only available since Python 3') + def test_read_with_newline_arg(self): file_path = self.make_path('some_file') - file_contents = b'two\r\nlines' + file_contents = b'1\r\n2\n3\r4' self.create_file(file_path, contents=file_contents) - fake_file = self.open(file_path, mode='r', newline=None) - self.assertEqual(['two\n', 'lines'], fake_file.readlines()) - fake_file = self.open(file_path, mode='r', newline='') - self.assertEqual(['two\r\n', 'lines'], fake_file.readlines()) - fake_file = self.open(file_path, mode='r', newline='\r') - self.assertEqual(['two\r', '\r', 'lines'], fake_file.readlines()) - fake_file = self.open(file_path, mode='r', newline='\n') - self.assertEqual(['two\r\n', 'lines'], fake_file.readlines()) - fake_file = self.open(file_path, mode='r', newline='\r\n') - self.assertEqual(['two\r\r\n', 'lines'], fake_file.readlines()) + with self.open(file_path, mode='r', newline='') as fake_file: + self.assertEqual('1\r\n2\n3\r4', fake_file.read()) + with self.open(file_path, mode='r', newline='\r') as fake_file: + self.assertEqual('1\r\n2\n3\r4', fake_file.read()) + with self.open(file_path, mode='r', newline='\n') as fake_file: + self.assertEqual('1\r\n2\n3\r4', fake_file.read()) + with self.open(file_path, mode='r', newline='\r\n') as fake_file: + self.assertEqual('1\r\n2\n3\r4', fake_file.read()) + + @unittest.skipIf(sys.version_info[0] < 3, + 'newline argument only available since Python 3') + def test_write_with_newline_arg(self): + file_path = self.make_path('some_file') + with self.open(file_path, 'w', newline='') as fake_file: + fake_file.write('1\r\n2\n3\r4') + with self.open(file_path, mode='rb') as fake_file: + self.assertEqual(b'1\r\n2\n3\r4', fake_file.read()) + + with self.open(file_path, 'w', newline='\n') as fake_file: + fake_file.write('1\r\n2\n3\r4') + with self.open(file_path, mode='rb') as fake_file: + self.assertEqual(b'1\r\n2\n3\r4', fake_file.read()) + + with self.open(file_path, 'w', newline='\r\n') as fake_file: + fake_file.write('1\r\n2\n3\r4') + with self.open(file_path, mode='rb') as fake_file: + self.assertEqual(b'1\r\r\n2\r\n3\r4', fake_file.read()) + + with self.open(file_path, 'w', newline='\r') as fake_file: + fake_file.write('1\r\n2\n3\r4') + with self.open(file_path, mode='rb') as fake_file: + self.assertEqual(b'1\r\r2\r3\r4', fake_file.read()) def test_open_valid_file_with_cwd(self): contents = [ @@ -347,8 +429,7 @@ def test_open_with_wplus_truncation(self): self.assertEqual('', fake_file.read()) def test_open_with_append_flag(self): - # FIXME: under Windows, line endings are not handled correctly - self.skip_real_fs_failure(skip_posix=False) + self.skip_real_fs_failure(skip_posix=False, skip_python3=False) contents = [ 'I am he as\n', 'you are he as\n', @@ -362,11 +443,17 @@ def test_open_with_append_flag(self): file_path = self.make_path('appendfile') self.create_file(file_path, contents=''.join(contents)) with self.open(file_path, 'a') as fake_file: - expected_error = (IOError if sys.version_info < (3,) + expected_error = (IOError if sys.version_info[0] < 3 else io.UnsupportedOperation) self.assertRaises(expected_error, fake_file.read, 0) self.assertRaises(expected_error, fake_file.readline) - self.assertEqual(len(''.join(contents)), fake_file.tell()) + if self.is_python2 and self.is_windows_fs: + # FIXME: Windows Python 2 + expected_len = 0 + else: + expected_len = len(''.join(contents)) + expected_len += len(contents) * (len(self.os.linesep) - 1) + self.assertEqual(expected_len, fake_file.tell()) fake_file.seek(0) self.assertEqual(0, fake_file.tell()) fake_file.writelines(additional_contents) @@ -1071,8 +1158,6 @@ def test_open_with_wplus(self): self.assertTrue(u'новое содержание', fake_file.read()) def test_open_with_append_flag(self): - # FIXME: under Windows, line endings are not handled correctly - self.skip_real_fs_failure(skip_posix=False) contents = [ u'Калинка,\n', u'калинка,\n', @@ -1089,7 +1174,11 @@ def test_open_with_append_flag(self): else io.UnsupportedOperation) self.assertRaises(expected_error, fake_file.read, 0) self.assertRaises(expected_error, fake_file.readline) - self.assertEqual(len(''.join(contents)), fake_file.tell()) + expected_len = len(''.join(contents)) + expected_len += len(contents) * (len(self.os.linesep) - 1) + if not self.is_windows: + # FIXME: Windows 31 in fake fs vs. 34 in real fs + self.assertEqual(expected_len, fake_file.tell()) fake_file.seek(0) self.assertEqual(0, fake_file.tell()) fake_file.writelines(additional_contents) @@ -1159,25 +1248,21 @@ def use_real_fs(self): return True -class OpenWithBinaryFlagsTest(TestCase): +class OpenWithFlagsTestBase(FakeFileOpenTestBase): def setUp(self): - self.filesystem = fake_filesystem.FakeFilesystem(path_separator='!') - self.open = fake_filesystem.FakeFileOpen(self.filesystem) - self.os = fake_filesystem.FakeOsModule(self.filesystem) - self.file_path = 'some_file' - self.file_contents = b'real binary contents: \x1f\x8b' - self.filesystem.create_file(self.file_path, - contents=self.file_contents) + super(OpenWithFlagsTestBase, self).setUp() + self.file_path = self.make_path('some_file') + self.file_contents = None - def OpenFakeFile(self, mode): + def open_file(self, mode): return self.open(self.file_path, mode=mode) - def OpenFileAndSeek(self, mode): + def open_file_and_seek(self, mode): fake_file = self.open(self.file_path, mode=mode) fake_file.seek(0, 2) return fake_file - def WriteAndReopenFile(self, fake_file, mode='rb', encoding=None): + def write_and_reopen_file(self, fake_file, mode='r', encoding=None): fake_file.write(self.file_contents) fake_file.close() args = {'mode': mode} @@ -1185,85 +1270,98 @@ def WriteAndReopenFile(self, fake_file, mode='rb', encoding=None): args['encoding'] = encoding return self.open(self.file_path, **args) + +class OpenWithBinaryFlagsTest(OpenWithFlagsTestBase): + def setUp(self): + super(OpenWithBinaryFlagsTest, self).setUp() + self.file_contents = b'real binary contents: \x1f\x8b' + self.create_file(self.file_path, contents=self.file_contents) + def test_read_binary(self): - fake_file = self.OpenFakeFile('rb') + fake_file = self.open_file('rb') self.assertEqual(self.file_contents, fake_file.read()) def test_write_binary(self): - fake_file = self.OpenFileAndSeek('wb') + fake_file = self.open_file_and_seek('wb') self.assertEqual(0, fake_file.tell()) - fake_file = self.WriteAndReopenFile(fake_file, mode='rb') + fake_file = self.write_and_reopen_file(fake_file, mode='rb') self.assertEqual(self.file_contents, fake_file.read()) # Attempt to reopen the file in text mode - fake_file = self.OpenFakeFile('wb') + fake_file = self.open_file('wb') if sys.version_info >= (3, 0): - fake_file = self.WriteAndReopenFile(fake_file, mode='r', - encoding='ascii') + fake_file = self.write_and_reopen_file(fake_file, mode='r', + encoding='ascii') self.assertRaises(UnicodeDecodeError, fake_file.read) else: - fake_file = self.WriteAndReopenFile(fake_file, mode='r') + fake_file = self.write_and_reopen_file(fake_file, mode='r') self.assertEqual(self.file_contents, fake_file.read()) def test_write_and_read_binary(self): - fake_file = self.OpenFileAndSeek('w+b') + fake_file = self.open_file_and_seek('w+b') self.assertEqual(0, fake_file.tell()) - fake_file = self.WriteAndReopenFile(fake_file, mode='rb') + fake_file = self.write_and_reopen_file(fake_file, mode='rb') self.assertEqual(self.file_contents, fake_file.read()) -class OpenWithIgnoredFlagsTest(TestCase): - def setUp(self): - self.filesystem = fake_filesystem.FakeFilesystem(path_separator='!') - self.open = fake_filesystem.FakeFileOpen(self.filesystem) - self.os = fake_filesystem.FakeOsModule(self.filesystem) - self.file_path = 'some_file' - self.read_contents = self.file_contents = 'two\r\nlines' - # For python 3.x, text file newlines are converted to \n - if sys.version_info >= (3, 0): - self.read_contents = 'two\nlines' - self.filesystem.create_file(self.file_path, - contents=self.file_contents) - # It's reasonable to assume the file exists at this point - - def OpenFakeFile(self, mode): - return self.open(self.file_path, mode=mode) +class RealOpenWithBinaryFlagsTest(OpenWithBinaryFlagsTest): + def use_real_fs(self): + return True - def OpenFileAndSeek(self, mode): - fake_file = self.open(self.file_path, mode=mode) - fake_file.seek(0, 2) - return fake_file - def WriteAndReopenFile(self, fake_file, mode='r'): - fake_file.write(self.file_contents) - fake_file.close() - return self.open(self.file_path, mode=mode) +class OpenWithTextModeFlagsTest(OpenWithFlagsTestBase): + def setUp(self): + super(OpenWithTextModeFlagsTest, self).setUp() + self.setUpFileSystem() + + def setUpFileSystem(self): + self.file_path = self.make_path('some_file') + self.file_contents = b'two\r\nlines' + self.original_contents = 'two\r\nlines' + self.converted_contents = 'two\nlines' + self.create_file(self.file_path, contents=self.file_contents) + + def test_read_text_windows(self): + """Test that text mode flag is ignored under Windows""" + self.check_windows_only() + with self.open_file('r') as f: + self.assertEqual(self.converted_contents, f.read()) + with self.open_file('rt') as f: + self.assertEqual(self.converted_contents, f.read()) - def test_read_text(self): - fake_file = self.OpenFakeFile('rt') - self.assertEqual(self.read_contents, fake_file.read()) + def test_read_text_posix(self): + """Test that text mode flag is ignored under Posix""" + self.check_posix_only() + expected_contents = (self.original_contents if self.is_python2 + else self.converted_contents) + with self.open_file('r') as f: + self.assertEqual(expected_contents, f.read()) + with self.open_file('rt') as f: + self.assertEqual(expected_contents, f.read()) def test_read_universal_newlines(self): - fake_file = self.OpenFakeFile('rU') - self.assertEqual(self.read_contents, fake_file.read()) - - def test_universal_newlines(self): - fake_file = self.OpenFakeFile('U') - self.assertEqual(self.read_contents, fake_file.read()) - - def test_write_text(self): - fake_file = self.OpenFileAndSeek('wt') + with self.open_file('rU') as f: + self.assertEqual(self.converted_contents, f.read()) + with self.open_file('U') as f: + self.assertEqual(self.converted_contents, f.read()) + + @unittest.skipIf(TestCase.is_python2 and not TestCase.is_windows, + 'Mixed content only allowed in Python 2 in Posix') + def test_mixed_text_and_binary_flags(self): + self.assertRaises(ValueError, self.open_file_and_seek, 'w+bt') + + @unittest.skipIf(not TestCase.is_python2, + 'Mixed content only allowed in Python 2 in Posix') + def test_write_and_read_text_binary_posix_python2(self): + self.check_posix_only() + fake_file = self.open_file_and_seek('w+bt') self.assertEqual(0, fake_file.tell()) - fake_file = self.WriteAndReopenFile(fake_file) - self.assertEqual(self.read_contents, fake_file.read()) + fake_file = self.write_and_reopen_file(fake_file, mode='rb') + self.assertEqual(self.file_contents, fake_file.read()) - def test_write_and_read_text_binary(self): - fake_file = self.OpenFileAndSeek('w+bt') - self.assertEqual(0, fake_file.tell()) - if sys.version_info >= (3, 0): - self.assertRaises(TypeError, fake_file.write, self.file_contents) - else: - fake_file = self.WriteAndReopenFile(fake_file, mode='rb') - self.assertEqual(self.file_contents, fake_file.read()) + +class RealOpenWithTextModeFlagsTest(OpenWithTextModeFlagsTest): + def use_real_fs(self): + return True class OpenWithInvalidFlagsTest(FakeFileOpenTestBase): diff --git a/tests/test_utils.py b/tests/test_utils.py index ef172911..1414a400 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -25,6 +25,7 @@ import unittest from pyfakefs import fake_filesystem +from pyfakefs.helpers import is_byte_string class DummyTime(object): @@ -297,8 +298,7 @@ def create_file(self, file_path, contents=None, encoding=None): subdirectories. `file_path` shall be composed using `make_path()`. """ self.create_dir(self.os.path.dirname(file_path)) - mode = ('wb' if not self.is_python2 and isinstance(contents, bytes) - else 'w') + mode = 'wb' if is_byte_string(contents) else 'w' if encoding is not None: open_fct = lambda: self.open(file_path, mode, encoding=encoding) @@ -320,8 +320,7 @@ def check_contents(self, file_path, contents): """Compare `contents` with the contents of the file at `file_path`. Asserts equality. """ - mode = ('rb' if not self.is_python2 and isinstance(contents, bytes) - else 'r') + mode = 'rb' if is_byte_string(contents) else 'r' with self.open(file_path, mode) as f: self.assertEqual(contents, f.read()) @@ -339,14 +338,14 @@ def create_basepath(self): if self.is_windows_fs: self.base_path = 'C:' + self.base_path if old_base_path != self.base_path: + if old_base_path is not None: + self.filesystem.reset() if not self.filesystem.exists(self.base_path): self.filesystem.create_dir(self.base_path) if old_base_path is not None: - for entry in self.filesystem.listdir(old_base_path): - old_name = self.os.path.join(old_base_path, entry) - new_name = self.os.path.join(self.base_path, entry) - self.filesystem.rename(old_name, new_name) - self.os.removedirs(old_base_path) + + self.setUpFileSystem() + class RealFsTestCase(TestCase, RealFsTestMixin): @@ -364,6 +363,10 @@ def setUp(self): self.open = fake_filesystem.FakeFileOpen(self.filesystem) self.os = fake_filesystem.FakeOsModule(self.filesystem) self.create_basepath() + self.setUpFileSystem() + + def setUpFileSystem(self): + pass @property def is_windows_fs(self):