Skip to content

Commit

Permalink
Error handling fixes
Browse files Browse the repository at this point in the history
- raise NotADirectoryError where needed so it raised by os.readlink
- made pathlib resolve() better conform to real implementation
- added strict argument to resolve in Python 3.6
- see #139
- made some fake_pathlib functions available only in 3.5 as in the real implementation
  • Loading branch information
mrbean-bremen authored Nov 30, 2016
1 parent e3449bf commit 6f9a0eb
Show file tree
Hide file tree
Showing 4 changed files with 245 additions and 74 deletions.
80 changes: 54 additions & 26 deletions fake_filesystem_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ class TestCase(unittest.TestCase):
def assertModeEqual(self, expected, actual):
return self.assertEqual(stat.S_IMODE(expected), stat.S_IMODE(actual))

def assertRaisesIOError(self, subtype, expression):
try:
expression()
except (IOError, OSError) as exc:
self.assertEqual(exc.errno, subtype)


class FakeDirectoryUnitTest(TestCase):
def setUp(self):
Expand Down Expand Up @@ -80,10 +86,10 @@ def testRemoveEntry(self):
self.assertRaises(KeyError, self.fake_dir.GetEntry, 'foobar')

def testShouldThrowIfSetSizeIsNotInteger(self):
self.assertRaises(IOError, self.fake_file.SetSize, 0.1)
self.assertRaisesIOError(errno.ENOSPC, lambda: self.fake_file.SetSize(0.1))

def testShouldThrowIfSetSizeIsNegative(self):
self.assertRaises(IOError, self.fake_file.SetSize, -1)
self.assertRaisesIOError(errno.ENOSPC, lambda: self.fake_file.SetSize(-1))

def testProduceEmptyFileIfSetSizeIsZero(self):
self.fake_file.SetSize(0)
Expand Down Expand Up @@ -137,10 +143,10 @@ def testDirectoryInode(self):

class SetLargeFileSizeTest(FakeDirectoryUnitTest):
def testShouldThrowIfSizeIsNotInteger(self):
self.assertRaises(IOError, self.fake_file.SetLargeFileSize, 0.1)
self.assertRaisesIOError(errno.ENOSPC, lambda: self.fake_file.SetLargeFileSize(0.1))

def testShouldThrowIfSizeIsNegative(self):
self.assertRaises(IOError, self.fake_file.SetLargeFileSize, -1)
self.assertRaisesIOError(errno.ENOSPC, lambda: self.fake_file.SetLargeFileSize(-1))

def testSetsContentNoneIfSizeIsNonNegativeInteger(self):
self.fake_file.SetLargeFileSize(1000000000)
Expand Down Expand Up @@ -266,17 +272,18 @@ def testGetObjectFromRoot(self):
def testGetNonexistentObjectFromRootError(self):
self.filesystem.AddObject(self.root_name, self.fake_file)
self.assertEqual(self.fake_file, self.filesystem.GetObject('foobar'))
self.assertRaises(IOError, self.filesystem.GetObject,
'some_bogus_filename')
self.assertRaisesIOError(errno.ENOENT,
lambda: self.filesystem.GetObject('some_bogus_filename'))

def testRemoveObjectFromRoot(self):
self.filesystem.AddObject(self.root_name, self.fake_file)
self.filesystem.RemoveObject(self.fake_file.name)
self.assertRaises(IOError, self.filesystem.GetObject, self.fake_file.name)
self.assertRaisesIOError(errno.ENOENT,
lambda: self.filesystem.GetObject(self.fake_file.name))

def testRemoveNonexistenObjectFromRootError(self):
self.assertRaises(IOError, self.filesystem.RemoveObject,
'some_bogus_filename')
self.assertRaisesIOError(errno.ENOENT,
lambda: self.filesystem.RemoveObject('some_bogus_filename'))

def testExistsRemovedFile(self):
self.filesystem.AddObject(self.root_name, self.fake_file)
Expand All @@ -292,8 +299,9 @@ def testAddObjectToChild(self):

def testAddObjectToRegularFileError(self):
self.filesystem.AddObject(self.root_name, self.fake_file)
self.assertRaises(IOError, self.filesystem.AddObject,
self.fake_file.name, self.fake_file)
self.assertRaisesIOError(errno.ENOTDIR,
lambda: self.filesystem.AddObject(self.fake_file.name,
self.fake_file))

def testExistsFileAddedToChild(self):
self.filesystem.AddObject(self.root_name, self.fake_child)
Expand Down Expand Up @@ -485,13 +493,15 @@ def testLresolveObject(self):
self.assertEqual(target_path, obj.contents)

def testDirectoryAccessOnFile(self):
self.filesystem.supports_drive_letter = False
self.filesystem.CreateFile('not_a_dir')
self.assertRaises(IOError, self.filesystem.ResolveObject, 'not_a_dir/foo')
self.assertRaises(IOError, self.filesystem.ResolveObject,
'not_a_dir/foo/bar')
self.assertRaises(IOError, self.filesystem.LResolveObject, 'not_a_dir/foo')
self.assertRaises(IOError, self.filesystem.LResolveObject,
'not_a_dir/foo/bar')
self.assertRaisesIOError(errno.ENOTDIR,
lambda: self.filesystem.LResolveObject('not_a_dir/foo'))
self.assertRaisesIOError(errno.ENOTDIR,
lambda: self.filesystem.LResolveObject('not_a_dir/foo/bar'))


class CaseInsensitiveFakeFilesystemTest(TestCase):
Expand Down Expand Up @@ -876,8 +886,10 @@ def testStatNoFollowSymlinks(self):
link_path = '%s/link' % directory
self.filesystem.CreateFile(file_path, contents=file_contents)
self.filesystem.CreateLink(link_path, base_name)
self.assertEqual(len(file_contents), self.os.stat(file_path, follow_symlinks=False)[stat.ST_SIZE])
self.assertEqual(len(base_name), self.os.stat(link_path, follow_symlinks=False)[stat.ST_SIZE])
self.assertEqual(len(file_contents),
self.os.stat(file_path, follow_symlinks=False)[stat.ST_SIZE])
self.assertEqual(len(base_name),
self.os.stat(link_path, follow_symlinks=False)[stat.ST_SIZE])

@unittest.skipIf(TestCase.is_windows and sys.version_info < (3, 3),
'Links are not supported under Windows before Python 3.3')
Expand Down Expand Up @@ -920,12 +932,23 @@ def testReadlink(self):
def testReadlinkRaisesIfPathIsNotALink(self):
file_path = 'foo/bar/eleventyone'
self.filesystem.CreateFile(file_path)
self.assertRaises(OSError, self.os.readlink, file_path)
self.assertRaisesIOError(errno.EINVAL, lambda: self.os.readlink(file_path))

@unittest.skipIf(TestCase.is_windows and sys.version_info < (3, 3),
'Links are not supported under Windows before Python 3.3')
def testReadlinkRaisesIfPathHasFile(self):
self.filesystem.supports_drive_letter = False
self.filesystem.CreateFile('/a_file')
file_path = '/a_file/foo'
self.assertRaisesIOError(errno.ENOTDIR, lambda: self.os.readlink(file_path))
file_path = '/a_file/foo/bar'
self.assertRaisesIOError(errno.ENOTDIR, lambda: self.os.readlink(file_path))

@unittest.skipIf(TestCase.is_windows and sys.version_info < (3, 3),
'Links are not supported under Windows before Python 3.3')
def testReadlinkRaisesIfPathDoesNotExist(self):
self.assertRaises(OSError, self.os.readlink, '/this/path/does/not/exist')
self.assertRaisesIOError(errno.ENOENT,
lambda: self.os.readlink('/this/path/does/not/exist'))

@unittest.skipIf(TestCase.is_windows and sys.version_info < (3, 3),
'Links are not supported under Windows before Python 3.3')
Expand Down Expand Up @@ -1897,7 +1920,6 @@ def assertWalkResults(self, expected, result):
self.assertEqual(expected_entry[1], sorted(entry[1]))
self.assertEqual(expected_entry[2], sorted(entry[2]))


def testWalkTopDown(self):
"""Walk down ordering is correct."""
self.filesystem.CreateFile('foo/1.txt')
Expand Down Expand Up @@ -1926,7 +1948,7 @@ def testWalkBottomUp(self):
('foo', ['bar1', 'bar2'], ['4.txt']),
]
self.assertWalkResults(expected,
[step for step in self.os.walk('foo', topdown=False)])
[step for step in self.os.walk('foo', topdown=False)])

def testWalkRaisesIfNonExistent(self):
"""Raises an exception when attempting to walk non-existent directory."""
Expand Down Expand Up @@ -3420,7 +3442,8 @@ def testAppendExistingFile(self):
self.assertEqual(contents, result)

def testOpenWithWplus(self):
self.filesystem.CreateFile(self.file_path, contents=u'старое содержание', encoding='cyrillic')
self.filesystem.CreateFile(self.file_path, contents=u'старое содержание',
encoding='cyrillic')
fake_file = self.open(self.file_path, 'r', encoding='cyrillic')
self.assertEqual(u'старое содержание', fake_file.read())
fake_file.close()
Expand Down Expand Up @@ -3455,7 +3478,8 @@ def testOpenWithAppendFlag(self):
self.assertEqual(contents + additional_contents, result)

def testAppendWithAplus(self):
self.filesystem.CreateFile(self.file_path, contents=u'старое содержание', encoding='cyrillic')
self.filesystem.CreateFile(self.file_path, contents=u'старое содержание',
encoding='cyrillic')
fake_file = self.open(self.file_path, 'r', encoding='cyrillic')
fake_file.close()

Expand All @@ -3469,7 +3493,8 @@ def testAppendWithAplus(self):
fake_file.close()

def testReadWithRplus(self):
self.filesystem.CreateFile(self.file_path, contents=u'старое содержание здесь', encoding='cyrillic')
self.filesystem.CreateFile(self.file_path, contents=u'старое содержание здесь',
encoding='cyrillic')
fake_file = self.open(self.file_path, 'r', encoding='cyrillic')
fake_file.close()

Expand Down Expand Up @@ -4039,7 +4064,8 @@ def setUp(self):
self.os = fake_filesystem.FakeOsModule(self.filesystem)

def testFileSystemSizeAfterLargeFileCreation(self):
filesystem = fake_filesystem.FakeFilesystem(path_separator='/', total_size=1024 * 1024 * 1024 * 100)
filesystem = fake_filesystem.FakeFilesystem(path_separator='/',
total_size=1024 * 1024 * 1024 * 100)
filesystem.CreateFile('/foo/baz', st_size=1024 * 1024 * 1024 * 10)
self.assertEqual((1024 * 1024 * 1024 * 100,
1024 * 1024 * 1024 * 10,
Expand Down Expand Up @@ -4141,7 +4167,8 @@ def testThatTheSizeOfCorrectMountPointIsUsed(self):
self.filesystem.AddMountPoint('/mount_limited', total_size=50)
self.filesystem.AddMountPoint('/mount_unlimited')

self.assertRaises(IOError, lambda: self.filesystem.CreateFile('/mount_limited/foo', st_size=60))
self.assertRaises(IOError,
lambda: self.filesystem.CreateFile('/mount_limited/foo', st_size=60))
self.assertRaises(IOError, lambda: self.filesystem.CreateFile('/bar', st_size=110))
try:
self.filesystem.CreateFile('/foo', st_size=60)
Expand Down Expand Up @@ -4172,7 +4199,8 @@ def testSetLargerDiskSize(self):
def testSetSmallerDiskSize(self):
self.filesystem.AddMountPoint('/mount1', total_size=200)
self.filesystem.CreateFile('/mount1/foo', st_size=100)
self.assertRaises(IOError, lambda: self.filesystem.SetDiskUsage(total_size=50, path='/mount1'))
self.assertRaises(IOError,
lambda: self.filesystem.SetDiskUsage(total_size=50, path='/mount1'))
self.filesystem.SetDiskUsage(total_size=150, path='/mount1')
self.assertEqual(50, self.filesystem.GetDiskUsage('/mount1/foo').free)

Expand Down
28 changes: 28 additions & 0 deletions fake_pathlib_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,36 @@ def test_resolve(self):
self.assertEqual(self.path('docs/../setup.py').resolve(),
self.path('/home/antoine/setup.py'))

@unittest.skipIf(sys.version_info >= (3, 6), 'Changed behavior in Python 3.6')
def test_resolve_nonexisting_file(self):
path = self.path('/foo/bar')
self.assertRaises(FileNotFoundError, path.resolve)

@unittest.skipIf(sys.version_info >= (3, 6), 'Changed behavior in Python 3.6')
def test_resolve_file_as_parent_windows(self):
self.filesystem.supports_drive_letter = True
self.filesystem.CreateFile('/a_file')
path = self.path('/a_file/this can not exist')
self.assertRaises(FileNotFoundError, path.resolve)

@unittest.skipIf(sys.version_info >= (3, 6), 'Changed behavior in Python 3.6')
def test_resolve_file_as_parent_posix(self):
self.filesystem.supports_drive_letter = False
self.filesystem.CreateFile('/a_file')
path = self.path('/a_file/this can not exist')
self.assertRaises(NotADirectoryError, path.resolve)

@unittest.skipIf(sys.version_info < (3, 6), 'Changed behavior in Python 3.6')
def test_resolve_nonexisting_file(self):
path = self.path('/path/to/file/this can not exist')
self.assertTrue(path, path.resolve())
self.assertRaises(FileNotFoundError, path.resolve, strict=True)

def test_cwd(self):
self.filesystem.cwd = '/home/jane'
self.assertEqual(self.path.cwd(), self.path('/home/jane'))

@unittest.skipIf(sys.version_info < (3, 5), 'New in version 3.5')
def test_expanduser(self):
if is_windows:
self.assertEqual(self.path('~').expanduser(),
Expand All @@ -289,6 +315,7 @@ def test_expanduser(self):
self.assertEqual(self.path('~').expanduser(),
self.path(os.environ['HOME']))

@unittest.skipIf(sys.version_info < (3, 5), 'New in version 3.5')
def test_home(self):
if is_windows:
self.assertEqual(self.path.home(),
Expand Down Expand Up @@ -394,6 +421,7 @@ def test_touch_existing(self):
self.assertTrue(file_obj)
self.assertEqual(file_obj.contents, 'test')

@unittest.skipIf(sys.version_info < (3, 5), 'New in version 3.5')
def test_samefile(self):
self.filesystem.CreateFile('/foo/bar.txt')
self.filesystem.CreateFile('/foo/baz.txt')
Expand Down
45 changes: 36 additions & 9 deletions pyfakefs/fake_filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -1331,6 +1331,12 @@ def GetObjectFromNormalizedPath(self, file_path):
try:
for component in path_components:
if not isinstance(target_object, FakeDirectory):
# we use supports_drive_letter instead of checking the OS
# to allow tests on other systems
if not self.supports_drive_letter:
raise IOError(errno.ENOTDIR,
'Not a directory in fake filesystem',
file_path)
raise IOError(errno.ENOENT,
'No such file or directory in fake filesystem',
file_path)
Expand Down Expand Up @@ -1404,6 +1410,9 @@ def LResolveObject(self, path):
parent_obj = self.ResolveObject(parent_directory)
assert parent_obj
if not isinstance(parent_obj, FakeDirectory):
if not self.supports_drive_letter and isinstance(parent_obj, FakeFile):
raise IOError(errno.ENOTDIR,
'The parent object is not a directory', path)
raise IOError(errno.ENOENT,
'No such file or directory in fake filesystem',
path)
Expand Down Expand Up @@ -1683,6 +1692,31 @@ def CreateHardLink(self, old_path, new_path):
self.AddObject(new_parent_directory, old_file)
return old_file

def ReadLink(self, path):
"""Read the target of a symlink.
New in pyfakefs 3.0.
Args:
path: symlink to read the target of.
Returns:
the string representing the path to which the symbolic link points.
Raises:
TypeError: if path is None
OSError: (with errno=ENOENT) if path is not a valid path, or
(with errno=EINVAL) if path is valid, but is not a symlink.
"""
if path is None:
raise TypeError
try:
link_obj = self.LResolveObject(path)
except IOError as exc:
raise OSError(exc.errno, 'Fake path does not exist', path)
if stat.S_IFMT(link_obj.st_mode) != stat.S_IFLNK:
raise OSError(errno.EINVAL, 'Fake filesystem: not a symlink', path)
return link_obj.contents

def MakeDirectory(self, dir_name, mode=PERM_DEF):
"""Create a leaf Fake directory.
New in pyfakefs 3.0.
Expand Down Expand Up @@ -2026,6 +2060,7 @@ class ScanDirIter:
"""Iterator for DirEntry objects returned from `scandir()` function.
New in pyfakefs 3.0.
"""

def __init__(self, filesystem, path):
self.filesystem = filesystem
self.path = self.filesystem.ResolvePath(path)
Expand Down Expand Up @@ -2686,15 +2721,7 @@ def readlink(self, path):
OSError: (with errno=ENOENT) if path is not a valid path, or
(with errno=EINVAL) if path is valid, but is not a symlink.
"""
if path is None:
raise TypeError
try:
link_obj = self.filesystem.LResolveObject(path)
except IOError:
raise OSError(errno.ENOENT, 'Fake os module: path does not exist', path)
if stat.S_IFMT(link_obj.st_mode) != stat.S_IFLNK:
raise OSError(errno.EINVAL, 'Fake os module: not a symlink', path)
return link_obj.contents
return self.filesystem.ReadLink(path)

def stat(self, entry_path, follow_symlinks=None):
"""Return the os.stat-like tuple for the FakeFile object of entry_path.
Expand Down
Loading

0 comments on commit 6f9a0eb

Please sign in to comment.