Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 40 additions & 37 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -220,9 +220,9 @@ bstr = { version = "1", default-features = false, features = ["unicode"] }
bzip2 = "0.6"
console_error_panic_hook = "0.1"
constant_time_eq = "0.6"
cranelift = "0.135.0"
cranelift-jit = "0.135.0"
cranelift-module = "0.135.0"
cranelift = "0.136.3"
cranelift-jit = "0.136.3"
cranelift-module = "0.136.3"
crc32fast = "1.3.2"
criterion = { package = "codspeed-criterion-compat", version = "5.0.1", features = ["html_reports"] }
crossbeam-utils = "0.8.21"
Expand Down Expand Up @@ -259,6 +259,7 @@ log = "0.4.30"
lz4_flex = "0.14"
nix = { version = "0.31", features = ["fs", "user", "process", "term", "time", "signal", "ioctl", "socket", "sched", "zerocopy", "dir", "hostname", "net", "poll"] }
mac_address = "1.1.3"
mach2 = "0.4.3"
malachite-bigint = "0.14.0"
malachite-q = "0.14.0"
malachite-base = "0.14.0"
Expand All @@ -271,6 +272,7 @@ num-integer = "0.1.46"
num-traits = "0.2"
num_cpus = "1.17.0"
num_enum = { version = "0.7", default-features = false }
object = { version = "0.37.3", default-features = false, features = ["read_core", "elf", "macho", "pe", "std", "unaligned"] }
openssl = "0.10.80"
openssl-sys = "0.9.110"
optional = "0.5"
Expand Down
87 changes: 87 additions & 0 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@


__all__ = [
"has_remote_subprocess_debugging", "requires_remote_subprocess_debugging",
"requires_root_user", "requires_non_root_user",
# globals
"PIPE_MAX_SIZE", "verbose", "max_memuse", "use_resources", "failfast",
Expand Down Expand Up @@ -657,6 +658,92 @@ def requires_working_socket(*, module=False):
else:
return unittest.skipUnless(has_socket_support, msg)

@functools.cache
def has_remote_subprocess_debugging():
"""Check if we have permissions to debug subprocesses remotely.

Returns True if we have permissions, False if we don't.
Checks for:
- Platform support (Linux, macOS, Windows only)
- On Linux: process_vm_readv support
- _remote_debugging module availability
- Actual subprocess debugging permissions (e.g., macOS entitlements)
Result is cached.
"""
# Check platform support
if sys.platform not in ("linux", "darwin", "win32"):
return False

try:
import _remote_debugging
except ImportError:
return False

# On Linux, check for process_vm_readv support
if sys.platform == "linux":
if not getattr(_remote_debugging, "PROCESS_VM_READV_SUPPORTED", False):
return False

# First check if we can read our own process
if not _remote_debugging.is_python_process(os.getpid()):
return False

# Check subprocess access - debugging child processes may require
# additional permissions depending on platform security settings
import socket
import subprocess

# Create a socket for child to signal readiness
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(("127.0.0.1", 0))
server.listen(1)
port = server.getsockname()[1]

# Child connects to signal it's ready, then waits for parent to close
child_code = f"""
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", {port}))
s.recv(1) # Wait for parent to signal done
"""
proc = subprocess.Popen(
[sys.executable, "-c", child_code],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
try:
server.settimeout(5.0)
conn, _ = server.accept()
# Child is ready, test if we can probe it
result = _remote_debugging.is_python_process(proc.pid)
# Check if subprocess is still alive after probing
if proc.poll() is not None:
return False
conn.close() # Signal child to exit
return result
except (socket.timeout, OSError):
return False
finally:
server.close()
proc.kill()
proc.wait()


def requires_remote_subprocess_debugging():
"""Skip tests that require remote subprocess debugging permissions.

This also implies subprocess support, so no need to use both
@requires_subprocess() and @requires_remote_subprocess_debugging().
"""
if not has_subprocess_support:
return unittest.skip("requires subprocess support")
return unittest.skipUnless(
has_remote_subprocess_debugging(),
"requires remote subprocess debugging permissions"
)


# Does strftime() support glibc extension like '%4Y'?
has_strftime_extensions = False
if sys.platform != "win32":
Expand Down
Loading
Loading