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