|
1 | 1 | """ |
2 | 2 | Test the redirection of stdio. |
3 | | -There are three ways to launch the debuggee |
| 3 | +There are three ways to launch the debuggee: |
4 | 4 | internalConsole, integratedTerminal and externalTerminal. |
5 | 5 |
|
6 | | -For the three configurations, we test if we can read data |
7 | | -from environments, stdin and cli arguments. |
| 6 | +For each redirection configuration we check the stdin, argv, and env |
| 7 | +input paths. The C++ test program writes whatever it receives from |
| 8 | +each available source. |
8 | 9 |
|
9 | 10 | NOTE: The testcases do not include all possible configurations of |
10 | | -consoles, environments, stdin and cli arguments. |
| 11 | +consoles and input sources. |
11 | 12 | """ |
12 | 13 |
|
13 | 14 | from abc import abstractmethod |
14 | | -import lldbdap_testcase |
15 | 15 | from tempfile import NamedTemporaryFile |
16 | 16 |
|
| 17 | +from lldbsuite.test.tools.lldb_dap import DAPTestCaseBase, DAPTestSession |
| 18 | +from lldbsuite.test.tools.lldb_dap.types import Console, LaunchArgs |
17 | 19 |
|
18 | | -class DAP_launchIO(lldbdap_testcase.DAPTestCaseBase): |
19 | | - """The class holds the implementation different ways to redirect the debuggee I/O streams |
20 | | - which is configurable from the Derived classes. |
21 | 20 |
|
22 | | - Depending on the console type the output will be in different places. |
23 | | - It also provides two abstract functions `_get_debuggee_stdout` and `_get_debuggee_stderr` |
24 | | - that provides the debuggee stdout and stderr. |
| 21 | +class DAP_launchIO(DAPTestCaseBase): |
| 22 | + """Implements the redirection scenarios that are common to every console. |
| 23 | +
|
| 24 | + Subclasses provide `console` and override `_get_debuggee_stdout` / |
| 25 | + `_get_debuggee_stderr` for the cases where stdout / stderr are not |
| 26 | + redirected to files (the streams have to be read from the console |
| 27 | + instead, which differs between InternalConsole and IntegratedTerminal). |
25 | 28 | """ |
26 | 29 |
|
27 | | - def all_redirection(self, console: str, with_args: bool = False): |
28 | | - """Test all standard io redirection.""" |
29 | | - self.build_and_create_debug_adapter() |
| 30 | + def all_redirection(self, console: Console): |
| 31 | + """All three streams redirected to files. Verify every input path.""" |
| 32 | + session = self.build_and_create_session() |
30 | 33 | program = self.getBuildArtifact("a.out") |
31 | | - input_text = "from stdin with redirection" |
32 | | - args_text = "string from argv" |
33 | | - program_args = [args_text] if with_args else None |
| 34 | + stdin_input = "from stdin" |
| 35 | + args_input = "from argv" |
| 36 | + env_input = "from env" |
34 | 37 |
|
35 | 38 | with NamedTemporaryFile("wt") as stdin, NamedTemporaryFile( |
36 | 39 | "rt" |
37 | 40 | ) as stdout, NamedTemporaryFile("rt") as stderr: |
38 | | - stdin.write(input_text) |
| 41 | + stdin.write(stdin_input) |
39 | 42 | stdin.flush() |
40 | | - self.launch_and_configurationDone( |
41 | | - program, |
42 | | - stdio=[stdin.name, stdout.name, stderr.name], |
43 | | - console=console, |
44 | | - args=program_args, |
45 | | - ) |
46 | | - self.verify_process_exited() |
47 | | - |
48 | | - all_stdout = stdout.read() |
49 | | - all_stderr = stderr.read() |
50 | 43 |
|
51 | | - if with_args: |
52 | | - self.assertEqual(f"[STDOUT][FROM_ARGV]: {args_text}", all_stdout) |
53 | | - self.assertEqual(f"[STDERR][FROM_ARGV]: {args_text}", all_stderr) |
54 | | - |
55 | | - self.assertNotIn(f"[STDOUT][FROM_ARGV]: {args_text}", all_stderr) |
56 | | - self.assertNotIn(f"[STDERR][FROM_ARGV]: {args_text}", all_stdout) |
| 44 | + session.launch( |
| 45 | + LaunchArgs( |
| 46 | + program, |
| 47 | + stdio=[stdin.name, stdout.name, stderr.name], |
| 48 | + console=console, |
| 49 | + args=["--read-stdin", args_input], |
| 50 | + env={"FROM_ENV": env_input}, |
| 51 | + ) |
| 52 | + ) |
| 53 | + session.verify_process_exited() |
57 | 54 |
|
58 | | - else: |
59 | | - self.assertEqual(f"[STDOUT][FROM_STDIN]: {input_text}", all_stdout) |
60 | | - self.assertEqual(f"[STDERR][FROM_STDIN]: {input_text}", all_stderr) |
| 55 | + stdout_text = stdout.read() |
| 56 | + stderr_text = stderr.read() |
| 57 | + self.assertIn(f"[STDOUT][FROM_STDIN]: {stdin_input}", stdout_text) |
| 58 | + self.assertIn(f"[STDOUT][FROM_ARGV]: {args_input}", stdout_text) |
| 59 | + self.assertIn(f"[STDOUT][FROM_ENV]: {env_input}", stdout_text) |
61 | 60 |
|
62 | | - self.assertNotIn(f"[STDERR][FROM_STDIN]: {input_text}", all_stdout) |
63 | | - self.assertNotIn(f"[STDOUT][FROM_STDIN]: {input_text}", all_stderr) |
| 61 | + self.assertIn(f"[STDERR][FROM_STDIN]: {stdin_input}", stderr_text) |
| 62 | + self.assertIn(f"[STDERR][FROM_ARGV]: {args_input}", stderr_text) |
| 63 | + self.assertIn(f"[STDERR][FROM_ENV]: {env_input}", stderr_text) |
64 | 64 |
|
65 | | - def stdin_redirection(self, console: str, with_args: bool = False): |
66 | | - """Test only stdin redirection.""" |
67 | | - self.build_and_create_debug_adapter() |
| 65 | + def stdin_redirection(self, console: Console): |
| 66 | + """Only stdin redirected. Verify every input path via console output.""" |
| 67 | + session = self.build_and_create_session() |
68 | 68 | program = self.getBuildArtifact("a.out") |
69 | | - input_text = "string from stdin" |
70 | | - args_text = "string from argv" |
71 | | - program_args = [args_text] if with_args else None |
| 69 | + stdin_input = "from stdin" |
| 70 | + args_input = "from argv" |
| 71 | + env_input = "from env" |
72 | 72 |
|
73 | 73 | with NamedTemporaryFile("w+t") as stdin: |
74 | | - stdin.write(input_text) |
| 74 | + stdin.write(stdin_input) |
75 | 75 | stdin.flush() |
76 | | - self.launch_and_configurationDone( |
77 | | - program, stdio=[stdin.name], console=console, args=program_args |
| 76 | + session.launch( |
| 77 | + LaunchArgs( |
| 78 | + program, |
| 79 | + stdio=[stdin.name], |
| 80 | + console=console, |
| 81 | + args=["--read-stdin", args_input], |
| 82 | + env={"FROM_ENV": env_input}, |
| 83 | + ) |
78 | 84 | ) |
79 | | - self.verify_process_exited() |
| 85 | + session.verify_process_exited() |
80 | 86 |
|
81 | | - stdout_text = self._get_debuggee_stdout() |
82 | | - stderr_text = self._get_debuggee_stderr() |
| 87 | + stdout_text = self._get_debuggee_stdout(session) |
| 88 | + stderr_text = self._get_debuggee_stderr(session) |
| 89 | + self.assertIn(f"[STDOUT][FROM_STDIN]: {stdin_input}", stdout_text) |
| 90 | + self.assertIn(f"[STDOUT][FROM_ARGV]: {args_input}", stdout_text) |
| 91 | + self.assertIn(f"[STDOUT][FROM_ENV]: {env_input}", stdout_text) |
83 | 92 |
|
84 | | - if with_args: |
85 | | - self.assertIn(f"[STDOUT][FROM_ARGV]: {args_text}", stdout_text) |
86 | | - self.assertIn(f"[STDERR][FROM_ARGV]: {args_text}", stderr_text) |
87 | | - else: |
88 | | - self.assertIn(f"[STDOUT][FROM_STDIN]: {input_text}", stdout_text) |
89 | | - self.assertIn(f"[STDERR][FROM_STDIN]: {input_text}", stderr_text) |
90 | | - |
91 | | - def stdout_redirection(self, console: str, with_env: bool = False): |
92 | | - """Test only stdout redirection.""" |
93 | | - self.build_and_create_debug_adapter() |
94 | | - program = self.getBuildArtifact("a.out") |
| 93 | + self.assertIn(f"[STDERR][FROM_STDIN]: {stdin_input}", stderr_text) |
| 94 | + self.assertIn(f"[STDERR][FROM_ARGV]: {args_input}", stderr_text) |
| 95 | + self.assertIn(f"[STDERR][FROM_ENV]: {env_input}", stderr_text) |
95 | 96 |
|
96 | | - argv_text = "output with\n multiline" |
97 | | - # By default unix terminals the ONLCR flag is enabled. which replaces '\n' with '\r\n' |
98 | | - # see https://man7.org/linux/man-pages/man3/termios.3.html. |
99 | | - # This does not affect writing to normal files. |
100 | | - argv_replaced_text = argv_text.replace("\n", "\r\n") |
| 97 | + def stdout_redirection(self, console: Console): |
| 98 | + """Only stdout redirected. Verify argv and env paths. |
101 | 99 |
|
102 | | - program_args = [argv_text] |
103 | | - env_text = "string from env" |
104 | | - env = {"FROM_ENV": env_text} if with_env else {} |
| 100 | + stdin is not set up — the C++ program skips reading it because the |
| 101 | + file descriptor is a tty (would block). |
| 102 | + """ |
| 103 | + session = self.build_and_create_session() |
| 104 | + program = self.getBuildArtifact("a.out") |
| 105 | + args_input = "from argv" |
| 106 | + env_input = "from env" |
105 | 107 |
|
106 | 108 | with NamedTemporaryFile("rt") as stdout: |
107 | | - self.launch_and_configurationDone( |
108 | | - program, |
109 | | - stdio=[None, stdout.name], |
110 | | - console=console, |
111 | | - args=program_args, |
112 | | - env=env, |
| 109 | + session.launch( |
| 110 | + LaunchArgs( |
| 111 | + program, |
| 112 | + stdio=[None, stdout.name], |
| 113 | + console=console, |
| 114 | + args=[args_input], |
| 115 | + env={"FROM_ENV": env_input}, |
| 116 | + ) |
113 | 117 | ) |
114 | | - self.verify_process_exited() |
| 118 | + session.verify_process_exited() |
115 | 119 |
|
116 | | - # check stdout |
117 | 120 | stdout_text = stdout.read() |
118 | | - stderr_text = self._get_debuggee_stderr() |
119 | | - if with_env: |
120 | | - self.assertIn(f"[STDOUT][FROM_ENV]: {env_text}", stdout_text) |
121 | | - self.assertIn(f"[STDERR][FROM_ENV]: {env_text}", stderr_text) |
122 | | - |
123 | | - self.assertNotIn(f"[STDERR][FROM_ENV]: {env_text}", stdout_text) |
124 | | - self.assertNotIn(f"[STDOUT][FROM_ENV]: {env_text}", stderr_text) |
125 | | - else: |
126 | | - self.assertIn(f"[STDOUT][FROM_ARGV]: {argv_text}", stdout_text) |
127 | | - |
128 | | - self.assertNotIn( |
129 | | - f"[STDERR][FROM_ARGV]: {argv_replaced_text}", stdout_text |
130 | | - ) |
131 | | - self.assertNotIn(f"[STDOUT][FROM_ARGV]: {argv_text}", stderr_text) |
132 | | - |
133 | | - # check stderr |
134 | | - stderr_text = self._get_debuggee_stderr() |
135 | | - # FIXME: when using 'integrated' or 'external' terminal we do not correctly |
136 | | - # escape newlines that are sent to the terminal. |
137 | | - if console == "integratedConsole": |
138 | | - if with_env: |
139 | | - self.assertNotIn(f"[STDOUT][FROM_ENV]: {env_text}", stderr_text) |
140 | | - self.assertIn(f"[STDERR][FROM_ENV]: {env_text}", stderr_text) |
141 | | - else: |
142 | | - self.assertNotIn( |
143 | | - f"[STDOUT][FROM_ARGV]: {argv_replaced_text}", stderr_text |
144 | | - ) |
145 | | - self.assertIn( |
146 | | - f"[STDERR][FROM_ARGV]: {argv_replaced_text}", stderr_text |
147 | | - ) |
148 | | - |
149 | | - def stderr_redirection(self, console: str, with_env: bool = False): |
150 | | - """Test only stdout redirection.""" |
151 | | - self.build_and_create_debug_adapter() |
152 | | - program = self.getBuildArtifact("a.out") |
| 121 | + stderr_text = self._get_debuggee_stderr(session) |
| 122 | + self.assertIn(f"[STDOUT][FROM_ARGV]: {args_input}", stdout_text) |
| 123 | + self.assertIn(f"[STDOUT][FROM_ENV]: {env_input}", stdout_text) |
153 | 124 |
|
154 | | - argv_text = "output with\n multiline" |
155 | | - # By default unix terminals the ONLCR flag is enabled. which replaces '\n' with '\r\n' |
156 | | - # see https://man7.org/linux/man-pages/man3/termios.3.html. |
157 | | - # This does not affect writing to normal files. |
158 | | - # Currently out test implementation for external and integrated Terminal does not run the |
159 | | - # program through a shell terminal. |
160 | | - argv_replaced_text = argv_text |
161 | | - if console == "internalConsole": |
162 | | - argv_replaced_text = argv_text.replace("\n", "\r\n") |
163 | | - program_args = [argv_text] |
164 | | - env_text = "string from env" |
165 | | - env = {"FROM_ENV": env_text} if with_env else {} |
| 125 | + self.assertIn(f"[STDERR][FROM_ARGV]: {args_input}", stderr_text) |
| 126 | + self.assertIn(f"[STDERR][FROM_ENV]: {env_input}", stderr_text) |
| 127 | + |
| 128 | + def stderr_redirection(self, console: Console): |
| 129 | + """Only stderr redirected. Verify argv and env paths.""" |
| 130 | + session = self.build_and_create_session() |
| 131 | + program = self.getBuildArtifact("a.out") |
| 132 | + args_input = "from argv" |
| 133 | + env_input = "from env" |
166 | 134 |
|
167 | 135 | with NamedTemporaryFile("rt") as stderr: |
168 | | - self.launch_and_configurationDone( |
169 | | - program, |
170 | | - stdio=[None, None, stderr.name], |
171 | | - console=console, |
172 | | - args=program_args, |
173 | | - env=env, |
| 136 | + session.launch( |
| 137 | + LaunchArgs( |
| 138 | + program, |
| 139 | + stdio=[None, None, stderr.name], |
| 140 | + console=console, |
| 141 | + args=[args_input], |
| 142 | + env={"FROM_ENV": env_input}, |
| 143 | + ) |
174 | 144 | ) |
175 | | - self.verify_process_exited() |
176 | | - stdout_text = self._get_debuggee_stdout() |
| 145 | + session.verify_process_exited() |
| 146 | + |
| 147 | + stdout_text = self._get_debuggee_stdout(session) |
177 | 148 | stderr_text = stderr.read() |
178 | | - if with_env: |
179 | | - self.assertIn(f"[STDOUT][FROM_ENV]: {env_text}", stdout_text) |
180 | | - self.assertIn(f"[STDERR][FROM_ENV]: {env_text}", stderr_text) |
181 | | - else: |
182 | | - self.assertIn(f"[STDOUT][FROM_ARGV]: {argv_replaced_text}", stdout_text) |
183 | | - self.assertIn(f"[STDERR][FROM_ARGV]: {argv_text}", stderr_text) |
| 149 | + self.assertIn(f"[STDOUT][FROM_ARGV]: {args_input}", stdout_text) |
| 150 | + self.assertIn(f"[STDOUT][FROM_ENV]: {env_input}", stdout_text) |
| 151 | + |
| 152 | + self.assertIn(f"[STDERR][FROM_ARGV]: {args_input}", stderr_text) |
| 153 | + self.assertIn(f"[STDERR][FROM_ENV]: {env_input}", stderr_text) |
184 | 154 |
|
185 | 155 | @abstractmethod |
186 | | - def _get_debuggee_stdout(self) -> str: |
| 156 | + def _get_debuggee_stdout(self, session: DAPTestSession) -> str: |
187 | 157 | """Retrieves the standard output (stdout) from the debuggee process. |
188 | 158 |
|
189 | | - The default destination of the debuggee's stdout can vary based on how the debugger |
| 159 | + The default destination of the debuggee's stdout can vary based on how the debuggee |
190 | 160 | was launched (either a debug console or a pseudo-terminal (pty)). |
191 | 161 | It requires subclasses to implement the specific mechanism for obtaining the stdout stream. |
192 | 162 | """ |
193 | 163 | raise RuntimeError(f"NotImplemented for {self}") |
194 | 164 |
|
195 | 165 | @abstractmethod |
196 | | - def _get_debuggee_stderr(self) -> str: |
| 166 | + def _get_debuggee_stderr(self, session: DAPTestSession) -> str: |
197 | 167 | """Retrieves the standard error (stderr) from the debuggee process. |
198 | 168 |
|
199 | | - The default destination of the debuggee's stderr can vary based on how the debugger |
| 169 | + The default destination of the debuggee's stderr can vary based on how the debuggee |
200 | 170 | was launched (either a debug console or a pseudo-terminal (pty)). |
201 | 171 | It requires subclasses to implement the specific mechanism for obtaining the stderr stream. |
202 | 172 | """ |
|
0 commit comments