1# Common functions and variables for testing the Python pretty printers.
2#
3# Copyright (C) 2016-2021 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
22Exported constants:
23    PASS, FAIL, UNSUPPORTED (int): Test exit codes, as per evaluate-test.sh.
24"""
25
26import os
27import re
28from test_printers_exceptions import *
29
30PASS = 0
31FAIL = 1
32UNSUPPORTED = 77
33
34gdb_bin = 'gdb'
35gdb_options = '-q -nx'
36gdb_invocation = '{0} {1}'.format(gdb_bin, gdb_options)
37pexpect_min_version = 4
38gdb_min_version = (7, 8)
39encoding = 'utf-8'
40
41try:
42    import pexpect
43except ImportError:
44    print('PExpect 4.0 or newer must be installed to test the pretty printers.')
45    exit(UNSUPPORTED)
46
47pexpect_version = pexpect.__version__.split('.')[0]
48
49if 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
53if not pexpect.which(gdb_bin):
54    print('gdb 7.8 or newer must be installed to test the pretty printers.')
55    exit(UNSUPPORTED)
56
57timeout = 5
58TIMEOUTFACTOR = os.environ.get('TIMEOUTFACTOR')
59
60if 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.
65os.environ["TERM"]="dumb"
66
67try:
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
104except pexpect.ExceptionPexpect as exception:
105    print('Error: {0}'.format(exception))
106    exit(FAIL)
107
108def 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
155def 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    # Load all the pretty printer files.  We're assuming these are safe.
165    for printer_file in printer_files:
166        test('source {0}'.format(printer_file))
167
168    # Disable all the pretty printers.
169    test('disable pretty-printer', r'0 of [0-9]+ printers enabled')
170
171    # Enable only the required printers.
172    for printer in printer_names:
173        test('enable pretty-printer {0}'.format(printer),
174             r'[1-9][0-9]* of [1-9]+ printers enabled')
175
176    # Finally, load the test binary.
177    test('file {0}'.format(test_bin))
178
179    # Disable lock elision.
180    test('set environment GLIBC_TUNABLES glibc.elision.enable=0')
181
182def go_to_main():
183    """Executes a gdb 'start' command, which takes us to main."""
184
185    test('start', r'main')
186
187def get_line_number(file_name, string):
188    """Returns the number of the line in which 'string' appears within a file.
189
190    Args:
191        file_name (string): The name of the file we'll search through.
192        string (string): The string we'll look for.
193
194    Returns:
195        int: The number of the line in which 'string' appears, starting from 1.
196    """
197    number = -1
198
199    with open(file_name) as src_file:
200        for i, line in enumerate(src_file):
201            if string in line:
202                number = i + 1
203                break
204
205    if number == -1:
206        raise NoLineError(file_name, string)
207
208    return number
209
210def break_at(file_name, string, temporary=True, thread=None):
211    """Places a breakpoint on the first line in 'file_name' containing 'string'.
212
213    'string' is usually a comment like "Stop here".  Notice this may fail unless
214    the comment is placed inline next to actual code, e.g.:
215
216        ...
217        /* Stop here */
218        ...
219
220    may fail, while:
221
222        ...
223        some_func(); /* Stop here */
224        ...
225
226    will succeed.
227
228    If 'thread' isn't None, the breakpoint will be set for all the threads.
229    Otherwise, it'll be set only for 'thread'.
230
231    Args:
232        file_name (string): The name of the file we'll place the breakpoint in.
233        string (string): A string we'll look for inside the file.
234            We'll place a breakpoint on the line which contains it.
235        temporary (bool): Whether the breakpoint should be automatically deleted
236            after we reach it.
237        thread (int): The number of the thread we'll place the breakpoint for,
238            as seen by gdb.  If specified, it should be greater than zero.
239    """
240
241    if not thread:
242        thread_str = ''
243    else:
244        thread_str = 'thread {0}'.format(thread)
245
246    if temporary:
247        command = 'tbreak'
248        break_type = 'Temporary breakpoint'
249    else:
250        command = 'break'
251        break_type = 'Breakpoint'
252
253    line_number = str(get_line_number(file_name, string))
254
255    test('{0} {1}:{2} {3}'.format(command, file_name, line_number, thread_str),
256         r'{0} [0-9]+ at 0x[a-f0-9]+: file {1}, line {2}\.'.format(break_type,
257                                                                   file_name,
258                                                                   line_number))
259
260def continue_cmd(thread=None):
261    """Executes a gdb 'continue' command.
262
263    If 'thread' isn't None, the command will be applied to all the threads.
264    Otherwise, it'll be applied only to 'thread'.
265
266    Args:
267        thread (int): The number of the thread we'll apply the command to,
268            as seen by gdb.  If specified, it should be greater than zero.
269    """
270
271    if not thread:
272        command = 'continue'
273    else:
274        command = 'thread apply {0} continue'.format(thread)
275
276    test(command)
277
278def next_cmd(count=1, thread=None):
279    """Executes a gdb 'next' command.
280
281    If 'thread' isn't None, the command will be applied to all the threads.
282    Otherwise, it'll be applied only to 'thread'.
283
284    Args:
285        count (int): The 'count' argument of the 'next' command.
286        thread (int): The number of the thread we'll apply the command to,
287            as seen by gdb.  If specified, it should be greater than zero.
288    """
289
290    if not thread:
291        command = 'next'
292    else:
293        command = 'thread apply {0} next'
294
295    test('{0} {1}'.format(command, count))
296
297def select_thread(thread):
298    """Selects the thread indicated by 'thread'.
299
300    Args:
301        thread (int): The number of the thread we'll switch to, as seen by gdb.
302            This should be greater than zero.
303    """
304
305    if thread > 0:
306        test('thread {0}'.format(thread))
307
308def get_current_thread_lwpid():
309    """Gets the current thread's Lightweight Process ID.
310
311    Returns:
312        string: The current thread's LWP ID.
313    """
314
315    # It's easier to get the LWP ID through the Python API than the gdb CLI.
316    command = 'python print(gdb.selected_thread().ptid[1])'
317
318    return test(command, r'[0-9]+')
319
320def set_scheduler_locking(mode):
321    """Executes the gdb 'set scheduler-locking' command.
322
323    Args:
324        mode (bool): Whether the scheduler locking mode should be 'on'.
325    """
326    modes = {
327        True: 'on',
328        False: 'off'
329    }
330
331    test('set scheduler-locking {0}'.format(modes[mode]))
332
333def test_printer(var, to_string, children=None, is_ptr=True):
334    """ Tests the output of a pretty printer.
335
336    For a variable called 'var', this tests whether its associated printer
337    outputs the expected 'to_string' and children (if any).
338
339    Args:
340        var (string): The name of the variable we'll print.
341        to_string (raw string): The expected output of the printer's 'to_string'
342            method.
343        children (map {raw string->raw string}): A map with the expected output
344            of the printer's children' method.
345        is_ptr (bool): Whether 'var' is a pointer, and thus should be
346            dereferenced.
347    """
348
349    if is_ptr:
350        var = '*{0}'.format(var)
351
352    test('print {0}'.format(var), to_string)
353
354    if children:
355        for name, value in children.items():
356            # Children are shown as 'name = value'.
357            test('print {0}'.format(var), r'{0} = {1}'.format(name, value))
358
359def check_debug_symbol(symbol):
360    """ Tests whether a given debugging symbol exists.
361
362    If the symbol doesn't exist, raises a DebugError.
363
364    Args:
365        symbol (string): The symbol we're going to check for.
366    """
367
368    try:
369        test('ptype {0}'.format(symbol), r'type = {0}'.format(symbol))
370
371    except pexpect.TIMEOUT:
372        # The symbol doesn't exist.
373        raise DebugError(symbol)
374