Skip to content

Commit a52c39e

Browse files
authored
pythongh-135953: Refactor test_sampling_profiler into multiple files (python#141689)
1 parent f46785f commit a52c39e

File tree

11 files changed

+3947
-3360
lines changed

11 files changed

+3947
-3360
lines changed

Lib/test/test_profiling/test_sampling_profiler.py

Lines changed: 0 additions & 3360 deletions
This file was deleted.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"""Tests for the sampling profiler (profiling.sampling)."""
2+
3+
import os
4+
from test.support import load_package_tests
5+
6+
7+
def load_tests(*args):
8+
"""Load all tests from this subpackage."""
9+
return load_package_tests(os.path.dirname(__file__), *args)
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
"""Helper utilities for sampling profiler tests."""
2+
3+
import contextlib
4+
import socket
5+
import subprocess
6+
import sys
7+
import unittest
8+
from collections import namedtuple
9+
10+
from test.support import SHORT_TIMEOUT
11+
from test.support.socket_helper import find_unused_port
12+
from test.support.os_helper import unlink
13+
14+
15+
PROCESS_VM_READV_SUPPORTED = False
16+
17+
try:
18+
from _remote_debugging import PROCESS_VM_READV_SUPPORTED # noqa: F401
19+
import _remote_debugging # noqa: F401
20+
except ImportError:
21+
raise unittest.SkipTest(
22+
"Test only runs when _remote_debugging is available"
23+
)
24+
else:
25+
import profiling.sampling # noqa: F401
26+
from profiling.sampling.sample import SampleProfiler # noqa: F401
27+
28+
29+
skip_if_not_supported = unittest.skipIf(
30+
(
31+
sys.platform != "darwin"
32+
and sys.platform != "linux"
33+
and sys.platform != "win32"
34+
),
35+
"Test only runs on Linux, Windows and MacOS",
36+
)
37+
38+
SubprocessInfo = namedtuple("SubprocessInfo", ["process", "socket"])
39+
40+
41+
@contextlib.contextmanager
42+
def test_subprocess(script):
43+
"""Context manager to create a test subprocess with socket synchronization.
44+
45+
Args:
46+
script: Python code to execute in the subprocess
47+
48+
Yields:
49+
SubprocessInfo: Named tuple with process and socket objects
50+
"""
51+
# Find an unused port for socket communication
52+
port = find_unused_port()
53+
54+
# Inject socket connection code at the beginning of the script
55+
socket_code = f"""
56+
import socket
57+
_test_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
58+
_test_sock.connect(('localhost', {port}))
59+
_test_sock.sendall(b"ready")
60+
"""
61+
62+
# Combine socket code with user script
63+
full_script = socket_code + script
64+
65+
# Create server socket to wait for process to be ready
66+
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
67+
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
68+
server_socket.bind(("localhost", port))
69+
server_socket.settimeout(SHORT_TIMEOUT)
70+
server_socket.listen(1)
71+
72+
proc = subprocess.Popen(
73+
[sys.executable, "-c", full_script],
74+
stdout=subprocess.DEVNULL,
75+
stderr=subprocess.DEVNULL,
76+
)
77+
78+
client_socket = None
79+
try:
80+
# Wait for process to connect and send ready signal
81+
client_socket, _ = server_socket.accept()
82+
server_socket.close()
83+
response = client_socket.recv(1024)
84+
if response != b"ready":
85+
raise RuntimeError(
86+
f"Unexpected response from subprocess: {response}"
87+
)
88+
89+
yield SubprocessInfo(proc, client_socket)
90+
finally:
91+
if client_socket is not None:
92+
client_socket.close()
93+
if proc.poll() is None:
94+
proc.kill()
95+
proc.wait()
96+
97+
98+
def close_and_unlink(file):
99+
"""Close a file and unlink it from the filesystem."""
100+
file.close()
101+
unlink(file.name)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""Mock classes for sampling profiler tests."""
2+
3+
4+
class MockFrameInfo:
5+
"""Mock FrameInfo for testing since the real one isn't accessible."""
6+
7+
def __init__(self, filename, lineno, funcname):
8+
self.filename = filename
9+
self.lineno = lineno
10+
self.funcname = funcname
11+
12+
def __repr__(self):
13+
return f"MockFrameInfo(filename='{self.filename}', lineno={self.lineno}, funcname='{self.funcname}')"
14+
15+
16+
class MockThreadInfo:
17+
"""Mock ThreadInfo for testing since the real one isn't accessible."""
18+
19+
def __init__(
20+
self, thread_id, frame_info, status=0
21+
): # Default to THREAD_STATE_RUNNING (0)
22+
self.thread_id = thread_id
23+
self.frame_info = frame_info
24+
self.status = status
25+
26+
def __repr__(self):
27+
return f"MockThreadInfo(thread_id={self.thread_id}, frame_info={self.frame_info}, status={self.status})"
28+
29+
30+
class MockInterpreterInfo:
31+
"""Mock InterpreterInfo for testing since the real one isn't accessible."""
32+
33+
def __init__(self, interpreter_id, threads):
34+
self.interpreter_id = interpreter_id
35+
self.threads = threads
36+
37+
def __repr__(self):
38+
return f"MockInterpreterInfo(interpreter_id={self.interpreter_id}, threads={self.threads})"

0 commit comments

Comments
 (0)