99import subprocess
1010import sys
1111import tempfile
12+ import threading
1213import time
1314import unittest
1415from unittest import mock
1718 import _remote_debugging
1819 import profiling .sampling
1920 import profiling .sampling .sample
21+ from profiling .sampling ._control import ControlServer
2022 from profiling .sampling .pstats_collector import PstatsCollector
2123 from profiling .sampling .stack_collector import CollapsedStackCollector
2224 from profiling .sampling .sample import SampleProfiler , _is_process_running
2931from test .support import (
3032 requires_remote_subprocess_debugging ,
3133 SHORT_TIMEOUT ,
34+ os_helper ,
35+ socket_helper ,
3236)
3337
3438from .helpers import (
3539 test_subprocess ,
3640 close_and_unlink ,
37- _cleanup_process ,
3841)
3942from .mocks import MockFrameInfo , MockThreadInfo , MockInterpreterInfo
4043
@@ -959,19 +962,20 @@ def test_all_stacks_share_same_base_frame(self):
959962
960963
961964@requires_remote_subprocess_debugging ()
965+ @socket_helper .skip_unless_bind_unix_socket
962966@unittest .skipIf (
963967 sys .platform == "darwin" and os .geteuid () != 0 ,
964968 "macOS profiling requires elevated permissions" ,
965969)
966970class TestControlSocketIntegration (unittest .TestCase ):
967- """End-to-end tests for the --control socket via a real profiler subprocess ."""
971+ """End-to-end tests for the --control socket via in-process sample() ."""
968972
969973 def _send_recv (self , client , request , expected_reply ):
970974 client .sendall (request )
971975 self .assertEqual (client .recv (4096 ), expected_reply )
972976
973977 def test_control_socket_disable_enable_quit_cycle (self ):
974- """Drive disable/enable/quit through a real ControlServer subprocess ."""
978+ """Drive disable/enable/quit through a real ControlServer."""
975979 script = '''
976980import time
977981_test_sock.sendall(b"working")
@@ -980,27 +984,31 @@ def test_control_socket_disable_enable_quit_cycle(self):
980984 time.sleep(0.05)
981985'''
982986 with test_subprocess (script , wait_for_working = True ) as target :
983- tmpdir = tempfile .mkdtemp ()
984- self .addCleanup (shutil .rmtree , tmpdir , ignore_errors = True )
985- socket_path = os .path .join (tmpdir , "tachyon.sock" )
986-
987- profiler = subprocess .Popen (
988- [sys .executable , "-m" , "profiling.sampling" , "attach" ,
989- "--control" , f"unix:{ socket_path } " ,
990- "-d" , "30" ,
991- str (target .process .pid )],
992- stdout = subprocess .PIPE ,
993- stderr = subprocess .PIPE ,
994- )
995- self .addCleanup (_cleanup_process , profiler )
987+ socket_path = socket_helper .create_unix_domain_name ()
988+ self .addCleanup (os_helper .unlink , socket_path )
996989
997- deadline = time .monotonic () + SHORT_TIMEOUT
998- while time .monotonic () < deadline :
999- if os .path .exists (socket_path ):
1000- break
1001- time .sleep (0.05 )
1002- else :
1003- self .fail ("profiler did not create the control socket" )
990+ server = ControlServer (f"unix:{ socket_path } " )
991+ server .start ()
992+ self .addCleanup (server .stop )
993+
994+ exception = [None ]
995+
996+ collector = PstatsCollector (sample_interval_usec = 1000 , skip_idle = False )
997+
998+ def sample_worker ():
999+ try :
1000+ with contextlib .redirect_stdout (io .StringIO ()):
1001+ profiling .sampling .sample .sample (
1002+ target .process .pid ,
1003+ collector ,
1004+ duration_sec = SHORT_TIMEOUT ,
1005+ control_server = server ,
1006+ )
1007+ except Exception as exc :
1008+ exception [0 ] = exc
1009+
1010+ thread = threading .Thread (target = sample_worker , daemon = True )
1011+ thread .start ()
10041012
10051013 with socket .socket (socket .AF_UNIX , socket .SOCK_STREAM ) as client :
10061014 client .settimeout (SHORT_TIMEOUT )
@@ -1010,9 +1018,7 @@ def test_control_socket_disable_enable_quit_cycle(self):
10101018 self ._send_recv (client , b"enable\n " , b"ok\n " )
10111019 self ._send_recv (client , b"quit\n " , b"ok\n " )
10121020
1013- stdout , stderr = profiler .communicate (timeout = SHORT_TIMEOUT )
1014- self .assertEqual (
1015- profiler .returncode , 0 ,
1016- msg = f"stdout={ stdout !r} stderr={ stderr !r} " ,
1017- )
1018- self .assertFalse (os .path .exists (socket_path ))
1021+ thread .join (timeout = SHORT_TIMEOUT )
1022+ self .assertFalse (thread .is_alive (), "sample() did not exit on quit" )
1023+ if exception [0 ] is not None :
1024+ raise exception [0 ]
0 commit comments