Repository: jmcgeheeiv/pyfakefs (python)

Issue:
Behavior difference for os.path.split
**Describe the bug**
On windows, the function python version of `os.path.split` does not convert path separators, while the pyfakefs version does. Is this intentional or is this a bug?

**How To Reproduce**

```python
import os
import unittest
from pyfakefs.fake_filesystem_unittest import TestCase


class TestPathSplit(TestCase):

    def test_split(self):
        head_before, tail_before = os.path.split('/')
        self.setUpPyfakefs()
        head_after, tail_after = os.path.split('/')
        print("Before setUpPyfakefs head=%s, tail=%s" % (head_before, tail_before))
        print("After  setUpPyfakefs head=%s, tail=%s" % (head_after, tail_after))
        self.assertEqual(head_before, head_after)

if __name__ == '__main__':
    unittest.main()
```

```bash
>test.py          
Before setUpPyfakefs head=/, tail=
After  setUpPyfakefs head=\, tail=
F
======================================================================
FAIL: test_split (__main__.TestPathSplit)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\Russ\Desktop\backup-tool\bug-report\test.py", line 14, in test_split
    self.assertEqual(head_before, head_after)
AssertionError: '/' != '\\'
- /
+ \
```

**Your environment**

```bash
>python -c "import platform; print(platform.platform())"
Windows-10-10.0.19041-SP0

>python -c "import sys; print('Python', sys.version)"
Python 3.9.1 (tags/v3.9.1:1e5d33e, Dec  7 2020, 17:08:21) [MSC v.1927 64 bit (AMD64)]

>python -c "from pyfakefs.fake_filesystem import __version__; print('pyfakefs', __version__)"
pyfakefs 4.5.0
```


Pull request:
Fixed handling of alternative path separator in some functions
- affects os.path.split, os.path.splitdrive and glob.glob
- fixes #632

Patch:
diff --git a/CHANGES.md b/CHANGES.md
index c9a2f3d..83ce724 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 19e30d1..f80de28 100644
--- a/pyfakefs/fake_filesystem.py
+++ b/pyfakefs/fake_filesystem.py
@@ -1587,31 +1587,18 @@ class FakeFilesystem:
             (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 @@ class FakeFilesystem:
         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 @@ class FakeFilesystem:
         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


Test patch:
diff --git a/pyfakefs/tests/example.py b/pyfakefs/tests/example.py
index 5793cdd..09d7a3d 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 b44a695..91225d0 100644
--- a/pyfakefs/tests/example_test.py
+++ b/pyfakefs/tests/example_test.py
@@ -113,7 +113,7 @@ class TestExample(fake_filesystem_unittest.TestCase):  # pylint: disable=R0904
         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 87ccbe1..7432fa8 100644
--- a/pyfakefs/tests/fake_filesystem_glob_test.py
+++ b/pyfakefs/tests/fake_filesystem_glob_test.py
@@ -35,7 +35,7 @@ class FakeGlobUnitTest(fake_filesystem_unittest.TestCase):
         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 @@ class FakeGlobUnitTest(fake_filesystem_unittest.TestCase):
         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 @@ class FakeGlobUnitTest(fake_filesystem_unittest.TestCase):
         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 bb587ad..dfaff5f 100644
--- a/pyfakefs/tests/fake_filesystem_test.py
+++ b/pyfakefs/tests/fake_filesystem_test.py
@@ -857,7 +857,7 @@ class FakePathModuleTest(TestCase):
         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 @@ class SplitPathTest(PathManipulationTestBase):
         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 @@ class AlternativePathSeparatorTest(TestCase):
 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 @@ class DriveLetterSupportTest(TestCase):
                          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 @@ class DriveLetterSupportTest(TestCase):
         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 @@ class DriveLetterSupportTest(TestCase):
         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 @@ class DriveLetterSupportTest(TestCase):
         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 @@ class DriveLetterSupportTest(TestCase):
         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 @@ class DriveLetterSupportTest(TestCase):
         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 @@ class DiskSpaceTest(TestCase):
         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 6b24332..f4da663 100644
--- a/pyfakefs/tests/fake_filesystem_unittest_test.py
+++ b/pyfakefs/tests/fake_filesystem_unittest_test.py
@@ -97,8 +97,8 @@ class TestPyfakefsUnittest(TestPyfakefsUnittestBase):  # pylint: disable=R0904
         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 @@ class TestPyfakefsUnittest(TestPyfakefsUnittestBase):  # pylint: disable=R0904
         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 @@ class TestPyfakefsUnittest(TestPyfakefsUnittestBase):  # pylint: disable=R0904
     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"""