diff --git a/CHANGES.md b/CHANGES.md index c9a2f3d4..83ce7247 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,11 @@ The released versions correspond to PyPi releases. ## Version 4.6.0 (as yet unreleased) +### Fixes +* fixed handling of alternative path separator in `os.path.split`, + `os.path.splitdrive` and `glob.glob` + (see [#632](../../issues/632)) + ## [Version 4.5.1](https://pypi.python.org/pypi/pyfakefs/4.5.1) (2021-08-29) This is a bugfix release. diff --git a/pyfakefs/fake_filesystem.py b/pyfakefs/fake_filesystem.py index 19e30d17..f80de28f 100644 --- a/pyfakefs/fake_filesystem.py +++ b/pyfakefs/fake_filesystem.py @@ -1587,31 +1587,18 @@ def splitpath(self, path: AnyStr) -> Tuple[AnyStr, AnyStr]: (str) A duple (pathname, basename) for which pathname does not end with a slash, and basename does not contain a slash. """ - full_path = path - path = self.normcase(path) - sep: AnyStr = self.get_path_separator(path) + path = make_string_path(path) + sep = self.get_path_separator(path) + alt_sep = self._alternative_path_separator(path) + seps = sep if alt_sep is None else sep + alt_sep drive, path = self.splitdrive(path) - path_components: List[AnyStr] = path.split(sep) - if not path_components: - return drive, matching_string(path, '') - basename = path_components.pop() - if not path_components: - return drive, basename - for component in path_components: - if component: - # The path is not the root; it contains a non-separator - # component. Strip all trailing separators. - while not path_components[-1]: - path_components.pop() - if not path_components: - return drive, basename - return drive + sep.join(path_components), basename - # Root path. Collapse all leading separators. - if drive and not basename: - return full_path, basename - if drive and len(drive) == 2: - drive += sep - return drive or sep, basename + i = len(path) + while i and path[i-1] not in seps: + i -= 1 + head, tail = path[:i], path[i:] # now tail has no slashes + # remove trailing slashes from head, unless it's all slashes + head = head.rstrip(seps) or head + return drive + head, tail def splitdrive(self, path: AnyStr) -> Tuple[AnyStr, AnyStr]: """Splits the path into the drive part and the rest of the path. @@ -1630,17 +1617,17 @@ def splitdrive(self, path: AnyStr) -> Tuple[AnyStr, AnyStr]: path_str = make_string_path(path) if self.is_windows_fs: if len(path_str) >= 2: - path_str = self.normcase(path_str) + norm_str = self.normcase(path_str) sep = self.get_path_separator(path_str) # UNC path_str handling - if (path_str[0:2] == sep * 2) and ( - path_str[2:3] != sep): + if (norm_str[0:2] == sep * 2) and ( + norm_str[2:3] != sep): # UNC path_str handling - splits off the mount point # instead of the drive - sep_index = path_str.find(sep, 2) + sep_index = norm_str.find(sep, 2) if sep_index == -1: return path_str[:0], path_str - sep_index2 = path_str.find(sep, sep_index + 1) + sep_index2 = norm_str.find(sep, sep_index + 1) if sep_index2 == sep_index + 1: return path_str[:0], path_str if sep_index2 == -1: @@ -2430,7 +2417,8 @@ def create_dir(self, directory_path: AnyPath, dir_path = self.make_string_path(directory_path) dir_path = self.absnormpath(dir_path) self._auto_mount_drive_if_needed(dir_path) - if self.exists(dir_path, check_link=True): + if (self.exists(dir_path, check_link=True) and + dir_path not in self.mount_points): self.raise_os_error(errno.EEXIST, dir_path) path_components = self._path_components(dir_path) current_dir = self.root diff --git a/pyfakefs/tests/example.py b/pyfakefs/tests/example.py index 5793cdd0..09d7a3da 100644 --- a/pyfakefs/tests/example.py +++ b/pyfakefs/tests/example.py @@ -121,7 +121,7 @@ def get_glob(glob_path): >>> import sys >>> if sys.platform.startswith('win'): ... # Windows style path - ... file_names == [r'\test\file1.txt', r'\test\file2.txt'] + ... file_names == [r'/test\file1.txt', r'/test\file2.txt'] ... else: ... # UNIX style path ... file_names == ['/test/file1.txt', '/test/file2.txt'] diff --git a/pyfakefs/tests/example_test.py b/pyfakefs/tests/example_test.py index b44a695e..91225d02 100644 --- a/pyfakefs/tests/example_test.py +++ b/pyfakefs/tests/example_test.py @@ -113,7 +113,7 @@ def test_get_globs(self): matching_paths = sorted(example.get_glob('/test/dir1/dir*')) if is_windows: self.assertEqual(matching_paths, - [r'\test\dir1\dir2a', r'\test\dir1\dir2b']) + [r'/test/dir1\dir2a', r'/test/dir1\dir2b']) else: self.assertEqual(matching_paths, ['/test/dir1/dir2a', '/test/dir1/dir2b']) diff --git a/pyfakefs/tests/fake_filesystem_glob_test.py b/pyfakefs/tests/fake_filesystem_glob_test.py index 87ccbe1e..7432fa89 100644 --- a/pyfakefs/tests/fake_filesystem_glob_test.py +++ b/pyfakefs/tests/fake_filesystem_glob_test.py @@ -35,7 +35,7 @@ def test_glob_empty(self): self.assertEqual(glob.glob(''), []) def test_glob_star(self): - basedir = os.sep + 'xyzzy' + basedir = '/xyzzy' self.assertEqual([os.path.join(basedir, 'subdir'), os.path.join(basedir, 'subdir2'), os.path.join(basedir, 'subfile')], @@ -46,7 +46,7 @@ def test_glob_exact(self): self.assertEqual(['/xyzzy/subfile'], glob.glob('/xyzzy/subfile')) def test_glob_question(self): - basedir = os.sep + 'xyzzy' + basedir = '/xyzzy' self.assertEqual([os.path.join(basedir, 'subdir'), os.path.join(basedir, 'subdir2'), os.path.join(basedir, 'subfile')], @@ -60,7 +60,7 @@ def test_non_existent_path(self): self.assertEqual([], glob.glob('nonexistent')) def test_magic_dir(self): - self.assertEqual([os.sep + '[Temp]'], glob.glob('/*emp*')) + self.assertEqual(['/[Temp]'], glob.glob('/*emp*')) def test_glob1(self): self.assertEqual(['[Temp]'], glob.glob1('/', '*Tem*')) diff --git a/pyfakefs/tests/fake_filesystem_test.py b/pyfakefs/tests/fake_filesystem_test.py index bb587ad5..dfaff5ff 100644 --- a/pyfakefs/tests/fake_filesystem_test.py +++ b/pyfakefs/tests/fake_filesystem_test.py @@ -857,7 +857,7 @@ def check_abspath(self, is_windows): self.filesystem.create_file(abspath) self.assertEqual(abspath, self.path.abspath(abspath)) self.assertEqual(abspath, self.path.abspath(filename)) - self.assertEqual(abspath, self.path.abspath(u'..!%s' % filename)) + self.assertEqual(abspath, self.path.abspath('..!%s' % filename)) def test_abspath_windows(self): self.check_abspath(is_windows=True) @@ -1268,9 +1268,9 @@ def test_eliminate_trailing_separators_from_head(self): self.assertEqual(('|a|b', 'c'), self.filesystem.splitpath('|a|b|c')) def test_root_separator_is_not_stripped(self): - self.assertEqual(('|', ''), self.filesystem.splitpath('|||')) + self.assertEqual(('|||', ''), self.filesystem.splitpath('|||')) self.assertEqual(('|', 'a'), self.filesystem.splitpath('|a')) - self.assertEqual(('|', 'a'), self.filesystem.splitpath('|||a')) + self.assertEqual(('|||', 'a'), self.filesystem.splitpath('|||a')) def test_empty_tail_if_path_ends_in_separator(self): self.assertEqual(('a|b', ''), self.filesystem.splitpath('a|b|')) @@ -1401,6 +1401,7 @@ def test_exists_with_mixed_separators(self): class DriveLetterSupportTest(TestCase): def setUp(self): self.filesystem = fake_filesystem.FakeFilesystem(path_separator='!') + self.filesystem.alternative_path_separator = '^' self.filesystem.is_windows_fs = True def test_initial_value(self): @@ -1419,11 +1420,11 @@ def test_collapse_unc_path(self): self.filesystem.normpath('!!foo!bar!!baz!!')) def test_normalize_path_str(self): - self.filesystem.cwd = u'' - self.assertEqual(u'c:!foo!bar', - self.filesystem.absnormpath(u'c:!foo!!bar')) - self.filesystem.cwd = u'c:!foo' - self.assertEqual(u'c:!foo!bar', self.filesystem.absnormpath(u'bar')) + self.filesystem.cwd = '' + self.assertEqual('c:!foo!bar', + self.filesystem.absnormpath('c:!foo!!bar')) + self.filesystem.cwd = 'c:!foo' + self.assertEqual('c:!foo!bar', self.filesystem.absnormpath('bar')) def test_normalize_path_bytes(self): self.filesystem.cwd = b'' @@ -1433,20 +1434,30 @@ def test_normalize_path_bytes(self): self.assertEqual(b'c:!foo!bar', self.filesystem.absnormpath(b'bar')) def test_split_path_str(self): - self.assertEqual((u'c:!foo', u'bar'), - self.filesystem.splitpath(u'c:!foo!bar')) - self.assertEqual((u'c:!', u'foo'), - self.filesystem.splitpath(u'c:!foo')) - self.assertEqual((u'!foo', u'bar'), - self.filesystem.splitpath(u'!foo!bar')) - self.assertEqual((u'!', u'foo'), - self.filesystem.splitpath(u'!foo')) - self.assertEqual((u'c:foo', u'bar'), - self.filesystem.splitpath(u'c:foo!bar')) - self.assertEqual((u'c:', u'foo'), - self.filesystem.splitpath(u'c:foo')) - self.assertEqual((u'foo', u'bar'), - self.filesystem.splitpath(u'foo!bar')) + self.assertEqual(('c:!foo', 'bar'), + self.filesystem.splitpath('c:!foo!bar')) + self.assertEqual(('c:!', 'foo'), + self.filesystem.splitpath('c:!foo')) + self.assertEqual(('!foo', 'bar'), + self.filesystem.splitpath('!foo!bar')) + self.assertEqual(('!', 'foo'), + self.filesystem.splitpath('!foo')) + self.assertEqual(('c:foo', 'bar'), + self.filesystem.splitpath('c:foo!bar')) + self.assertEqual(('c:', 'foo'), + self.filesystem.splitpath('c:foo')) + self.assertEqual(('foo', 'bar'), + self.filesystem.splitpath('foo!bar')) + + def test_split_with_alt_separator(self): + self.assertEqual(('a^b', 'c'), self.filesystem.splitpath('a^b^c')) + self.assertEqual(('a^b!c', 'd'), self.filesystem.splitpath('a^b!c^d')) + self.assertEqual(('a^b!c', 'd'), self.filesystem.splitpath('a^b!c!d')) + self.assertEqual((b'a^b', b'c'), self.filesystem.splitpath(b'a^b^c')) + self.assertEqual((b'a^b!c', b'd'), + self.filesystem.splitpath(b'a^b!c^d')) + self.assertEqual((b'a^b!c', b'd'), + self.filesystem.splitpath(b'a^b!c!d')) def test_split_path_bytes(self): self.assertEqual((b'c:!foo', b'bar'), @@ -1477,14 +1488,14 @@ def test_get_path_components(self): self.assertEqual(['c:'], self.filesystem._path_components('c:')) def test_split_drive_str(self): - self.assertEqual((u'c:', u'!foo!bar'), - self.filesystem.splitdrive(u'c:!foo!bar')) - self.assertEqual((u'', u'!foo!bar'), - self.filesystem.splitdrive(u'!foo!bar')) - self.assertEqual((u'c:', u'foo!bar'), - self.filesystem.splitdrive(u'c:foo!bar')) - self.assertEqual((u'', u'foo!bar'), - self.filesystem.splitdrive(u'foo!bar')) + self.assertEqual(('c:', '!foo!bar'), + self.filesystem.splitdrive('c:!foo!bar')) + self.assertEqual(('', '!foo!bar'), + self.filesystem.splitdrive('!foo!bar')) + self.assertEqual(('c:', 'foo!bar'), + self.filesystem.splitdrive('c:foo!bar')) + self.assertEqual(('', 'foo!bar'), + self.filesystem.splitdrive('foo!bar')) def test_split_drive_bytes(self): self.assertEqual((b'c:', b'!foo!bar'), @@ -1492,6 +1503,20 @@ def test_split_drive_bytes(self): self.assertEqual((b'', b'!foo!bar'), self.filesystem.splitdrive(b'!foo!bar')) + def test_split_drive_alt_sep(self): + self.assertEqual(('c:', '^foo^bar'), + self.filesystem.splitdrive('c:^foo^bar')) + self.assertEqual(('', 'foo^bar'), + self.filesystem.splitdrive('foo^bar')) + self.assertEqual(('', 'foo^bar!baz'), + self.filesystem.splitdrive('foo^bar!baz')) + self.assertEqual((b'c:', b'^foo^bar'), + self.filesystem.splitdrive(b'c:^foo^bar')) + self.assertEqual((b'', b'^foo^bar'), + self.filesystem.splitdrive(b'^foo^bar')) + self.assertEqual((b'', b'^foo^bar!baz'), + self.filesystem.splitdrive(b'^foo^bar!baz')) + def test_split_drive_with_unc_path(self): self.assertEqual(('!!foo!bar', '!baz'), self.filesystem.splitdrive('!!foo!bar!baz')) @@ -1501,6 +1526,15 @@ def test_split_drive_with_unc_path(self): self.assertEqual(('!!foo!bar', '!!'), self.filesystem.splitdrive('!!foo!bar!!')) + def test_split_drive_with_unc_path_alt_sep(self): + self.assertEqual(('^^foo^bar', '!baz'), + self.filesystem.splitdrive('^^foo^bar!baz')) + self.assertEqual(('', '^^foo'), self.filesystem.splitdrive('^^foo')) + self.assertEqual(('', '^^foo^^bar'), + self.filesystem.splitdrive('^^foo^^bar')) + self.assertEqual(('^^foo^bar', '^^'), + self.filesystem.splitdrive('^^foo^bar^^')) + def test_split_path_with_drive(self): self.assertEqual(('d:!foo', 'baz'), self.filesystem.splitpath('d:!foo!baz')) @@ -1513,14 +1547,34 @@ def test_split_path_with_drive(self): self.assertEqual(('c:!!', ''), self.filesystem.splitpath('c:!!')) + def test_split_path_with_drive_alt_sep(self): + self.assertEqual(('d:^foo', 'baz'), + self.filesystem.splitpath('d:^foo^baz')) + self.assertEqual(('d:^foo^baz', ''), + self.filesystem.splitpath('d:^foo^baz^')) + self.assertEqual(('c:', ''), + self.filesystem.splitpath('c:')) + self.assertEqual(('c:^', ''), + self.filesystem.splitpath('c:^')) + self.assertEqual(('c:^^', ''), + self.filesystem.splitpath('c:^^')) + def test_split_path_with_unc_path(self): - self.assertEqual(('!!foo!bar', 'baz'), + self.assertEqual(('!!foo!bar!', 'baz'), self.filesystem.splitpath('!!foo!bar!baz')) self.assertEqual(('!!foo!bar', ''), self.filesystem.splitpath('!!foo!bar')) self.assertEqual(('!!foo!bar!!', ''), self.filesystem.splitpath('!!foo!bar!!')) + def test_split_path_with_unc_path_alt_sep(self): + self.assertEqual(('^^foo^bar^', 'baz'), + self.filesystem.splitpath('^^foo^bar^baz')) + self.assertEqual(('^^foo^bar', ''), + self.filesystem.splitpath('^^foo^bar')) + self.assertEqual(('^^foo^bar^^', ''), + self.filesystem.splitpath('^^foo^bar^^')) + class DiskSpaceTest(TestCase): def setUp(self): @@ -1563,16 +1617,16 @@ def test_file_system_size_after_binary_file_creation(self): self.assertEqual((100, 5, 95), self.filesystem.get_disk_usage()) def test_file_system_size_after_ascii_string_file_creation(self): - self.filesystem.create_file('!foo!bar', contents=u'complicated') + self.filesystem.create_file('!foo!bar', contents='complicated') self.assertEqual((100, 11, 89), self.filesystem.get_disk_usage()) def test_filesystem_size_after_2byte_unicode_file_creation(self): - self.filesystem.create_file('!foo!bar', contents=u'сложно', + self.filesystem.create_file('!foo!bar', contents='сложно', encoding='utf-8') self.assertEqual((100, 12, 88), self.filesystem.get_disk_usage()) def test_filesystem_size_after_3byte_unicode_file_creation(self): - self.filesystem.create_file('!foo!bar', contents=u'複雑', + self.filesystem.create_file('!foo!bar', contents='複雑', encoding='utf-8') self.assertEqual((100, 6, 94), self.filesystem.get_disk_usage()) diff --git a/pyfakefs/tests/fake_filesystem_unittest_test.py b/pyfakefs/tests/fake_filesystem_unittest_test.py index 6b24332c..f4da6631 100644 --- a/pyfakefs/tests/fake_filesystem_unittest_test.py +++ b/pyfakefs/tests/fake_filesystem_unittest_test.py @@ -97,8 +97,8 @@ def test_open(self): self.assertTrue(self.fs.exists('/fake_file.txt')) with open('/fake_file.txt') as f: content = f.read() - self.assertEqual(content, 'This test file was created using the ' - 'open() function.\n') + self.assertEqual('This test file was created using the ' + 'open() function.\n', content) def test_io_open(self): """Fake io module is bound""" @@ -109,8 +109,8 @@ def test_io_open(self): self.assertTrue(self.fs.exists('/fake_file.txt')) with open('/fake_file.txt') as f: content = f.read() - self.assertEqual(content, 'This test file was created using the ' - 'io.open() function.\n') + self.assertEqual('This test file was created using the ' + 'io.open() function.\n', content) def test_os(self): """Fake os module is bound""" @@ -121,22 +121,21 @@ def test_os(self): def test_glob(self): """Fake glob module is bound""" is_windows = sys.platform.startswith('win') - self.assertEqual(glob.glob('/test/dir1/dir*'), - []) + self.assertEqual([], glob.glob('/test/dir1/dir*')) self.fs.create_dir('/test/dir1/dir2a') matching_paths = glob.glob('/test/dir1/dir*') if is_windows: - self.assertEqual(matching_paths, [r'\test\dir1\dir2a']) + self.assertEqual([r'/test/dir1\dir2a'], matching_paths) else: - self.assertEqual(matching_paths, ['/test/dir1/dir2a']) + self.assertEqual(['/test/dir1/dir2a'], matching_paths) self.fs.create_dir('/test/dir1/dir2b') matching_paths = sorted(glob.glob('/test/dir1/dir*')) if is_windows: - self.assertEqual(matching_paths, - [r'\test\dir1\dir2a', r'\test\dir1\dir2b']) + self.assertEqual([r'/test/dir1\dir2a', r'/test/dir1\dir2b'], + matching_paths) else: - self.assertEqual(matching_paths, - ['/test/dir1/dir2a', '/test/dir1/dir2b']) + self.assertEqual(['/test/dir1/dir2a', '/test/dir1/dir2b'], + matching_paths) def test_shutil(self): """Fake shutil module is bound"""