|
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 | TESTFN = support.TESTFN |
@@ -1767,6 +1768,35 @@ def test_mkdir_no_parents_file(self): |
1767 | 1768 | p.mkdir(exist_ok=True) |
1768 | 1769 | self.assertEqual(cm.exception.errno, errno.EEXIST) |
1769 | 1770 |
|
| 1771 | + def test_mkdir_concurrent_parent_creation(self): |
| 1772 | + for pattern_num in range(32): |
| 1773 | + p = self.cls(BASE, 'dirCPC%d' % pattern_num) |
| 1774 | + self.assertFalse(p.exists()) |
| 1775 | + |
| 1776 | + def my_mkdir(path, mode=0o777): |
| 1777 | + path = str(path) |
| 1778 | + # Emulate another process that would create the directory |
| 1779 | + # just before we try to create it ourselves. We do it |
| 1780 | + # in all possible pattern combinations, assuming that this |
| 1781 | + # function is called at most 5 times (dirCPC/dir1/dir2, |
| 1782 | + # dirCPC/dir1, dirCPC, dirCPC/dir1, dirCPC/dir1/dir2). |
| 1783 | + if pattern.pop(): |
| 1784 | + os.mkdir(path, mode) # from another process |
| 1785 | + concurrently_created.add(path) |
| 1786 | + os.mkdir(path, mode) # our real call |
| 1787 | + |
| 1788 | + pattern = [bool(pattern_num & (1 << n)) for n in range(5)] |
| 1789 | + concurrently_created = set() |
| 1790 | + p12 = p / 'dir1' / 'dir2' |
| 1791 | + try: |
| 1792 | + with mock.patch("pathlib._normal_accessor.mkdir", my_mkdir): |
| 1793 | + p12.mkdir(parents=True, exist_ok=False) |
| 1794 | + except FileExistsError: |
| 1795 | + self.assertIn(str(p12), concurrently_created) |
| 1796 | + else: |
| 1797 | + self.assertNotIn(str(p12), concurrently_created) |
| 1798 | + self.assertTrue(p.exists()) |
| 1799 | + |
1770 | 1800 | @with_symlinks |
1771 | 1801 | def test_symlink_to(self): |
1772 | 1802 | P = self.cls(BASE) |
|
0 commit comments