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