]> git.ipfire.org Git - thirdparty/glibc.git/blob - scripts/test_printers_common.py
Add pretty printers for the NPTL lock types
[thirdparty/glibc.git] / scripts / test_printers_common.py
1 # Common functions and variables for testing the Python pretty printers.
2 #
3 # Copyright (C) 2016 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 exit(UNSUPPORTED)
91
92 # If everything's ok, spawn the gdb process we'll use for testing.
93 gdb = pexpect.spawn(gdb_invocation, echo=False, timeout=timeout,
94 encoding=encoding)
95 gdb_prompt = u'\(gdb\)'
96 gdb.expect(gdb_prompt)
97
98 except pexpect.ExceptionPexpect as exception:
99 print('Error: {0}'.format(exception))
100 exit(FAIL)
101
102 def test(command, pattern=None):
103 """Sends 'command' to gdb and expects the given 'pattern'.
104
105 If 'pattern' is None, simply consumes everything up to and including
106 the gdb prompt.
107
108 Args:
109 command (string): The command we'll send to gdb.
110 pattern (raw string): A pattern the gdb output should match.
111
112 Returns:
113 string: The string that matched 'pattern', or an empty string if
114 'pattern' was None.
115 """
116
117 match = ''
118
119 gdb.sendline(command)
120
121 if pattern:
122 # PExpect does a non-greedy match for '+' and '*'. Since it can't look
123 # ahead on the gdb output stream, if 'pattern' ends with a '+' or a '*'
124 # we may end up matching only part of the required output.
125 # To avoid this, we'll consume 'pattern' and anything that follows it
126 # up to and including the gdb prompt, then extract 'pattern' later.
127 index = gdb.expect([u'{0}.+{1}'.format(pattern, gdb_prompt),
128 pexpect.TIMEOUT])
129
130 if index == 0:
131 # gdb.after now contains the whole match. Extract the text that
132 # matches 'pattern'.
133 match = re.match(pattern, gdb.after, re.DOTALL).group()
134 elif index == 1:
135 # We got a timeout exception. Print information on what caused it
136 # and bail out.
137 error = ('Response does not match the expected pattern.\n'
138 'Command: {0}\n'
139 'Expected pattern: {1}\n'
140 'Response: {2}'.format(command, pattern, gdb.before))
141
142 raise pexpect.TIMEOUT(error)
143 else:
144 # Consume just the the gdb prompt.
145 gdb.expect(gdb_prompt)
146
147 return match
148
149 def init_test(test_bin, printer_files, printer_names):
150 """Loads the test binary file and the required pretty printers to gdb.
151
152 Args:
153 test_bin (string): The name of the test binary file.
154 pretty_printers (list of strings): A list with the names of the pretty
155 printer files.
156 """
157
158 # Load all the pretty printer files. We're assuming these are safe.
159 for printer_file in printer_files:
160 test('source {0}'.format(printer_file))
161
162 # Disable all the pretty printers.
163 test('disable pretty-printer', r'0 of [0-9]+ printers enabled')
164
165 # Enable only the required printers.
166 for printer in printer_names:
167 test('enable pretty-printer {0}'.format(printer),
168 r'[1-9][0-9]* of [1-9]+ printers enabled')
169
170 # Finally, load the test binary.
171 test('file {0}'.format(test_bin))
172
173 def go_to_main():
174 """Executes a gdb 'start' command, which takes us to main."""
175
176 test('start', r'main')
177
178 def get_line_number(file_name, string):
179 """Returns the number of the line in which 'string' appears within a file.
180
181 Args:
182 file_name (string): The name of the file we'll search through.
183 string (string): The string we'll look for.
184
185 Returns:
186 int: The number of the line in which 'string' appears, starting from 1.
187 """
188 number = -1
189
190 with open(file_name) as src_file:
191 for i, line in enumerate(src_file):
192 if string in line:
193 number = i + 1
194 break
195
196 if number == -1:
197 raise NoLineError(file_name, string)
198
199 return number
200
201 def break_at(file_name, string, temporary=True, thread=None):
202 """Places a breakpoint on the first line in 'file_name' containing 'string'.
203
204 'string' is usually a comment like "Stop here". Notice this may fail unless
205 the comment is placed inline next to actual code, e.g.:
206
207 ...
208 /* Stop here */
209 ...
210
211 may fail, while:
212
213 ...
214 some_func(); /* Stop here */
215 ...
216
217 will succeed.
218
219 If 'thread' isn't None, the breakpoint will be set for all the threads.
220 Otherwise, it'll be set only for 'thread'.
221
222 Args:
223 file_name (string): The name of the file we'll place the breakpoint in.
224 string (string): A string we'll look for inside the file.
225 We'll place a breakpoint on the line which contains it.
226 temporary (bool): Whether the breakpoint should be automatically deleted
227 after we reach it.
228 thread (int): The number of the thread we'll place the breakpoint for,
229 as seen by gdb. If specified, it should be greater than zero.
230 """
231
232 if not thread:
233 thread_str = ''
234 else:
235 thread_str = 'thread {0}'.format(thread)
236
237 if temporary:
238 command = 'tbreak'
239 break_type = 'Temporary breakpoint'
240 else:
241 command = 'break'
242 break_type = 'Breakpoint'
243
244 line_number = str(get_line_number(file_name, string))
245
246 test('{0} {1}:{2} {3}'.format(command, file_name, line_number, thread_str),
247 r'{0} [0-9]+ at 0x[a-f0-9]+: file {1}, line {2}\.'.format(break_type,
248 file_name,
249 line_number))
250
251 def continue_cmd(thread=None):
252 """Executes a gdb 'continue' command.
253
254 If 'thread' isn't None, the command will be applied to all the threads.
255 Otherwise, it'll be applied only to 'thread'.
256
257 Args:
258 thread (int): The number of the thread we'll apply the command to,
259 as seen by gdb. If specified, it should be greater than zero.
260 """
261
262 if not thread:
263 command = 'continue'
264 else:
265 command = 'thread apply {0} continue'.format(thread)
266
267 test(command)
268
269 def next_cmd(count=1, thread=None):
270 """Executes a gdb 'next' command.
271
272 If 'thread' isn't None, the command will be applied to all the threads.
273 Otherwise, it'll be applied only to 'thread'.
274
275 Args:
276 count (int): The 'count' argument of the 'next' command.
277 thread (int): The number of the thread we'll apply the command to,
278 as seen by gdb. If specified, it should be greater than zero.
279 """
280
281 if not thread:
282 command = 'next'
283 else:
284 command = 'thread apply {0} next'
285
286 test('{0} {1}'.format(command, count))
287
288 def select_thread(thread):
289 """Selects the thread indicated by 'thread'.
290
291 Args:
292 thread (int): The number of the thread we'll switch to, as seen by gdb.
293 This should be greater than zero.
294 """
295
296 if thread > 0:
297 test('thread {0}'.format(thread))
298
299 def get_current_thread_lwpid():
300 """Gets the current thread's Lightweight Process ID.
301
302 Returns:
303 string: The current thread's LWP ID.
304 """
305
306 # It's easier to get the LWP ID through the Python API than the gdb CLI.
307 command = 'python print(gdb.selected_thread().ptid[1])'
308
309 return test(command, r'[0-9]+')
310
311 def set_scheduler_locking(mode):
312 """Executes the gdb 'set scheduler-locking' command.
313
314 Args:
315 mode (bool): Whether the scheduler locking mode should be 'on'.
316 """
317 modes = {
318 True: 'on',
319 False: 'off'
320 }
321
322 test('set scheduler-locking {0}'.format(modes[mode]))
323
324 def test_printer(var, to_string, children=None, is_ptr=True):
325 """ Tests the output of a pretty printer.
326
327 For a variable called 'var', this tests whether its associated printer
328 outputs the expected 'to_string' and children (if any).
329
330 Args:
331 var (string): The name of the variable we'll print.
332 to_string (raw string): The expected output of the printer's 'to_string'
333 method.
334 children (map {raw string->raw string}): A map with the expected output
335 of the printer's children' method.
336 is_ptr (bool): Whether 'var' is a pointer, and thus should be
337 dereferenced.
338 """
339
340 if is_ptr:
341 var = '*{0}'.format(var)
342
343 test('print {0}'.format(var), to_string)
344
345 if children:
346 for name, value in children.items():
347 # Children are shown as 'name = value'.
348 test('print {0}'.format(var), r'{0} = {1}'.format(name, value))
349
350 def check_debug_symbol(symbol):
351 """ Tests whether a given debugging symbol exists.
352
353 If the symbol doesn't exist, raises a DebugError.
354
355 Args:
356 symbol (string): The symbol we're going to check for.
357 """
358
359 try:
360 test('ptype {0}'.format(symbol), r'type = {0}'.format(symbol))
361
362 except pexpect.TIMEOUT:
363 # The symbol doesn't exist.
364 raise DebugError(symbol)