]>
Commit | Line | Data |
---|---|---|
23b5cae1 MG |
1 | # Common functions and variables for testing the Python pretty printers. |
2 | # | |
bfff8b1b | 3 | # Copyright (C) 2016-2017 Free Software Foundation, Inc. |
23b5cae1 MG |
4 | # This file is part of the GNU C Library. |
5 | # | |
6 | # The GNU C Library is free software; you can redistribute it and/or | |
7 | # modify it under the terms of the GNU Lesser General Public | |
8 | # License as published by the Free Software Foundation; either | |
9 | # version 2.1 of the License, or (at your option) any later version. | |
10 | # | |
11 | # The GNU C Library is distributed in the hope that it will be useful, | |
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | # Lesser General Public License for more details. | |
15 | # | |
16 | # You should have received a copy of the GNU Lesser General Public | |
17 | # License along with the GNU C Library; if not, see | |
18 | # <http://www.gnu.org/licenses/>. | |
19 | ||
20 | """These tests require PExpect 4.0 or newer. | |
21 | ||
22 | Exported constants: | |
23 | PASS, FAIL, UNSUPPORTED (int): Test exit codes, as per evaluate-test.sh. | |
24 | """ | |
25 | ||
26 | import os | |
27 | import re | |
28 | from test_printers_exceptions import * | |
29 | ||
30 | PASS = 0 | |
31 | FAIL = 1 | |
32 | UNSUPPORTED = 77 | |
33 | ||
34 | gdb_bin = 'gdb' | |
35 | gdb_options = '-q -nx' | |
36 | gdb_invocation = '{0} {1}'.format(gdb_bin, gdb_options) | |
37 | pexpect_min_version = 4 | |
38 | gdb_min_version = (7, 8) | |
39 | encoding = 'utf-8' | |
40 | ||
41 | try: | |
42 | import pexpect | |
43 | except ImportError: | |
44 | print('PExpect 4.0 or newer must be installed to test the pretty printers.') | |
45 | exit(UNSUPPORTED) | |
46 | ||
47 | pexpect_version = pexpect.__version__.split('.')[0] | |
48 | ||
49 | if int(pexpect_version) < pexpect_min_version: | |
50 | print('PExpect 4.0 or newer must be installed to test the pretty printers.') | |
51 | exit(UNSUPPORTED) | |
52 | ||
53 | if not pexpect.which(gdb_bin): | |
54 | print('gdb 7.8 or newer must be installed to test the pretty printers.') | |
55 | exit(UNSUPPORTED) | |
56 | ||
57 | timeout = 5 | |
58 | TIMEOUTFACTOR = os.environ.get('TIMEOUTFACTOR') | |
59 | ||
60 | if TIMEOUTFACTOR: | |
61 | timeout = int(TIMEOUTFACTOR) | |
62 | ||
63 | try: | |
64 | # Check the gdb version. | |
65 | version_cmd = '{0} --version'.format(gdb_invocation, timeout=timeout) | |
66 | gdb_version_out = pexpect.run(version_cmd, encoding=encoding) | |
67 | ||
68 | # The gdb version string is "GNU gdb <PKGVERSION><version>", where | |
69 | # PKGVERSION can be any text. We assume that there'll always be a space | |
70 | # between PKGVERSION and the version number for the sake of the regexp. | |
71 | version_match = re.search(r'GNU gdb .* ([1-9]+)\.([0-9]+)', gdb_version_out) | |
72 | ||
73 | if not version_match: | |
74 | print('The gdb version string (gdb -v) is incorrectly formatted.') | |
75 | exit(UNSUPPORTED) | |
76 | ||
77 | gdb_version = (int(version_match.group(1)), int(version_match.group(2))) | |
78 | ||
79 | if gdb_version < gdb_min_version: | |
80 | print('gdb 7.8 or newer must be installed to test the pretty printers.') | |
81 | exit(UNSUPPORTED) | |
82 | ||
83 | # Check if gdb supports Python. | |
84 | gdb_python_cmd = '{0} -ex "python import os" -batch'.format(gdb_invocation, | |
85 | timeout=timeout) | |
86 | gdb_python_error = pexpect.run(gdb_python_cmd, encoding=encoding) | |
87 | ||
88 | if gdb_python_error: | |
89 | print('gdb must have python support to test the pretty printers.') | |
b064bba5 | 90 | print('gdb output: {!r}'.format(gdb_python_error)) |
23b5cae1 MG |
91 | exit(UNSUPPORTED) |
92 | ||
93 | # If everything's ok, spawn the gdb process we'll use for testing. | |
94 | gdb = pexpect.spawn(gdb_invocation, echo=False, timeout=timeout, | |
95 | encoding=encoding) | |
96 | gdb_prompt = u'\(gdb\)' | |
97 | gdb.expect(gdb_prompt) | |
98 | ||
99 | except pexpect.ExceptionPexpect as exception: | |
100 | print('Error: {0}'.format(exception)) | |
101 | exit(FAIL) | |
102 | ||
103 | def test(command, pattern=None): | |
104 | """Sends 'command' to gdb and expects the given 'pattern'. | |
105 | ||
106 | If 'pattern' is None, simply consumes everything up to and including | |
107 | the gdb prompt. | |
108 | ||
109 | Args: | |
110 | command (string): The command we'll send to gdb. | |
111 | pattern (raw string): A pattern the gdb output should match. | |
112 | ||
113 | Returns: | |
114 | string: The string that matched 'pattern', or an empty string if | |
115 | 'pattern' was None. | |
116 | """ | |
117 | ||
118 | match = '' | |
119 | ||
120 | gdb.sendline(command) | |
121 | ||
122 | if pattern: | |
123 | # PExpect does a non-greedy match for '+' and '*'. Since it can't look | |
124 | # ahead on the gdb output stream, if 'pattern' ends with a '+' or a '*' | |
125 | # we may end up matching only part of the required output. | |
126 | # To avoid this, we'll consume 'pattern' and anything that follows it | |
127 | # up to and including the gdb prompt, then extract 'pattern' later. | |
128 | index = gdb.expect([u'{0}.+{1}'.format(pattern, gdb_prompt), | |
129 | pexpect.TIMEOUT]) | |
130 | ||
131 | if index == 0: | |
132 | # gdb.after now contains the whole match. Extract the text that | |
133 | # matches 'pattern'. | |
134 | match = re.match(pattern, gdb.after, re.DOTALL).group() | |
135 | elif index == 1: | |
136 | # We got a timeout exception. Print information on what caused it | |
137 | # and bail out. | |
138 | error = ('Response does not match the expected pattern.\n' | |
139 | 'Command: {0}\n' | |
140 | 'Expected pattern: {1}\n' | |
141 | 'Response: {2}'.format(command, pattern, gdb.before)) | |
142 | ||
143 | raise pexpect.TIMEOUT(error) | |
144 | else: | |
145 | # Consume just the the gdb prompt. | |
146 | gdb.expect(gdb_prompt) | |
147 | ||
148 | return match | |
149 | ||
150 | def init_test(test_bin, printer_files, printer_names): | |
151 | """Loads the test binary file and the required pretty printers to gdb. | |
152 | ||
153 | Args: | |
154 | test_bin (string): The name of the test binary file. | |
155 | pretty_printers (list of strings): A list with the names of the pretty | |
156 | printer files. | |
157 | """ | |
158 | ||
159 | # Load all the pretty printer files. We're assuming these are safe. | |
160 | for printer_file in printer_files: | |
161 | test('source {0}'.format(printer_file)) | |
162 | ||
163 | # Disable all the pretty printers. | |
164 | test('disable pretty-printer', r'0 of [0-9]+ printers enabled') | |
165 | ||
166 | # Enable only the required printers. | |
167 | for printer in printer_names: | |
168 | test('enable pretty-printer {0}'.format(printer), | |
169 | r'[1-9][0-9]* of [1-9]+ printers enabled') | |
170 | ||
171 | # Finally, load the test binary. | |
172 | test('file {0}'.format(test_bin)) | |
173 | ||
174 | def go_to_main(): | |
175 | """Executes a gdb 'start' command, which takes us to main.""" | |
176 | ||
177 | test('start', r'main') | |
178 | ||
179 | def get_line_number(file_name, string): | |
180 | """Returns the number of the line in which 'string' appears within a file. | |
181 | ||
182 | Args: | |
183 | file_name (string): The name of the file we'll search through. | |
184 | string (string): The string we'll look for. | |
185 | ||
186 | Returns: | |
187 | int: The number of the line in which 'string' appears, starting from 1. | |
188 | """ | |
189 | number = -1 | |
190 | ||
191 | with open(file_name) as src_file: | |
192 | for i, line in enumerate(src_file): | |
193 | if string in line: | |
194 | number = i + 1 | |
195 | break | |
196 | ||
197 | if number == -1: | |
198 | raise NoLineError(file_name, string) | |
199 | ||
200 | return number | |
201 | ||
202 | def break_at(file_name, string, temporary=True, thread=None): | |
203 | """Places a breakpoint on the first line in 'file_name' containing 'string'. | |
204 | ||
205 | 'string' is usually a comment like "Stop here". Notice this may fail unless | |
206 | the comment is placed inline next to actual code, e.g.: | |
207 | ||
208 | ... | |
209 | /* Stop here */ | |
210 | ... | |
211 | ||
212 | may fail, while: | |
213 | ||
214 | ... | |
215 | some_func(); /* Stop here */ | |
216 | ... | |
217 | ||
218 | will succeed. | |
219 | ||
220 | If 'thread' isn't None, the breakpoint will be set for all the threads. | |
221 | Otherwise, it'll be set only for 'thread'. | |
222 | ||
223 | Args: | |
224 | file_name (string): The name of the file we'll place the breakpoint in. | |
225 | string (string): A string we'll look for inside the file. | |
226 | We'll place a breakpoint on the line which contains it. | |
227 | temporary (bool): Whether the breakpoint should be automatically deleted | |
228 | after we reach it. | |
229 | thread (int): The number of the thread we'll place the breakpoint for, | |
230 | as seen by gdb. If specified, it should be greater than zero. | |
231 | """ | |
232 | ||
233 | if not thread: | |
234 | thread_str = '' | |
235 | else: | |
236 | thread_str = 'thread {0}'.format(thread) | |
237 | ||
238 | if temporary: | |
239 | command = 'tbreak' | |
240 | break_type = 'Temporary breakpoint' | |
241 | else: | |
242 | command = 'break' | |
243 | break_type = 'Breakpoint' | |
244 | ||
245 | line_number = str(get_line_number(file_name, string)) | |
246 | ||
247 | test('{0} {1}:{2} {3}'.format(command, file_name, line_number, thread_str), | |
248 | r'{0} [0-9]+ at 0x[a-f0-9]+: file {1}, line {2}\.'.format(break_type, | |
249 | file_name, | |
250 | line_number)) | |
251 | ||
252 | def continue_cmd(thread=None): | |
253 | """Executes a gdb 'continue' command. | |
254 | ||
255 | If 'thread' isn't None, the command will be applied to all the threads. | |
256 | Otherwise, it'll be applied only to 'thread'. | |
257 | ||
258 | Args: | |
259 | thread (int): The number of the thread we'll apply the command to, | |
260 | as seen by gdb. If specified, it should be greater than zero. | |
261 | """ | |
262 | ||
263 | if not thread: | |
264 | command = 'continue' | |
265 | else: | |
266 | command = 'thread apply {0} continue'.format(thread) | |
267 | ||
268 | test(command) | |
269 | ||
270 | def next_cmd(count=1, thread=None): | |
271 | """Executes a gdb 'next' command. | |
272 | ||
273 | If 'thread' isn't None, the command will be applied to all the threads. | |
274 | Otherwise, it'll be applied only to 'thread'. | |
275 | ||
276 | Args: | |
277 | count (int): The 'count' argument of the 'next' command. | |
278 | thread (int): The number of the thread we'll apply the command to, | |
279 | as seen by gdb. If specified, it should be greater than zero. | |
280 | """ | |
281 | ||
282 | if not thread: | |
283 | command = 'next' | |
284 | else: | |
285 | command = 'thread apply {0} next' | |
286 | ||
287 | test('{0} {1}'.format(command, count)) | |
288 | ||
289 | def select_thread(thread): | |
290 | """Selects the thread indicated by 'thread'. | |
291 | ||
292 | Args: | |
293 | thread (int): The number of the thread we'll switch to, as seen by gdb. | |
294 | This should be greater than zero. | |
295 | """ | |
296 | ||
297 | if thread > 0: | |
298 | test('thread {0}'.format(thread)) | |
299 | ||
300 | def get_current_thread_lwpid(): | |
301 | """Gets the current thread's Lightweight Process ID. | |
302 | ||
303 | Returns: | |
304 | string: The current thread's LWP ID. | |
305 | """ | |
306 | ||
307 | # It's easier to get the LWP ID through the Python API than the gdb CLI. | |
308 | command = 'python print(gdb.selected_thread().ptid[1])' | |
309 | ||
310 | return test(command, r'[0-9]+') | |
311 | ||
312 | def set_scheduler_locking(mode): | |
313 | """Executes the gdb 'set scheduler-locking' command. | |
314 | ||
315 | Args: | |
316 | mode (bool): Whether the scheduler locking mode should be 'on'. | |
317 | """ | |
318 | modes = { | |
319 | True: 'on', | |
320 | False: 'off' | |
321 | } | |
322 | ||
323 | test('set scheduler-locking {0}'.format(modes[mode])) | |
324 | ||
325 | def test_printer(var, to_string, children=None, is_ptr=True): | |
326 | """ Tests the output of a pretty printer. | |
327 | ||
328 | For a variable called 'var', this tests whether its associated printer | |
329 | outputs the expected 'to_string' and children (if any). | |
330 | ||
331 | Args: | |
332 | var (string): The name of the variable we'll print. | |
333 | to_string (raw string): The expected output of the printer's 'to_string' | |
334 | method. | |
335 | children (map {raw string->raw string}): A map with the expected output | |
336 | of the printer's children' method. | |
337 | is_ptr (bool): Whether 'var' is a pointer, and thus should be | |
338 | dereferenced. | |
339 | """ | |
340 | ||
341 | if is_ptr: | |
342 | var = '*{0}'.format(var) | |
343 | ||
344 | test('print {0}'.format(var), to_string) | |
345 | ||
346 | if children: | |
347 | for name, value in children.items(): | |
348 | # Children are shown as 'name = value'. | |
349 | test('print {0}'.format(var), r'{0} = {1}'.format(name, value)) | |
350 | ||
351 | def check_debug_symbol(symbol): | |
352 | """ Tests whether a given debugging symbol exists. | |
353 | ||
354 | If the symbol doesn't exist, raises a DebugError. | |
355 | ||
356 | Args: | |
357 | symbol (string): The symbol we're going to check for. | |
358 | """ | |
359 | ||
360 | try: | |
361 | test('ptype {0}'.format(symbol), r'type = {0}'.format(symbol)) | |
362 | ||
363 | except pexpect.TIMEOUT: | |
364 | # The symbol doesn't exist. | |
365 | raise DebugError(symbol) |