]> git.ipfire.org Git - thirdparty/glibc.git/blob - scripts/test_printers_common.py
cf4de5ae23aa47da1881282ad81c1752cd3e550c
[thirdparty/glibc.git] / scripts / test_printers_common.py
1 # Common functions and variables for testing the Python pretty printers.
2 #
3 # Copyright (C) 2016-2018 Free Software Foundation, Inc.
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.')
90 print('gdb output: {!r}'.format(gdb_python_error))
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 # Disable lock elision.
175 test('set environment GLIBC_TUNABLES glibc.elision.enable=0')
176
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)