From 059933c431acfd416f9c73ce3957de8c8856e149 Mon Sep 17 00:00:00 2001 From: mrbean-bremen Date: Sat, 10 Jun 2017 21:41:39 +0200 Subject: [PATCH] Removed most of FakeShutilModule functionality - adapted tests to work with fake_filesystem_unittest.TestCase - fixed some tests - FakePathModule: do not implement samefile if not existent - fake_filesystem.ResolvePath: consider alternative path separator for resolving links - fixes #194 --- fake_filesystem_shutil_test.py | 51 ++---- pyfakefs/fake_filesystem.py | 38 ++-- pyfakefs/fake_filesystem_shutil.py | 280 +---------------------------- 3 files changed, 41 insertions(+), 328 deletions(-) diff --git a/fake_filesystem_shutil_test.py b/fake_filesystem_shutil_test.py index 2130749a..75587ddf 100755 --- a/fake_filesystem_shutil_test.py +++ b/fake_filesystem_shutil_test.py @@ -16,9 +16,10 @@ """Unittest for fake_filesystem_shutil.""" +import shutil import stat -import time import sys +import time if sys.version_info < (2, 7): import unittest2 as unittest @@ -26,13 +27,15 @@ import unittest from pyfakefs import fake_filesystem -from pyfakefs import fake_filesystem_shutil +from pyfakefs import fake_filesystem_unittest -class FakeShutilModuleTest(unittest.TestCase): +class FakeShutilModuleTest(fake_filesystem_unittest.TestCase): def setUp(self): - self.filesystem = fake_filesystem.FakeFilesystem(path_separator='/', total_size=1000) - self.shutil = fake_filesystem_shutil.FakeShutilModule(self.filesystem) + self.setUpPyfakefs() + self.filesystem = self.fs + self.filesystem.SetDiskUsage(1000) + self.shutil = shutil def testRmtree(self): directory = 'xyzzy' @@ -81,10 +84,10 @@ def testRmtreeWithOpenFileFailsUnderWindows(self): def testRmtreeNonExistingDir(self): directory = 'nonexisting' - self.assertRaises(IOError, self.shutil.rmtree, directory) + self.assertRaises(OSError, self.shutil.rmtree, directory) try: self.shutil.rmtree(directory, ignore_errors=True) - except IOError: + except OSError: self.fail('rmtree raised despite ignore_errors True') def testRmtreeNonExistingDirWithHandler(self): @@ -148,16 +151,12 @@ def testCopystat(self): src_obj = self.filesystem.CreateFile(src_file) dst_obj = self.filesystem.CreateFile(dst_file) src_obj.st_mode = ((src_obj.st_mode & ~0o7777) | 0o750) - src_obj.st_uid = 123 - src_obj.st_gid = 123 src_obj.st_atime = time.time() src_obj.st_mtime = time.time() self.assertTrue(self.filesystem.Exists(src_file)) self.assertTrue(self.filesystem.Exists(dst_file)) self.shutil.copystat(src_file, dst_file) self.assertEqual(src_obj.st_mode, dst_obj.st_mode) - self.assertEqual(src_obj.st_uid, dst_obj.st_uid) - self.assertEqual(src_obj.st_gid, dst_obj.st_gid) self.assertEqual(src_obj.st_atime, dst_obj.st_atime) self.assertEqual(src_obj.st_mtime, dst_obj.st_mtime) @@ -166,8 +165,6 @@ def testCopy2(self): dst_file = 'xyzzy_copy' src_obj = self.filesystem.CreateFile(src_file) src_obj.st_mode = ((src_obj.st_mode & ~0o7777) | 0o750) - src_obj.st_uid = 123 - src_obj.st_gid = 123 src_obj.st_atime = time.time() src_obj.st_mtime = time.time() self.assertTrue(self.filesystem.Exists(src_file)) @@ -176,8 +173,6 @@ def testCopy2(self): self.assertTrue(self.filesystem.Exists(dst_file)) dst_obj = self.filesystem.GetObject(dst_file) self.assertEqual(src_obj.st_mode, dst_obj.st_mode) - self.assertEqual(src_obj.st_uid, dst_obj.st_uid) - self.assertEqual(src_obj.st_gid, dst_obj.st_gid) self.assertEqual(src_obj.st_atime, dst_obj.st_atime) self.assertEqual(src_obj.st_mtime, dst_obj.st_mtime) @@ -188,8 +183,6 @@ def testCopy2Directory(self): src_obj = self.filesystem.CreateFile(src_file) self.filesystem.CreateDirectory(parent_directory) src_obj.st_mode = ((src_obj.st_mode & ~0o7777) | 0o750) - src_obj.st_uid = 123 - src_obj.st_gid = 123 src_obj.st_atime = time.time() src_obj.st_mtime = time.time() self.assertTrue(self.filesystem.Exists(src_file)) @@ -199,8 +192,6 @@ def testCopy2Directory(self): self.assertTrue(self.filesystem.Exists(dst_file)) dst_obj = self.filesystem.GetObject(dst_file) self.assertEqual(src_obj.st_mode, dst_obj.st_mode) - self.assertEqual(src_obj.st_uid, dst_obj.st_uid) - self.assertEqual(src_obj.st_gid, dst_obj.st_gid) self.assertEqual(src_obj.st_atime, dst_obj.st_atime) self.assertEqual(src_obj.st_mtime, dst_obj.st_mtime) @@ -283,8 +274,8 @@ def testMoveDirectory(self): self.assertFalse(self.filesystem.Exists(dst_directory)) self.shutil.move(src_directory, dst_directory) self.assertTrue(self.filesystem.Exists(dst_directory)) - self.assertTrue(self.filesystem.Exists('%s/%s/subfile' % (dst_directory, src_directory))) - self.assertTrue(self.filesystem.Exists('%s/%s/subdir' % (dst_directory, src_directory))) + self.assertTrue(self.filesystem.Exists('%s/subfile' % dst_directory)) + self.assertTrue(self.filesystem.Exists('%s/subdir' % dst_directory)) self.assertFalse(self.filesystem.Exists(src_directory)) @unittest.skipIf(sys.version_info < (3, 3), 'New in Python 3.3') @@ -302,10 +293,11 @@ def testDiskUsage(self): self.assertEqual((500, 400, 100), disk_usage) -class CopyFileTest(unittest.TestCase): +class CopyFileTest(fake_filesystem_unittest.TestCase): def setUp(self): - self.filesystem = fake_filesystem.FakeFilesystem(path_separator='/') - self.shutil = fake_filesystem_shutil.FakeShutilModule(self.filesystem) + self.setUpPyfakefs() + self.filesystem = self.fs + self.shutil = shutil def testCommonCase(self): src_file = 'xyzzy' @@ -366,17 +358,6 @@ def testRaisesIfDestExistsAndIsNotWritable(self): self.assertTrue(self.filesystem.Exists(dst_file)) self.assertRaises(IOError, self.shutil.copyfile, src_file, dst_file) - def testRaisesIfDestDirIsNotWritable(self): - src_file = 'xyzzy' - dst_dir = '/tmp/foo' - dst_file = '%s/%s' % (dst_dir, src_file) - src_contents = 'contents of source file' - self.filesystem.CreateFile(src_file, contents=src_contents) - self.filesystem.CreateDirectory(dst_dir, perm_bits=0o555) - self.assertTrue(self.filesystem.Exists(src_file)) - self.assertTrue(self.filesystem.Exists(dst_dir)) - self.assertRaises(IOError, self.shutil.copyfile, src_file, dst_file) - def testRaisesIfSrcDoesntExist(self): src_file = 'xyzzy' dst_file = 'xyzzy_copy' diff --git a/pyfakefs/fake_filesystem.py b/pyfakefs/fake_filesystem.py index 2e15b1c4..a4a0efaf 100644 --- a/pyfakefs/fake_filesystem.py +++ b/pyfakefs/fake_filesystem.py @@ -1558,10 +1558,12 @@ def _FollowLink(link_path_components, link): """ link_path = link.contents sep = self._path_separator(link_path) + alt_sep = self._alternative_path_separator(link_path) # For links to absolute paths, we want to throw out everything in the # path built so far and replace with the link. For relative links, we # have to append the link to what we have so far, - if not link_path.startswith(sep): + if (not link_path.startswith(sep) and + (alt_sep is None or not link_path.startswith(alt_sep))): # Relative path. Append remainder of path to what we have processed # so far, excluding the name of the link itself. # /a/b => ../c should yield /a/../c (which will normalize to /c) @@ -2792,25 +2794,27 @@ def realpath(self, filename): path, ok = self._joinrealpath(filename[:0], filename, {}) return self.abspath(path) - def samefile(self, path1, path2): - """Return whether path1 and path2 point to the same file. - Windows support new in Python 3.2. - New in pyfakefs 3.3. + if sys.platform != 'win32' or sys.version_info >= (3, 2): + def samefile(self, path1, path2): + """Return whether path1 and path2 point to the same file. + Windows support new in Python 3.2. + New in pyfakefs 3.3. - Args: - path1: first file path or path object (Python >=3.6) - path2: second file path or path object (Python >=3.6) - - Raises: - OSError: if one of the paths does not point to an existing file system object. - """ - if self.filesystem.is_windows_fs and sys.version_info < (3, 2): - raise (AttributeError, "'module' object has no attribute 'samefile'") + Args: + path1: first file path or path object (Python >=3.6) + path2: second file path or path object (Python >=3.6) - stat1 = self.filesystem.GetStat(path1) - stat2 = self.filesystem.GetStat(path2) - return stat1.st_ino == stat2.st_ino and stat1.st_dev == stat2.st_dev + Raises: + OSError: if one of the paths does not point to an existing file system object. + """ + stat1 = self.filesystem.GetStat(path1) + stat2 = self.filesystem.GetStat(path2) + return stat1.st_ino == stat2.st_ino and stat1.st_dev == stat2.st_dev + if sys.platform.startswith('linux') and sys.version_info >= (3, 3): + def listxattr(path=None, follow_symlinks=True): + """Dummy implementation that returns an empty list - used by shutil.""" + return [] def _joinrealpath(self, path, rest, seen): """Join two paths, normalizing and eliminating any symbolic links diff --git a/pyfakefs/fake_filesystem_shutil.py b/pyfakefs/fake_filesystem_shutil.py index 5549b144..38c53e8a 100755 --- a/pyfakefs/fake_filesystem_shutil.py +++ b/pyfakefs/fake_filesystem_shutil.py @@ -15,45 +15,21 @@ # limitations under the License. """A fake shutil module implementation that uses fake_filesystem for unit tests. +Note that only `shutildisk_usage()` is faked, the rest of the functions shall +work fine with the fake file system if `os`/`os.path` are patched. :Includes: FakeShutil: Uses a FakeFilesystem to provide a fake replacement for the shutil module. :Usage: - ->>> from pyfakefs import fake_filesystem ->>> from pyfakefs import fake_filesystem_shutil ->>> filesystem = fake_filesystem.FakeFilesystem() ->>> shutil_module = fake_filesystem_shutil.FakeShutilModule(filesystem) - -Copy a fake_filesystem directory tree: - ->>> new_file = filesystem.CreateFile('/src/new-file') ->>> shutil_module.copytree('/src', '/dst') ->>> filesystem.Exists('/dst/new-file') -True - -Remove a fake_filesystem directory tree: - ->>> shutil_module.rmtree('/src') ->>> filesystem.Exists('/src/new-file') -False + The fake implementation is automatically involved if using + `fake_filesystem_unittest.TestCase`, pytest fs fixture, or directly `Patcher`. """ -import errno -import os import shutil import sys -import stat - -__pychecker__ = 'no-reimportself' - -_PERM_WRITE = 0o200 # Write permission bit. -_PERM_READ = 0o400 # Read permission bit. -_PERM_ALL = 0o7777 # All permission bits. - class FakeShutilModule(object): """Uses a FakeFilesystem to provide a fake replacement for shutil module.""" @@ -67,243 +43,6 @@ def __init__(self, filesystem): self.filesystem = filesystem self._shutil_module = shutil - def rmtree(self, path, ignore_errors=False, onerror=None): - # Docstring from the real rmtree() documentation - """Delete an entire directory tree; path must point to a directory (but not - a symbolic link to a directory). If ignore_errors is true, errors resulting - from failed removals will be ignored; if false or omitted, such errors are - handled by calling a handler specified by onerror or, if that is omitted, - they raise an exception. - - Args: - path: (str) Directory tree to remove. - ignore_errors: (bool) If ignore_errors is true, errors resulting from - failed removals will be ignored; if false or omitted, such - errors are handled by calling a handler specified by - onerror. - New in pyfakefs 2.9. - onerror: (func) If onerror is provided, it must be a callable that accepts - three parameters: function, path, and excinfo. - - The first parameter, function, is the function which raised the - exception; it depends on the platform and implementation. The - second parameter, path, will be the path name passed to function. - The third parameter, excinfo, will be the exception information - returned by sys.exc_info(). Exceptions raised by onerror will not - be caught. - New in pyfakefs 2.9. - """ - if ignore_errors: - def onerror(*args): # pylint: disable=unused-argument,function-redefined - pass - try: - if not self.filesystem.Exists(path): - raise IOError("The specified path does not exist") - if stat.S_ISLNK(self.filesystem.GetObject(path).st_mode): - # symlinks to directories are forbidden. - raise OSError("Cannot call rmtree on a symbolic link") - except Exception: - if onerror is None: - raise - onerror(os.path.islink, path, sys.exc_info()) - # can't continue even if onerror hook returns - return - try: - self.filesystem.RemoveObject(path) - except (IOError, OSError): - if onerror is None: - raise - onerror(FakeShutilModule.rmtree, path, sys.exc_info()) - - def copy(self, src, dst): - """Copy data and mode bits ("cp src dst"). - - Args: - src: (str) source file - dst: (str) destination, may be a directory - """ - if self.filesystem.Exists(dst): - if stat.S_ISDIR(self.filesystem.GetObject(dst).st_mode): - dst = self.filesystem.JoinPaths(dst, os.path.basename(src)) - self.copyfile(src, dst) - src_object = self.filesystem.GetObject(src) - dst_object = self.filesystem.GetObject(dst) - dst_object.st_mode = ((dst_object.st_mode & ~_PERM_ALL) | - (src_object.st_mode & _PERM_ALL)) - - def copyfile(self, src, dst): - """Copy data from src to dst. - - Args: - src: (str) source file - dst: (dst) destination file - - Raises: - IOError: if the file can't be copied - shutil.Error: if the src and dst files are the same - """ - src_file_object = self.filesystem.GetObject(src) - if not src_file_object.st_mode & _PERM_READ: - raise IOError(errno.EACCES, 'Permission denied', src) - if stat.S_ISDIR(src_file_object.st_mode): - raise IOError(errno.EISDIR, 'Is a directory', src) - - dst_dir = os.path.dirname(dst) - if dst_dir: - if not self.filesystem.Exists(dst_dir): - raise IOError(errno.ENOTDIR, 'Not a directory', dst) - dst_dir_object = self.filesystem.GetObject(dst_dir) - if not dst_dir_object.st_mode & _PERM_WRITE: - raise IOError(errno.EACCES, 'Permission denied', dst_dir) - - abspath_src = self.filesystem.NormalizePath( - self.filesystem.ResolvePath(src)) - abspath_dst = self.filesystem.NormalizePath( - self.filesystem.ResolvePath(dst)) - if abspath_src == abspath_dst: - raise shutil.Error('`%s` and `%s` are the same file' % (src, dst)) - - if self.filesystem.Exists(dst): - dst_file_object = self.filesystem.GetObject(dst) - if stat.S_ISDIR(dst_file_object.st_mode): - raise IOError(errno.EISDIR, 'Is a directory', dst) - if not dst_file_object.st_mode & _PERM_WRITE: - raise IOError(errno.EACCES, 'Permission denied', dst) - dst_file_object.SetContents(src_file_object.contents) - - else: - self.filesystem.CreateFile(dst, contents=src_file_object.contents) - - def copystat(self, src, dst): - """Copy all stat info (mode bits, atime, and mtime) from src to dst. - - Args: - src: (str) source file - dst: (str) destination file - """ - src_object = self.filesystem.GetObject(src) - dst_object = self.filesystem.GetObject(dst) - dst_object.st_mode = ((dst_object.st_mode & ~_PERM_ALL) | - (src_object.st_mode & _PERM_ALL)) - dst_object.st_uid = src_object.st_uid - dst_object.st_gid = src_object.st_gid - dst_object.st_atime = src_object.st_atime - dst_object.st_mtime = src_object.st_mtime - - def copy2(self, src, dst): - """Copy data and all stat info ("cp -p src dst"). - - Args: - src: (str) source file - dst: (str) destination, may be a directory - """ - if self.filesystem.Exists(dst): - if stat.S_ISDIR(self.filesystem.GetObject(dst).st_mode): - dst = self.filesystem.JoinPaths(dst, os.path.basename(src)) - self.copyfile(src, dst) - self.copystat(src, dst) - - def _copytree(self, src, dst, copy_function, symlinks): - self.filesystem.CreateDirectory(dst) - try: - directory = self.filesystem.GetObject(src) - except IOError as exception: - raise OSError(exception.errno, exception.message) - if not stat.S_ISDIR(directory.st_mode): - raise OSError(errno.ENOTDIR, - 'Fake os module: %r not a directory' % src) - for name in directory.contents: - srcname = self.filesystem.JoinPaths(src, name) - dstname = self.filesystem.JoinPaths(dst, name) - src_mode = self.filesystem.GetObject(srcname).st_mode - if stat.S_ISDIR(src_mode): - self._copytree(srcname, dstname, copy_function=copy_function, symlinks=symlinks) - else: - copy_function(srcname, dstname) - - # argument order changed between versions, have to use separate definitions - if sys.version_info < (3, 2): - def copytree(self, src, dst, symlinks=False): - """Recursively copy a directory tree. - - Args: - src: (str) source directory - dst: (str) destination directory, must not already exist - symlinks: (bool) copy symlinks as symlinks instead of copying the - contents of the linked files. Currently unused. - - Raises: - OSError: if src is missing or isn't a directory - """ - self._copytree(src, dst, copy_function=self.copy2, symlinks=symlinks) - else: - def copytree(self, src, dst, copy_function=None, symlinks=False): - """Recursively copy a directory tree. - - Args: - src: (str) source directory - dst: (str) destination directory, must not already exist - copy_function: replacement for copy2. - New in python 3.2. New in pyfakefs 2.9. - symlinks: (bool) copy symlinks as symlinks instead of copying the - contents of the linked files. Currently unused. - - Raises: - OSError: if src is missing or isn't a directory - """ - copy_function = copy_function or self.copy2 - self._copytree(src, dst, copy_function=copy_function, symlinks=symlinks) - - def move(self, src, dst, copy_function=None): - """Rename a file or directory. - - Args: - src: (str) source file or directory - dst: (str) if the src is a directory, the dst must not already exist - copy_function: replacement for copy2 if copying is needed. - New in Python 3.5. New in pyfakefs 2.9. - """ - - def _destinsrc(src, dst): - src = os.path.abspath(src) - dst = os.path.abspath(dst) - if not src.endswith(self.filesystem.path_separator): - src += os.path.sep - if not dst.endswith(self.filesystem.path_separator): - dst += self.filesystem.path_separator - return dst.startswith(src) - - if copy_function is not None: - if sys.version_info < (3, 5): - raise TypeError("move() got an unexpected keyword argument 'copy_function") - else: - copy_function = self.copy2 - - src = self.filesystem.NormalizePath(src) - dst = self.filesystem.NormalizePath(dst) - if src == dst: - return dst - - source_is_dir = stat.S_ISDIR(self.filesystem.GetObject(src).st_mode) - if source_is_dir: - dst = self.filesystem.JoinPaths(dst, os.path.basename(src)) - if self.filesystem.Exists(dst): - raise shutil.Error("Destination path '%s' already exists" % dst) - - try: - self.filesystem.RenameObject(src, dst) - except OSError: - if source_is_dir: - if _destinsrc(src, dst): - raise shutil.Error("Cannot move a directory '%s' into itself" - " '%s'." % (src, dst)) - self._copytree(src, dst, copy_function=copy_function, symlinks=True) - self.rmtree(src) - else: - copy_function(src, dst) - self.filesystem.RemoveObject(src) - return dst - if sys.version_info >= (3, 3): def disk_usage(self, path): """Return the total, used and free disk space in bytes as named tuple @@ -318,14 +57,3 @@ def disk_usage(self, path): def __getattr__(self, name): """Forwards any non-faked calls to the standard shutil module.""" return getattr(self._shutil_module, name) - - -def _RunDoctest(): - # pylint: disable=import-self - import doctest - from pyfakefs import fake_filesystem_shutil - return doctest.testmod(fake_filesystem_shutil) - - -if __name__ == '__main__': - _RunDoctest()