From ca451385dcdf81efe0a4de09f9662975c27e4b84 Mon Sep 17 00:00:00 2001 From: mrbean-bremen Date: Wed, 14 Jun 2017 20:43:30 +0200 Subject: [PATCH] Added possibility to use open file descriptor as path - added for os.utime, os.chmod, os.chdir, os.chown, os.listdir, os.stat and os.lstat - fixes #205 --- CHANGES.md | 3 + fake_filesystem_test.py | 541 +++++++++++++++++++++--------------- pyfakefs/fake_filesystem.py | 38 ++- 3 files changed, 346 insertions(+), 236 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index df79a96f..4f4e3bef 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,9 @@ The release versions are PyPi releases. ## Version 3.3 (as yet unreleased) #### New Features + * Added support for open file descriptor as path argument + in `os.utime`, `os.chmod`, `os.chdir`, `os.chown`, `os.listdir`, `os.stat` and `os.lstat` + (Python >= 3.3) ([#205](../../issues/205)). * Added support for basic modes in fake `os.open()` ([#204](../../issues/204)). * Added fake `os.path.samefile` implementation ([#193](../../issues/193)) * Added support for `ns` argument in `os.utime()` (Python >= 3.3) ([#192](../../issues/192)). diff --git a/fake_filesystem_test.py b/fake_filesystem_test.py index 4178cf13..3f03f654 100755 --- a/fake_filesystem_test.py +++ b/fake_filesystem_test.py @@ -24,6 +24,8 @@ import sys import time +from pyfakefs.fake_filesystem import FakeFileOpen + if sys.version_info < (2, 7): import unittest2 as unittest else: @@ -705,17 +707,29 @@ def setUp(self): self.filesystem = fake_filesystem.FakeFilesystem(path_separator='/') self.os = fake_filesystem.FakeOsModule(self.filesystem) + def _CreateTestFile(self, path): + test_file = self.filesystem.CreateFile(path) + self.assertTrue(self.filesystem.Exists(path)) + st = self.os.stat(path) + self.assertEqual(0o666, stat.S_IMODE(st.st_mode)) + self.assertTrue(st.st_mode & stat.S_IFREG) + self.assertFalse(st.st_mode & stat.S_IFDIR) + return test_file + + def _CreateTestDirectory(self, path): + self.filesystem.CreateDirectory(path) + self.assertTrue(self.filesystem.Exists(path)) + st = self.os.stat(path) + self.assertEqual(0o777, stat.S_IMODE(st.st_mode)) + self.assertFalse(st.st_mode & stat.S_IFREG) + self.assertTrue(st.st_mode & stat.S_IFDIR) + class FakeOsModuleTest(FakeOsModuleTestBase): def setUp(self): super(FakeOsModuleTest, self).setUp() self.rwx = self.os.R_OK | self.os.W_OK | self.os.X_OK self.rw = self.os.R_OK | self.os.W_OK - self.orig_time = time.time - time.time = _DummyTime(200, 20) - - def tearDown(self): - time.time = self.orig_time def testChdir(self): """chdir should work on a directory.""" @@ -772,6 +786,20 @@ def testListdir(self): files.sort() self.assertEqual(files, sorted(self.os.listdir(directory))) + @unittest.skipIf(sys.version_info < (3, 3), + 'file descriptor as path new in Python 3.3') + def testListdirUsesOpenFdAsPath(self): + self.filesystem.is_windows_fs = False + self.assertRaisesOSError(errno.EBADF, self.os.listdir, 5) + dir_path = 'xyzzy/plugh' + files = ['foo', 'bar', 'baz'] + for f in files: + self.filesystem.CreateFile('%s/%s' % (dir_path, f)) + files.sort() + + path_des = self.os.open(dir_path, os.O_RDONLY) + self.assertEqual(files, sorted(self.os.listdir(path_des))) + def testListdirReturnsList(self): directory_root = 'xyzzy' self.os.mkdir(directory_root) @@ -881,6 +909,16 @@ def testStat(self): self.assertTrue(stat.S_IFREG & self.os.stat(file_path).st_mode) self.assertEqual(5, self.os.stat(file_path)[stat.ST_SIZE]) + @unittest.skipIf(sys.version_info < (3, 3), + 'file descriptor as path new in Python 3.3') + def testStatUsesOpenFdAsPath(self): + self.assertRaisesOSError(errno.EBADF, self.os.stat, 5) + file_path = '/foo/bar' + self.filesystem.CreateFile(file_path) + + with FakeFileOpen(self.filesystem)(file_path) as f: + self.assertTrue(stat.S_IFREG & self.os.stat(f.filedes)[stat.ST_MODE]) + @unittest.skipIf(sys.version_info < (3, 3), 'follow_symlinks new in Python 3.3') def testStatNoFollowSymlinks(self): """Test that stat with follow_symlinks=False behaves like lstat.""" @@ -913,6 +951,19 @@ def testLstat(self): self.assertEqual(len(file_contents), self.os.lstat(file_path)[stat.ST_SIZE]) self.assertEqual(len(base_name), self.os.lstat(link_path)[stat.ST_SIZE]) + @unittest.skipIf(sys.version_info < (3, 3), + 'file descriptor as path new in Python 3.3') + def testLstatUsesOpenFdAsPath(self): + self.assertRaisesOSError(errno.EBADF, self.os.lstat, 5) + file_path = '/foo/bar' + link_path = '/foo/link' + file_contents = b'contents' + self.filesystem.CreateFile(file_path, contents=file_contents) + self.filesystem.CreateLink(link_path, file_path) + + with FakeFileOpen(self.filesystem)(file_path) as f: + self.assertEqual(len(file_contents), self.os.lstat(f.filedes)[stat.ST_SIZE]) + def testStatNonExistentFile(self): # set up file_path = '/non/existent/file' @@ -1536,23 +1587,6 @@ def testFdatasyncPass(self): # And just for sanity, double-check that this still raises self.assertRaisesOSError(errno.EBADF, self.os.fdatasync, test_fd + 1) - def _CreateTestFile(self, path): - test_file = self.filesystem.CreateFile(path) - self.assertTrue(self.filesystem.Exists(path)) - st = self.os.stat(path) - self.assertEqual(0o666, stat.S_IMODE(st.st_mode)) - self.assertTrue(st.st_mode & stat.S_IFREG) - self.assertFalse(st.st_mode & stat.S_IFDIR) - return test_file - - def _CreateTestDirectory(self, path): - self.filesystem.CreateDirectory(path) - self.assertTrue(self.filesystem.Exists(path)) - st = self.os.stat(path) - self.assertEqual(0o777, stat.S_IMODE(st.st_mode)) - self.assertFalse(st.st_mode & stat.S_IFREG) - self.assertTrue(st.st_mode & stat.S_IFDIR) - def testAccess700(self): # set up path = '/some_file' @@ -1641,6 +1675,18 @@ def testChmod(self): self.assertTrue(st.st_mode & stat.S_IFREG) self.assertFalse(st.st_mode & stat.S_IFDIR) + @unittest.skipIf(sys.version_info < (3, 3), + 'file descriptor as path new in Python 3.3') + def testChmodUsesOpenFdAsPath(self): + self.assertRaisesOSError(errno.EBADF, self.os.chmod, 5, 0o6543) + path = '/some_file' + self._CreateTestFile(path) + + with FakeFileOpen(self.filesystem)(path) as f: + self.os.chmod(f.filedes, 0o6543) + st = self.os.stat(path) + self.assertModeEqual(0o6543, st.st_mode) + @unittest.skipIf(sys.version_info < (3, 3), 'follow_symlinks new in Python 3.3') def testChmodFollowSymlink(self): path = '/some_file' @@ -1705,193 +1751,6 @@ def testChmodNonExistent(self): self.assertEqual(errno.ENOENT, os_error.errno) self.assertEqual(path, os_error.filename) - def testChmodStCtime(self): - # set up - file_path = 'some_file' - self.filesystem.CreateFile(file_path) - self.assertTrue(self.filesystem.Exists(file_path)) - time.time.start() - - st = self.os.stat(file_path) - self.assertEqual(200, st.st_ctime) - # tests - self.os.chmod(file_path, 0o765) - st = self.os.stat(file_path) - self.assertEqual(220, st.st_ctime) - - def testUtimeSetsCurrentTimeIfArgsIsNone(self): - # set up - path = '/some_file' - self._CreateTestFile(path) - time.time.start() - - st = self.os.stat(path) - # 200 is the current time established in setUp(). - self.assertEqual(200, st.st_atime) - self.assertEqual(200, st.st_mtime) - # actual tests - self.os.utime(path, None) - st = self.os.stat(path) - self.assertEqual(220, st.st_atime) - self.assertEqual(220, st.st_mtime) - - def testUtimeSetsCurrentTimeIfArgsIsNoneWithFloats(self): - # set up - # we set os.stat_float_times() to False, so atime/ctime/mtime - # are converted as ints (seconds since epoch) - time.time = _DummyTime(200.9123, 20) - path = '/some_file' - fake_filesystem.FakeOsModule.stat_float_times(False) - self._CreateTestFile(path) - time.time.start() - - st = self.os.stat(path) - # 200 is the current time established above (if converted to int). - self.assertEqual(200, st.st_atime) - self.assertTrue(isinstance(st.st_atime, int)) - self.assertEqual(200, st.st_mtime) - self.assertTrue(isinstance(st.st_mtime, int)) - - if sys.version_info >= (3, 3): - self.assertEqual(200912300000, st.st_atime_ns) - self.assertEqual(200912300000, st.st_mtime_ns) - - self.assertEqual(200, st.st_mtime) - # actual tests - self.os.utime(path, None) - st = self.os.stat(path) - self.assertEqual(220, st.st_atime) - self.assertTrue(isinstance(st.st_atime, int)) - self.assertEqual(220, st.st_mtime) - self.assertTrue(isinstance(st.st_mtime, int)) - if sys.version_info >= (3, 3): - self.assertEqual(220912300000, st.st_atime_ns) - self.assertEqual(220912300000, st.st_mtime_ns) - - def testUtimeSetsCurrentTimeIfArgsIsNoneWithFloatsNSec(self): - self.filesystem = fake_filesystem.FakeFilesystem(path_separator='/') - self.os = fake_filesystem.FakeOsModule(self.filesystem) - fake_filesystem.FakeOsModule.stat_float_times(False) - - time.time = _DummyTime(200.9123, 20) - path = '/some_file' - test_file = self._CreateTestFile(path) - - time.time.start() - st = self.os.stat(path) - self.assertEqual(200, st.st_ctime) - self.assertEqual(200, test_file.st_ctime) - self.assertTrue(isinstance(st.st_ctime, int)) - self.assertTrue(isinstance(test_file.st_ctime, int)) - - self.os.stat_float_times(True) # first time float time - self.assertEqual(200, st.st_ctime) # st does not change - self.assertEqual(200.9123, test_file.st_ctime) # but the file does - self.assertTrue(isinstance(st.st_ctime, int)) - self.assertTrue(isinstance(test_file.st_ctime, float)) - - self.os.stat_float_times(False) # reverting to int - self.assertEqual(200, test_file.st_ctime) - self.assertTrue(isinstance(test_file.st_ctime, int)) - - self.assertEqual(200, st.st_ctime) - self.assertTrue(isinstance(st.st_ctime, int)) - - self.os.stat_float_times(True) - st = self.os.stat(path) - # 200.9123 not converted to int - self.assertEqual(200.9123, test_file.st_atime, test_file.st_mtime) - self.assertEqual(200.9123, st.st_atime, st.st_mtime) - self.os.utime(path, None) - st = self.os.stat(path) - self.assertEqual(220.9123, st.st_atime) - self.assertEqual(220.9123, st.st_mtime) - - def testUtimeSetsSpecifiedTime(self): - # set up - path = '/some_file' - self._CreateTestFile(path) - st = self.os.stat(path) - # actual tests - self.os.utime(path, (1, 2)) - st = self.os.stat(path) - self.assertEqual(1, st.st_atime) - self.assertEqual(2, st.st_mtime) - - def testUtimeDir(self): - # set up - path = '/some_dir' - self._CreateTestDirectory(path) - # actual tests - self.os.utime(path, (1.0, 2.0)) - st = self.os.stat(path) - self.assertEqual(1.0, st.st_atime) - self.assertEqual(2.0, st.st_mtime) - - @unittest.skipIf(sys.version_info < (3, 3), 'follow_symlinks new in Python 3.3') - def testUtimeFollowSymlinks(self): - path = '/some_file' - self._CreateTestFile(path) - link_path = '/link_to_some_file' - self.filesystem.CreateLink(link_path, path) - - self.os.utime(link_path, (1, 2)) - st = self.os.stat(link_path) - self.assertEqual(1, st.st_atime) - self.assertEqual(2, st.st_mtime) - - @unittest.skipIf(sys.version_info < (3, 3), 'follow_symlinks new in Python 3.3') - def testUtimeNoFollowSymlinks(self): - path = '/some_file' - self._CreateTestFile(path) - link_path = '/link_to_some_file' - self.filesystem.CreateLink(link_path, path) - - self.os.utime(link_path, (1, 2), follow_symlinks=False) - st = self.os.stat(link_path) - self.assertNotEqual(1, st.st_atime) - self.assertNotEqual(2, st.st_mtime) - st = self.os.stat(link_path, follow_symlinks=False) - self.assertEqual(1, st.st_atime) - self.assertEqual(2, st.st_mtime) - - def testUtimeNonExistent(self): - path = '/non/existent/file' - self.assertFalse(self.filesystem.Exists(path)) - self.assertRaisesOSError(errno.ENOENT, self.os.utime, path, (1, 2)) - - def testUtimeInvalidTimesArgRaises(self): - path = '/some_dir' - self._CreateTestDirectory(path) - - # the error message differs with different Python versions - # we don't expect the same message here - self.assertRaises(TypeError, self.os.utime, path, (1, 2, 3)) - self.assertRaises(TypeError, self.os.utime, path, (1, 'str')) - - @unittest.skipIf(sys.version_info < (3, 3), 'ns new in Python 3.3') - def testUtimeSetsSpecifiedTimeInNs(self): - # set up - path = '/some_file' - self._CreateTestFile(path) - time.time.start() - - st = self.os.stat(path) - # actual tests - self.os.utime(path, ns=(200000000, 400000000)) - st = self.os.stat(path) - self.assertEqual(0.2, st.st_atime) - self.assertEqual(0.4, st.st_mtime) - - @unittest.skipIf(sys.version_info < (3, 3), 'ns new in Python 3.3') - def testUtimeIncorrectNsArgumentRaises(self): - file_path = 'some_file' - self.filesystem.CreateFile(file_path) - - self.assertRaises(TypeError, self.os.utime, file_path, ns=(200000000)) - self.assertRaises(TypeError, self.os.utime, file_path, ns=('a', 'b')) - self.assertRaises(ValueError, self.os.utime, file_path, times=(1, 2), ns=(100, 200)) - def testChownExistingFile(self): # set up file_path = 'some_file' @@ -1912,6 +1771,18 @@ def testChownExistingFile(self): self.assertEqual(st[stat.ST_UID], 200) self.assertEqual(st[stat.ST_GID], 201) + @unittest.skipIf(sys.version_info < (3, 3), + 'file descriptor as path new in Python 3.3') + def testChownUsesOpenFdAsPath(self): + self.assertRaisesOSError(errno.EBADF, self.os.chown, 5, 100, 101) + file_path = '/foo/bar' + self.filesystem.CreateFile(file_path) + + with FakeFileOpen(self.filesystem)(file_path) as f: + self.os.chown(f.filedes, 100, 101) + st = self.os.stat(file_path) + self.assertEqual(st[stat.ST_UID], 100) + @unittest.skipIf(sys.version_info < (3, 3), 'follow_symlinks new in Python 3.3') def testChownFollowSymlink(self): file_path = 'some_file' @@ -2300,12 +2171,227 @@ def testOpenUmaskApplied(self): self.assertModeEqual(0o640, self.os.stat('file2').st_mode) +class FakeOsModuleTimeTest(FakeOsModuleTestBase): + def setUp(self): + super(FakeOsModuleTimeTest, self).setUp() + self.orig_time = time.time + self.dummy_time = None + self.setDummyTime(200) + + def tearDown(self): + time.time = self.orig_time + + def setDummyTime(self, start): + self.dummy_time = _DummyTime(start, 20) + time.time = self.dummy_time + + def testChmodStCtime(self): + # set up + file_path = 'some_file' + self.filesystem.CreateFile(file_path) + self.assertTrue(self.filesystem.Exists(file_path)) + self.dummy_time.start() + + st = self.os.stat(file_path) + self.assertEqual(200, st.st_ctime) + # tests + self.os.chmod(file_path, 0o765) + st = self.os.stat(file_path) + self.assertEqual(220, st.st_ctime) + + def testUtimeSetsCurrentTimeIfArgsIsNone(self): + # set up + path = '/some_file' + self._CreateTestFile(path) + self.dummy_time.start() + + st = self.os.stat(path) + # 200 is the current time established in setUp(). + self.assertEqual(200, st.st_atime) + self.assertEqual(200, st.st_mtime) + # actual tests + self.os.utime(path, None) + st = self.os.stat(path) + self.assertEqual(220, st.st_atime) + self.assertEqual(220, st.st_mtime) + + def testUtimeSetsCurrentTimeIfArgsIsNoneWithFloats(self): + # set up + # we set os.stat_float_times() to False, so atime/ctime/mtime + # are converted as ints (seconds since epoch) + self.setDummyTime(200.9123) + path = '/some_file' + fake_filesystem.FakeOsModule.stat_float_times(False) + self._CreateTestFile(path) + self.dummy_time.start() + + st = self.os.stat(path) + # 200 is the current time established above (if converted to int). + self.assertEqual(200, st.st_atime) + self.assertTrue(isinstance(st.st_atime, int)) + self.assertEqual(200, st.st_mtime) + self.assertTrue(isinstance(st.st_mtime, int)) + + if sys.version_info >= (3, 3): + self.assertEqual(200912300000, st.st_atime_ns) + self.assertEqual(200912300000, st.st_mtime_ns) + + self.assertEqual(200, st.st_mtime) + # actual tests + self.os.utime(path, None) + st = self.os.stat(path) + self.assertEqual(220, st.st_atime) + self.assertTrue(isinstance(st.st_atime, int)) + self.assertEqual(220, st.st_mtime) + self.assertTrue(isinstance(st.st_mtime, int)) + if sys.version_info >= (3, 3): + self.assertEqual(220912300000, st.st_atime_ns) + self.assertEqual(220912300000, st.st_mtime_ns) + + def testUtimeSetsCurrentTimeIfArgsIsNoneWithFloatsNSec(self): + self.filesystem = fake_filesystem.FakeFilesystem(path_separator='/') + self.os = fake_filesystem.FakeOsModule(self.filesystem) + fake_filesystem.FakeOsModule.stat_float_times(False) + + self.setDummyTime(200.9123) + path = '/some_file' + test_file = self._CreateTestFile(path) + + self.dummy_time.start() + st = self.os.stat(path) + self.assertEqual(200, st.st_ctime) + self.assertEqual(200, test_file.st_ctime) + self.assertTrue(isinstance(st.st_ctime, int)) + self.assertTrue(isinstance(test_file.st_ctime, int)) + + self.os.stat_float_times(True) # first time float time + self.assertEqual(200, st.st_ctime) # st does not change + self.assertEqual(200.9123, test_file.st_ctime) # but the file does + self.assertTrue(isinstance(st.st_ctime, int)) + self.assertTrue(isinstance(test_file.st_ctime, float)) + + self.os.stat_float_times(False) # reverting to int + self.assertEqual(200, test_file.st_ctime) + self.assertTrue(isinstance(test_file.st_ctime, int)) + + self.assertEqual(200, st.st_ctime) + self.assertTrue(isinstance(st.st_ctime, int)) + + self.os.stat_float_times(True) + st = self.os.stat(path) + # 200.9123 not converted to int + self.assertEqual(200.9123, test_file.st_atime, test_file.st_mtime) + self.assertEqual(200.9123, st.st_atime, st.st_mtime) + self.os.utime(path, None) + st = self.os.stat(path) + self.assertEqual(220.9123, st.st_atime) + self.assertEqual(220.9123, st.st_mtime) + + def testUtimeSetsSpecifiedTime(self): + # set up + path = '/some_file' + self._CreateTestFile(path) + st = self.os.stat(path) + # actual tests + self.os.utime(path, (1, 2)) + st = self.os.stat(path) + self.assertEqual(1, st.st_atime) + self.assertEqual(2, st.st_mtime) + + def testUtimeDir(self): + # set up + path = '/some_dir' + self._CreateTestDirectory(path) + # actual tests + self.os.utime(path, (1.0, 2.0)) + st = self.os.stat(path) + self.assertEqual(1.0, st.st_atime) + self.assertEqual(2.0, st.st_mtime) + + @unittest.skipIf(sys.version_info < (3, 3), 'follow_symlinks new in Python 3.3') + def testUtimeFollowSymlinks(self): + path = '/some_file' + self._CreateTestFile(path) + link_path = '/link_to_some_file' + self.filesystem.CreateLink(link_path, path) + + self.os.utime(link_path, (1, 2)) + st = self.os.stat(link_path) + self.assertEqual(1, st.st_atime) + self.assertEqual(2, st.st_mtime) + + @unittest.skipIf(sys.version_info < (3, 3), 'follow_symlinks new in Python 3.3') + def testUtimeNoFollowSymlinks(self): + path = '/some_file' + self._CreateTestFile(path) + link_path = '/link_to_some_file' + self.filesystem.CreateLink(link_path, path) + + self.os.utime(link_path, (1, 2), follow_symlinks=False) + st = self.os.stat(link_path) + self.assertNotEqual(1, st.st_atime) + self.assertNotEqual(2, st.st_mtime) + st = self.os.stat(link_path, follow_symlinks=False) + self.assertEqual(1, st.st_atime) + self.assertEqual(2, st.st_mtime) + + def testUtimeNonExistent(self): + path = '/non/existent/file' + self.assertFalse(self.filesystem.Exists(path)) + self.assertRaisesOSError(errno.ENOENT, self.os.utime, path, (1, 2)) + + def testUtimeInvalidTimesArgRaises(self): + path = '/some_dir' + self._CreateTestDirectory(path) + + # the error message differs with different Python versions + # we don't expect the same message here + self.assertRaises(TypeError, self.os.utime, path, (1, 2, 3)) + self.assertRaises(TypeError, self.os.utime, path, (1, 'str')) + + @unittest.skipIf(sys.version_info < (3, 3), 'ns new in Python 3.3') + def testUtimeSetsSpecifiedTimeInNs(self): + # set up + path = '/some_file' + self._CreateTestFile(path) + self.dummy_time.start() + + st = self.os.stat(path) + # actual tests + self.os.utime(path, ns=(200000000, 400000000)) + st = self.os.stat(path) + self.assertEqual(0.2, st.st_atime) + self.assertEqual(0.4, st.st_mtime) + + @unittest.skipIf(sys.version_info < (3, 3), 'ns new in Python 3.3') + def testUtimeIncorrectNsArgumentRaises(self): + file_path = 'some_file' + self.filesystem.CreateFile(file_path) + + self.assertRaises(TypeError, self.os.utime, file_path, ns=(200000000)) + self.assertRaises(TypeError, self.os.utime, file_path, ns=('a', 'b')) + self.assertRaises(ValueError, self.os.utime, file_path, times=(1, 2), ns=(100, 200)) + + @unittest.skipIf(sys.version_info < (3, 3), + 'file descriptor as path new in Python 3.3') + def testUtimeUsesOpenFdAsPath(self): + self.assertRaisesOSError(errno.EBADF, self.os.utime, 5, (1, 2)) + path = '/some_file' + self._CreateTestFile(path) + + with FakeFileOpen(self.filesystem)(path) as f: + self.os.utime(f.filedes, times=(1, 2)) + st = self.os.stat(path) + self.assertEqual(1, st.st_atime) + self.assertEqual(2, st.st_mtime) + + class FakeOsModuleLowLevelFileOpTest(FakeOsModuleTestBase): """Test low level functions `os.open()`, `os.read()` and `os.write()`.""" def setUp(self): super(FakeOsModuleLowLevelFileOpTest, self).setUp() - if sys.version_info < (3, ): + if sys.version_info < (3,): self.operation_error = IOError else: self.operation_error = io.UnsupportedOperation @@ -2792,7 +2878,7 @@ def testRealpathVsAbspath(self): self.assertEqual('!george!washington!bridge', self.os.path.realpath('bridge')) - @unittest.skipIf(TestCase.is_windows and sys.version_info < (3,2), + @unittest.skipIf(TestCase.is_windows and sys.version_info < (3, 2), 'No Windows support before 3.2') def testSamefile(self): file_path1 = '!foo!bar!baz' @@ -3074,7 +3160,6 @@ def setUp(self): self.open = self.file self.os = fake_filesystem.FakeOsModule(self.filesystem) self.orig_time = time.time - time.time = _DummyTime(100, 10) def tearDown(self): time.time = self.orig_time @@ -3452,6 +3537,7 @@ def testReadWithRplus(self): def testOpenStCtime(self): # set up + time.time = _DummyTime(100, 10) file_path = 'some_file' self.assertFalse(self.filesystem.Exists(file_path)) # tests @@ -4094,11 +4180,10 @@ def testRelativeLinksWorkAfterChdir(self): self.assertEqual(final_target, self.filesystem.ResolvePath('!foo!bar')) - os_module = fake_filesystem.FakeOsModule(self.filesystem) - self.assertTrue(os_module.path.islink('!foo!bar')) - os_module.chdir('!foo') - self.assertEqual('!foo', os_module.getcwd()) - self.assertTrue(os_module.path.islink('bar')) + self.assertTrue(self.os.path.islink('!foo!bar')) + self.os.chdir('!foo') + self.assertEqual('!foo', self.os.getcwd()) + self.assertTrue(self.os.path.islink('bar')) self.assertEqual('!foo!baz!bip', self.filesystem.ResolvePath('bar')) @@ -4135,13 +4220,25 @@ def testChdirThroughRelativeLink(self): self.filesystem.CreateLink('!x!foo!bar', '..!bar') self.assertEqual('!x!bar', self.filesystem.ResolvePath('!x!foo!bar')) - os_module = fake_filesystem.FakeOsModule(self.filesystem) - os_module.chdir('!x!foo') - self.assertEqual('!x!foo', os_module.getcwd()) + self.os.chdir('!x!foo') + self.assertEqual('!x!foo', self.os.getcwd()) self.assertEqual('!x!bar', self.filesystem.ResolvePath('bar')) - os_module.chdir('bar') - self.assertEqual('!x!bar', os_module.getcwd()) + self.os.chdir('bar') + self.assertEqual('!x!bar', self.os.getcwd()) + + @unittest.skipIf(sys.version_info < (3, 3), + 'file descriptor as path new in Python 3.3') + def testChdirUsesOpenFdAsPath(self): + self.filesystem.is_windows_fs = False + self.assertRaisesOSError(errno.EBADF, self.os.chdir, 5) + dir_path = '!foo!bar' + self.filesystem.CreateDirectory(dir_path) + + path_des = self.os.open(dir_path, os.O_RDONLY) + self.os.chdir(path_des) + self.os.close(path_des) + self.assertEqual(dir_path, self.os.getcwd()) @unittest.skipIf(TestCase.is_windows and sys.version_info < (3, 3), 'Links are not supported under Windows before Python 3.3') diff --git a/pyfakefs/fake_filesystem.py b/pyfakefs/fake_filesystem.py index 13c14b99..e2a67173 100644 --- a/pyfakefs/fake_filesystem.py +++ b/pyfakefs/fake_filesystem.py @@ -994,7 +994,7 @@ def GetStat(self, entry_path, follow_symlinks=True): """ # stat should return the tuple representing return value of os.stat try: - file_object = self.ResolveObject(entry_path, follow_symlinks) + file_object = self.ResolveObject(entry_path, follow_symlinks, allow_fd=True) return file_object.stat_result.copy() except IOError as io_error: raise OSError(io_error.errno, io_error.strerror, entry_path) @@ -1010,7 +1010,7 @@ def ChangeMode(self, path, mode, follow_symlinks=True): instead of the linked object. """ try: - file_object = self.ResolveObject(path, follow_symlinks) + file_object = self.ResolveObject(path, follow_symlinks, allow_fd=True) except IOError as io_error: if io_error.errno == errno.ENOENT: raise OSError(errno.ENOENT, @@ -1052,7 +1052,7 @@ def UpdateTime(self, path, times=None, ns=None, follow_symlinks=True): raise TypeError("utime: 'ns' must be a tuple of two ints") try: - file_object = self.ResolveObject(path, follow_symlinks) + file_object = self.ResolveObject(path, follow_symlinks, allow_fd=True) except IOError as io_error: if io_error.errno == errno.ENOENT: raise OSError(errno.ENOENT, @@ -1515,7 +1515,7 @@ def Exists(self, file_path): return False return True - def ResolvePath(self, file_path): + def ResolvePath(self, file_path, allow_fd=False): """Follow a path, resolving symlinks. ResolvePath traverses the filesystem along the specified file path, @@ -1541,14 +1541,15 @@ def ResolvePath(self, file_path): /a/b/c/d/e => /a/b2/d/e Args: - file_path: path to examine. + file_path: path to examine. + allow_fd: If `True`, `file_path` may be open file descriptor Returns: - resolved_path (string) or None. + resolved_path (string) or None. Raises: - TypeError: if file_path is None. - IOError: if file_path is '' or a part of the path doesn't exist. + TypeError: if file_path is None. + IOError: if file_path is '' or a part of the path doesn't exist. """ def _ComponentsToPath(component_folders): @@ -1604,6 +1605,9 @@ def _FollowLink(link_path_components, link): # Don't call self.NormalizePath(), as we don't want to prepend self.cwd. return self.CollapsePath(link_path) + if allow_fd and sys.version_info >= (3, 3) and isinstance(file_path, int): + return self.GetOpenFile(file_path).GetObject().GetPath() + if sys.version_info >= (3, 6): file_path = os.fspath(file_path) if file_path is None: @@ -1709,12 +1713,14 @@ def GetObject(self, file_path): file_path = self.NormalizePath(self.NormalizeCase(file_path)) return self.GetObjectFromNormalizedPath(file_path) - def ResolveObject(self, file_path, follow_symlinks=True): + def ResolveObject(self, file_path, follow_symlinks=True, allow_fd=False): """Search for the specified filesystem object, resolving all links. Args: - file_path: specifies target FakeFile object to retrieve. - follow_symlinks: if False, the link itself is resolved, otherwise the object linked to. + file_path: Specifies target FakeFile object to retrieve. + follow_symlinks: If `False`, the link itself is resolved, + otherwise the object linked to. + allow_fd: If `True`, `file_path` may be open file descriptor Returns: the FakeFile object corresponding to file_path. @@ -1722,6 +1728,9 @@ def ResolveObject(self, file_path, follow_symlinks=True): Raises: IOError: if the object is not found. """ + if allow_fd and sys.version_info >= (3, 3) and isinstance(file_path, int): + return self.GetOpenFile(file_path).GetObject() + if follow_symlinks: if sys.version_info >= (3, 6): file_path = os.fspath(file_path) @@ -2439,7 +2448,7 @@ def ListDir(self, target_directory): Raises: OSError: if the target is not a directory. """ - target_directory = self.ResolvePath(target_directory) + target_directory = self.ResolvePath(target_directory, allow_fd=True) directory = self.ConfirmDir(target_directory) directory_contents = directory.contents return list(directory_contents.keys()) @@ -3207,7 +3216,7 @@ def chdir(self, target_directory): OSError: if user lacks permission to enter the argument directory or if the target is not a directory """ - target_directory = self.filesystem.ResolvePath(target_directory) + target_directory = self.filesystem.ResolvePath(target_directory, allow_fd=True) self.filesystem.ConfirmDir(target_directory) directory = self.filesystem.GetObject(target_directory) # A full implementation would check permissions all the way up the tree. @@ -3611,12 +3620,13 @@ def chown(self, path, uid, gid, follow_symlinks=None): elif sys.version_info < (3, 3): raise TypeError("chown() got an unexpected keyword argument 'follow_symlinks'") try: - file_object = self.filesystem.ResolveObject(path, follow_symlinks) + file_object = self.filesystem.ResolveObject(path, follow_symlinks, allow_fd=True) except IOError as io_error: if io_error.errno == errno.ENOENT: raise OSError(errno.ENOENT, 'No such file or directory in fake filesystem', path) + raise if not ((isinstance(uid, int) or uid is None) and (isinstance(gid, int) or gid is None)): raise TypeError("An integer is required")