|
8 | 8 | import stat |
9 | 9 | import tempfile |
10 | 10 | import unittest |
| 11 | +from unittest import mock |
11 | 12 |
|
12 | 13 | from test import support |
13 | 14 | android_not_root = support.android_not_root |
@@ -1801,6 +1802,35 @@ def test_mkdir_no_parents_file(self): |
1801 | 1802 | p.mkdir(exist_ok=True) |
1802 | 1803 | self.assertEqual(cm.exception.errno, errno.EEXIST) |
1803 | 1804 |
|
| 1805 | + def test_mkdir_concurrent_parent_creation(self): |
| 1806 | + for pattern_num in range(32): |
| 1807 | + p = self.cls(BASE, 'dirCPC%d' % pattern_num) |
| 1808 | + self.assertFalse(p.exists()) |
| 1809 | + |
| 1810 | + def my_mkdir(path, mode=0o777): |
| 1811 | + path = str(path) |
| 1812 | + # Emulate another process that would create the directory |
| 1813 | + # just before we try to create it ourselves. We do it |
| 1814 | + # in all possible pattern combinations, assuming that this |
| 1815 | + # function is called at most 5 times (dirCPC/dir1/dir2, |
| 1816 | + # dirCPC/dir1, dirCPC, dirCPC/dir1, dirCPC/dir1/dir2). |
| 1817 | + if pattern.pop(): |
| 1818 | + os.mkdir(path, mode) # from another process |
| 1819 | + concurrently_created.add(path) |
| 1820 | + os.mkdir(path, mode) # our real call |
| 1821 | + |
| 1822 | + pattern = [bool(pattern_num & (1 << n)) for n in range(5)] |
| 1823 | + concurrently_created = set() |
| 1824 | + p12 = p / 'dir1' / 'dir2' |
| 1825 | + try: |
| 1826 | + with mock.patch("pathlib._normal_accessor.mkdir", my_mkdir): |
| 1827 | + p12.mkdir(parents=True, exist_ok=False) |
| 1828 | + except FileExistsError: |
| 1829 | + self.assertIn(str(p12), concurrently_created) |
| 1830 | + else: |
| 1831 | + self.assertNotIn(str(p12), concurrently_created) |
| 1832 | + self.assertTrue(p.exists()) |
| 1833 | + |
1804 | 1834 | @support.skip_unless_symlink |
1805 | 1835 | def test_symlink_to(self): |
1806 | 1836 | P = self.cls(BASE) |
|
0 commit comments