diff --git a/CHANGES.md b/CHANGES.md index 1327efd1..0472b854 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,8 @@ The released versions correspond to PyPi releases. #### Fixes * suppress deprecation warnings while collecting modules (see [#542](../../issues/542)) + * add support for `os.truncate` and `os.ftruncate` + (see [#545](../../issues/545)) ## [Version 4.1.0](https://pypi.python.org/pypi/pyfakefs/4.1.0) diff --git a/pyfakefs/fake_filesystem.py b/pyfakefs/fake_filesystem.py index 770587a4..81ab908b 100644 --- a/pyfakefs/fake_filesystem.py +++ b/pyfakefs/fake_filesystem.py @@ -4136,6 +4136,38 @@ def _path_with_dir_fd(self, path, fct, dir_fd): dir_fd).get_object().path, path) return path + def truncate(self, path, length): + """Truncate the file corresponding to path, so that it is + length bytes in size. If length is larger than the current size, + the file is filled up with zero bytes. + + Args: + path: (str or int) Path to the file, or an integer file + descriptor for the file object. + length: (int) Length of the file after truncating it. + + Raises: + OSError: if the file does not exist or the file descriptor is + invalid. + """ + file_object = self.filesystem.resolve(path, allow_fd=True) + file_object.size = length + + def ftruncate(self, fd, length): + """Truncate the file corresponding to fd, so that it is + length bytes in size. If length is larger than the current size, + the file is filled up with zero bytes. + + Args: + fd: (int) File descriptor for the file object. + length: (int) Maximum length of the file after truncating it. + + Raises: + OSError: if the file descriptor is invalid + """ + file_object = self.filesystem.get_open_file(fd).get_object() + file_object.size = length + def access(self, path, mode, *, dir_fd=None, follow_symlinks=True): """Check if a file exists and has the specified permissions. diff --git a/pyfakefs/tests/fake_os_test.py b/pyfakefs/tests/fake_os_test.py index 1c933d7e..7c2e9ea3 100644 --- a/pyfakefs/tests/fake_os_test.py +++ b/pyfakefs/tests/fake_os_test.py @@ -2708,6 +2708,52 @@ def test_write_to_read_fd(self): self.os.close(read_fd) self.os.close(write_fd) + def test_truncate(self): + file_path = self.make_path('foo', 'bar') + self.create_file(file_path, contents='012345678901234567') + self.os.truncate(file_path, 10) + with self.open(file_path) as f: + self.assertEqual('0123456789', f.read()) + + def test_truncate_non_existing(self): + self.assert_raises_os_error(errno.ENOENT, self.os.truncate, 'foo', 10) + + def test_truncate_to_larger(self): + file_path = self.make_path('foo', 'bar') + self.create_file(file_path, contents='0123456789') + fd = self.os.open(file_path, os.O_RDWR) + self.os.truncate(fd, 20) + self.assertEqual(20, self.os.stat(file_path).st_size) + with self.open(file_path) as f: + self.assertEqual('0123456789' + '\0' * 10, f.read()) + + def test_truncate_with_fd(self): + if os.truncate not in os.supports_fd: + self.skip_real_fs() + self.assert_raises_os_error(errno.EBADF, self.os.ftruncate, 50, 10) + file_path = self.make_path('some_file') + self.create_file(file_path, contents='01234567890123456789') + + fd = self.os.open(file_path, os.O_RDWR) + self.os.truncate(fd, 10) + self.assertEqual(10, self.os.stat(file_path).st_size) + with self.open(file_path) as f: + self.assertEqual('0123456789', f.read()) + + def test_ftruncate(self): + if self.is_pypy: + # not correctly supported + self.skip_real_fs() + self.assert_raises_os_error(errno.EBADF, self.os.ftruncate, 50, 10) + file_path = self.make_path('some_file') + self.create_file(file_path, contents='0123456789012345') + + fd = self.os.open(file_path, os.O_RDWR) + self.os.truncate(fd, 10) + self.assertEqual(10, self.os.stat(file_path).st_size) + with self.open(file_path) as f: + self.assertEqual('0123456789', f.read()) + class RealOsModuleTest(FakeOsModuleTest): def use_real_fs(self): diff --git a/pyfakefs/tests/fake_pathlib_test.py b/pyfakefs/tests/fake_pathlib_test.py index 31634afd..705a3b80 100644 --- a/pyfakefs/tests/fake_pathlib_test.py +++ b/pyfakefs/tests/fake_pathlib_test.py @@ -973,6 +973,13 @@ def test_utime(self): self.assertEqual(1, st.st_atime) self.assertEqual(2, st.st_mtime) + def test_truncate(self): + path = self.make_path('some_file') + self.create_file(path, contents='test_test') + self.os.truncate(self.path(path), length=4) + st = self.os.stat(path) + self.assertEqual(4, st.st_size) + class RealPathlibUsageInOsFunctionsTest(FakePathlibUsageInOsFunctionsTest): def use_real_fs(self):