Skip to content

Commit 78f54f8

Browse files
committed
Add an integration test
1 parent a85bc9b commit 78f54f8

File tree

1 file changed

+131
-1
lines changed

1 file changed

+131
-1
lines changed

Lib/test/test_remote_pdb.py

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import itertools
44
import json
55
import os
6+
import re
67
import signal
78
import socket
89
import subprocess
@@ -14,7 +15,7 @@
1415
import unittest.mock
1516
from contextlib import closing, contextmanager, redirect_stdout, ExitStack
1617
from pathlib import Path
17-
from test.support import is_wasi, os_helper, requires_subprocess, SHORT_TIMEOUT
18+
from test.support import is_wasi, cpython_only, force_color, requires_subprocess, SHORT_TIMEOUT
1819
from test.support.os_helper import temp_dir, TESTFN, unlink
1920
from typing import Dict, List, Optional, Tuple, Union, Any
2021

@@ -1431,5 +1432,134 @@ def test_multi_line_commands(self):
14311432
self.assertIn("Function returned: 42", stdout)
14321433
self.assertEqual(process.returncode, 0)
14331434

1435+
1436+
def _supports_remote_attaching():
1437+
from contextlib import suppress
1438+
PROCESS_VM_READV_SUPPORTED = False
1439+
1440+
try:
1441+
from _remote_debugging import PROCESS_VM_READV_SUPPORTED
1442+
except ImportError:
1443+
pass
1444+
1445+
return PROCESS_VM_READV_SUPPORTED
1446+
1447+
1448+
@unittest.skipIf(not sys.is_remote_debug_enabled(), "Remote debugging is not enabled")
1449+
@unittest.skipIf(sys.platform != "darwin" and sys.platform != "linux" and sys.platform != "win32",
1450+
"Test only runs on Linux, Windows and MacOS")
1451+
@unittest.skipIf(sys.platform == "linux" and not _supports_remote_attaching(),
1452+
"Testing on Linux requires process_vm_readv support")
1453+
@cpython_only
1454+
@requires_subprocess()
1455+
class PdbAttachTestCase(unittest.TestCase):
1456+
def setUp(self):
1457+
# Create a server socket that will wait for the debugger to connect
1458+
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1459+
self.sock.bind(('127.0.0.1', 0)) # Let OS assign port
1460+
self.sock.listen(1)
1461+
self.port = self.sock.getsockname()[1]
1462+
self._create_script()
1463+
1464+
def _create_script(self, script=None):
1465+
# Create a file for subprocess script
1466+
script = textwrap.dedent(
1467+
f"""
1468+
import socket
1469+
import time
1470+
1471+
def foo():
1472+
return bar()
1473+
1474+
def bar():
1475+
return baz()
1476+
1477+
def baz():
1478+
x = 1
1479+
# Trigger attach
1480+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1481+
sock.connect(('127.0.0.1', {self.port}))
1482+
sock.close()
1483+
count = 0
1484+
while x == 1 and count < 100:
1485+
count += 1
1486+
time.sleep(0.1)
1487+
return x
1488+
1489+
result = foo()
1490+
print(f"Function returned: {{result}}")
1491+
"""
1492+
)
1493+
1494+
self.script_path = TESTFN + "_connect_test.py"
1495+
with open(self.script_path, 'w') as f:
1496+
f.write(script)
1497+
1498+
def tearDown(self):
1499+
self.sock.close()
1500+
try:
1501+
unlink(self.script_path)
1502+
except OSError:
1503+
pass
1504+
1505+
def do_integration_test(self, client_stdin):
1506+
process = subprocess.Popen(
1507+
[sys.executable, self.script_path],
1508+
stdout=subprocess.PIPE,
1509+
stderr=subprocess.PIPE,
1510+
text=True
1511+
)
1512+
1513+
# Wait for the process to reach our attachment point
1514+
self.sock.settimeout(10)
1515+
conn, _ = self.sock.accept()
1516+
conn.close()
1517+
1518+
attach_process = subprocess.Popen(
1519+
[sys.executable, "-m", "pdb", "-p", str(process.pid)],
1520+
stdout=subprocess.PIPE,
1521+
stderr=subprocess.PIPE,
1522+
stdin=subprocess.PIPE,
1523+
text=True,
1524+
env=os.environ,
1525+
)
1526+
stdout, stderr = attach_process.communicate(client_stdin, timeout=5)
1527+
process.wait()
1528+
1529+
self.assertEqual(process.returncode, 0)
1530+
self.assertEqual(attach_process.returncode, 0)
1531+
1532+
return {
1533+
"client": {
1534+
"stdout": stdout,
1535+
"stderr": stderr,
1536+
},
1537+
"server": {
1538+
"stdout": process.stdout.read(),
1539+
"stderr": process.stderr.read(),
1540+
},
1541+
}
1542+
1543+
def test_attach_to_process_without_colors(self):
1544+
with force_color(False):
1545+
output = self.do_integration_test("ll\nx=42\n")
1546+
self.assertEqual(output["client"]["stderr"], "")
1547+
self.assertEqual(output["server"]["stderr"], "")
1548+
1549+
self.assertEqual(output["server"]["stdout"], "Function returned: 42\n")
1550+
self.assertIn("while x == 1", output["client"]["stdout"])
1551+
self.assertNotIn("\x1b", output["client"]["stdout"])
1552+
1553+
def test_attach_to_process_with_colors(self):
1554+
with force_color(True):
1555+
output = self.do_integration_test("ll\nx=42\n")
1556+
self.assertEqual(output["client"]["stderr"], "")
1557+
self.assertEqual(output["server"]["stderr"], "")
1558+
1559+
self.assertEqual(output["server"]["stdout"], "Function returned: 42\n")
1560+
self.assertIn("\x1b", output["client"]["stdout"])
1561+
self.assertNotIn("while x == 1", output["client"]["stdout"])
1562+
self.assertIn("while x == 1", re.sub("\x1b[^m]*m", "", output["client"]["stdout"]))
1563+
14341564
if __name__ == "__main__":
14351565
unittest.main()

0 commit comments

Comments
 (0)