|
1 | | -import itertools |
2 | 1 | import io |
| 2 | +import itertools |
3 | 3 | import os |
4 | 4 | import rlcompleter |
5 | | -from unittest import TestCase |
| 5 | +import select |
| 6 | +import subprocess |
| 7 | +import sys |
| 8 | +from unittest import TestCase, skipUnless |
6 | 9 | from unittest.mock import patch |
| 10 | +from test.support import force_not_colorized |
7 | 11 |
|
8 | 12 | from .support import ( |
9 | 13 | FakeConsole, |
|
17 | 21 | from _pyrepl.readline import ReadlineAlikeReader, ReadlineConfig |
18 | 22 | from _pyrepl.readline import multiline_input as readline_multiline_input |
19 | 23 |
|
| 24 | +try: |
| 25 | + import pty |
| 26 | +except ImportError: |
| 27 | + pty = None |
20 | 28 |
|
21 | 29 | class TestCursorPosition(TestCase): |
22 | 30 | def prepare_reader(self, events): |
@@ -828,3 +836,54 @@ def test_bracketed_paste_single_line(self): |
828 | 836 | reader = self.prepare_reader(events) |
829 | 837 | output = multiline_input(reader) |
830 | 838 | self.assertEqual(output, input_code) |
| 839 | + |
| 840 | + |
| 841 | +@skipUnless(pty, "requires pty") |
| 842 | +class TestMain(TestCase): |
| 843 | + @force_not_colorized |
| 844 | + def test_exposed_globals_in_repl(self): |
| 845 | + expected_output = ( |
| 846 | + "[\'__annotations__\', \'__builtins__\', \'__doc__\', \'__loader__\', " |
| 847 | + "\'__name__\', \'__package__\', \'__spec__\']" |
| 848 | + ) |
| 849 | + output, exit_code = self.run_repl(["sorted(dir())", "exit"]) |
| 850 | + if "can\'t use pyrepl" in output: |
| 851 | + self.skipTest("pyrepl not available") |
| 852 | + self.assertEqual(exit_code, 0) |
| 853 | + self.assertIn(expected_output, output) |
| 854 | + |
| 855 | + def test_dumb_terminal_exits_cleanly(self): |
| 856 | + env = os.environ.copy() |
| 857 | + env.update({"TERM": "dumb"}) |
| 858 | + output, exit_code = self.run_repl("exit()\n", env=env) |
| 859 | + self.assertEqual(exit_code, 0) |
| 860 | + self.assertIn("warning: can\'t use pyrepl", output) |
| 861 | + self.assertNotIn("Exception", output) |
| 862 | + self.assertNotIn("Traceback", output) |
| 863 | + |
| 864 | + def run_repl(self, repl_input: str | list[str], env: dict | None = None) -> tuple[str, int]: |
| 865 | + master_fd, slave_fd = pty.openpty() |
| 866 | + process = subprocess.Popen( |
| 867 | + [sys.executable, "-i", "-u"], |
| 868 | + stdin=slave_fd, |
| 869 | + stdout=slave_fd, |
| 870 | + stderr=slave_fd, |
| 871 | + text=True, |
| 872 | + close_fds=True, |
| 873 | + env=env if env else os.environ, |
| 874 | + ) |
| 875 | + if isinstance(repl_input, list): |
| 876 | + repl_input = "\n".join(repl_input) + "\n" |
| 877 | + os.write(master_fd, repl_input.encode("utf-8")) |
| 878 | + |
| 879 | + output = [] |
| 880 | + while select.select([master_fd], [], [], 0.5)[0]: |
| 881 | + data = os.read(master_fd, 1024).decode("utf-8") |
| 882 | + if not data: |
| 883 | + break |
| 884 | + output.append(data) |
| 885 | + |
| 886 | + os.close(master_fd) |
| 887 | + os.close(slave_fd) |
| 888 | + exit_code = process.wait() |
| 889 | + return "\n".join(output), exit_code |
0 commit comments