From a69d59d1b7da31e540c57f0f48a08f775b9d92c3 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Fri, 1 Aug 2025 15:03:06 +0200 Subject: [PATCH 1/2] Update `contextlib` from 3.13.5 --- Lib/contextlib.py | 58 +++++++++++------- Lib/test/archivetestdata/README.md | 36 +++++++++++ Lib/test/archivetestdata/exe_with_z64 | Bin 0 -> 978 bytes Lib/test/archivetestdata/exe_with_zip | Bin 0 -> 990 bytes Lib/test/archivetestdata/header.sh | 24 ++++++++ Lib/test/archivetestdata/recursion.tar | Bin 0 -> 516 bytes .../testdata_module_inside_zip.py | 2 + Lib/test/archivetestdata/testtar.tar | Bin 0 -> 435200 bytes Lib/test/archivetestdata/testtar.tar.xz | Bin 0 -> 172 bytes Lib/test/archivetestdata/zip_cp437_header.zip | Bin 0 -> 270 bytes Lib/test/archivetestdata/zipdir.zip | Bin 0 -> 374 bytes Lib/test/archivetestdata/zipdir_backslash.zip | Bin 0 -> 192 bytes Lib/test/test_contextlib.py | 20 +++++- 13 files changed, 115 insertions(+), 25 deletions(-) create mode 100644 Lib/test/archivetestdata/README.md create mode 100755 Lib/test/archivetestdata/exe_with_z64 create mode 100755 Lib/test/archivetestdata/exe_with_zip create mode 100755 Lib/test/archivetestdata/header.sh create mode 100644 Lib/test/archivetestdata/recursion.tar create mode 100644 Lib/test/archivetestdata/testdata_module_inside_zip.py create mode 100644 Lib/test/archivetestdata/testtar.tar create mode 100644 Lib/test/archivetestdata/testtar.tar.xz create mode 100644 Lib/test/archivetestdata/zip_cp437_header.zip create mode 100644 Lib/test/archivetestdata/zipdir.zip create mode 100644 Lib/test/archivetestdata/zipdir_backslash.zip diff --git a/Lib/contextlib.py b/Lib/contextlib.py index b831d8916c..5b646fabca 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -20,6 +20,8 @@ class AbstractContextManager(abc.ABC): __class_getitem__ = classmethod(GenericAlias) + __slots__ = () + def __enter__(self): """Return `self` upon entering the runtime context.""" return self @@ -42,6 +44,8 @@ class AbstractAsyncContextManager(abc.ABC): __class_getitem__ = classmethod(GenericAlias) + __slots__ = () + async def __aenter__(self): """Return `self` upon entering the runtime context.""" return self @@ -565,11 +569,12 @@ def __enter__(self): return self def __exit__(self, *exc_details): - received_exc = exc_details[0] is not None + exc = exc_details[1] + received_exc = exc is not None # We manipulate the exception state so it behaves as though # we were actually nesting multiple with statements - frame_exc = sys.exc_info()[1] + frame_exc = sys.exception() def _fix_exception_context(new_exc, old_exc): # Context may not be correct, so find the end of the chain while 1: @@ -592,24 +597,28 @@ def _fix_exception_context(new_exc, old_exc): is_sync, cb = self._exit_callbacks.pop() assert is_sync try: + if exc is None: + exc_details = None, None, None + else: + exc_details = type(exc), exc, exc.__traceback__ if cb(*exc_details): suppressed_exc = True pending_raise = False - exc_details = (None, None, None) - except: - new_exc_details = sys.exc_info() + exc = None + except BaseException as new_exc: # simulate the stack of exceptions by setting the context - _fix_exception_context(new_exc_details[1], exc_details[1]) + _fix_exception_context(new_exc, exc) pending_raise = True - exc_details = new_exc_details + exc = new_exc + if pending_raise: try: - # bare "raise exc_details[1]" replaces our carefully + # bare "raise exc" replaces our carefully # set-up context - fixed_ctx = exc_details[1].__context__ - raise exc_details[1] + fixed_ctx = exc.__context__ + raise exc except BaseException: - exc_details[1].__context__ = fixed_ctx + exc.__context__ = fixed_ctx raise return received_exc and suppressed_exc @@ -705,11 +714,12 @@ async def __aenter__(self): return self async def __aexit__(self, *exc_details): - received_exc = exc_details[0] is not None + exc = exc_details[1] + received_exc = exc is not None # We manipulate the exception state so it behaves as though # we were actually nesting multiple with statements - frame_exc = sys.exc_info()[1] + frame_exc = sys.exception() def _fix_exception_context(new_exc, old_exc): # Context may not be correct, so find the end of the chain while 1: @@ -731,6 +741,10 @@ def _fix_exception_context(new_exc, old_exc): while self._exit_callbacks: is_sync, cb = self._exit_callbacks.pop() try: + if exc is None: + exc_details = None, None, None + else: + exc_details = type(exc), exc, exc.__traceback__ if is_sync: cb_suppress = cb(*exc_details) else: @@ -739,21 +753,21 @@ def _fix_exception_context(new_exc, old_exc): if cb_suppress: suppressed_exc = True pending_raise = False - exc_details = (None, None, None) - except: - new_exc_details = sys.exc_info() + exc = None + except BaseException as new_exc: # simulate the stack of exceptions by setting the context - _fix_exception_context(new_exc_details[1], exc_details[1]) + _fix_exception_context(new_exc, exc) pending_raise = True - exc_details = new_exc_details + exc = new_exc + if pending_raise: try: - # bare "raise exc_details[1]" replaces our carefully + # bare "raise exc" replaces our carefully # set-up context - fixed_ctx = exc_details[1].__context__ - raise exc_details[1] + fixed_ctx = exc.__context__ + raise exc except BaseException: - exc_details[1].__context__ = fixed_ctx + exc.__context__ = fixed_ctx raise return received_exc and suppressed_exc diff --git a/Lib/test/archivetestdata/README.md b/Lib/test/archivetestdata/README.md new file mode 100644 index 0000000000..7b555fa327 --- /dev/null +++ b/Lib/test/archivetestdata/README.md @@ -0,0 +1,36 @@ +# Test data for `test_zipfile`, `test_tarfile` (and even some others) + +## `test_zipfile` + +The test executables in this directory are created manually from `header.sh` and +the `testdata_module_inside_zip.py` file. You must have Info-ZIP's zip utility +installed (`apt install zip` on Debian). + +### Purpose of `exe_with_zip` and `exe_with_z64` + +These are used to test executable files with an appended zipfile, in a scenario +where the executable is _not_ a Python interpreter itself so our automatic +zipimport machinery (that'd look for `__main__.py`) is not being used. + +### Updating the test executables + +If you update header.sh or the testdata_module_inside_zip.py file, rerun the +commands below. These are expected to be rarely changed, if ever. + +#### Standard old format (2.0) zip file + +``` +zip -0 zip2.zip testdata_module_inside_zip.py +cat header.sh zip2.zip >exe_with_zip +rm zip2.zip +``` + +#### Modern format (4.5) zip64 file + +Redirecting from stdin forces Info-ZIP's zip tool to create a zip64. + +``` +zip -0 zip64.zip +cat header.sh zip64.zip >exe_with_z64 +rm zip64.zip +``` diff --git a/Lib/test/archivetestdata/exe_with_z64 b/Lib/test/archivetestdata/exe_with_z64 new file mode 100755 index 0000000000000000000000000000000000000000..82b03cf39d919d9de05866b4f7cdf1dfe47e6eb3 GIT binary patch literal 978 zcmaJ<-EY${5KkEl$xl4ME4rnV(xNp330}q~w6RoFg|_N41e7XrQ)jhEoXByzLbV6} z7bN~nxXZ`b5aOcPclPh+`)uy)&!pO)@qEF01K%5u#vZQ0`QQ{+-#hbQJlMzfN zumhbn*t?s5Bd=_jPG5pq2*m(Jgo_mHo-#sbTHp%FGB+?21c5M360YVDOC^Boi)A8| zaqW`1mIj`)NHXt(_xjvFK6&e598YZ!YZ3l8f{q6rI6U+Qr@^orj6V8rh65&(EY$|m zyw<+SERwNcOz}kI84m>G zSHN@NP(AKCZFVWm;@bWsvo1d0s^NQ(q;qlPXs1m?Of5j_0ahSNH4rM0DoR1B`pzXg zmbq!Q2?j9dhGVD|)zyN}i{}esyQ-xKTZG$#>tt`JC1{4sF91y#s`x7`^Rh*e)YvZy zgkqqkaUCw?O1P{lg45-zR7)d3Et45`xQsPi8a|7~fpf#r#O@xyAC7yz7Yxqdop@t= z+GecTY{GH*C{6^9iZVG|$~dMm;TcwVF6O`^njW)|c@d2ZNMpBKJnC=V?N}s_K0g`u zf>&q1Drr~`txm&wV0p#0b-g#i7nomB!y-wOlGog%8hujhFq@*CrC0V>0$BJLY}9Yu zdAxPoGdZHaQ8}dT$9GygqyF~x9%(2wjr1B?@B4I!vMx6ZdG|^ES=ode_3v$y*}#wR GCH6PMMGy`E literal 0 HcmV?d00001 diff --git a/Lib/test/archivetestdata/exe_with_zip b/Lib/test/archivetestdata/exe_with_zip new file mode 100755 index 0000000000000000000000000000000000000000..c833cdf9f934b08fc449da077efae44aa9a06f18 GIT binary patch literal 990 zcmah{&2G~`5Ozt6#BxUB0BGt|a)MeHBzh^WP)i*V6_O}*5D+43W3QVP_S)KAHz`pi zcm>WJc^aOC$6(e@N~!o+%den-3;)Sr%Y!Z0+w(d{LAMq3-uf@P z9m3N*lNvI$JbmPO%o9e4pea*14H@ji{DR<>2{Yf&&6LZ;8JC$DI=^T*Ba%xlbR%}U zITKu*!h8w30IGn(BDw1{$&~BKrT>oSEll57hHpZeMQq=ZPSXIfv;d*Is6d=aFi`;) zaRyv0|GCCbxYCWL2?L0zrbu-GbtR)wnZ5)z7h1BgVd6I7ve+xfDrk(z4*+%OisT#$ zRkbMQ68mL{7!IasRE86N#$2)x!D-R6OmfXY6zLc{TyYHxO~(n_b*@}Av|9(SyZyHB z1)agGL$7a-nuOHrbvUS!;zZ!62(4hslf;Y(%~9cqML=USJ$k}b$;JhQk>6X~JFcw~ z%d9)^A9mZpvl9=`=Dly-vourMXb_;{MX9UeQ7N~ZpAY<7R_*(II{NZyIx1$jt(Dau zHOneZ9ejjVI+sG|%rH|rlgP`o7b`AXUNIxrip1vZklyjijR&>AvAb(Xm+RYSv;Bwb WTE+Dm&))IcO#@!RC&c}$ajc&zD=F;& literal 0 HcmV?d00001 diff --git a/Lib/test/archivetestdata/header.sh b/Lib/test/archivetestdata/header.sh new file mode 100755 index 0000000000..52dc91acf7 --- /dev/null +++ b/Lib/test/archivetestdata/header.sh @@ -0,0 +1,24 @@ +#!/bin/bash +INTERPRETER_UNDER_TEST="$1" +if [[ ! -x "${INTERPRETER_UNDER_TEST}" ]]; then + echo "Interpreter must be the command line argument." + exit 4 +fi +EXECUTABLE="$0" exec "${INTERPRETER_UNDER_TEST}" -E - <8xq$A1Gm}!7)KUsFc41m#O8A5+e I1_}|j06>QaCIA2c literal 0 HcmV?d00001 diff --git a/Lib/test/archivetestdata/testdata_module_inside_zip.py b/Lib/test/archivetestdata/testdata_module_inside_zip.py new file mode 100644 index 0000000000..6f8dcd6c0e --- /dev/null +++ b/Lib/test/archivetestdata/testdata_module_inside_zip.py @@ -0,0 +1,2 @@ +# Test data file to be stored within a zip file. +FAVORITE_NUMBER = 5 diff --git a/Lib/test/archivetestdata/testtar.tar b/Lib/test/archivetestdata/testtar.tar new file mode 100644 index 0000000000000000000000000000000000000000..bb9345373e9701b01f16b70844445dfe1863abc6 GIT binary patch literal 435200 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 literal 0 HcmV?d00001 diff --git a/Lib/test/archivetestdata/testtar.tar.xz b/Lib/test/archivetestdata/testtar.tar.xz new file mode 100644 index 0000000000000000000000000000000000000000..512fa149e6cfda08642420ce6e17cabe02893c92 GIT binary patch literal 172 zcmV;d08{_{H+ooF000E$*0e?f03iVu0001VFXf})C;tF!T>v^6O3odKphUYNlple5 z-KB(swrH#_Kh4gW8PxCL$PXe;bP4fv*C?D?e7=IN*d=Ue5?MXm8m{)BT;m+sUm^*9 z2LypHe9a+e2UjA5aLw5l`PTevNAB*#-*&KcY?^%qYIEz+o%=^AjQ{{`s~PQt=TFf9 a0f+&BPyhf^^D~XH#Ao{g000001X)`2a!Qu~ literal 0 HcmV?d00001 diff --git a/Lib/test/archivetestdata/zip_cp437_header.zip b/Lib/test/archivetestdata/zip_cp437_header.zip new file mode 100644 index 0000000000000000000000000000000000000000..f7c6cf170422c48ce1337a4c05843fe66a4b0bbf GIT binary patch literal 270 zcmWIWW@Zs#U}E54IGj1(Hjhth3p)b?11k`V0&!YqPHJ9aZfbmaW=Tf;#QZkBl8Tbz zujP-QGBE_;)G30lGrzP1MI$4V2m@|Qfx3Y}0Zig>LV!1JBS5x8fC7-R0%8=FkcTh5&DbW=z*2 uYc>UI2Du%m9b!Lz?W_zi?FI&&mdJ6*Ca-0n$rA90maKRyZ91 literal 0 HcmV?d00001 diff --git a/Lib/test/archivetestdata/zipdir_backslash.zip b/Lib/test/archivetestdata/zipdir_backslash.zip new file mode 100644 index 0000000000000000000000000000000000000000..979126ef5e37ebd46762c76439e9b4e77431103c GIT binary patch literal 192 zcmWIWW@Zs#0D Date: Fri, 1 Aug 2025 15:08:50 +0200 Subject: [PATCH 2/2] Add `test_contextlib_async.py` --- Lib/test/test_contextlib_async.py | 780 ++++++++++++++++++++++++++++++ 1 file changed, 780 insertions(+) create mode 100644 Lib/test/test_contextlib_async.py diff --git a/Lib/test/test_contextlib_async.py b/Lib/test/test_contextlib_async.py new file mode 100644 index 0000000000..5673c1b4bc --- /dev/null +++ b/Lib/test/test_contextlib_async.py @@ -0,0 +1,780 @@ +import asyncio +from contextlib import ( + asynccontextmanager, AbstractAsyncContextManager, + AsyncExitStack, nullcontext, aclosing, contextmanager) +from test import support +import unittest +import traceback + +from test.test_contextlib import TestBaseExitStack + +support.requires_working_socket(module=True) + +def tearDownModule(): + asyncio.set_event_loop_policy(None) + + +class TestAbstractAsyncContextManager(unittest.IsolatedAsyncioTestCase): + + async def test_enter(self): + class DefaultEnter(AbstractAsyncContextManager): + async def __aexit__(self, *args): + await super().__aexit__(*args) + + manager = DefaultEnter() + self.assertIs(await manager.__aenter__(), manager) + + async with manager as context: + self.assertIs(manager, context) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + async def test_slots(self): + class DefaultAsyncContextManager(AbstractAsyncContextManager): + __slots__ = () + + async def __aexit__(self, *args): + await super().__aexit__(*args) + + with self.assertRaises(AttributeError): + manager = DefaultAsyncContextManager() + manager.var = 42 + + # TODO: RUSTPYTHON + @unittest.expectedFailure + async def test_async_gen_propagates_generator_exit(self): + # A regression test for https://bugs.python.org/issue33786. + + @asynccontextmanager + async def ctx(): + yield + + async def gen(): + async with ctx(): + yield 11 + + g = gen() + async for val in g: + self.assertEqual(val, 11) + break + await g.aclose() + + def test_exit_is_abstract(self): + class MissingAexit(AbstractAsyncContextManager): + pass + + with self.assertRaises(TypeError): + MissingAexit() + + def test_structural_subclassing(self): + class ManagerFromScratch: + async def __aenter__(self): + return self + async def __aexit__(self, exc_type, exc_value, traceback): + return None + + self.assertTrue(issubclass(ManagerFromScratch, AbstractAsyncContextManager)) + + class DefaultEnter(AbstractAsyncContextManager): + async def __aexit__(self, *args): + await super().__aexit__(*args) + + self.assertTrue(issubclass(DefaultEnter, AbstractAsyncContextManager)) + + class NoneAenter(ManagerFromScratch): + __aenter__ = None + + self.assertFalse(issubclass(NoneAenter, AbstractAsyncContextManager)) + + class NoneAexit(ManagerFromScratch): + __aexit__ = None + + self.assertFalse(issubclass(NoneAexit, AbstractAsyncContextManager)) + + +class AsyncContextManagerTestCase(unittest.IsolatedAsyncioTestCase): + + # TODO: RUSTPYTHON + @unittest.expectedFailure + async def test_contextmanager_plain(self): + state = [] + @asynccontextmanager + async def woohoo(): + state.append(1) + yield 42 + state.append(999) + async with woohoo() as x: + self.assertEqual(state, [1]) + self.assertEqual(x, 42) + state.append(x) + self.assertEqual(state, [1, 42, 999]) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + async def test_contextmanager_finally(self): + state = [] + @asynccontextmanager + async def woohoo(): + state.append(1) + try: + yield 42 + finally: + state.append(999) + with self.assertRaises(ZeroDivisionError): + async with woohoo() as x: + self.assertEqual(state, [1]) + self.assertEqual(x, 42) + state.append(x) + raise ZeroDivisionError() + self.assertEqual(state, [1, 42, 999]) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + async def test_contextmanager_traceback(self): + @asynccontextmanager + async def f(): + yield + + try: + async with f(): + 1/0 + except ZeroDivisionError as e: + frames = traceback.extract_tb(e.__traceback__) + + self.assertEqual(len(frames), 1) + self.assertEqual(frames[0].name, 'test_contextmanager_traceback') + self.assertEqual(frames[0].line, '1/0') + + # Repeat with RuntimeError (which goes through a different code path) + class RuntimeErrorSubclass(RuntimeError): + pass + + try: + async with f(): + raise RuntimeErrorSubclass(42) + except RuntimeErrorSubclass as e: + frames = traceback.extract_tb(e.__traceback__) + + self.assertEqual(len(frames), 1) + self.assertEqual(frames[0].name, 'test_contextmanager_traceback') + self.assertEqual(frames[0].line, 'raise RuntimeErrorSubclass(42)') + + class StopIterationSubclass(StopIteration): + pass + + class StopAsyncIterationSubclass(StopAsyncIteration): + pass + + for stop_exc in ( + StopIteration('spam'), + StopAsyncIteration('ham'), + StopIterationSubclass('spam'), + StopAsyncIterationSubclass('spam') + ): + with self.subTest(type=type(stop_exc)): + try: + async with f(): + raise stop_exc + except type(stop_exc) as e: + self.assertIs(e, stop_exc) + frames = traceback.extract_tb(e.__traceback__) + else: + self.fail(f'{stop_exc} was suppressed') + + self.assertEqual(len(frames), 1) + self.assertEqual(frames[0].name, 'test_contextmanager_traceback') + self.assertEqual(frames[0].line, 'raise stop_exc') + + # TODO: RUSTPYTHON + @unittest.expectedFailure + async def test_contextmanager_no_reraise(self): + @asynccontextmanager + async def whee(): + yield + ctx = whee() + await ctx.__aenter__() + # Calling __aexit__ should not result in an exception + self.assertFalse(await ctx.__aexit__(TypeError, TypeError("foo"), None)) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + async def test_contextmanager_trap_yield_after_throw(self): + @asynccontextmanager + async def whoo(): + try: + yield + except: + yield + ctx = whoo() + await ctx.__aenter__() + with self.assertRaises(RuntimeError): + await ctx.__aexit__(TypeError, TypeError('foo'), None) + if support.check_impl_detail(cpython=True): + # The "gen" attribute is an implementation detail. + self.assertFalse(ctx.gen.ag_suspended) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + async def test_contextmanager_trap_no_yield(self): + @asynccontextmanager + async def whoo(): + if False: + yield + ctx = whoo() + with self.assertRaises(RuntimeError): + await ctx.__aenter__() + + # TODO: RUSTPYTHON + @unittest.expectedFailure + async def test_contextmanager_trap_second_yield(self): + @asynccontextmanager + async def whoo(): + yield + yield + ctx = whoo() + await ctx.__aenter__() + with self.assertRaises(RuntimeError): + await ctx.__aexit__(None, None, None) + if support.check_impl_detail(cpython=True): + # The "gen" attribute is an implementation detail. + self.assertFalse(ctx.gen.ag_suspended) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + async def test_contextmanager_non_normalised(self): + @asynccontextmanager + async def whoo(): + try: + yield + except RuntimeError: + raise SyntaxError + + ctx = whoo() + await ctx.__aenter__() + with self.assertRaises(SyntaxError): + await ctx.__aexit__(RuntimeError, None, None) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + async def test_contextmanager_except(self): + state = [] + @asynccontextmanager + async def woohoo(): + state.append(1) + try: + yield 42 + except ZeroDivisionError as e: + state.append(e.args[0]) + self.assertEqual(state, [1, 42, 999]) + async with woohoo() as x: + self.assertEqual(state, [1]) + self.assertEqual(x, 42) + state.append(x) + raise ZeroDivisionError(999) + self.assertEqual(state, [1, 42, 999]) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + async def test_contextmanager_except_stopiter(self): + @asynccontextmanager + async def woohoo(): + yield + + class StopIterationSubclass(StopIteration): + pass + + class StopAsyncIterationSubclass(StopAsyncIteration): + pass + + for stop_exc in ( + StopIteration('spam'), + StopAsyncIteration('ham'), + StopIterationSubclass('spam'), + StopAsyncIterationSubclass('spam') + ): + with self.subTest(type=type(stop_exc)): + try: + async with woohoo(): + raise stop_exc + except Exception as ex: + self.assertIs(ex, stop_exc) + else: + self.fail(f'{stop_exc} was suppressed') + + # TODO: RUSTPYTHON + @unittest.expectedFailure + async def test_contextmanager_wrap_runtimeerror(self): + @asynccontextmanager + async def woohoo(): + try: + yield + except Exception as exc: + raise RuntimeError(f'caught {exc}') from exc + + with self.assertRaises(RuntimeError): + async with woohoo(): + 1 / 0 + + # If the context manager wrapped StopAsyncIteration in a RuntimeError, + # we also unwrap it, because we can't tell whether the wrapping was + # done by the generator machinery or by the generator itself. + with self.assertRaises(StopAsyncIteration): + async with woohoo(): + raise StopAsyncIteration + + def _create_contextmanager_attribs(self): + def attribs(**kw): + def decorate(func): + for k,v in kw.items(): + setattr(func,k,v) + return func + return decorate + @asynccontextmanager + @attribs(foo='bar') + async def baz(spam): + """Whee!""" + yield + return baz + + def test_contextmanager_attribs(self): + baz = self._create_contextmanager_attribs() + self.assertEqual(baz.__name__,'baz') + self.assertEqual(baz.foo, 'bar') + + @support.requires_docstrings + def test_contextmanager_doc_attrib(self): + baz = self._create_contextmanager_attribs() + self.assertEqual(baz.__doc__, "Whee!") + + # TODO: RUSTPYTHON + @unittest.expectedFailure + @support.requires_docstrings + async def test_instance_docstring_given_cm_docstring(self): + baz = self._create_contextmanager_attribs()(None) + self.assertEqual(baz.__doc__, "Whee!") + async with baz: + pass # suppress warning + + # TODO: RUSTPYTHON + @unittest.expectedFailure + async def test_keywords(self): + # Ensure no keyword arguments are inhibited + @asynccontextmanager + async def woohoo(self, func, args, kwds): + yield (self, func, args, kwds) + async with woohoo(self=11, func=22, args=33, kwds=44) as target: + self.assertEqual(target, (11, 22, 33, 44)) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + async def test_recursive(self): + depth = 0 + ncols = 0 + + @asynccontextmanager + async def woohoo(): + nonlocal ncols + ncols += 1 + + nonlocal depth + before = depth + depth += 1 + yield + depth -= 1 + self.assertEqual(depth, before) + + @woohoo() + async def recursive(): + if depth < 10: + await recursive() + + await recursive() + + self.assertEqual(ncols, 10) + self.assertEqual(depth, 0) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + async def test_decorator(self): + entered = False + + @asynccontextmanager + async def context(): + nonlocal entered + entered = True + yield + entered = False + + @context() + async def test(): + self.assertTrue(entered) + + self.assertFalse(entered) + await test() + self.assertFalse(entered) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + async def test_decorator_with_exception(self): + entered = False + + @asynccontextmanager + async def context(): + nonlocal entered + try: + entered = True + yield + finally: + entered = False + + @context() + async def test(): + self.assertTrue(entered) + raise NameError('foo') + + self.assertFalse(entered) + with self.assertRaisesRegex(NameError, 'foo'): + await test() + self.assertFalse(entered) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + async def test_decorating_method(self): + + @asynccontextmanager + async def context(): + yield + + + class Test(object): + + @context() + async def method(self, a, b, c=None): + self.a = a + self.b = b + self.c = c + + # these tests are for argument passing when used as a decorator + test = Test() + await test.method(1, 2) + self.assertEqual(test.a, 1) + self.assertEqual(test.b, 2) + self.assertEqual(test.c, None) + + test = Test() + await test.method('a', 'b', 'c') + self.assertEqual(test.a, 'a') + self.assertEqual(test.b, 'b') + self.assertEqual(test.c, 'c') + + test = Test() + await test.method(a=1, b=2) + self.assertEqual(test.a, 1) + self.assertEqual(test.b, 2) + + +class AclosingTestCase(unittest.IsolatedAsyncioTestCase): + + @support.requires_docstrings + def test_instance_docs(self): + cm_docstring = aclosing.__doc__ + obj = aclosing(None) + self.assertEqual(obj.__doc__, cm_docstring) + + async def test_aclosing(self): + state = [] + class C: + async def aclose(self): + state.append(1) + x = C() + self.assertEqual(state, []) + async with aclosing(x) as y: + self.assertEqual(x, y) + self.assertEqual(state, [1]) + + async def test_aclosing_error(self): + state = [] + class C: + async def aclose(self): + state.append(1) + x = C() + self.assertEqual(state, []) + with self.assertRaises(ZeroDivisionError): + async with aclosing(x) as y: + self.assertEqual(x, y) + 1 / 0 + self.assertEqual(state, [1]) + + async def test_aclosing_bpo41229(self): + state = [] + + @contextmanager + def sync_resource(): + try: + yield + finally: + state.append(1) + + async def agenfunc(): + with sync_resource(): + yield -1 + yield -2 + + x = agenfunc() + self.assertEqual(state, []) + with self.assertRaises(ZeroDivisionError): + async with aclosing(x) as y: + self.assertEqual(x, y) + self.assertEqual(-1, await x.__anext__()) + 1 / 0 + self.assertEqual(state, [1]) + + +class TestAsyncExitStack(TestBaseExitStack, unittest.IsolatedAsyncioTestCase): + class SyncAsyncExitStack(AsyncExitStack): + @staticmethod + def run_coroutine(coro): + loop = asyncio.get_event_loop_policy().get_event_loop() + t = loop.create_task(coro) + t.add_done_callback(lambda f: loop.stop()) + loop.run_forever() + + exc = t.exception() + if not exc: + return t.result() + else: + context = exc.__context__ + + try: + raise exc + except: + exc.__context__ = context + raise exc + + def close(self): + return self.run_coroutine(self.aclose()) + + def __enter__(self): + return self.run_coroutine(self.__aenter__()) + + def __exit__(self, *exc_details): + return self.run_coroutine(self.__aexit__(*exc_details)) + + exit_stack = SyncAsyncExitStack + callback_error_internal_frames = [ + ('__exit__', 'return self.run_coroutine(self.__aexit__(*exc_details))'), + ('run_coroutine', 'raise exc'), + ('run_coroutine', 'raise exc'), + ('__aexit__', 'raise exc'), + ('__aexit__', 'cb_suppress = cb(*exc_details)'), + ] + + async def test_async_callback(self): + expected = [ + ((), {}), + ((1,), {}), + ((1,2), {}), + ((), dict(example=1)), + ((1,), dict(example=1)), + ((1,2), dict(example=1)), + ] + result = [] + async def _exit(*args, **kwds): + """Test metadata propagation""" + result.append((args, kwds)) + + async with AsyncExitStack() as stack: + for args, kwds in reversed(expected): + if args and kwds: + f = stack.push_async_callback(_exit, *args, **kwds) + elif args: + f = stack.push_async_callback(_exit, *args) + elif kwds: + f = stack.push_async_callback(_exit, **kwds) + else: + f = stack.push_async_callback(_exit) + self.assertIs(f, _exit) + for wrapper in stack._exit_callbacks: + self.assertIs(wrapper[1].__wrapped__, _exit) + self.assertNotEqual(wrapper[1].__name__, _exit.__name__) + self.assertIsNone(wrapper[1].__doc__, _exit.__doc__) + + self.assertEqual(result, expected) + + result = [] + async with AsyncExitStack() as stack: + with self.assertRaises(TypeError): + stack.push_async_callback(arg=1) + with self.assertRaises(TypeError): + self.exit_stack.push_async_callback(arg=2) + with self.assertRaises(TypeError): + stack.push_async_callback(callback=_exit, arg=3) + self.assertEqual(result, []) + + async def test_async_push(self): + exc_raised = ZeroDivisionError + async def _expect_exc(exc_type, exc, exc_tb): + self.assertIs(exc_type, exc_raised) + async def _suppress_exc(*exc_details): + return True + async def _expect_ok(exc_type, exc, exc_tb): + self.assertIsNone(exc_type) + self.assertIsNone(exc) + self.assertIsNone(exc_tb) + class ExitCM(object): + def __init__(self, check_exc): + self.check_exc = check_exc + async def __aenter__(self): + self.fail("Should not be called!") + async def __aexit__(self, *exc_details): + await self.check_exc(*exc_details) + + async with self.exit_stack() as stack: + stack.push_async_exit(_expect_ok) + self.assertIs(stack._exit_callbacks[-1][1], _expect_ok) + cm = ExitCM(_expect_ok) + stack.push_async_exit(cm) + self.assertIs(stack._exit_callbacks[-1][1].__self__, cm) + stack.push_async_exit(_suppress_exc) + self.assertIs(stack._exit_callbacks[-1][1], _suppress_exc) + cm = ExitCM(_expect_exc) + stack.push_async_exit(cm) + self.assertIs(stack._exit_callbacks[-1][1].__self__, cm) + stack.push_async_exit(_expect_exc) + self.assertIs(stack._exit_callbacks[-1][1], _expect_exc) + stack.push_async_exit(_expect_exc) + self.assertIs(stack._exit_callbacks[-1][1], _expect_exc) + 1/0 + + async def test_enter_async_context(self): + class TestCM(object): + async def __aenter__(self): + result.append(1) + async def __aexit__(self, *exc_details): + result.append(3) + + result = [] + cm = TestCM() + + async with AsyncExitStack() as stack: + @stack.push_async_callback # Registered first => cleaned up last + async def _exit(): + result.append(4) + self.assertIsNotNone(_exit) + await stack.enter_async_context(cm) + self.assertIs(stack._exit_callbacks[-1][1].__self__, cm) + result.append(2) + + self.assertEqual(result, [1, 2, 3, 4]) + + async def test_enter_async_context_errors(self): + class LacksEnterAndExit: + pass + class LacksEnter: + async def __aexit__(self, *exc_info): + pass + class LacksExit: + async def __aenter__(self): + pass + + async with self.exit_stack() as stack: + with self.assertRaisesRegex(TypeError, 'asynchronous context manager'): + await stack.enter_async_context(LacksEnterAndExit()) + with self.assertRaisesRegex(TypeError, 'asynchronous context manager'): + await stack.enter_async_context(LacksEnter()) + with self.assertRaisesRegex(TypeError, 'asynchronous context manager'): + await stack.enter_async_context(LacksExit()) + self.assertFalse(stack._exit_callbacks) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + async def test_async_exit_exception_chaining(self): + # Ensure exception chaining matches the reference behaviour + async def raise_exc(exc): + raise exc + + saved_details = None + async def suppress_exc(*exc_details): + nonlocal saved_details + saved_details = exc_details + return True + + try: + async with self.exit_stack() as stack: + stack.push_async_callback(raise_exc, IndexError) + stack.push_async_callback(raise_exc, KeyError) + stack.push_async_callback(raise_exc, AttributeError) + stack.push_async_exit(suppress_exc) + stack.push_async_callback(raise_exc, ValueError) + 1 / 0 + except IndexError as exc: + self.assertIsInstance(exc.__context__, KeyError) + self.assertIsInstance(exc.__context__.__context__, AttributeError) + # Inner exceptions were suppressed + self.assertIsNone(exc.__context__.__context__.__context__) + else: + self.fail("Expected IndexError, but no exception was raised") + # Check the inner exceptions + inner_exc = saved_details[1] + self.assertIsInstance(inner_exc, ValueError) + self.assertIsInstance(inner_exc.__context__, ZeroDivisionError) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + async def test_async_exit_exception_explicit_none_context(self): + # Ensure AsyncExitStack chaining matches actual nested `with` statements + # regarding explicit __context__ = None. + + class MyException(Exception): + pass + + @asynccontextmanager + async def my_cm(): + try: + yield + except BaseException: + exc = MyException() + try: + raise exc + finally: + exc.__context__ = None + + @asynccontextmanager + async def my_cm_with_exit_stack(): + async with self.exit_stack() as stack: + await stack.enter_async_context(my_cm()) + yield stack + + for cm in (my_cm, my_cm_with_exit_stack): + with self.subTest(): + try: + async with cm(): + raise IndexError() + except MyException as exc: + self.assertIsNone(exc.__context__) + else: + self.fail("Expected IndexError, but no exception was raised") + + async def test_instance_bypass_async(self): + class Example(object): pass + cm = Example() + cm.__aenter__ = object() + cm.__aexit__ = object() + stack = self.exit_stack() + with self.assertRaisesRegex(TypeError, 'asynchronous context manager'): + await stack.enter_async_context(cm) + stack.push_async_exit(cm) + self.assertIs(stack._exit_callbacks[-1][1], cm) + + +class TestAsyncNullcontext(unittest.IsolatedAsyncioTestCase): + async def test_async_nullcontext(self): + class C: + pass + c = C() + async with nullcontext(c) as c_in: + self.assertIs(c_in, c) + + +if __name__ == '__main__': + unittest.main()