From a848aa88da4033dd4d16729e8ac1362294803fa2 Mon Sep 17 00:00:00 2001 From: mrbean-bremen Date: Thu, 14 Dec 2017 19:43:08 +0100 Subject: [PATCH] Fixed scandir iterator for Python 2 - adapted example test to work with scandir module - fixed scandir test to work with scandir module - see #332 --- CHANGES.md | 2 + example.py | 14 ++++++- example_test.py | 24 +++++++++-- fake_filesystem_test.py | 87 ++++++++++++++++++++++++---------------- pyfakefs/fake_scandir.py | 8 +++- 5 files changed, 94 insertions(+), 41 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c0799077..c754c3be 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,11 +7,13 @@ This version of pyfakefs does not support Python 2.6. Python 2.6 users must use pyfakefs 3.3 or earlier. #### New Features + * Added support to fake out backported `scandir` module ([#332](../../issues/332)) #### Infrastructure * Removed Python 2.6 support [#293](../../issues/293) #### Fixes + * `os.path.split()` and `os.path.dirname()` gave incorrect results under Windows ([#335](../../issues/335)) ## [Version 3.3](https://pypi.python.org/pypi/pyfakefs/3.3) diff --git a/example.py b/example.py index 3bce909a..78551558 100644 --- a/example.py +++ b/example.py @@ -40,6 +40,12 @@ import glob import shutil +try: + import scandir + has_scandir = True +except ImportError: + has_scandir = False + def create_file(path): """Create the specified file and add some content to it. Use the `open()` @@ -131,9 +137,13 @@ def rm_tree(path): """Delete the specified file hierarchy.""" shutil.rmtree(path) -def scandir(path): + +def scan_dir(path): """Return a list of directory entries for the given path.""" - return list(os.scandir(path)) + if has_scandir: + return list(scandir.scandir(path)) + else: + return list(os.scandir(path)) def file_contents(path): """Return the contents of the given path as byte array.""" diff --git a/example_test.py b/example_test.py index f1212e35..76ee4243 100644 --- a/example_test.py +++ b/example_test.py @@ -33,6 +33,7 @@ import unittest from pyfakefs import fake_filesystem_unittest +from pyfakefs.fake_filesystem_unittest import has_scandir # The module under test is pyfakefs/example import example @@ -126,8 +127,9 @@ def test_rm_tree(self): example.rm_tree('/test/dir1') self.assertFalse(os.path.exists('/test/dir1')) - @unittest.skipIf(sys.version_info < (3, 5), 'os.scandir was introduced in Python 3.5') - def test_scandir(self): + @unittest.skipIf(sys.version_info < (3, 5), + 'os.scandir was introduced in Python 3.5') + def test_os_scandir(self): """Test example.scandir() which uses `os.scandir()`. The os module has been replaced with the fake os module so the @@ -138,13 +140,29 @@ def test_scandir(self): self.fs.CreateFile('/linktest/linked') self.fs.CreateLink('/test/linked_file', '/linktest/linked') - entries = sorted(example.scandir('/test'), key=lambda e: e.name) + entries = sorted(example.scan_dir('/test'), key=lambda e: e.name) self.assertEqual(3, len(entries)) self.assertEqual('linked_file', entries[1].name) self.assertTrue(entries[0].is_dir()) self.assertTrue(entries[1].is_symlink()) self.assertTrue(entries[2].is_file()) + @unittest.skipIf(not has_scandir, 'Testing only if scandir module is installed') + def test_scandir_scandir(self): + """Test example.scandir() which uses `scandir.scandir()`. + + The scandir module has been replaced with the fake_scandir module so the + fake filesystem path entries are returned instead of `scandir.DirEntry` objects. + """ + self.fs.CreateFile('/test/text.txt') + self.fs.CreateDirectory('/test/dir') + + entries = sorted(example.scan_dir('/test'), key=lambda e: e.name) + self.assertEqual(2, len(entries)) + self.assertEqual('text.txt', entries[1].name) + self.assertTrue(entries[0].is_dir()) + self.assertTrue(entries[1].is_file()) + def test_real_file_access(self): """Test `example.file_contents()` for a real file after adding it using `add_real_file()`.""" filename = __file__ diff --git a/fake_filesystem_test.py b/fake_filesystem_test.py index 18121f3c..0328430b 100755 --- a/fake_filesystem_test.py +++ b/fake_filesystem_test.py @@ -4823,13 +4823,16 @@ def testOpen(self): class FakeScandirTest(FakeOsModuleTestBase): def setUp(self): super(FakeScandirTest, self).setUp() - self.skipIfSymlinkNotSupported() + + self.supports_symlinks = (not self.is_windows or + not self.useRealFs() and not self.is_python2) if has_scandir: if self.useRealFs(): from scandir import scandir else: - from fake_scandir import scandir + import pyfakefs.fake_scandir + scandir = lambda p: pyfakefs.fake_scandir.scandir(self.filesystem, p) else: scandir = self.os.scandir @@ -4845,16 +4848,20 @@ def setUp(self): self.file_path = self.os.path.join(directory, 'file') self.createFile(self.file_path, contents=b'b' * 50) self.file_link_path = self.os.path.join(directory, 'link_file') - self.createLink(self.file_link_path, self.linked_file_path) - self.dir_link_path = self.os.path.join(directory, 'link_dir') - self.createLink(self.dir_link_path, self.linked_dir_path) + if self.supports_symlinks: + self.createLink(self.file_link_path, self.linked_file_path) + self.dir_link_path = self.os.path.join(directory, 'link_dir') + self.createLink(self.dir_link_path, self.linked_dir_path) self.dir_entries = [entry for entry in scandir(directory)] self.dir_entries = sorted(self.dir_entries, key=lambda entry: entry.name) def testPaths(self): - self.assertEqual(4, len(self.dir_entries)) - sorted_names = ['dir', 'file', 'link_dir', 'link_file'] + sorted_names = ['dir', 'file'] + if self.supports_symlinks: + sorted_names.extend(['link_dir', 'link_file']) + + self.assertEqual(len(sorted_names), len(self.dir_entries)) self.assertEqual(sorted_names, [entry.name for entry in self.dir_entries]) self.assertEqual(self.dir_path, self.dir_entries[0].path) @@ -4862,47 +4869,55 @@ def testPaths(self): def testIsfile(self): self.assertFalse(self.dir_entries[0].is_file()) self.assertTrue(self.dir_entries[1].is_file()) - self.assertFalse(self.dir_entries[2].is_file()) - self.assertFalse(self.dir_entries[2].is_file(follow_symlinks=False)) - self.assertTrue(self.dir_entries[3].is_file()) - self.assertFalse(self.dir_entries[3].is_file(follow_symlinks=False)) + if self.supports_symlinks: + self.assertFalse(self.dir_entries[2].is_file()) + self.assertFalse(self.dir_entries[2].is_file(follow_symlinks=False)) + self.assertTrue(self.dir_entries[3].is_file()) + self.assertFalse(self.dir_entries[3].is_file(follow_symlinks=False)) def testIsdir(self): self.assertTrue(self.dir_entries[0].is_dir()) self.assertFalse(self.dir_entries[1].is_dir()) - self.assertTrue(self.dir_entries[2].is_dir()) - self.assertFalse(self.dir_entries[2].is_dir(follow_symlinks=False)) - self.assertFalse(self.dir_entries[3].is_dir()) - self.assertFalse(self.dir_entries[3].is_dir(follow_symlinks=False)) + if self.supports_symlinks: + self.assertTrue(self.dir_entries[2].is_dir()) + self.assertFalse(self.dir_entries[2].is_dir(follow_symlinks=False)) + self.assertFalse(self.dir_entries[3].is_dir()) + self.assertFalse(self.dir_entries[3].is_dir(follow_symlinks=False)) def testIsLink(self): - self.assertFalse(self.dir_entries[0].is_symlink()) - self.assertFalse(self.dir_entries[1].is_symlink()) - self.assertTrue(self.dir_entries[2].is_symlink()) - self.assertTrue(self.dir_entries[3].is_symlink()) + if self.supports_symlinks: + self.assertFalse(self.dir_entries[0].is_symlink()) + self.assertFalse(self.dir_entries[1].is_symlink()) + self.assertTrue(self.dir_entries[2].is_symlink()) + self.assertTrue(self.dir_entries[3].is_symlink()) def testInode(self): + if has_scandir and self.is_windows and self.useRealFs(): + self.skipTest('inode seems not to work in scandir module under Windows') self.assertEqual(self.os.stat(self.dir_path).st_ino, self.dir_entries[0].inode()) self.assertEqual(self.os.stat(self.file_path).st_ino, self.dir_entries[1].inode()) - self.assertEqual(self.os.lstat(self.dir_link_path).st_ino, - self.dir_entries[2].inode()) - self.assertEqual(self.os.lstat(self.file_link_path).st_ino, - self.dir_entries[3].inode()) + if self.supports_symlinks: + self.assertEqual(self.os.lstat(self.dir_link_path).st_ino, + self.dir_entries[2].inode()) + self.assertEqual(self.os.lstat(self.file_link_path).st_ino, + self.dir_entries[3].inode()) def checkStat(self, expected_size): self.assertEqual(50, self.dir_entries[1].stat().st_size) - self.assertEqual(10, self.dir_entries[3].stat().st_size) - self.assertEqual(expected_size, - self.dir_entries[3].stat( - follow_symlinks=False).st_size) self.assertEqual( self.os.stat(self.dir_path).st_ctime, self.dir_entries[0].stat().st_ctime) - self.assertEqual( - self.os.stat(self.linked_dir_path).st_mtime, - self.dir_entries[2].stat().st_mtime) + + if self.supports_symlinks: + self.assertEqual(10, self.dir_entries[3].stat().st_size) + self.assertEqual(expected_size, + self.dir_entries[3].stat( + follow_symlinks=False).st_size) + self.assertEqual( + self.os.stat(self.linked_dir_path).st_mtime, + self.dir_entries[2].stat().st_mtime) @unittest.skipIf(TestCase.is_windows, 'POSIX specific behavior') def testStatPosix(self): @@ -4915,13 +4930,15 @@ def testStatWindows(self): def testIndexAccessToStatTimesReturnsInt(self): self.assertEqual(self.os.stat(self.dir_path)[stat.ST_CTIME], int(self.dir_entries[0].stat().st_ctime)) - self.assertEqual(self.os.stat(self.linked_dir_path)[stat.ST_MTIME], - int(self.dir_entries[2].stat().st_mtime)) + if self.supports_symlinks: + self.assertEqual(self.os.stat(self.linked_dir_path)[stat.ST_MTIME], + int(self.dir_entries[2].stat().st_mtime)) def testStatInoDev(self): - file_stat = self.os.stat(self.linked_file_path) - self.assertEqual(file_stat.st_ino, self.dir_entries[3].stat().st_ino) - self.assertEqual(file_stat.st_dev, self.dir_entries[3].stat().st_dev) + if self.supports_symlinks: + file_stat = self.os.stat(self.linked_file_path) + self.assertEqual(file_stat.st_ino, self.dir_entries[3].stat().st_ino) + self.assertEqual(file_stat.st_dev, self.dir_entries[3].stat().st_dev) class RealScandirTest(FakeScandirTest): diff --git a/pyfakefs/fake_scandir.py b/pyfakefs/fake_scandir.py index ba1de7ce..446c7bcc 100644 --- a/pyfakefs/fake_scandir.py +++ b/pyfakefs/fake_scandir.py @@ -97,7 +97,10 @@ def __iter__(self): return self def __next__(self): - entry = self.contents_iter.__next__() + try: + entry = self.contents_iter.next() + except AttributeError: + entry = self.contents_iter.__next__() dir_entry = DirEntry(self.filesystem) dir_entry.name = entry dir_entry.path = self.filesystem.JoinPaths(self.path, dir_entry.name) @@ -105,6 +108,9 @@ def __next__(self): dir_entry._islink = self.filesystem.IsLink(dir_entry.path) return dir_entry + # satisfy both Python 2 and 3 + next = __next__ + if sys.version_info >= (3, 6): def __enter__(self): return self