diff --git a/lldb/packages/Python/lldbsuite/test/lldbpexpect.py b/lldb/packages/Python/lldbsuite/test/lldbpexpect.py index 3279e1fd39f8c..03b2500fbda52 100644 --- a/lldb/packages/Python/lldbsuite/test/lldbpexpect.py +++ b/lldb/packages/Python/lldbsuite/test/lldbpexpect.py @@ -10,6 +10,7 @@ @skipIfRemote +@skipIfWindows @add_test_categories(["pexpect"]) class PExpectTest(TestBase): NO_DEBUG_INFO_TESTCASE = True diff --git a/lldb/source/Interpreter/embedded_interpreter.py b/lldb/source/Interpreter/embedded_interpreter.py index 42a9ab5fc367a..12c47bd712816 100644 --- a/lldb/source/Interpreter/embedded_interpreter.py +++ b/lldb/source/Interpreter/embedded_interpreter.py @@ -32,18 +32,6 @@ def is_libedit(): g_run_one_line_str = None -def get_terminal_size(fd): - try: - import fcntl - import termios - import struct - - hw = struct.unpack("hh", fcntl.ioctl(fd, termios.TIOCGWINSZ, "1234")) - except: - hw = (0, 0) - return hw - - class LLDBExit(SystemExit): pass @@ -74,50 +62,21 @@ def readfunc_stdio(prompt): def run_python_interpreter(local_dict): # Pass in the dictionary, for continuity from one session to the next. try: - fd = sys.stdin.fileno() - interacted = False - if get_terminal_size(fd)[1] == 0: - try: - import termios - - old = termios.tcgetattr(fd) - if old[3] & termios.ECHO: - # Need to turn off echoing and restore - new = termios.tcgetattr(fd) - new[3] = new[3] & ~termios.ECHO - try: - termios.tcsetattr(fd, termios.TCSADRAIN, new) - interacted = True - code.interact( - banner="Python Interactive Interpreter. To exit, type 'quit()', 'exit()'.", - readfunc=readfunc_stdio, - local=local_dict, - ) - finally: - termios.tcsetattr(fd, termios.TCSADRAIN, old) - except: - pass - # Don't need to turn off echoing - if not interacted: - code.interact( - banner="Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D.", - readfunc=readfunc_stdio, - local=local_dict, - ) - else: - # We have a real interactive terminal - code.interact( - banner="Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D.", - readfunc=readfunc, - local=local_dict, - ) + banner = "Python Interactive Interpreter. To exit, type 'quit()', 'exit()'." + input_func = readfunc_stdio + + is_atty = sys.stdin.isatty() + if is_atty: + banner = "Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D." + input_func = readfunc + + code.interact(banner=banner, readfunc=input_func, local=local_dict) except LLDBExit: pass except SystemExit as e: if e.code: print("Script exited with code %s" % e.code) - def run_one_line(local_dict, input_string): global g_run_one_line_str try: diff --git a/lldb/test/API/python_api/file_handle/TestFileHandle.py b/lldb/test/API/python_api/file_handle/TestFileHandle.py index b38585577f6f6..707044a3afb0f 100644 --- a/lldb/test/API/python_api/file_handle/TestFileHandle.py +++ b/lldb/test/API/python_api/file_handle/TestFileHandle.py @@ -111,10 +111,11 @@ def setUp(self): super(FileHandleTestCase, self).setUp() self.out_filename = self.getBuildArtifact("output") self.in_filename = self.getBuildArtifact("input") + self.err_filename = self.getBuildArtifact("error") def tearDown(self): super(FileHandleTestCase, self).tearDown() - for name in (self.out_filename, self.in_filename): + for name in (self.out_filename, self.in_filename, self.err_filename): if os.path.exists(name): os.unlink(name) @@ -679,6 +680,51 @@ def test_stdout_file(self): lines = [x for x in f.read().strip().split() if x != "7"] self.assertEqual(lines, ["foobar"]) + def test_stdout_file_interactive(self): + """Ensure when we read stdin from a file, outputs from python goes to the right I/O stream.""" + with open(self.in_filename, "w") as f: + f.write( + "script --language python --\nvalue = 250 + 5\nprint(value)\nprint(vel)" + ) + + with open(self.out_filename, "w") as outf, open( + self.in_filename, "r" + ) as inf, open(self.err_filename, "w") as errf: + status = self.dbg.SetOutputFile(lldb.SBFile(outf)) + self.assertSuccess(status) + status = self.dbg.SetErrorFile(lldb.SBFile(errf)) + self.assertSuccess(status) + status = self.dbg.SetInputFile(lldb.SBFile(inf)) + self.assertSuccess(status) + auto_handle_events = True + spawn_thread = False + num_errs = 0 + quit_requested = False + stopped_for_crash = False + opts = lldb.SBCommandInterpreterRunOptions() + self.dbg.RunCommandInterpreter( + auto_handle_events, + spawn_thread, + opts, + num_errs, + quit_requested, + stopped_for_crash, + ) + self.dbg.GetOutputFile().Flush() + expected_out_text = "255" + expected_err_text = "NameError" + # check stdout + with open(self.out_filename, "r") as f: + out_text = f.read() + self.assertIn(expected_out_text, out_text) + self.assertNotIn(expected_err_text, out_text) + + # check stderr + with open(self.err_filename, "r") as f: + err_text = f.read() + self.assertIn(expected_err_text, err_text) + self.assertNotIn(expected_out_text, err_text) + def test_identity(self): f = io.StringIO() sbf = lldb.SBFile(f) diff --git a/lldb/test/API/terminal/TestPythonInterpreterEcho.py b/lldb/test/API/terminal/TestPythonInterpreterEcho.py new file mode 100644 index 0000000000000..758a4f9cede5a --- /dev/null +++ b/lldb/test/API/terminal/TestPythonInterpreterEcho.py @@ -0,0 +1,62 @@ +""" +Test that typing python expression in the terminal is echoed back to stdout. +""" + +from lldbsuite.test.decorators import skipIfAsan +from lldbsuite.test.lldbpexpect import PExpectTest + + +@skipIfAsan +class PythonInterpreterEchoTest(PExpectTest): + PYTHON_PROMPT = ">>> " + + def verify_command_echo( + self, command: str, expected_output: str = "", is_regex: bool = False + ): + assert self.child != None + child = self.child + self.assertIsNotNone(self.child, "expected a running lldb process.") + + child.sendline(command) + + # Build pattern list: match whichever comes first (output or prompt). + # This prevents waiting for a timeout if there's no match. + pattern = [] + match_expected = expected_output and len(expected_output) > 0 + + if match_expected: + pattern.append(expected_output) + pattern.append(self.PYTHON_PROMPT) + + expect_func = child.expect if is_regex else child.expect_exact + match_idx = expect_func(pattern) + if match_expected: + self.assertEqual( + match_idx, 0, "Expected output `{expected_output}` in stdout." + ) + + self.assertIsNotNone(self.child.before, "Expected output before prompt") + self.assertIsInstance(self.child.before, bytes) + echoed_text: str = self.child.before.decode("ascii").strip() + self.assertEqual( + command, echoed_text, f"Command '{command}' should be echoed to stdout." + ) + + if match_expected: + child.expect_exact(self.PYTHON_PROMPT) + + def test_python_interpreter_echo(self): + """Test that that the user typed commands is echoed to stdout""" + + self.launch(use_colors=False, dimensions=(100, 100)) + + # Enter the python interpreter. + self.verify_command_echo( + "script --language python --", expected_output="Python.*\\.", is_regex=True + ) + self.child_in_script_interpreter = True + + self.verify_command_echo("val = 300") + self.verify_command_echo( + "print('result =', 300)", expected_output="result = 300" + ) diff --git a/lldb/test/Shell/ScriptInterpreter/Python/io.test b/lldb/test/Shell/ScriptInterpreter/Python/io.test new file mode 100644 index 0000000000000..25e3de41724e0 --- /dev/null +++ b/lldb/test/Shell/ScriptInterpreter/Python/io.test @@ -0,0 +1,12 @@ +# RUN: rm -rf %t.stdout %t.stderr +# RUN: cat %s | %lldb --script-language python > %t.stdout 2> %t.stderr +# RUN: cat %t.stdout | FileCheck %s --check-prefix STDOUT +# RUN: cat %t.stderr | FileCheck %s --check-prefix STDERR +script +variable = 300 +print(variable) +print(not_value) +quit + +# STDOUT: 300 +# STDERR: NameError{{.*}}is not defined