|
39 | 39 | "has_fork_support", "requires_fork", |
40 | 40 | "has_subprocess_support", "requires_subprocess", |
41 | 41 | "has_socket_support", "requires_working_socket", |
| 42 | + "has_remote_subprocess_debugging", "requires_remote_subprocess_debugging", |
42 | 43 | "anticipate_failure", "load_package_tests", "detect_api_mismatch", |
43 | 44 | "check__all__", "skip_if_buggy_ucrt_strfptime", |
44 | 45 | "check_disallow_instantiation", "check_sanitizer", "skip_if_sanitizer", |
@@ -643,6 +644,93 @@ def requires_working_socket(*, module=False): |
643 | 644 | else: |
644 | 645 | return unittest.skipUnless(has_socket_support, msg) |
645 | 646 |
|
| 647 | + |
| 648 | +@functools.cache |
| 649 | +def has_remote_subprocess_debugging(): |
| 650 | + """Check if we have permissions to debug subprocesses remotely. |
| 651 | +
|
| 652 | + Returns True if we have permissions, False if we don't. |
| 653 | + Checks for: |
| 654 | + - Platform support (Linux, macOS, Windows only) |
| 655 | + - On Linux: process_vm_readv support |
| 656 | + - _remote_debugging module availability |
| 657 | + - Actual subprocess debugging permissions (e.g., macOS entitlements) |
| 658 | + Result is cached. |
| 659 | + """ |
| 660 | + # Check platform support |
| 661 | + if sys.platform not in ("linux", "darwin", "win32"): |
| 662 | + return False |
| 663 | + |
| 664 | + try: |
| 665 | + import _remote_debugging |
| 666 | + except ImportError: |
| 667 | + return False |
| 668 | + |
| 669 | + # On Linux, check for process_vm_readv support |
| 670 | + if sys.platform == "linux": |
| 671 | + if not getattr(_remote_debugging, "PROCESS_VM_READV_SUPPORTED", False): |
| 672 | + return False |
| 673 | + |
| 674 | + # First check if we can read our own process |
| 675 | + if not _remote_debugging.is_python_process(os.getpid()): |
| 676 | + return False |
| 677 | + |
| 678 | + # Check subprocess access - debugging child processes may require |
| 679 | + # additional permissions depending on platform security settings |
| 680 | + import socket |
| 681 | + import subprocess |
| 682 | + |
| 683 | + # Create a socket for child to signal readiness |
| 684 | + server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
| 685 | + server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) |
| 686 | + server.bind(("127.0.0.1", 0)) |
| 687 | + server.listen(1) |
| 688 | + port = server.getsockname()[1] |
| 689 | + |
| 690 | + # Child connects to signal it's ready, then waits for parent to close |
| 691 | + child_code = f""" |
| 692 | +import socket |
| 693 | +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
| 694 | +s.connect(("127.0.0.1", {port})) |
| 695 | +s.recv(1) # Wait for parent to signal done |
| 696 | +""" |
| 697 | + proc = subprocess.Popen( |
| 698 | + [sys.executable, "-c", child_code], |
| 699 | + stdout=subprocess.DEVNULL, |
| 700 | + stderr=subprocess.DEVNULL, |
| 701 | + ) |
| 702 | + try: |
| 703 | + server.settimeout(5.0) |
| 704 | + conn, _ = server.accept() |
| 705 | + # Child is ready, test if we can probe it |
| 706 | + result = _remote_debugging.is_python_process(proc.pid) |
| 707 | + # Check if subprocess is still alive after probing |
| 708 | + if proc.poll() is not None: |
| 709 | + return False |
| 710 | + conn.close() # Signal child to exit |
| 711 | + return result |
| 712 | + except (socket.timeout, OSError): |
| 713 | + return False |
| 714 | + finally: |
| 715 | + server.close() |
| 716 | + proc.kill() |
| 717 | + proc.wait() |
| 718 | + |
| 719 | + |
| 720 | +def requires_remote_subprocess_debugging(): |
| 721 | + """Skip tests that require remote subprocess debugging permissions. |
| 722 | +
|
| 723 | + This also implies subprocess support, so no need to use both |
| 724 | + @requires_subprocess() and @requires_remote_subprocess_debugging(). |
| 725 | + """ |
| 726 | + if not has_subprocess_support: |
| 727 | + return unittest.skip("requires subprocess support") |
| 728 | + return unittest.skipUnless( |
| 729 | + has_remote_subprocess_debugging(), |
| 730 | + "requires remote subprocess debugging permissions" |
| 731 | + ) |
| 732 | + |
| 733 | + |
646 | 734 | # Does strftime() support glibc extension like '%4Y'? |
647 | 735 | has_strftime_extensions = False |
648 | 736 | if sys.platform != "win32": |
|
0 commit comments