1"""Test Python APIs for process IO.""" 2 3import os 4import lldb 5from lldbsuite.test.decorators import * 6from lldbsuite.test.lldbtest import * 7from lldbsuite.test import lldbutil 8 9 10class ProcessIOTestCase(TestBase): 11 NO_DEBUG_INFO_TESTCASE = True 12 13 def setup_test(self): 14 # Get the full path to our executable to be debugged. 15 self.exe = self.getBuildArtifact("process_io") 16 self.local_input_file = self.getBuildArtifact("input.txt") 17 self.local_output_file = self.getBuildArtifact("output.txt") 18 self.local_error_file = self.getBuildArtifact("error.txt") 19 20 self.input_file = os.path.join( 21 self.get_process_working_directory(), "input.txt" 22 ) 23 self.output_file = os.path.join( 24 self.get_process_working_directory(), "output.txt" 25 ) 26 self.error_file = os.path.join( 27 self.get_process_working_directory(), "error.txt" 28 ) 29 self.lines = ["Line 1", "Line 2", "Line 3"] 30 31 @skipIfWindows # stdio manipulation unsupported on Windows 32 @expectedFlakeyLinux(bugnumber="llvm.org/pr26437") 33 @skipIfDarwinEmbedded # I/O redirection like this is not supported on remote iOS devices yet <rdar://problem/54581135> 34 def test_stdin_by_api(self): 35 """Exercise SBProcess.PutSTDIN().""" 36 self.setup_test() 37 self.build() 38 self.create_target() 39 self.run_process(True) 40 output = self.process.GetSTDOUT(1000) 41 self.check_process_output(output, output) 42 43 @skipIfWindows # stdio manipulation unsupported on Windows 44 @expectedFlakeyLinux(bugnumber="llvm.org/pr26437") 45 def test_stdin_redirection(self): 46 """Exercise SBLaunchInfo::AddOpenFileAction() for STDIN without specifying STDOUT or STDERR.""" 47 self.setup_test() 48 self.build() 49 self.create_target() 50 self.redirect_stdin() 51 self.run_process(False) 52 output = self.process.GetSTDOUT(1000) 53 self.check_process_output(output, output) 54 55 @skipIfWindows # stdio manipulation unsupported on Windows 56 @expectedFlakeyLinux(bugnumber="llvm.org/pr26437") 57 @skipIfDarwinEmbedded # debugserver can't create/write files on the device 58 def test_stdout_redirection(self): 59 """Exercise SBLaunchInfo::AddOpenFileAction() for STDOUT without specifying STDIN or STDERR.""" 60 self.setup_test() 61 self.build() 62 self.create_target() 63 self.redirect_stdout() 64 self.run_process(True) 65 output = self.read_output_file_and_delete() 66 error = self.process.GetSTDOUT(1000) 67 self.check_process_output(output, error) 68 69 @skipIfWindows # stdio manipulation unsupported on Windows 70 @expectedFlakeyLinux(bugnumber="llvm.org/pr26437") 71 @skipIfDarwinEmbedded # debugserver can't create/write files on the device 72 def test_stderr_redirection(self): 73 """Exercise SBLaunchInfo::AddOpenFileAction() for STDERR without specifying STDIN or STDOUT.""" 74 self.setup_test() 75 self.build() 76 self.create_target() 77 self.redirect_stderr() 78 self.run_process(True) 79 output = self.process.GetSTDOUT(1000) 80 error = self.read_error_file_and_delete() 81 self.check_process_output(output, error) 82 83 @skipIfWindows # stdio manipulation unsupported on Windows 84 @expectedFlakeyLinux(bugnumber="llvm.org/pr26437") 85 @skipIfDarwinEmbedded # debugserver can't create/write files on the device 86 def test_stdout_stderr_redirection(self): 87 """Exercise SBLaunchInfo::AddOpenFileAction() for STDOUT and STDERR without redirecting STDIN.""" 88 self.setup_test() 89 self.build() 90 self.create_target() 91 self.redirect_stdout() 92 self.redirect_stderr() 93 self.run_process(True) 94 output = self.read_output_file_and_delete() 95 error = self.read_error_file_and_delete() 96 self.check_process_output(output, error) 97 98 # target_file - path on local file system or remote file system if running remote 99 # local_file - path on local system 100 def read_file_and_delete(self, target_file, local_file): 101 if lldb.remote_platform: 102 self.runCmd( 103 'platform get-file "{remote}" "{local}"'.format( 104 remote=target_file, local=local_file 105 ) 106 ) 107 108 self.assertTrue( 109 os.path.exists(local_file), 110 'Make sure "{local}" file exists'.format(local=local_file), 111 ) 112 f = open(local_file, "r") 113 contents = f.read() 114 f.close() 115 116 # TODO: add 'platform delete-file' file command 117 # if lldb.remote_platform: 118 # self.runCmd('platform delete-file "{remote}"'.format(remote=target_file)) 119 os.unlink(local_file) 120 return contents 121 122 def read_output_file_and_delete(self): 123 return self.read_file_and_delete(self.output_file, self.local_output_file) 124 125 def read_error_file_and_delete(self): 126 return self.read_file_and_delete(self.error_file, self.local_error_file) 127 128 def create_target(self): 129 """Create the target and launch info that will be used by all tests""" 130 self.target = self.dbg.CreateTarget(self.exe) 131 self.launch_info = self.target.GetLaunchInfo() 132 self.launch_info.SetWorkingDirectory(self.get_process_working_directory()) 133 134 def redirect_stdin(self): 135 """Redirect STDIN (file descriptor 0) to use our input.txt file 136 137 Make the input.txt file to use when redirecting STDIN, setup a cleanup action 138 to delete the input.txt at the end of the test in case exceptions are thrown, 139 and redirect STDIN in the launch info.""" 140 f = open(self.local_input_file, "w") 141 for line in self.lines: 142 f.write(line + "\n") 143 f.close() 144 145 if lldb.remote_platform: 146 self.runCmd( 147 'platform put-file "{local}" "{remote}"'.format( 148 local=self.local_input_file, remote=self.input_file 149 ) 150 ) 151 152 # This is the function to remove the custom formats in order to have a 153 # clean slate for the next test case. 154 def cleanup(): 155 os.unlink(self.local_input_file) 156 # TODO: add 'platform delete-file' file command 157 # if lldb.remote_platform: 158 # self.runCmd('platform delete-file "{remote}"'.format(remote=self.input_file)) 159 160 # Execute the cleanup function during test case tear down. 161 self.addTearDownHook(cleanup) 162 self.launch_info.AddOpenFileAction(0, self.input_file, True, False) 163 164 def redirect_stdout(self): 165 """Redirect STDOUT (file descriptor 1) to use our output.txt file""" 166 self.launch_info.AddOpenFileAction(1, self.output_file, False, True) 167 168 def redirect_stderr(self): 169 """Redirect STDERR (file descriptor 2) to use our error.txt file""" 170 self.launch_info.AddOpenFileAction(2, self.error_file, False, True) 171 172 def run_process(self, put_stdin): 173 """Run the process to completion and optionally put lines to STDIN via the API if "put_stdin" is True""" 174 # Set the breakpoints 175 self.breakpoint = self.target.BreakpointCreateBySourceRegex( 176 "Set breakpoint here", lldb.SBFileSpec("main.c") 177 ) 178 self.assertGreater(self.breakpoint.GetNumLocations(), 0, VALID_BREAKPOINT) 179 180 # Launch the process, and do not stop at the entry point. 181 error = lldb.SBError() 182 # This should launch the process and it should exit by the time we get back 183 # because we have synchronous mode enabled 184 self.process = self.target.Launch(self.launch_info, error) 185 186 self.assertTrue(error.Success(), "Make sure process launched successfully") 187 self.assertTrue(self.process, PROCESS_IS_VALID) 188 189 if self.TraceOn(): 190 print("process launched.") 191 192 # Frame #0 should be at our breakpoint. 193 threads = lldbutil.get_threads_stopped_at_breakpoint( 194 self.process, self.breakpoint 195 ) 196 197 self.assertEqual(len(threads), 1) 198 self.thread = threads[0] 199 self.frame = self.thread.frames[0] 200 self.assertTrue(self.frame, "Frame 0 is valid.") 201 202 if self.TraceOn(): 203 print("process stopped at breakpoint, sending STDIN via LLDB API.") 204 205 # Write data to stdin via the public API if we were asked to 206 if put_stdin: 207 for line in self.lines: 208 self.process.PutSTDIN(line + "\n") 209 210 # Let process continue so it will exit 211 self.process.Continue() 212 state = self.process.GetState() 213 self.assertState(state, lldb.eStateExited, PROCESS_IS_VALID) 214 215 def check_process_output(self, output, error): 216 # Since we launched the process without specifying stdin/out/err, 217 # a pseudo terminal is used for stdout/err, and we are satisfied 218 # once "input line=>1" appears in stdout. 219 # See also main.c. 220 if self.TraceOn(): 221 print("output = '%s'" % output) 222 print("error = '%s'" % error) 223 224 for line in self.lines: 225 check_line = "input line to stdout: %s" % (line) 226 self.assertIn(check_line, output, "verify stdout line shows up in STDOUT") 227 for line in self.lines: 228 check_line = "input line to stderr: %s" % (line) 229 self.assertIn(check_line, error, "verify stderr line shows up in STDERR") 230