]>
Commit | Line | Data |
---|---|---|
23b5cae1 MG |
1 | # Common functions and variables for testing the Python pretty printers. |
2 | # | |
04277e02 | 3 | # Copyright (C) 2016-2019 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 | ||
0085be14 SL |
174 | # Disable lock elision. |
175 | test('set environment GLIBC_TUNABLES glibc.elision.enable=0') | |
176 | ||
23b5cae1 MG |
177 | def go_to_main(): |
178 | """Executes a gdb 'start' command, which takes us to main.""" | |
179 | ||
180 | test('start', r'main') | |
181 | ||
182 | def get_line_number(file_name, string): | |
183 | """Returns the number of the line in which 'string' appears within a file. | |
184 | ||
185 | Args: | |
186 | file_name (string): The name of the file we'll search through. | |
187 | string (string): The string we'll look for. | |
188 | ||
189 | Returns: | |
190 | int: The number of the line in which 'string' appears, starting from 1. | |
191 | """ | |
192 | number = -1 | |
193 | ||
194 | with open(file_name) as src_file: | |
195 | for i, line in enumerate(src_file): | |
196 | if string in line: | |
197 | number = i + 1 | |
198 | break | |
199 | ||
200 | if number == -1: | |
201 | raise NoLineError(file_name, string) | |
202 | ||
203 | return number | |
204 | ||
205 | def break_at(file_name, string, temporary=True, thread=None): | |
206 | """Places a breakpoint on the first line in 'file_name' containing 'string'. | |
207 | ||
208 | 'string' is usually a comment like "Stop here". Notice this may fail unless | |
209 | the comment is placed inline next to actual code, e.g.: | |
210 | ||
211 | ... | |
212 | /* Stop here */ | |
213 | ... | |
214 | ||
215 | may fail, while: | |
216 | ||
217 | ... | |
218 | some_func(); /* Stop here */ | |
219 | ... | |
220 | ||
221 | will succeed. | |
222 | ||
223 | If 'thread' isn't None, the breakpoint will be set for all the threads. | |
224 | Otherwise, it'll be set only for 'thread'. | |
225 | ||
226 | Args: | |
227 | file_name (string): The name of the file we'll place the breakpoint in. | |
228 | string (string): A string we'll look for inside the file. | |
229 | We'll place a breakpoint on the line which contains it. | |
230 | temporary (bool): Whether the breakpoint should be automatically deleted | |
231 | after we reach it. | |
232 | thread (int): The number of the thread we'll place the breakpoint for, | |
233 | as seen by gdb. If specified, it should be greater than zero. | |
234 | """ | |
235 | ||
236 | if not thread: | |
237 | thread_str = '' | |
238 | else: | |
239 | thread_str = 'thread {0}'.format(thread) | |
240 | ||
241 | if temporary: | |
242 | command = 'tbreak' | |
243 | break_type = 'Temporary breakpoint' | |
244 | else: | |
245 | command = 'break' | |
246 | break_type = 'Breakpoint' | |
247 | ||
248 | line_number = str(get_line_number(file_name, string)) | |
249 | ||
250 | test('{0} {1}:{2} {3}'.format(command, file_name, line_number, thread_str), | |
251 | r'{0} [0-9]+ at 0x[a-f0-9]+: file {1}, line {2}\.'.format(break_type, | |
252 | file_name, | |
253 | line_number)) | |
254 | ||
255 | def continue_cmd(thread=None): | |
256 | """Executes a gdb 'continue' command. | |
257 | ||
258 | If 'thread' isn't None, the command will be applied to all the threads. | |
259 | Otherwise, it'll be applied only to 'thread'. | |
260 | ||
261 | Args: | |
262 | thread (int): The number of the thread we'll apply the command to, | |
263 | as seen by gdb. If specified, it should be greater than zero. | |
264 | """ | |
265 | ||
266 | if not thread: | |
267 | command = 'continue' | |
268 | else: | |
269 | command = 'thread apply {0} continue'.format(thread) | |
270 | ||
271 | test(command) | |
272 | ||
273 | def next_cmd(count=1, thread=None): | |
274 | """Executes a gdb 'next' command. | |
275 | ||
276 | If 'thread' isn't None, the command will be applied to all the threads. | |
277 | Otherwise, it'll be applied only to 'thread'. | |
278 | ||
279 | Args: | |
280 | count (int): The 'count' argument of the 'next' command. | |
281 | thread (int): The number of the thread we'll apply the command to, | |
282 | as seen by gdb. If specified, it should be greater than zero. | |
283 | """ | |
284 | ||
285 | if not thread: | |
286 | command = 'next' | |
287 | else: | |
288 | command = 'thread apply {0} next' | |
289 | ||
290 | test('{0} {1}'.format(command, count)) | |
291 | ||
292 | def select_thread(thread): | |
293 | """Selects the thread indicated by 'thread'. | |
294 | ||
295 | Args: | |
296 | thread (int): The number of the thread we'll switch to, as seen by gdb. | |
297 | This should be greater than zero. | |
298 | """ | |
299 | ||
300 | if thread > 0: | |
301 | test('thread {0}'.format(thread)) | |
302 | ||
303 | def get_current_thread_lwpid(): | |
304 | """Gets the current thread's Lightweight Process ID. | |
305 | ||
306 | Returns: | |
307 | string: The current thread's LWP ID. | |
308 | """ | |
309 | ||
310 | # It's easier to get the LWP ID through the Python API than the gdb CLI. | |
311 | command = 'python print(gdb.selected_thread().ptid[1])' | |
312 | ||
313 | return test(command, r'[0-9]+') | |
314 | ||
315 | def set_scheduler_locking(mode): | |
316 | """Executes the gdb 'set scheduler-locking' command. | |
317 | ||
318 | Args: | |
319 | mode (bool): Whether the scheduler locking mode should be 'on'. | |
320 | """ | |
321 | modes = { | |
322 | True: 'on', | |
323 | False: 'off' | |
324 | } | |
325 | ||
326 | test('set scheduler-locking {0}'.format(modes[mode])) | |
327 | ||
328 | def test_printer(var, to_string, children=None, is_ptr=True): | |
329 | """ Tests the output of a pretty printer. | |
330 | ||
331 | For a variable called 'var', this tests whether its associated printer | |
332 | outputs the expected 'to_string' and children (if any). | |
333 | ||
334 | Args: | |
335 | var (string): The name of the variable we'll print. | |
336 | to_string (raw string): The expected output of the printer's 'to_string' | |
337 | method. | |
338 | children (map {raw string->raw string}): A map with the expected output | |
339 | of the printer's children' method. | |
340 | is_ptr (bool): Whether 'var' is a pointer, and thus should be | |
341 | dereferenced. | |
342 | """ | |
343 | ||
344 | if is_ptr: | |
345 | var = '*{0}'.format(var) | |
346 | ||
347 | test('print {0}'.format(var), to_string) | |
348 | ||
349 | if children: | |
350 | for name, value in children.items(): | |
351 | # Children are shown as 'name = value'. | |
352 | test('print {0}'.format(var), r'{0} = {1}'.format(name, value)) | |
353 | ||
354 | def check_debug_symbol(symbol): | |
355 | """ Tests whether a given debugging symbol exists. | |
356 | ||
357 | If the symbol doesn't exist, raises a DebugError. | |
358 | ||
359 | Args: | |
360 | symbol (string): The symbol we're going to check for. | |
361 | """ | |
362 | ||
363 | try: | |
364 | test('ptype {0}'.format(symbol), r'type = {0}'.format(symbol)) | |
365 | ||
366 | except pexpect.TIMEOUT: | |
367 | # The symbol doesn't exist. | |
368 | raise DebugError(symbol) |