|
6 | 6 | the 1:1 mapping. Tests with no PEP 380 analogue go in `TestPEP828Extras`. |
7 | 7 | """ |
8 | 8 |
|
| 9 | +import asyncio |
9 | 10 | import unittest |
10 | 11 | import inspect |
11 | 12 | from functools import partial |
12 | 13 |
|
13 | 14 | lazy from test import test_yield_from |
14 | | -from test.support import captured_stderr, disable_gc, gc_collect, run_no_yield_async_fn, catch_unraisable_exception |
| 15 | +from test.support import ( |
| 16 | + captured_stderr, disable_gc, gc_collect, requires_working_socket, |
| 17 | + run_no_yield_async_fn, |
| 18 | +) |
15 | 19 |
|
16 | 20 | _async_test = partial(partial, run_no_yield_async_fn) |
17 | 21 |
|
@@ -599,13 +603,12 @@ async def g(): |
599 | 603 | self.assertEqual(await anext(gi), 1) |
600 | 604 | await gi.athrow(AttributeError) |
601 | 605 |
|
602 | | - with catch_unraisable_exception() as cm: |
| 606 | + # PEP 828 only ignores AttributeError when looking up aclose(). |
| 607 | + with self.assertRaises(ZeroDivisionError): |
603 | 608 | gi = g() |
604 | 609 | self.assertEqual(await anext(gi), 1) |
605 | 610 | await gi.aclose() |
606 | 611 |
|
607 | | - self.assertEqual(ZeroDivisionError, cm.unraisable.exc_type) |
608 | | - |
609 | 612 | @_async_test |
610 | 613 | async def test_exception_in_initial_next_call_ayf(self): |
611 | 614 | """ |
@@ -1004,7 +1007,7 @@ def __aiter__(self): |
1004 | 1007 | async def __anext__(self): |
1005 | 1008 | return 42 |
1006 | 1009 | async def aclose(self_): |
1007 | | - self.assertTrue(g1.gi_running) |
| 1010 | + self.assertTrue(g1.ag_running) |
1008 | 1011 | with self.assertRaises(RuntimeError): |
1009 | 1012 | await anext(g1) |
1010 | 1013 | async def one(): |
@@ -1744,5 +1747,298 @@ async def outer(): |
1744 | 1747 | self.assertIs(raised, error.exception) |
1745 | 1748 |
|
1746 | 1749 |
|
| 1750 | +@requires_working_socket() |
| 1751 | +class TestAsyncDelegation(unittest.IsolatedAsyncioTestCase): |
| 1752 | + async def test_cleanup_suspends(self): |
| 1753 | + async def inner(): |
| 1754 | + try: |
| 1755 | + yield 1 |
| 1756 | + finally: |
| 1757 | + trace.append("cleanup started") |
| 1758 | + await asyncio.sleep(0) |
| 1759 | + trace.append("cleanup completed") |
| 1760 | + |
| 1761 | + async def outer(sub): |
| 1762 | + yield from sub |
| 1763 | + |
| 1764 | + for depth in (1, 3): |
| 1765 | + for close in (True, False): |
| 1766 | + with self.subTest(depth=depth, close=close): |
| 1767 | + trace = [] |
| 1768 | + gen = inner() |
| 1769 | + for _ in range(depth): |
| 1770 | + gen = outer(gen) |
| 1771 | + self.assertEqual(await anext(gen), 1) |
| 1772 | + if close: |
| 1773 | + await gen.aclose() |
| 1774 | + else: |
| 1775 | + original = GeneratorExit() |
| 1776 | + with self.assertRaises(GeneratorExit) as caught: |
| 1777 | + await gen.athrow(original) |
| 1778 | + self.assertIs(caught.exception, original) |
| 1779 | + self.assertEqual(trace, |
| 1780 | + ["cleanup started", "cleanup completed"]) |
| 1781 | + |
| 1782 | + async def test_generator_exit_after_suspension(self): |
| 1783 | + async def inner(): |
| 1784 | + try: |
| 1785 | + yield 1 |
| 1786 | + except GeneratorExit: |
| 1787 | + await asyncio.sleep(0) |
| 1788 | + if action == "raise": |
| 1789 | + raise GeneratorExit() |
| 1790 | + if action == "yield": |
| 1791 | + yield 2 |
| 1792 | + return 42 |
| 1793 | + |
| 1794 | + async def outer(sub): |
| 1795 | + yield from sub |
| 1796 | + self.fail("delegation continued after GeneratorExit") |
| 1797 | + |
| 1798 | + for action in ("return", "raise", "yield"): |
| 1799 | + for close in (True, False): |
| 1800 | + with self.subTest(action=action, close=close): |
| 1801 | + sub = inner() |
| 1802 | + try: |
| 1803 | + gen = outer(sub) |
| 1804 | + self.assertEqual(await anext(gen), 1) |
| 1805 | + original = GeneratorExit() |
| 1806 | + operation = gen.aclose() if close else gen.athrow(original) |
| 1807 | + if action == "yield": |
| 1808 | + with self.assertRaisesRegex( |
| 1809 | + RuntimeError, "async generator ignored GeneratorExit"): |
| 1810 | + await operation |
| 1811 | + elif close: |
| 1812 | + await operation |
| 1813 | + else: |
| 1814 | + with self.assertRaises(GeneratorExit) as caught: |
| 1815 | + await operation |
| 1816 | + self.assertIs(caught.exception, original) |
| 1817 | + finally: |
| 1818 | + if action == "yield": |
| 1819 | + # Finish the delegate after its illegal yield |
| 1820 | + # during closing. |
| 1821 | + await anext(sub, None) |
| 1822 | + else: |
| 1823 | + await sub.aclose() |
| 1824 | + |
| 1825 | + async def test_custom_iterator_methods_suspend(self): |
| 1826 | + class Iterator: |
| 1827 | + def __aiter__(self): |
| 1828 | + return self |
| 1829 | + |
| 1830 | + async def __anext__(self): |
| 1831 | + return 1 |
| 1832 | + |
| 1833 | + async def athrow(self, exc): |
| 1834 | + trace.append(exc) |
| 1835 | + await asyncio.sleep(0) |
| 1836 | + trace.append("athrow completed") |
| 1837 | + return 2 |
| 1838 | + |
| 1839 | + async def aclose(self): |
| 1840 | + await asyncio.sleep(0) |
| 1841 | + trace.append("aclose completed") |
| 1842 | + |
| 1843 | + def throw(self, *args): |
| 1844 | + raise AssertionError("synchronous throw called") |
| 1845 | + |
| 1846 | + def close(self): |
| 1847 | + raise AssertionError("synchronous close called") |
| 1848 | + |
| 1849 | + async def outer(): |
| 1850 | + yield from Iterator() |
| 1851 | + |
| 1852 | + trace = [] |
| 1853 | + gen = outer() |
| 1854 | + self.assertEqual(await anext(gen), 1) |
| 1855 | + original = ValueError() |
| 1856 | + self.assertEqual(await gen.athrow(original), 2) |
| 1857 | + self.assertEqual(trace, [original, "athrow completed"]) |
| 1858 | + await gen.aclose() |
| 1859 | + self.assertEqual(trace, |
| 1860 | + [original, "athrow completed", "aclose completed"]) |
| 1861 | + |
| 1862 | + async def test_custom_iterator_throw_return_value(self): |
| 1863 | + class Iterator: |
| 1864 | + def __aiter__(self): |
| 1865 | + return self |
| 1866 | + |
| 1867 | + async def __anext__(self): |
| 1868 | + return 1 |
| 1869 | + |
| 1870 | + async def athrow(self, exc): |
| 1871 | + await asyncio.sleep(0) |
| 1872 | + raise StopAsyncIteration(result) |
| 1873 | + |
| 1874 | + async def outer(): |
| 1875 | + return (yield from Iterator()) |
| 1876 | + |
| 1877 | + for result in (None, 42, (1, 2)): |
| 1878 | + with self.subTest(result=result): |
| 1879 | + gen = outer() |
| 1880 | + self.assertEqual(await anext(gen), 1) |
| 1881 | + with self.assertRaises(StopAsyncIteration) as caught: |
| 1882 | + await gen.athrow(ValueError()) |
| 1883 | + self.assertIs(caught.exception.value, result) |
| 1884 | + |
| 1885 | + async def test_missing_throw_does_not_swallow_stop_async_iteration(self): |
| 1886 | + class Iterator: |
| 1887 | + def __aiter__(self): |
| 1888 | + return self |
| 1889 | + |
| 1890 | + async def __anext__(self): |
| 1891 | + return 1 |
| 1892 | + |
| 1893 | + async def outer(): |
| 1894 | + yield from Iterator() |
| 1895 | + |
| 1896 | + gen = outer() |
| 1897 | + self.assertEqual(await anext(gen), 1) |
| 1898 | + original = StopAsyncIteration(42) |
| 1899 | + with self.assertRaisesRegex( |
| 1900 | + RuntimeError, "async generator raised StopAsyncIteration") as caught: |
| 1901 | + await gen.athrow(original) |
| 1902 | + self.assertIs(caught.exception.__cause__, original) |
| 1903 | + |
| 1904 | + async def test_close_does_not_swallow_stop_async_iteration(self): |
| 1905 | + class Iterator: |
| 1906 | + def __aiter__(self): |
| 1907 | + return self |
| 1908 | + |
| 1909 | + async def __anext__(self): |
| 1910 | + return 1 |
| 1911 | + |
| 1912 | + async def aclose(self): |
| 1913 | + await asyncio.sleep(0) |
| 1914 | + raise original |
| 1915 | + |
| 1916 | + async def outer(): |
| 1917 | + yield from Iterator() |
| 1918 | + |
| 1919 | + for close in (True, False): |
| 1920 | + with self.subTest(close=close): |
| 1921 | + gen = outer() |
| 1922 | + self.assertEqual(await anext(gen), 1) |
| 1923 | + original = StopAsyncIteration(42) |
| 1924 | + with self.assertRaisesRegex( |
| 1925 | + RuntimeError, "async generator raised StopAsyncIteration") as caught: |
| 1926 | + if close: |
| 1927 | + await gen.aclose() |
| 1928 | + else: |
| 1929 | + await gen.athrow(GeneratorExit()) |
| 1930 | + self.assertIs(caught.exception.__cause__, original) |
| 1931 | + |
| 1932 | + async def test_methods_return_general_awaitables(self): |
| 1933 | + class Awaitable: |
| 1934 | + def __init__(self, result): |
| 1935 | + self.result = result |
| 1936 | + |
| 1937 | + def __await__(self): |
| 1938 | + yield |
| 1939 | + return self.result |
| 1940 | + |
| 1941 | + class Iterator: |
| 1942 | + def __aiter__(self): |
| 1943 | + return self |
| 1944 | + |
| 1945 | + def __anext__(self): |
| 1946 | + return Awaitable(1) |
| 1947 | + |
| 1948 | + def asend(self, value): |
| 1949 | + return Awaitable(value) |
| 1950 | + |
| 1951 | + def athrow(self, exc): |
| 1952 | + return Awaitable(exc) |
| 1953 | + |
| 1954 | + def aclose(self): |
| 1955 | + return Awaitable(None) |
| 1956 | + |
| 1957 | + async def outer(): |
| 1958 | + yield from Iterator() |
| 1959 | + |
| 1960 | + gen = outer() |
| 1961 | + self.assertEqual(await anext(gen), 1) |
| 1962 | + self.assertEqual(await gen.asend(2), 2) |
| 1963 | + original = ValueError() |
| 1964 | + self.assertIs(await gen.athrow(original), original) |
| 1965 | + await gen.aclose() |
| 1966 | + |
| 1967 | + async def test_optional_method_lookup_errors(self): |
| 1968 | + class Iterator: |
| 1969 | + def __aiter__(self): |
| 1970 | + return self |
| 1971 | + |
| 1972 | + async def __anext__(self): |
| 1973 | + return 1 |
| 1974 | + |
| 1975 | + def __getattr__(self, name): |
| 1976 | + if name in ("aclose", "athrow"): |
| 1977 | + raise lookup_error |
| 1978 | + raise AttributeError(name) |
| 1979 | + |
| 1980 | + async def outer(): |
| 1981 | + yield from Iterator() |
| 1982 | + |
| 1983 | + for exc_type in (AttributeError, LookupError, StopAsyncIteration): |
| 1984 | + for mode in ("close", "exit", "throw"): |
| 1985 | + with self.subTest(exc_type=exc_type, mode=mode): |
| 1986 | + lookup_error = exc_type("method lookup failed") |
| 1987 | + gen = outer() |
| 1988 | + self.assertEqual(await anext(gen), 1) |
| 1989 | + original = GeneratorExit() if mode == "exit" else ValueError() |
| 1990 | + operation = (gen.aclose() if mode == "close" |
| 1991 | + else gen.athrow(original)) |
| 1992 | + if exc_type is AttributeError: |
| 1993 | + if mode == "close": |
| 1994 | + await operation |
| 1995 | + else: |
| 1996 | + with self.assertRaises(type(original)) as caught: |
| 1997 | + await operation |
| 1998 | + self.assertIs(caught.exception, original) |
| 1999 | + elif exc_type is StopAsyncIteration: |
| 2000 | + with self.assertRaisesRegex( |
| 2001 | + RuntimeError, |
| 2002 | + "async generator raised StopAsyncIteration") as caught: |
| 2003 | + await operation |
| 2004 | + self.assertIs(caught.exception.__cause__, lookup_error) |
| 2005 | + else: |
| 2006 | + with self.assertRaises(exc_type) as caught: |
| 2007 | + await operation |
| 2008 | + self.assertIs(caught.exception, lookup_error) |
| 2009 | + |
| 2010 | + async def test_attribute_error_from_async_methods(self): |
| 2011 | + class Iterator: |
| 2012 | + def __aiter__(self): |
| 2013 | + return self |
| 2014 | + |
| 2015 | + async def __anext__(self): |
| 2016 | + return 1 |
| 2017 | + |
| 2018 | + async def aclose(self): |
| 2019 | + await asyncio.sleep(0) |
| 2020 | + raise method_error |
| 2021 | + |
| 2022 | + async def athrow(self, exc): |
| 2023 | + await asyncio.sleep(0) |
| 2024 | + raise method_error |
| 2025 | + |
| 2026 | + async def outer(): |
| 2027 | + yield from Iterator() |
| 2028 | + |
| 2029 | + for mode in ("close", "exit", "throw"): |
| 2030 | + with self.subTest(mode=mode): |
| 2031 | + method_error = AttributeError("method failed") |
| 2032 | + gen = outer() |
| 2033 | + self.assertEqual(await anext(gen), 1) |
| 2034 | + original = GeneratorExit() if mode == "exit" else ValueError() |
| 2035 | + with self.assertRaises(AttributeError) as caught: |
| 2036 | + if mode == "close": |
| 2037 | + await gen.aclose() |
| 2038 | + else: |
| 2039 | + await gen.athrow(original) |
| 2040 | + self.assertIs(caught.exception, method_error) |
| 2041 | + |
| 2042 | + |
1747 | 2043 | if __name__ == '__main__': |
1748 | 2044 | unittest.main() |
0 commit comments