Ë
    V.j^„  ã                   óx   — d Z ddlZddlZddlZddlZddlmZ  G d„ de«      Zedk(  r ej                  «        yy)u:  
Tests for greenlet behavior during interpreter shutdown (Py_FinalizeEx).

These tests are organized into four groups:

  A. Core safety (smoke): no crashes with active greenlets at shutdown.
  B. Cleanup semantics: GreenletExit / finally still works during
     normal thread exit (the standard production path).
  C. Atexit "still works" tests: getcurrent() / greenlet construction
     during atexit handlers registered AFTER greenlet import (i.e.
     BEFORE greenlet's cleanup handler in LIFO order) must return
     valid objects â€” verifies the guards don't over-block.
  D. TDD-certified regression tests: getcurrent() must return None
     when called AFTER greenlet's cleanup (GC finalization phase
     or late atexit phase).  These tests fail on greenlet 3.3.2
     and pass with the fix across Python 3.10-3.14.
é    N)ÚTestCasec                   óœ   — e Zd Zd„ Zd„ Zd„ Zd„ Zd„ Zd„ Zd„ Z	d„ Z
d	„ Zd
„ Zd„ Zd„ Zd„ Zd„ Zd„ Zd„ Zd„ Zd„ Zd„ Zd„ Zd„ Zd„ Zd„ Zd„ Zy)ÚTestInterpreterShutdownc                 óÈ   — t        j                  |«      }t        j                  t        j
                  d|gdddd¬«      }|j                  |j                  |j                  fS )z˜
        Run a Python script in a subprocess that exercises greenlet
        during interpreter shutdown. Returns (returncode, stdout, stderr).
        z-cTé   F)Úcapture_outputÚtextÚtimeoutÚcheck)	ÚtextwrapÚdedentÚ
subprocessÚrunÚsysÚ
executableÚ
returncodeÚstdoutÚstderr)ÚselfÚscript_bodyÚfull_scriptÚresults       újC:\xampp\htdocs\tradingbinance\backend\.venv\Lib\site-packages\greenlet/tests/test_interpreter_shutdown.pyÚ_run_shutdown_scriptz,TestInterpreterShutdown._run_shutdown_script   sW   € ô
 —o‘o kÓ2ˆÜ—‘Ü�^‰^˜T ;Ð/ØØØØô
ˆð × Ñ  &§-¡-°·±Ð>Ð>ó    c           	      óˆ   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       y )NaT              import greenlet

            def worker():
                greenlet.getcurrent().parent.switch("from worker")
                return "done"

            g = greenlet.greenlet(worker)
            result = g.switch()
            assert result == "from worker", result
            print("OK: exiting with active greenlet")
        r   úProcess crashed (rc=ú):
z OK: exiting with active greenlet©r   ÚassertEqualÚassertIn©r   Úrcr   r   s       r   Ú)test_active_greenlet_at_shutdown_no_crashzATestInterpreterShutdown.test_active_greenlet_at_shutdown_no_crash0   sU   € ð "×6Ñ6ð 8ó ÑˆˆF�Fð 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰Ð8¸&ÕAr   c           	      óˆ   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       y )Na­              import greenlet

            def worker(name):
                greenlet.getcurrent().parent.switch(f"hello from {name}")
                return "done"

            greenlets = []
            for i in range(10):
                g = greenlet.greenlet(worker)
                result = g.switch(f"g{i}")
                greenlets.append(g)

            print(f"OK: {len(greenlets)} active greenlets at shutdown")
        r   r   r   z#OK: 10 active greenlets at shutdownr   r"   s       r   Ú*test_multiple_active_greenlets_at_shutdownzBTestInterpreterShutdown.test_multiple_active_greenlets_at_shutdownH   sU   € ð "×6Ñ6ð 8ó ÑˆˆF�Fð 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰Ð;¸VÕDr   c           	      óˆ   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       y )Naâ              import greenlet

            def inner():
                greenlet.getcurrent().parent.switch("inner done")

            def outer():
                g_inner = greenlet.greenlet(inner)
                g_inner.switch()
                greenlet.getcurrent().parent.switch("outer done")

            g = greenlet.greenlet(outer)
            result = g.switch()
            assert result == "outer done", result
            print("OK: nested greenlets at shutdown")
        r   r   r   z OK: nested greenlets at shutdownr   r"   s       r   Ú!test_nested_greenlets_at_shutdownz9TestInterpreterShutdown.test_nested_greenlets_at_shutdown^   sU   € ð "×6Ñ6ð 8ó ÑˆˆF�Fð  	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰Ð8¸&ÕAr   c           	      óˆ   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       y )NaÅ              import greenlet
            import threading

            results = []

            def thread_worker():
                def greenlet_func():
                    greenlet.getcurrent().parent.switch("from thread greenlet")
                    return "done"

                g = greenlet.greenlet(greenlet_func)
                val = g.switch()
                results.append((g, val))

            threads = []
            for _ in range(3):
                t = threading.Thread(target=thread_worker)
                t.start()
                threads.append(t)

            for t in threads:
                t.join()

            print(f"OK: {len(results)} threaded greenlets at shutdown")
        r   r   r   z$OK: 3 threaded greenlets at shutdownr   r"   s       r   Ú#test_threaded_greenlets_at_shutdownz;TestInterpreterShutdown.test_threaded_greenlets_at_shutdownt   sU   € ð "×6Ñ6ð 8ó ÑˆˆF�Fð4 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰Ð<¸fÕEr   c           	      ó¬   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       | j                  d|«       y )Na4              import os
            import threading
            import greenlet

            _write = os.write

            def thread_func():
                def worker(_w=_write,
                           _GreenletExit=greenlet.GreenletExit):
                    try:
                        greenlet.getcurrent().parent.switch("suspended")
                    except _GreenletExit:
                        _w(1, b"CLEANUP: GreenletExit caught\n")
                        raise

                g = greenlet.greenlet(worker)
                g.switch()
                # Thread exits with active greenlet -> thread-state
                # cleanup triggers GreenletExit

            t = threading.Thread(target=thread_func)
            t.start()
            t.join()
            print("OK: thread cleanup done")
        r   r   r   úOK: thread cleanup donezCLEANUP: GreenletExit caughtr   r"   s       r   Ú(test_greenlet_cleanup_during_thread_exitz@TestInterpreterShutdown.test_greenlet_cleanup_during_thread_exitž   sd   € ð "×6Ñ6ð 8ó ÑˆˆF�Fð4 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰Ð/°Ô8Ø�‰Ð4°fÕ=r   c           	      ó¬   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       | j                  d|«       y )NaR              import os
            import threading
            import greenlet

            _write = os.write

            def thread_func():
                def worker(_w=_write):
                    try:
                        greenlet.getcurrent().parent.switch("suspended")
                    finally:
                        _w(1, b"FINALLY: cleanup executed\n")

                g = greenlet.greenlet(worker)
                g.switch()

            t = threading.Thread(target=thread_func)
            t.start()
            t.join()
            print("OK: thread cleanup done")
        r   r   r   r,   zFINALLY: cleanup executedr   r"   s       r   Ú%test_finally_block_during_thread_exitz=TestInterpreterShutdown.test_finally_block_during_thread_exitÂ   sd   € ð "×6Ñ6ð 8ó ÑˆˆF�Fð, 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰Ð/°Ô8Ø�‰Ð1°6Õ:r   c           	      óˆ   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       y )Naï              import sys
            import greenlet

            cleanup_count = 0

            def worker(idx):
                global cleanup_count
                try:
                    greenlet.getcurrent().parent.switch(f"ready-{idx}")
                except greenlet.GreenletExit:
                    cleanup_count += 1
                    raise

            greenlets = []
            for i in range(50):
                g = greenlet.greenlet(worker)
                result = g.switch(i)
                greenlets.append(g)

            print(f"OK: {len(greenlets)} greenlets about to shut down")
            # Note: we can't easily print cleanup_count during shutdown
            # since it happens after the main module's code runs.
        r   r   r   z#OK: 50 greenlets about to shut downr   r"   s       r   Ú,test_many_greenlets_with_cleanup_at_shutdownzDTestInterpreterShutdown.test_many_greenlets_with_cleanup_at_shutdownß   sU   € ð "×6Ñ6ð 8ó ÑˆˆF�Fð0 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰Ð;¸VÕDr   c           	      óˆ   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       y )Na¨              import greenlet

            def level(depth, max_depth):
                if depth < max_depth:
                    g = greenlet.greenlet(level)
                    g.switch(depth + 1, max_depth)
                greenlet.getcurrent().parent.switch(f"depth-{depth}")

            g = greenlet.greenlet(level)
            result = g.switch(0, 10)
            print(f"OK: nested to depth 10, got {result}")
        r   r   r   zOK: nested to depth 10r   r"   s       r   Ú(test_deeply_nested_greenlets_at_shutdownz@TestInterpreterShutdown.test_deeply_nested_greenlets_at_shutdownþ   sU   € ð "×6Ñ6ð 8ó ÑˆˆF�Fð 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰Ð.°Õ7r   c           	      óˆ   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       y )Na              import greenlet

            def worker():
                try:
                    raise ValueError("test error")
                except ValueError:
                    # Suspend while an exception is active on the stack
                    greenlet.getcurrent().parent.switch("suspended with exc")
                return "done"

            g = greenlet.greenlet(worker)
            result = g.switch()
            assert result == "suspended with exc"
            print("OK: greenlet with active exception at shutdown")
        r   r   r   z.OK: greenlet with active exception at shutdownr   r"   s       r   Ú(test_greenlet_with_traceback_at_shutdownz@TestInterpreterShutdown.test_greenlet_with_traceback_at_shutdown  sU   € ð "×6Ñ6ð 8ó ÑˆˆF�Fð  	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰ÐFÈÕOr   c           	      ó®   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       | j                  d|d«       y )NaÙ              import atexit
            import greenlet

            def call_getcurrent_at_exit():
                try:
                    g = greenlet.getcurrent()
                    if g is not None and type(g).__name__ == 'greenlet':
                        print(f"OK: getcurrent returned valid greenlet")
                    elif g is None:
                        print("FAIL: getcurrent returned None (over-blocked)")
                    else:
                        print(f"FAIL: unexpected {g!r}")
                except Exception as e:
                    print(f"OK: getcurrent raised {type(e).__name__}: {e}")

            atexit.register(call_getcurrent_at_exit)
            print("OK: atexit registered")
        r   r   r   úOK: atexit registeredú&OK: getcurrent returned valid greenletúigetcurrent() should return a valid greenlet when called before greenlet's cleanup handler (LIFO ordering)r   r"   s       r   Ú&test_getcurrent_during_atexit_no_crashz>TestInterpreterShutdown.test_getcurrent_during_atexit_no_crash.  sm   € ð
 "×6Ñ6ð 8ó ÑˆˆF�Fð& 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰Ð-¨vÔ6Ø�‰Ð>ÀðJõ	Kr   c           	      óˆ   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       y )Na¦              import atexit
            import greenlet

            def check_at_exit():
                try:
                    result = greenlet.gettrace()
                    print(f"OK: gettrace returned {result!r}")
                except Exception as e:
                    print(f"OK: gettrace raised {type(e).__name__}: {e}")

            atexit.register(check_at_exit)
            print("OK: registered")
        r   r   r   úOK: registeredr   r"   s       r   Ú$test_gettrace_during_atexit_no_crashz<TestInterpreterShutdown.test_gettrace_during_atexit_no_crashL  óU   € ð "×6Ñ6ð 8ó ÑˆˆF�Fð 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰Ð&¨Õ/r   c           	      óˆ   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       y )Na¤              import atexit
            import greenlet

            def check_at_exit():
                try:
                    greenlet.settrace(lambda *args: None)
                    print("OK: settrace succeeded")
                except Exception as e:
                    print(f"OK: settrace raised {type(e).__name__}: {e}")

            atexit.register(check_at_exit)
            print("OK: registered")
        r   r   r   r<   r   r"   s       r   Ú$test_settrace_during_atexit_no_crashz<TestInterpreterShutdown.test_settrace_during_atexit_no_crash`  r>   r   c           	      ó®   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       | j                  d|d«       y )Naò              import atexit
            import greenlet

            def worker():
                greenlet.getcurrent().parent.switch("ready")

            greenlets = []
            for i in range(5):
                g = greenlet.greenlet(worker)
                result = g.switch()
                greenlets.append(g)

            def check_at_exit():
                try:
                    g = greenlet.getcurrent()
                    if g is not None and type(g).__name__ == 'greenlet':
                        print(f"OK: getcurrent returned valid greenlet")
                    elif g is None:
                        print("FAIL: getcurrent returned None (over-blocked)")
                    else:
                        print(f"FAIL: unexpected {g!r}")
                except Exception as e:
                    print(f"OK: getcurrent raised {type(e).__name__}: {e}")

            atexit.register(check_at_exit)
            print(f"OK: {len(greenlets)} active greenlets, atexit registered")
        r   r   r   z)OK: 5 active greenlets, atexit registeredr8   r9   r   r"   s       r   Ú3test_getcurrent_with_active_greenlets_during_atexitzKTestInterpreterShutdown.test_getcurrent_with_active_greenlets_during_atexitt  sm   € ð
 "×6Ñ6ð 8ó ÑˆˆF�Fð8 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰ÐAÀ6ÔJØ�‰Ð>ÀðJõ	Kr   c           	      ó¬   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       | j                  d|«       y )Naˆ              import atexit
            import greenlet

            def create_greenlets_at_exit():
                try:
                    def noop():
                        pass
                    g = greenlet.greenlet(noop)
                    if g is not None:
                        print(f"OK: created greenlet successfully")
                    else:
                        print("FAIL: greenlet() returned None")
                except Exception as e:
                    print(f"OK: construction raised {type(e).__name__}: {e}")

            atexit.register(create_greenlets_at_exit)
            print("OK: atexit registered")
        r   r   r   r7   z!OK: created greenlet successfullyr   r"   s       r   Ú1test_greenlet_construction_during_atexit_no_crashzITestInterpreterShutdown.test_greenlet_construction_during_atexit_no_crash›  sd   € ð "×6Ñ6ð 8ó ÑˆˆF�Fð& 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰Ð-¨vÔ6Ø�‰Ð9¸6ÕBr   c           	      óˆ   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       y )Naf              import atexit
            import greenlet

            def worker():
                greenlet.getcurrent().parent.switch("ready")

            greenlets = []
            for i in range(10):
                g = greenlet.greenlet(worker)
                g.switch()
                greenlets.append(g)

            def create_at_exit():
                try:
                    new_greenlets = []
                    for i in range(5):
                        g = greenlet.greenlet(lambda: None)
                        new_greenlets.append(g)
                    print(f"OK: created {len(new_greenlets)} greenlets at exit")
                except Exception as e:
                    print(f"OK: raised {type(e).__name__}: {e}")

            atexit.register(create_at_exit)
            print(f"OK: {len(greenlets)} active greenlets, atexit registered")
        r   r   r   ú*OK: 10 active greenlets, atexit registeredr   r"   s       r   Ú>test_greenlet_construction_with_active_greenlets_during_atexitzVTestInterpreterShutdown.test_greenlet_construction_with_active_greenlets_during_atexit¶  sU   € ð
 "×6Ñ6ð 8ó ÑˆˆF�Fð4 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰ÐBÀFÕKr   c           	      óˆ   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       y )Na               import atexit
            import greenlet
            import threading

            cross_thread_refs = []

            def thread_worker():
                # Create greenlets in this thread
                def gl_body():
                    greenlet.getcurrent().parent.switch("ready")
                for _ in range(20):
                    g = greenlet.greenlet(gl_body)
                    g.switch()
                    cross_thread_refs.append(g)

            t = threading.Thread(target=thread_worker)
            t.start()
            t.join()

            # Dropping these references in the main thread
            # causes them to be added to the main thread's
            # deleteme list (deferred cross-thread dealloc).
            cross_thread_refs.clear()

            def create_at_exit():
                try:
                    g = greenlet.greenlet(lambda: None)
                    print(f"OK: created greenlet at exit {g!r}")
                except Exception as e:
                    print(f"OK: raised {type(e).__name__}: {e}")

            atexit.register(create_at_exit)
            print("OK: cross-thread setup done, atexit registered")
        r   r   r   z.OK: cross-thread setup done, atexit registeredr   r"   s       r   ÚCtest_greenlet_construction_with_cross_thread_deleteme_during_atexitz[TestInterpreterShutdown.test_greenlet_construction_with_cross_thread_deleteme_during_atexitØ  sV   € ð "×6Ñ6ð "8ó "ÑˆˆF�FðF 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰ÐFÈÕOr   c           	      ó¬   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       | j                  d|«       y )Na˜              import gc
            import os
            import greenlet

            gc.disable()

            class CleanupChecker:
                def __del__(self):
                    try:
                        try:
                            greenlet.getcurrent()
                        except RuntimeError:
                            os.write(1, b"GUARDED: getcurrent=None\n")
                        else:
                            os.write(1, b"UNGUARDED: getcurrent="
                                     + type(cur).__name__.encode() + b"\n")
                    except Exception as e:
                        os.write(1, b"EXCEPTION: " + str(e).encode() + b"\n")

            # Reference cycle: only collected during Py_FinalizeEx GC pass
            a = CleanupChecker()
            b = {"ref": a}
            a._cycle = b
            del a, b

            print("OK: deferred cycle created")
        r   r   r   zOK: deferred cycle createdúGUARDED: getcurrent=Noner   r"   s       r   Ú3test_getcurrent_returns_none_during_gc_finalizationzKTestInterpreterShutdown.test_getcurrent_returns_none_during_gc_finalization  sd   € ð "×6Ñ6ð 8ó ÑˆˆF�Fð8 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰Ð2°FÔ;Ø�‰Ð0°&Õ9r   c           	      ó¬   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       | j                  d|«       y )NaÓ              import gc
            import os
            import greenlet

            gc.disable()

            class CleanupChecker:
                def __del__(self):
                    try:
                        try:
                            greenlet.getcurrent()
                        except RuntimeError:
                            os.write(1, b"GUARDED: getcurrent=None\n")
                        else:
                            os.write(1, b"UNGUARDED: getcurrent="
                                     + type(cur).__name__.encode() + b"\n")
                    except Exception as e:
                        os.write(1, b"EXCEPTION: " + str(e).encode() + b"\n")

            # Create active greenlets
            greenlets = []
            for _ in range(10):
                def worker():
                    greenlet.getcurrent().parent.switch("suspended")
                g = greenlet.greenlet(worker)
                g.switch()
                greenlets.append(g)

            # Reference cycle deferred to Py_FinalizeEx
            a = CleanupChecker()
            b = {"ref": a}
            a._cycle = b
            del a, b

            print(f"OK: {len(greenlets)} active greenlets, cycle deferred")
        r   r   r   z'OK: 10 active greenlets, cycle deferredrK   r   r"   s       r   ÚItest_getcurrent_returns_none_during_gc_finalization_with_active_greenletszaTestInterpreterShutdown.test_getcurrent_returns_none_during_gc_finalization_with_active_greenlets>  se   € ð "×6Ñ6ð $8ó $ÑˆˆF�FðJ 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰Ð?ÀÔHØ�‰Ð0°&Õ9r   c           	      ó¬   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       | j                  d|«       y )Na›              import gc
            import os
            import threading
            import greenlet

            gc.disable()

            class CleanupChecker:
                def __del__(self):
                    try:
                        try:
                            greenlet.getcurrent()
                        except RuntimeError:
                            os.write(1, b"GUARDED: getcurrent=None\n")
                        else:
                            os.write(1, b"UNGUARDED: getcurrent="
                                     + type(cur).__name__.encode() + b"\n")
                    except Exception as e:
                        os.write(1, b"EXCEPTION: " + str(e).encode() + b"\n")

            # Create cross-thread greenlet references
            cross_refs = []
            def thread_fn():
                for _ in range(20):
                    def body():
                        greenlet.getcurrent().parent.switch("x")
                    g = greenlet.greenlet(body)
                    g.switch()
                    cross_refs.append(g)
            t = threading.Thread(target=thread_fn)
            t.start()
            t.join()
            cross_refs.clear()

            # Reference cycle deferred to Py_FinalizeEx
            a = CleanupChecker()
            b = {"ref": a}
            a._cycle = b
            del a, b

            print("OK: cross-thread cleanup + cycle deferred")
        r   r   r   z)OK: cross-thread cleanup + cycle deferredrK   r   r"   s       r   Ú@test_getcurrent_returns_none_during_gc_finalization_cross_threadzXTestInterpreterShutdown.test_getcurrent_returns_none_during_gc_finalization_cross_threadk  se   € ð "×6Ñ6ð *8ó *ÑˆˆF�FðV 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰ÐAÀ6ÔJØ�‰Ð0°&Õ9r   c           	      ó¬   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       | j                  d|«       y )Nu�              import atexit
            import os

            def late_checker():
                try:
                    import greenlet
                    cur = greenlet.getcurrent()
                    if cur is None:
                        os.write(1, b"GUARDED: getcurrent=None\n")
                    else:
                        os.write(1, b"UNGUARDED: getcurrent="
                                 + type(cur).__name__.encode() + b"\n")
                except Exception as e:
                    os.write(1, b"EXCEPTION: " + str(e).encode() + b"\n")

            # Register BEFORE importing greenlet.  LIFO order:
            # greenlet's handler (registered at import) runs FIRST,
            # late_checker runs SECOND â€” seeing the flag already set.
            atexit.register(late_checker)

            import greenlet
            print("OK: atexit registered before greenlet import")
        r   r   r   z,OK: atexit registered before greenlet importÚ	UNGUARDEDr   r"   s       r   Ú0test_getcurrent_returns_none_during_atexit_phasezHTestInterpreterShutdown.test_getcurrent_returns_none_during_atexit_phase¶  sc   € ð "×6Ñ6ð 8ó ÑˆˆF�Fð0 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰ÐDÀfÔMØ�‰�k 6Õ*r   c           	      ó¬   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       | j                  d|«       y )NaÛ              import atexit
            import os

            def late_checker():
                try:
                    import greenlet
                    cur = greenlet.getcurrent()
                    if cur is None:
                        os.write(1, b"GUARDED: getcurrent=None\n")
                    else:
                        os.write(1, b"UNGUARDED: getcurrent="
                                 + type(cur).__name__.encode() + b"\n")
                except Exception as e:
                    os.write(1, b"EXCEPTION: " + str(e).encode() + b"\n")

            atexit.register(late_checker)

            import greenlet

            greenlets = []
            for _ in range(10):
                def worker():
                    greenlet.getcurrent().parent.switch("parked")
                g = greenlet.greenlet(worker)
                g.switch()
                greenlets.append(g)

            print(f"OK: {len(greenlets)} active greenlets, atexit registered")
        r   r   r   rF   rR   r   r"   s       r   ÚFtest_getcurrent_returns_none_during_atexit_phase_with_active_greenletsz^TestInterpreterShutdown.test_getcurrent_returns_none_during_atexit_phase_with_active_greenlets×  sa   € à!×6Ñ6ð 8ó ÑˆˆF�Fð< 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰ÐBÀFÔKØ�‰�k 6Õ*r   c           	      ó¬   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       | j                  d|«       y )Nañ              import greenlet
            from greenlet.tests import _test_extension

            class WithDel:
                # must cache the method we want, because by the time we
                # run, module globals may have been cleaned up.
                def __del__(self, gc=_test_extension.getcurrent_api):
                    print('Destructor running')
                    gc() # Should print an unraisable RuntimeException

            greenlet._greenlet.with_del = WithDel()
        r   r   r   úDestructor runningz)RuntimeError: greenlet is being finalizedr   r"   s       r   Ú5test_api_getcurrent_no_system_error_at_module_gc_timezMTestInterpreterShutdown.test_api_getcurrent_no_system_error_at_module_gc_timeû  sd   € ð "×6Ñ6ð 8ó ÑˆˆF�Fð 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰Ð*¨FÔ3Ø�‰ÐAÀ6ÕJr   c           	      ó¬   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       | j                  d|«       y )NaÑ              import greenlet
            from greenlet.tests import _test_extension

            gs = []
            # must cache the objects we want, because by the time we
            # run, module globals may have been cleaned up.
            def do_it(gs=gs):
                print('current', gs)
                gs[0].parent.switch(1)


            gs.append(greenlet.greenlet(do_it))
            gs.append(greenlet.greenlet(do_it))
            gs[1].switch()

            class WithDel:
                def __del__(self, gs=gs):
                    print('Destructor running')
                    r = gs[0].switch()
                    print('Result', r)

            greenlet._greenlet.with_del = WithDel()
        r   r   r   rW   zResult 1r   r"   s       r   Ú&test_switch_no_error_at_module_gc_timez>TestInterpreterShutdown.test_switch_no_error_at_module_gc_time  sc   € ð "×6Ñ6ð 8ó ÑˆˆF�Fð0 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰Ð*¨FÔ3Ø�‰�j &Õ)r   N)Ú__name__Ú
__module__Ú__qualname__r   r$   r&   r(   r*   r-   r/   r1   r3   r5   r:   r=   r@   rB   rD   rG   rI   rL   rN   rP   rS   rU   rX   rZ   © r   r   r   r      s�   „ ò?ò&Bò0Eò,Bò,FòT">òH;ò:Eò>8ò(Pò8Kò<0ò(0ò(%KòNCò6 LòD+Pò|&:òP+:òZ4:òV+òB"+òHKó2*r   r   Ú__main__)
Ú__doc__r   r   Úunittestr   Úgreenlet.testsr   r   r[   Úmainr^   r   r   Ú<module>rd      sD   ðñó" Û Û Û å #ôV*˜hô V*ðr ˆzÒØ€H‡M�M…Oð r   