@@ -3955,109 +3955,52 @@ def test_get_stats_disabled_raises(self):
39553955class TestMetadataDegradation (RemoteInspectionTestBase ):
39563956 """Tests for graceful degradation of oversized code-object metadata."""
39573957
3958- @contextmanager
3959- def _running_target (self , script_body ):
3960- """Run a target script (socket handshake prepended), yield (process, socket)."""
3961- port = find_unused_port ()
3962- script = (
3963- textwrap .dedent (
3964- f"""\
3965- import time, socket
3966- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
3967- sock.connect(('localhost', { port } ))
3968- """
3969- )
3970- + textwrap .dedent (script_body )
3971- )
3972-
3973- with os_helper .temp_dir () as work_dir :
3974- script_dir = os .path .join (work_dir , "script_pkg" )
3975- os .mkdir (script_dir )
3976-
3977- server_socket = _create_server_socket (port )
3978- script_name = _make_test_script (script_dir , "script" , script )
3979- client_socket = None
3980-
3981- try :
3982- with _managed_subprocess ([sys .executable , script_name ]) as p :
3983- client_socket , _ = server_socket .accept ()
3984- server_socket .close ()
3985- server_socket = None
3986- yield p , client_socket
3987- finally :
3988- _cleanup_sockets (client_socket , server_socket )
3989-
3990- def _sample_until_frame (self , pid , predicate ):
3991- """Sample until a frame matching predicate appears; return that frame."""
3992- unwinder = RemoteUnwinder (pid , all_threads = True )
3993- traces = _get_stack_trace_with_retry (
3994- unwinder ,
3995- condition = lambda t : self ._find_frame_in_trace (t , predicate )
3996- is not None ,
3997- )
3998- return self ._find_frame_in_trace (traces , predicate )
3999-
40003958 def test_long_qualname_truncated_not_dropped (self ):
40013959 """A qualname longer than 1024 chars is truncated instead of
40023960 failing the whole sample."""
4003- script_body = """\
4004- src = ("def " + "f" * 1100 + "():\\ n"
4005- " sock.sendall(b'ready')\\ n"
4006- " time.sleep(10_000)\\ n")
4007- ns = {"sock": sock, "time": time}
4008- exec(src, ns)
4009- ns["f" * 1100]()
4010- """
4011- with self ._running_target (script_body ) as (p , client_socket ):
4012- _wait_for_signal (client_socket , b"ready" )
4013- frame = self ._sample_until_frame (
4014- p .pid , lambda f : f .funcname .startswith ("fff" )
4015- )
4016- self .assertEqual (frame .funcname , "f" * 1024 )
3961+ name = "f" * 1100
3962+ src = f"def { name } (sample):\n return sample()\n "
3963+ ns = {}
3964+ exec (src , ns )
3965+
3966+ trace = ns [name ](RemoteUnwinder (os .getpid ()).get_stack_trace )
3967+ frame = self ._find_frame_in_trace (
3968+ trace , lambda f : f .funcname .startswith ("fff" )
3969+ )
3970+ self .assertIsNotNone (frame )
3971+ self .assertEqual (frame .funcname , "f" * 1024 )
40173972
40183973 def test_long_filename_truncated (self ):
40193974 """A filename longer than 1024 chars is truncated instead of
40203975 failing the whole sample."""
4021- script_body = """\
4022- src = ("def g():\\ n"
4023- " sock.sendall(b'ready')\\ n"
4024- " time.sleep(10_000)\\ n")
4025- ns = {"sock": sock, "time": time}
4026- exec(compile(src, "x" * 1500 + ".py", "exec"), ns)
4027- ns["g"]()
4028- """
4029- with self ._running_target (script_body ) as (p , client_socket ):
4030- _wait_for_signal (client_socket , b"ready" )
4031- frame = self ._sample_until_frame (
4032- p .pid , lambda f : f .funcname == "g"
4033- )
4034- self .assertEqual (frame .filename , "x" * 1024 )
3976+ src = "def g(sample):\n return sample()\n "
3977+ ns = {}
3978+ exec (compile (src , "x" * 1500 + ".py" , "exec" ), ns )
3979+
3980+ trace = ns ["g" ](RemoteUnwinder (os .getpid ()).get_stack_trace )
3981+ frame = self ._find_frame_in_trace (trace , lambda f : f .funcname == "g" )
3982+ self .assertIsNotNone (frame )
3983+ self .assertEqual (frame .filename , "x" * 1024 )
40353984
40363985 def test_oversized_linetable_degrades_to_no_location (self ):
40373986 """A linetable over MAX_LINETABLE_SIZE degrades to a frame without
40383987 location instead of failing the whole sample."""
4039- script_body = """\
4040- body = " x = 1\\ n" * 16_000
4041- src = ("def big():\\ n" + body + "\\ n"
4042- " sock.sendall(b'ready')\\ n"
4043- " time.sleep(10_000)\\ n")
4044- ns = {"sock": sock, "time": time}
4045- exec(compile(src, "big_linetable.py", "exec"), ns)
4046- sock.sendall(b"lt:%d\\ n" % len(ns["big"].__code__.co_linetable))
4047- ns["big"]()
4048- """
4049- with self ._running_target (script_body ) as (p , client_socket ):
4050- buffer = _wait_for_signal (client_socket , [b"lt:" , b"ready" ])
4051- linetable_size = int (
4052- buffer .partition (b"lt:" )[2 ].partition (b"\n " )[0 ]
4053- )
4054- self .assertGreater (linetable_size , 64 * 1024 )
4055-
4056- frame = self ._sample_until_frame (
4057- p .pid , lambda f : f .funcname == "big"
4058- )
4059- self .assertIsNone (frame .location )
4060- self .assertEqual (frame .filename , "big_linetable.py" )
3988+ src = (
3989+ "def big(sample):\n "
3990+ + " x = 1\n " * 16_000
3991+ + " return sample()\n "
3992+ )
3993+ ns = {}
3994+ exec (compile (src , "big_linetable.py" , "exec" ), ns )
3995+ big = ns ["big" ]
3996+ self .assertGreater (len (big .__code__ .co_linetable ), 64 * 1024 )
3997+
3998+ trace = big (RemoteUnwinder (os .getpid ()).get_stack_trace )
3999+ frame = self ._find_frame_in_trace (
4000+ trace , lambda f : f .funcname == "big"
4001+ )
4002+ self .assertIsNone (frame .location )
4003+ self .assertEqual (frame .filename , "big_linetable.py" )
40614004
40624005 @unittest .skipIf (
40634006 sys .platform == "win32" ,
@@ -4066,29 +4009,51 @@ def test_oversized_linetable_degrades_to_no_location(self):
40664009 def test_dead_process_raises_not_degrades (self ):
40674010 """Death of the target raises ProcessLookupError instead of
40684011 degrading to synthetic frames."""
4069- script_body = """\
4012+ port = find_unused_port ()
4013+ script = textwrap .dedent (
4014+ f"""\
4015+ import time, socket
4016+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
4017+ sock.connect(('localhost', { port } ))
40704018 sock.sendall(b"ready")
40714019 time.sleep(10_000)
40724020 """
4073- with self ._running_target (script_body ) as (p , client_socket ):
4074- _wait_for_signal (client_socket , b"ready" )
4075- unwinder = RemoteUnwinder (p .pid , all_threads = True )
4076- _get_stack_trace_with_retry (unwinder )
4021+ )
40774022
4078- p .kill ()
4079- p .wait ()
4023+ with os_helper .temp_dir () as work_dir :
4024+ script_dir = os .path .join (work_dir , "script_pkg" )
4025+ os .mkdir (script_dir )
40804026
4081- for _ in busy_retry (SHORT_TIMEOUT , error = False ):
4082- try :
4083- unwinder .get_stack_trace ()
4084- except ProcessLookupError :
4085- break
4086- except RuntimeError :
4087- continue
4088- else :
4089- self .fail (
4090- "ProcessLookupError never raised for dead process"
4091- )
4027+ server_socket = _create_server_socket (port )
4028+ script_name = _make_test_script (script_dir , "script" , script )
4029+ client_socket = None
4030+
4031+ try :
4032+ with _managed_subprocess ([sys .executable , script_name ]) as p :
4033+ client_socket , _ = server_socket .accept ()
4034+ server_socket .close ()
4035+ server_socket = None
4036+ _wait_for_signal (client_socket , b"ready" )
4037+
4038+ unwinder = RemoteUnwinder (p .pid , all_threads = True )
4039+ _get_stack_trace_with_retry (unwinder )
4040+
4041+ p .kill ()
4042+ p .wait ()
4043+
4044+ for _ in busy_retry (SHORT_TIMEOUT , error = False ):
4045+ try :
4046+ unwinder .get_stack_trace ()
4047+ except ProcessLookupError :
4048+ break
4049+ except RuntimeError :
4050+ continue
4051+ else :
4052+ self .fail (
4053+ "ProcessLookupError never raised for dead process"
4054+ )
4055+ finally :
4056+ _cleanup_sockets (client_socket , server_socket )
40924057
40934058
40944059if __name__ == "__main__" :
0 commit comments