Skip to content

Commit 45a470a

Browse files
authored
Fix add_real_file() MacOS atime, prepare for release (#190)
* Update atime when CopyRealFile() accesses real file This is a platform-specific issue for MacOS and BSD * Deprecate CopyRealFile(), prepare for Release 3.2
1 parent a3cf7be commit 45a470a

File tree

5 files changed

+123
-120
lines changed

5 files changed

+123
-120
lines changed

CHANGES.md

Lines changed: 45 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,31 @@
11
# pyfakefs Release Notes
22
The release versions are PyPi releases.
33

4-
## Version 3.2 (as yet unreleased)
4+
## Version 3.2
55

66
#### New Features
7-
* `io.open`, `os.open`: support for `errors` argument
8-
* Added new methods to `fake_filesystem.FakeFilesystem` that make real files
9-
and directories appear within the fake file system:
10-
`add_real_file()`, `add_real_directory()` and `add_real_paths()`.
11-
File contents are read from the real file system only when needed ([#170](../../issues/170)).
12-
* Added the CHANGES.md release notes to the release manifest
7+
* The `errors` argument is supported for `io.open()` and `os.open()`
8+
* New methods `add_real_file()`, `add_real_directory()` and `add_real_paths()`
9+
make real files and directories appear within the fake file system.
10+
File contents are read from the real file system only as needed ([#170](../../issues/170)).
11+
See `example_test.py` for a usage example.
12+
* Deprecated `TestCase.copyRealFile()` in favor of `add_real_file()`.
13+
`copyRealFile()` remains only for backward compatability. Also, some
14+
less-popular argument combinations have been disallowed.
15+
* Added this file you are reading, `CHANGES.md`, to the release manifest
1316

1417
#### Infrastructure
15-
* `mox3` is no longer required - the relevant part has been integrated into pyfakefs ([#182](../../issues/182))
18+
* The `mox3` package is no longer a prerequisite--the portion required by pyfakefs
19+
has been integrated into pyfakefs ([#182](../../issues/182))
1620

1721
#### Fixes
18-
* Corrected handling of byte/unicode paths in several functions ([#187](../../issues/187))
19-
* `FakeShutilModule.rmtree` failed for directory ending with path separator ([#177](../../issues/177))
20-
* Case incorrectly handled for added Windows drives
22+
* Corrected the handling of byte/unicode paths in several functions ([#187](../../issues/187))
23+
* `FakeShutilModule.rmtree()` failed for directories ending with path separator ([#177](../../issues/177))
24+
* Case was incorrectly handled for added Windows drives
2125
* `pathlib.glob()` incorrectly handled case under MacOS ([#167](../../issues/167))
2226
* tox support was broken ([#163](../../issues/163))
23-
* Rename that only changes case was not possible under Windows ([#160](../../issues/160))
27+
* On Windows it was not possible to rename a file when only the case of the file
28+
name changed ([#160](../../issues/160))
2429

2530
## [Version 3.1](https://pypi.python.org/pypi/pyfakefs/3.1)
2631

@@ -37,20 +42,20 @@ The release versions are PyPi releases.
3742
## [Version 3.0](https://pypi.python.org/pypi/pyfakefs/3.0)
3843

3944
#### New Features
40-
* support for path-like objects as arguments in fake `os`
45+
* Support for path-like objects as arguments in fake `os`
4146
and `os.path` modules (Python >= 3.6)
42-
* some changes to make pyfakefs work with Python 3.6
43-
* added fake `pathlib` module (Python >= 3.4) ([#29](../../issues/29))
44-
* support for `os.replace` (Python >= 3.3)
47+
* Some changes to make pyfakefs work with Python 3.6
48+
* Added fake `pathlib` module (Python >= 3.4) ([#29](../../issues/29))
49+
* Support for `os.replace` (Python >= 3.3)
4550
* `os.access`, `os.chmod`, `os.chown`, `os.stat`, `os.utime`:
4651
support for `follow_symlinks` argument (Python >= 3.3)
47-
* support for `os.scandir` (Python >= 3.5) ([#119](../../issues/119))
48-
* option to not fake modules named `path` ([#53](../../issues/53))
52+
* Support for `os.scandir` (Python >= 3.5) ([#119](../../issues/119))
53+
* Option to not fake modules named `path` ([#53](../../issues/53))
4954
* `glob.glob`, `glob.iglob`: support for `recursive` argument (Python >= 3.5) ([#116](../../issues/116))
50-
* support for `glob.iglob` ([#59](../../issues/59))
55+
* Support for `glob.iglob` ([#59](../../issues/59))
5156

5257
#### Infrastructure
53-
* added [auto-generated documentation](http://jmcgeheeiv.github.io/pyfakefs/)
58+
* Added [auto-generated documentation](http://jmcgeheeiv.github.io/pyfakefs/)
5459

5560
#### Fixes
5661
* `shutil.move` incorrectly moves directories ([#145](../../issues/145))
@@ -64,57 +69,55 @@ The release versions are PyPi releases.
6469
#### New Features
6570
* `io.open`, `os.open`: support for `encoding` argument ([#120](../../issues/120))
6671
* `os.makedirs`: support for `exist_ok` argument (Python >= 3.2) ([#98](../../issues/98))
67-
* support for fake `io.open()` ([#70](../../issues/70))
68-
* support for mount points ([#25](../../issues/25))
69-
* support for hard links ([#75](../../issues/75))
70-
* support for float times (mtime, ctime)
72+
* Support for fake `io.open()` ([#70](../../issues/70))
73+
* Support for mount points ([#25](../../issues/25))
74+
* Support for hard links ([#75](../../issues/75))
75+
* Support for float times (mtime, ctime)
7176
* Windows support:
7277
* support for alternative path separator
7378
* support for case-insensitive filesystems ([#69](../../issues/69))
7479
* support for drive letters and UNC paths
75-
* support for filesystem size ([#86](../../issues/86))
80+
* Support for filesystem size ([#86](../../issues/86))
7681
* `shutil.rmtree`: support for `ignore_errors` and `onerror` arguments ([#72](../../issues/72))
77-
* support for `os.fsync()` and `os.fdatasync()` ([#73](../../issues/73))
82+
* Support for `os.fsync()` and `os.fdatasync()` ([#73](../../issues/73))
7883
* `os.walk`: Support for `followlinks` argument
7984

8085
#### Fixes
8186
* `shutil` functions like `make_archive` do not work with pyfakefs ([#104](../../issues/104))
82-
* file permissions on deletion not correctly handled ([#27](../../issues/27))
87+
* File permissions on deletion not correctly handled ([#27](../../issues/27))
8388
* `shutil.copy` error with bytes contents ([#105](../../issues/105))
8489
* mtime and ctime not updated on content changes
8590

8691
## [Version 2.7](https://pypi.python.org/pypi/pyfakefs/2.7)
8792

8893
#### Infrastructure
89-
* moved repository from GoogleCode to GitHub, merging 3 projects
90-
* added continuous integration testing with Travis CI
91-
* added usage documentation in project wiki
92-
* better support for pypi releases
94+
* Moved repository from GoogleCode to GitHub, merging 3 projects
95+
* Added continuous integration testing with Travis CI
96+
* Added usage documentation in project wiki
97+
* Better support for pypi releases
9398

9499
#### New Features
95-
* added direct unit test support in `fake_filesystem_unittest`
100+
* Added direct unit test support in `fake_filesystem_unittest`
96101
(transparently patches all calls to faked implementations)
97-
* added support for doctests
98-
* added support for cygwin
99-
* better support for Python 3
102+
* Added support for doctests
103+
* Added support for cygwin
104+
* Better support for Python 3
100105

101106
#### Fixes
102107
* `os.utime` fails to traverse symlinks ([#49](../../issues/49))
103108
* `chown` incorrectly accepts non-integer uid/gid arguments ([#30](../../issues/30))
104109
* Reading from fake block devices doesn't work ([#24](../../issues/24))
105110
* `fake_tempfile` is using `AddOpenFile` incorrectly ([#23](../../issues/23))
106-
* incorrect behavior of `relpath`, `abspath` and `normpath` on Windows.
107-
* cygwin wasn't treated as Windows ([#37](../../issues/37))
111+
* Incorrect behavior of `relpath`, `abspath` and `normpath` on Windows.
112+
* Cygwin wasn't treated as Windows ([#37](../../issues/37))
108113
* Python 3 `open` in binary mode not working ([#32](../../issues/32))
109114
* `os.remove` doesn't work with relative paths ([#31](../../issues/31))
110115
* `mkstemp` returns no valid file descriptor ([#19](../../issues/19))
111116
* `open` methods lack `IOError` for prohibited operations ([#18](../../issues/18))
112-
* incorrectly resolved relative path ([#3](../../issues/3))
117+
* Incorrectly resolved relative path ([#3](../../issues/3))
113118
* `FakeFileOpen` keyword args do not match the `__builtin__` equivalents ([#5](../../issues/5))
114-
* relative paths not supported ([#16](../../issues/16), [#17](../../issues/17)))
119+
* Relative paths not supported ([#16](../../issues/16), [#17](../../issues/17)))
115120

116121
## Older Versions
117-
As there have been three different projects that have been merged together
118-
for release 2.7, no older release notes are given.
119-
The following versions are still available in PyPi:
122+
There are no release notes for releases 2.6 and below. The following versions are still available on PyPi:
120123
* [1.1](https://pypi.python.org/pypi/pyfakefs/1.1), [1.2](https://pypi.python.org/pypi/pyfakefs/1.2), [2.0](https://pypi.python.org/pypi/pyfakefs/2.0), [2.1](https://pypi.python.org/pypi/pyfakefs/2.1), [2.2](https://pypi.python.org/pypi/pyfakefs/2.2), [2.3](https://pypi.python.org/pypi/pyfakefs/2.3) and [2.4](https://pypi.python.org/pypi/pyfakefs/2.4)

example_test.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,10 @@
2727
and allows you to specify the contents or the size of the file.
2828
"""
2929

30+
import io
3031
import os
3132
import sys
3233

33-
from pyfakefs.fake_filesystem_unittest import REAL_OPEN
34-
3534
if sys.version_info < (2, 7):
3635
import unittest2 as unittest
3736
else:
@@ -65,6 +64,11 @@ def setUp(self):
6564
:py:class:`pyfakefs.mox3_stubout.StubOutForTesting`. Use this if you need to
6665
define additional stubs.
6766
"""
67+
68+
# This is before setUpPyfakefs(), so still using the real file system
69+
with io.open(__file__, 'rb') as f:
70+
self.real_contents = f.read()
71+
6872
self.setUpPyfakefs()
6973

7074
def tearDown(self):
@@ -146,12 +150,11 @@ def test_scandir(self):
146150

147151
def test_real_file_access(self):
148152
"""Test `example.file_contents()` for a real file after adding it using `add_real_file()`."""
149-
real_file = __file__
150-
with REAL_OPEN(real_file, 'rb') as f:
151-
real_contents = f.read()
152-
self.assertRaises(IOError, example.file_contents, real_file)
153-
self.fs.add_real_file(real_file)
154-
self.assertEqual(example.file_contents(real_file), real_contents)
153+
filename = __file__
154+
with self.assertRaises(IOError):
155+
example.file_contents(filename)
156+
self.fs.add_real_file(filename)
157+
self.assertEqual(example.file_contents(filename), self.real_contents)
155158

156159

157160
if __name__ == "__main__":

fake_filesystem_unittest_test.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -191,22 +191,20 @@ def test_own_path_module(self):
191191

192192
@unittest.skipIf(sys.version_info < (2, 7), "No byte strings in Python 2.6")
193193
class TestCopyOrAddRealFile(TestPyfakefsUnittestBase):
194-
"""Tests the `fake_filesystem_unittest.TestCase.copyRealFile()` method."""
194+
"""Tests the `fake_filesystem_unittest.TestCase.copyRealFile()` method.
195+
Note that `copyRealFile()` is deprecated in favor of `FakeFilesystem.add_real_file()`.
196+
"""
195197
with open(__file__) as f:
196198
real_string_contents = f.read()
197199
with open(__file__, 'rb') as f:
198200
real_byte_contents = f.read()
199-
# It is essential to do os.stat() after opening the real file, not before.
200-
# This is because opening the file on MacOS and BSD updates access time
201-
# st_atime. Windows offers the option to enable this behavior as well.
202201
real_stat = os.stat(__file__)
203202

204203
def testCopyRealFile(self):
205-
'''Copy a real file to the fake file system'''
204+
'''Typical usage of deprecated copyRealFile()'''
206205
# Use this file as the file to be copied to the fake file system
207206
real_file_path = __file__
208-
fake_file_path = 'foo/bar/baz'
209-
fake_file = self.copyRealFile(real_file_path, fake_file_path)
207+
fake_file = self.copyRealFile(real_file_path)
210208

211209
self.assertTrue('class TestCopyRealFile(TestPyfakefsUnittestBase)' in self.real_string_contents,
212210
'Verify real file string contents')
@@ -216,25 +214,29 @@ def testCopyRealFile(self):
216214
# note that real_string_contents may differ to fake_file.contents due to newline conversions in open()
217215
self.assertEqual(fake_file.byte_contents, self.real_byte_contents)
218216

219-
self.assertEqual(fake_file.st_mode, self.real_stat.st_mode)
217+
self.assertEqual(oct(fake_file.st_mode), oct(self.real_stat.st_mode))
220218
self.assertEqual(fake_file.st_size, self.real_stat.st_size)
221219
self.assertEqual(fake_file.st_ctime, self.real_stat.st_ctime)
222-
self.assertEqual(fake_file.st_atime, self.real_stat.st_atime)
220+
self.assertGreaterEqual(fake_file.st_atime, self.real_stat.st_atime)
221+
self.assertLess(fake_file.st_atime, self.real_stat.st_atime + 10)
223222
self.assertEqual(fake_file.st_mtime, self.real_stat.st_mtime)
224223
self.assertEqual(fake_file.st_uid, self.real_stat.st_uid)
225224
self.assertEqual(fake_file.st_gid, self.real_stat.st_gid)
226225

227-
fake_file_path = '/nonexistent/directory/file'
228-
with self.assertRaises(IOError):
229-
self.copyRealFile(real_file_path, fake_file_path,
230-
create_missing_dirs=False)
231-
232-
def testCopyRealFileNoDestination(self):
226+
def testCopyRealFileDeprecatedArguments(self):
227+
'''Deprecated copyRealFile() arguments'''
233228
real_file_path = __file__
234229
self.assertFalse(self.fs.Exists(real_file_path))
235-
self.copyRealFile(real_file_path)
230+
# Specify redundant fake file path
231+
self.copyRealFile(real_file_path, real_file_path)
236232
self.assertTrue(self.fs.Exists(real_file_path))
237233

234+
# Test deprecated argument values
235+
with self.assertRaises(ValueError):
236+
self.copyRealFile(real_file_path, '/different/filename')
237+
with self.assertRaises(ValueError):
238+
self.copyRealFile(real_file_path, create_missing_dirs=False)
239+
238240
def testAddRealFile(self):
239241
'''Add a real file to the fake file system to be read on demand'''
240242

pyfakefs/fake_filesystem.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,8 @@ def byte_contents(self):
458458
self.contents_read = True
459459
with io.open(self.file_path, 'rb') as f:
460460
self._byte_contents = f.read()
461+
# On MacOS and BSD, the above io.open() updates atime on the real file
462+
self.st_atime = os.stat(self.file_path).st_atime
461463
return self._byte_contents
462464

463465
def IsLargeFile(self):
@@ -1772,29 +1774,39 @@ def CreateFile(self, file_path, st_mode=stat.S_IFREG | PERM_DEF_FILE,
17721774
file_path, st_mode, contents, st_size, create_missing_dirs, apply_umask, encoding, errors)
17731775

17741776
def add_real_file(self, file_path, read_only=True):
1775-
"""Create file_path, including all the parent directories along the way, for a file
1776-
existing in the real file system without reading the contents, which will be read on demand.
1777+
"""Create file_path, including all the parent directories along the way, for an existing
1778+
real file. The contents of the real file are read only on demand.
17771779
New in pyfakefs 3.2.
17781780
17791781
Args:
1780-
file_path: path to the existing file.
1781-
read_only: if set, the file is treated as read-only, e.g. a write access raises an exception;
1782-
otherwise, writing to the file changes the fake file only as usually.
1782+
file_path: Path to an existing file in the real file system
1783+
read_only: If `True` (the default), writing to the fake file
1784+
raises an exception. Otherwise, writing to the file changes
1785+
the fake file only.
17831786
17841787
Returns:
17851788
the newly created FakeFile object.
17861789
17871790
Raises:
17881791
OSError: if the file does not exist in the real file system.
17891792
IOError: if the file already exists in the fake file system.
1793+
1794+
.. note:: On MacOS and BSD, accessing the fake file's contents will update \
1795+
both the real and fake files' `atime.` (access time). In this \
1796+
particular case, `add_real_file()` violates the rule that `pyfakefs` \
1797+
must not modify the real file system. \
1798+
\
1799+
Further, Windows offers the option to enable atime, and older \
1800+
versions of Linux may also modify atime.
17901801
"""
17911802
return self._CreateFile(file_path,
17921803
read_from_real_fs=True,
17931804
read_only=read_only)
17941805

17951806
def add_real_directory(self, dir_path, read_only=True, lazy_read=True):
1796-
"""Create fake directory for the existing directory at path, and entries for all contained
1797-
files in the real file system.
1807+
"""Create a fake directory corresponding to the real directory at the specified
1808+
path. Add entries in the fake directory corresponding to the entries in the
1809+
real directory.
17981810
New in pyfakefs 3.2.
17991811
18001812
Args:
@@ -1835,8 +1847,9 @@ def add_real_directory(self, dir_path, read_only=True, lazy_read=True):
18351847
return new_dir
18361848

18371849
def add_real_paths(self, path_list, read_only=True, lazy_dir_read=True):
1838-
"""Convenience method to add several files and directories from the real file system
1839-
in the fake file system. See `add_real_file()` and `add_real_directory()`.
1850+
"""This convenience method adds multiple files and/or directories from the
1851+
real file system to the fake file system. See `add_real_file()` and
1852+
`add_real_directory()`.
18401853
New in pyfakefs 3.2.
18411854
18421855
Args:
@@ -1861,7 +1874,8 @@ def _CreateFile(self, file_path, st_mode=stat.S_IFREG | PERM_DEF_FILE,
18611874
contents='', st_size=None, create_missing_dirs=True,
18621875
apply_umask=False, encoding=None, errors=None,
18631876
read_from_real_fs=False, read_only=True):
1864-
"""Internal fake file creation, supports both normal fake files and fake files from real files.
1877+
"""Internal fake file creator that supports both normal fake files and fake
1878+
files based on real files.
18651879
18661880
Args:
18671881
file_path: path to the file to create.

0 commit comments

Comments
 (0)