|
3 | 3 | """
|
4 | 4 |
|
5 | 5 | import collections.abc
|
| 6 | +import io |
6 | 7 | import unittest
|
7 | 8 | from test import support
|
8 | 9 | from test.support import import_helper
|
9 | 10 | from test.support import os_helper
|
| 11 | +from test.support import threading_helper |
10 | 12 | from test.support import _2G
|
11 | 13 | import weakref
|
12 | 14 | import pickle
|
13 | 15 | import operator
|
| 16 | +import random |
14 | 17 | import struct
|
15 | 18 | import sys
|
| 19 | +import sysconfig |
| 20 | +import threading |
16 | 21 | import warnings
|
17 | 22 |
|
18 | 23 | import array
|
@@ -1673,5 +1678,266 @@ def test_gh_128961(self):
|
1673 | 1678 | self.assertRaises(StopIteration, next, it)
|
1674 | 1679 |
|
1675 | 1680 |
|
| 1681 | +class FreeThreadingTest(unittest.TestCase): |
| 1682 | + # Test pretty much everything that can break under free-threading. |
| 1683 | + # Non-deterministic, but at least one of these things will fail if |
| 1684 | + # array module is not free-thread safe. |
| 1685 | + |
| 1686 | + @unittest.skipUnless(support.Py_GIL_DISABLED, 'this test can only possibly fail with GIL disabled') |
| 1687 | + @threading_helper.reap_threads |
| 1688 | + @threading_helper.requires_working_threading() |
| 1689 | + def test_free_threading(self): |
| 1690 | + def pop1(b, a): # MODIFIES! |
| 1691 | + b.wait() |
| 1692 | + try: a.pop() |
| 1693 | + except IndexError: pass |
| 1694 | + |
| 1695 | + def append1(b, a): # MODIFIES! |
| 1696 | + b.wait() |
| 1697 | + a.append(2) |
| 1698 | + |
| 1699 | + def insert1(b, a): # MODIFIES! |
| 1700 | + b.wait() |
| 1701 | + a.insert(0, 2) |
| 1702 | + |
| 1703 | + def extend(b, a): # MODIFIES! |
| 1704 | + c = array.array('i', [2]) |
| 1705 | + b.wait() |
| 1706 | + a.extend(c) |
| 1707 | + |
| 1708 | + def extend2(b, a, c): # MODIFIES! |
| 1709 | + b.wait() |
| 1710 | + a.extend(c) |
| 1711 | + |
| 1712 | + def inplace_concat(b, a): # MODIFIES! |
| 1713 | + c = array.array('i', [2]) |
| 1714 | + b.wait() |
| 1715 | + a += c |
| 1716 | + |
| 1717 | + def inplace_concat2(b, a, c): # MODIFIES! |
| 1718 | + b.wait() |
| 1719 | + a += c |
| 1720 | + |
| 1721 | + def inplace_repeat2(b, a): # MODIFIES! |
| 1722 | + b.wait() |
| 1723 | + a *= 2 |
| 1724 | + |
| 1725 | + def clear(b, a, *args): # MODIFIES! |
| 1726 | + b.wait() |
| 1727 | + a.clear() |
| 1728 | + |
| 1729 | + def clear2(b, a, c): # MODIFIES c! |
| 1730 | + b.wait() |
| 1731 | + try: c.clear() |
| 1732 | + except BufferError: pass |
| 1733 | + |
| 1734 | + def remove1(b, a): # MODIFIES! |
| 1735 | + b.wait() |
| 1736 | + try: a.remove(1) |
| 1737 | + except ValueError: pass |
| 1738 | + |
| 1739 | + def fromunicode(b, a): # MODIFIES! |
| 1740 | + b.wait() |
| 1741 | + a.fromunicode('test') |
| 1742 | + |
| 1743 | + def frombytes(b, a): # MODIFIES! |
| 1744 | + b.wait() |
| 1745 | + a.frombytes(b'0000') |
| 1746 | + |
| 1747 | + def frombytes2(b, a, c): # MODIFIES! |
| 1748 | + b.wait() |
| 1749 | + a.frombytes(c) |
| 1750 | + |
| 1751 | + def fromlist(b, a): # MODIFIES! |
| 1752 | + n = random.randint(0, 100) |
| 1753 | + b.wait() |
| 1754 | + a.fromlist([2] * n) |
| 1755 | + |
| 1756 | + def ass_subscr2(b, a, c): # MODIFIES! |
| 1757 | + b.wait() |
| 1758 | + a[:] = c |
| 1759 | + |
| 1760 | + def ass0(b, a): # modifies inplace |
| 1761 | + b.wait() |
| 1762 | + try: a[0] = 0 |
| 1763 | + except IndexError: pass |
| 1764 | + |
| 1765 | + def byteswap(b, a): # modifies inplace |
| 1766 | + b.wait() |
| 1767 | + a.byteswap() |
| 1768 | + |
| 1769 | + def tounicode(b, a): |
| 1770 | + b.wait() |
| 1771 | + a.tounicode() |
| 1772 | + |
| 1773 | + def tobytes(b, a): |
| 1774 | + b.wait() |
| 1775 | + a.tobytes() |
| 1776 | + |
| 1777 | + def tolist(b, a): |
| 1778 | + b.wait() |
| 1779 | + a.tolist() |
| 1780 | + |
| 1781 | + def tofile(b, a): |
| 1782 | + f = io.BytesIO() |
| 1783 | + b.wait() |
| 1784 | + a.tofile(f) |
| 1785 | + |
| 1786 | + def reduce_ex2(b, a): |
| 1787 | + b.wait() |
| 1788 | + a.__reduce_ex__(2) |
| 1789 | + |
| 1790 | + def reduce_ex3(b, a): |
| 1791 | + b.wait() |
| 1792 | + c = a.__reduce_ex__(3) |
| 1793 | + assert not c[1] or 0xdd not in c[1][3] |
| 1794 | + |
| 1795 | + def copy(b, a): |
| 1796 | + b.wait() |
| 1797 | + c = a.__copy__() |
| 1798 | + assert not c or 0xdd not in c |
| 1799 | + |
| 1800 | + def repr1(b, a): |
| 1801 | + b.wait() |
| 1802 | + repr(a) |
| 1803 | + |
| 1804 | + def repeat2(b, a): |
| 1805 | + b.wait() |
| 1806 | + a * 2 |
| 1807 | + |
| 1808 | + def count1(b, a): |
| 1809 | + b.wait() |
| 1810 | + a.count(1) |
| 1811 | + |
| 1812 | + def index1(b, a): |
| 1813 | + b.wait() |
| 1814 | + try: a.index(1) |
| 1815 | + except ValueError: pass |
| 1816 | + |
| 1817 | + def contains1(b, a): |
| 1818 | + b.wait() |
| 1819 | + try: 1 in a |
| 1820 | + except ValueError: pass |
| 1821 | + |
| 1822 | + def subscr0(b, a): |
| 1823 | + b.wait() |
| 1824 | + try: a[0] |
| 1825 | + except IndexError: pass |
| 1826 | + |
| 1827 | + def concat(b, a): |
| 1828 | + b.wait() |
| 1829 | + a + a |
| 1830 | + |
| 1831 | + def concat2(b, a, c): |
| 1832 | + b.wait() |
| 1833 | + a + c |
| 1834 | + |
| 1835 | + def richcmplhs(b, a): |
| 1836 | + c = a[:] |
| 1837 | + b.wait() |
| 1838 | + a == c |
| 1839 | + |
| 1840 | + def richcmprhs(b, a): |
| 1841 | + c = a[:] |
| 1842 | + b.wait() |
| 1843 | + c == a |
| 1844 | + |
| 1845 | + def new(b, a): |
| 1846 | + tc = a.typecode |
| 1847 | + b.wait() |
| 1848 | + array.array(tc, a) |
| 1849 | + |
| 1850 | + def repr_(b, a): |
| 1851 | + b.wait() |
| 1852 | + repr(a) |
| 1853 | + |
| 1854 | + def irepeat(b, a): # MODIFIES! |
| 1855 | + b.wait() |
| 1856 | + a *= 2 |
| 1857 | + |
| 1858 | + def newi(b, l): |
| 1859 | + b.wait() |
| 1860 | + array.array('i', l) |
| 1861 | + |
| 1862 | + def fromlistl(b, a, l): # MODIFIES! |
| 1863 | + b.wait() |
| 1864 | + a.fromlist(l) |
| 1865 | + |
| 1866 | + def fromlistlclear(b, a, l): # MODIFIES LIST! |
| 1867 | + b.wait() |
| 1868 | + l.clear() |
| 1869 | + |
| 1870 | + def iter_next(b, a, it): # MODIFIES ITERATOR! |
| 1871 | + b.wait() |
| 1872 | + list(it) |
| 1873 | + |
| 1874 | + def iter_reduce(b, a, it): |
| 1875 | + b.wait() |
| 1876 | + c = it.__reduce__() |
| 1877 | + assert not c[1] or 0xdd not in c[1][0] |
| 1878 | + |
| 1879 | + def check(funcs, a=None, *args): |
| 1880 | + if a is None: |
| 1881 | + a = array.array('i', [1]) |
| 1882 | + |
| 1883 | + barrier = threading.Barrier(len(funcs)) |
| 1884 | + threads = [] |
| 1885 | + |
| 1886 | + for func in funcs: |
| 1887 | + thread = threading.Thread(target=func, args=(barrier, a, *args)) |
| 1888 | + |
| 1889 | + threads.append(thread) |
| 1890 | + |
| 1891 | + with threading_helper.start_threads(threads): |
| 1892 | + pass |
| 1893 | + |
| 1894 | + check([pop1] * 10) |
| 1895 | + check([pop1] + [subscr0] * 10) |
| 1896 | + check([append1] * 10) |
| 1897 | + check([insert1] * 10) |
| 1898 | + check([pop1] + [index1] * 10) |
| 1899 | + check([pop1] + [contains1] * 10) |
| 1900 | + check([insert1] + [repeat2] * 10) |
| 1901 | + check([pop1] + [repr1] * 10) |
| 1902 | + check([inplace_repeat2] * 10) |
| 1903 | + check([byteswap] * 10) |
| 1904 | + check([insert1] + [clear] * 10) |
| 1905 | + check([pop1] + [count1] * 10) |
| 1906 | + check([remove1] * 10) |
| 1907 | + check([clear] + [copy] * 10, array.array('B', b'0' * 0x400000)) |
| 1908 | + check([pop1] + [reduce_ex2] * 10) |
| 1909 | + check([clear] + [reduce_ex3] * 10, array.array('B', b'0' * 0x400000)) |
| 1910 | + check([pop1] + [tobytes] * 10) |
| 1911 | + check([pop1] + [tolist] * 10) |
| 1912 | + check([clear, tounicode] * 10, array.array('w', 'a'*10000)) |
| 1913 | + check([clear, tofile] * 10, array.array('w', 'a'*10000)) |
| 1914 | + check([clear] + [extend] * 10) |
| 1915 | + check([clear] + [inplace_concat] * 10) |
| 1916 | + check([clear] + [concat] * 10, array.array('w', 'a'*10000)) |
| 1917 | + check([fromunicode] * 10, array.array('w', 'a')) |
| 1918 | + check([frombytes] * 10) |
| 1919 | + check([fromlist] * 10) |
| 1920 | + check([clear] + [richcmplhs] * 10, array.array('i', [1]*10000)) |
| 1921 | + check([clear] + [richcmprhs] * 10, array.array('i', [1]*10000)) |
| 1922 | + check([clear, ass0] * 10, array.array('i', [1]*10000)) # to test array_ass_item must disable Py_mp_ass_subscript |
| 1923 | + check([clear] + [new] * 10, array.array('w', 'a'*10000)) |
| 1924 | + check([clear] + [repr_] * 10, array.array('B', b'0' * 0x40000)) |
| 1925 | + check([clear] + [repr_] * 10, array.array('B', b'0' * 0x40000)) |
| 1926 | + check([clear] + [irepeat] * 10, array.array('B', b'0' * 0x40000)) |
| 1927 | + check([clear] + [iter_reduce] * 10, a := array.array('B', b'0' * 0x400), iter(a)) |
| 1928 | + |
| 1929 | + # make sure we handle non-self objects correctly |
| 1930 | + check([clear] + [newi] * 10, [2] * random.randint(0, 100)) |
| 1931 | + check([fromlistlclear] + [fromlistl] * 10, array.array('i', [1]), [2] * random.randint(0, 100)) |
| 1932 | + check([clear2] + [concat2] * 10, array.array('w', 'a'*10000), array.array('w', 'a'*10000)) |
| 1933 | + check([clear2] + [inplace_concat2] * 10, array.array('w', 'a'*10000), array.array('w', 'a'*10000)) |
| 1934 | + check([clear2] + [extend2] * 10, array.array('w', 'a'*10000), array.array('w', 'a'*10000)) |
| 1935 | + check([clear2] + [ass_subscr2] * 10, array.array('w', 'a'*10000), array.array('w', 'a'*10000)) |
| 1936 | + check([clear2] + [frombytes2] * 10, array.array('w', 'a'*10000), array.array('B', b'a'*10000)) |
| 1937 | + |
| 1938 | + # iterator stuff |
| 1939 | + check([clear] + [iter_next] * 10, a := array.array('i', [1] * 10), iter(a)) |
| 1940 | + |
| 1941 | + |
1676 | 1942 | if __name__ == "__main__":
|
1677 | 1943 | unittest.main()
|
0 commit comments