Blame scripts/test_printers_common.py

Packit Service 82fcde
# Common functions and variables for testing the Python pretty printers.
Packit Service 82fcde
#
Packit Service 82fcde
# Copyright (C) 2016-2018 Free Software Foundation, Inc.
Packit Service 82fcde
# This file is part of the GNU C Library.
Packit Service 82fcde
#
Packit Service 82fcde
# The GNU C Library is free software; you can redistribute it and/or
Packit Service 82fcde
# modify it under the terms of the GNU Lesser General Public
Packit Service 82fcde
# License as published by the Free Software Foundation; either
Packit Service 82fcde
# version 2.1 of the License, or (at your option) any later version.
Packit Service 82fcde
#
Packit Service 82fcde
# The GNU C Library is distributed in the hope that it will be useful,
Packit Service 82fcde
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service 82fcde
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit Service 82fcde
# Lesser General Public License for more details.
Packit Service 82fcde
#
Packit Service 82fcde
# You should have received a copy of the GNU Lesser General Public
Packit Service 82fcde
# License along with the GNU C Library; if not, see
Packit Service 82fcde
# <http://www.gnu.org/licenses/>.
Packit Service 82fcde
Packit Service 82fcde
"""These tests require PExpect 4.0 or newer.
Packit Service 82fcde
Packit Service 82fcde
Exported constants:
Packit Service 82fcde
    PASS, FAIL, UNSUPPORTED (int): Test exit codes, as per evaluate-test.sh.
Packit Service 82fcde
"""
Packit Service 82fcde
Packit Service 82fcde
import os
Packit Service 82fcde
import re
Packit Service 82fcde
from test_printers_exceptions import *
Packit Service 82fcde
Packit Service 82fcde
PASS = 0
Packit Service 82fcde
FAIL = 1
Packit Service 82fcde
UNSUPPORTED = 77
Packit Service 82fcde
Packit Service 82fcde
gdb_bin = 'gdb'
Packit Service 82fcde
gdb_options = '-q -nx'
Packit Service 82fcde
gdb_invocation = '{0} {1}'.format(gdb_bin, gdb_options)
Packit Service 82fcde
pexpect_min_version = 4
Packit Service 82fcde
gdb_min_version = (7, 8)
Packit Service 82fcde
encoding = 'utf-8'
Packit Service 82fcde
Packit Service 82fcde
try:
Packit Service 82fcde
    import pexpect
Packit Service 82fcde
except ImportError:
Packit Service 82fcde
    print('PExpect 4.0 or newer must be installed to test the pretty printers.')
Packit Service 82fcde
    exit(UNSUPPORTED)
Packit Service 82fcde
Packit Service 82fcde
pexpect_version = pexpect.__version__.split('.')[0]
Packit Service 82fcde
Packit Service 82fcde
if int(pexpect_version) < pexpect_min_version:
Packit Service 82fcde
    print('PExpect 4.0 or newer must be installed to test the pretty printers.')
Packit Service 82fcde
    exit(UNSUPPORTED)
Packit Service 82fcde
Packit Service 82fcde
if not pexpect.which(gdb_bin):
Packit Service 82fcde
    print('gdb 7.8 or newer must be installed to test the pretty printers.')
Packit Service 82fcde
    exit(UNSUPPORTED)
Packit Service 82fcde
Packit Service 82fcde
timeout = 5
Packit Service 82fcde
TIMEOUTFACTOR = os.environ.get('TIMEOUTFACTOR')
Packit Service 82fcde
Packit Service 82fcde
if TIMEOUTFACTOR:
Packit Service 82fcde
    timeout = int(TIMEOUTFACTOR)
Packit Service 82fcde
Packit Service 82fcde
try:
Packit Service 82fcde
    # Check the gdb version.
Packit Service 82fcde
    version_cmd = '{0} --version'.format(gdb_invocation, timeout=timeout)
Packit Service 82fcde
    gdb_version_out = pexpect.run(version_cmd, encoding=encoding)
Packit Service 82fcde
Packit Service 82fcde
    # The gdb version string is "GNU gdb <PKGVERSION><version>", where
Packit Service 82fcde
    # PKGVERSION can be any text.  We assume that there'll always be a space
Packit Service 82fcde
    # between PKGVERSION and the version number for the sake of the regexp.
Packit Service 82fcde
    version_match = re.search(r'GNU gdb .* ([1-9]+)\.([0-9]+)', gdb_version_out)
Packit Service 82fcde
Packit Service 82fcde
    if not version_match:
Packit Service 82fcde
        print('The gdb version string (gdb -v) is incorrectly formatted.')
Packit Service 82fcde
        exit(UNSUPPORTED)
Packit Service 82fcde
Packit Service 82fcde
    gdb_version = (int(version_match.group(1)), int(version_match.group(2)))
Packit Service 82fcde
Packit Service 82fcde
    if gdb_version < gdb_min_version:
Packit Service 82fcde
        print('gdb 7.8 or newer must be installed to test the pretty printers.')
Packit Service 82fcde
        exit(UNSUPPORTED)
Packit Service 82fcde
Packit Service 82fcde
    # Check if gdb supports Python.
Packit Service 82fcde
    gdb_python_cmd = '{0} -ex "python import os" -batch'.format(gdb_invocation,
Packit Service 82fcde
                                                                timeout=timeout)
Packit Service 82fcde
    gdb_python_error = pexpect.run(gdb_python_cmd, encoding=encoding)
Packit Service 82fcde
Packit Service 82fcde
    if gdb_python_error:
Packit Service 82fcde
        print('gdb must have python support to test the pretty printers.')
Packit Service 82fcde
        print('gdb output: {!r}'.format(gdb_python_error))
Packit Service 82fcde
        exit(UNSUPPORTED)
Packit Service 82fcde
Packit Service 82fcde
    # If everything's ok, spawn the gdb process we'll use for testing.
Packit Service 82fcde
    gdb = pexpect.spawn(gdb_invocation, echo=False, timeout=timeout,
Packit Service 82fcde
                        encoding=encoding)
Packit Service 82fcde
    gdb_prompt = u'\(gdb\)'
Packit Service 82fcde
    gdb.expect(gdb_prompt)
Packit Service 82fcde
Packit Service 82fcde
except pexpect.ExceptionPexpect as exception:
Packit Service 82fcde
    print('Error: {0}'.format(exception))
Packit Service 82fcde
    exit(FAIL)
Packit Service 82fcde
Packit Service 82fcde
def test(command, pattern=None):
Packit Service 82fcde
    """Sends 'command' to gdb and expects the given 'pattern'.
Packit Service 82fcde
Packit Service 82fcde
    If 'pattern' is None, simply consumes everything up to and including
Packit Service 82fcde
    the gdb prompt.
Packit Service 82fcde
Packit Service 82fcde
    Args:
Packit Service 82fcde
        command (string): The command we'll send to gdb.
Packit Service 82fcde
        pattern (raw string): A pattern the gdb output should match.
Packit Service 82fcde
Packit Service 82fcde
    Returns:
Packit Service 82fcde
        string: The string that matched 'pattern', or an empty string if
Packit Service 82fcde
            'pattern' was None.
Packit Service 82fcde
    """
Packit Service 82fcde
Packit Service 82fcde
    match = ''
Packit Service 82fcde
Packit Service 82fcde
    gdb.sendline(command)
Packit Service 82fcde
Packit Service 82fcde
    if pattern:
Packit Service 82fcde
        # PExpect does a non-greedy match for '+' and '*'.  Since it can't look
Packit Service 82fcde
        # ahead on the gdb output stream, if 'pattern' ends with a '+' or a '*'
Packit Service 82fcde
        # we may end up matching only part of the required output.
Packit Service 82fcde
        # To avoid this, we'll consume 'pattern' and anything that follows it
Packit Service 82fcde
        # up to and including the gdb prompt, then extract 'pattern' later.
Packit Service 82fcde
        index = gdb.expect([u'{0}.+{1}'.format(pattern, gdb_prompt),
Packit Service 82fcde
                            pexpect.TIMEOUT])
Packit Service 82fcde
Packit Service 82fcde
        if index == 0:
Packit Service 82fcde
            # gdb.after now contains the whole match.  Extract the text that
Packit Service 82fcde
            # matches 'pattern'.
Packit Service 82fcde
            match = re.match(pattern, gdb.after, re.DOTALL).group()
Packit Service 82fcde
        elif index == 1:
Packit Service 82fcde
            # We got a timeout exception.  Print information on what caused it
Packit Service 82fcde
            # and bail out.
Packit Service 82fcde
            error = ('Response does not match the expected pattern.\n'
Packit Service 82fcde
                     'Command: {0}\n'
Packit Service 82fcde
                     'Expected pattern: {1}\n'
Packit Service 82fcde
                     'Response: {2}'.format(command, pattern, gdb.before))
Packit Service 82fcde
Packit Service 82fcde
            raise pexpect.TIMEOUT(error)
Packit Service 82fcde
    else:
Packit Service 82fcde
        # Consume just the the gdb prompt.
Packit Service 82fcde
        gdb.expect(gdb_prompt)
Packit Service 82fcde
Packit Service 82fcde
    return match
Packit Service 82fcde
Packit Service 82fcde
def init_test(test_bin, printer_files, printer_names):
Packit Service 82fcde
    """Loads the test binary file and the required pretty printers to gdb.
Packit Service 82fcde
Packit Service 82fcde
    Args:
Packit Service 82fcde
        test_bin (string): The name of the test binary file.
Packit Service 82fcde
        pretty_printers (list of strings): A list with the names of the pretty
Packit Service 82fcde
            printer files.
Packit Service 82fcde
    """
Packit Service 82fcde
Packit Service 82fcde
    # Load all the pretty printer files.  We're assuming these are safe.
Packit Service 82fcde
    for printer_file in printer_files:
Packit Service 82fcde
        test('source {0}'.format(printer_file))
Packit Service 82fcde
Packit Service 82fcde
    # Disable all the pretty printers.
Packit Service 82fcde
    test('disable pretty-printer', r'0 of [0-9]+ printers enabled')
Packit Service 82fcde
Packit Service 82fcde
    # Enable only the required printers.
Packit Service 82fcde
    for printer in printer_names:
Packit Service 82fcde
        test('enable pretty-printer {0}'.format(printer),
Packit Service 82fcde
             r'[1-9][0-9]* of [1-9]+ printers enabled')
Packit Service 82fcde
Packit Service 82fcde
    # Finally, load the test binary.
Packit Service 82fcde
    test('file {0}'.format(test_bin))
Packit Service 82fcde
Packit Service 82fcde
    # Disable lock elision.
Packit Service 82fcde
    test('set environment GLIBC_TUNABLES glibc.elision.enable=0')
Packit Service 82fcde
Packit Service 82fcde
def go_to_main():
Packit Service 82fcde
    """Executes a gdb 'start' command, which takes us to main."""
Packit Service 82fcde
Packit Service 82fcde
    test('start', r'main')
Packit Service 82fcde
Packit Service 82fcde
def get_line_number(file_name, string):
Packit Service 82fcde
    """Returns the number of the line in which 'string' appears within a file.
Packit Service 82fcde
Packit Service 82fcde
    Args:
Packit Service 82fcde
        file_name (string): The name of the file we'll search through.
Packit Service 82fcde
        string (string): The string we'll look for.
Packit Service 82fcde
Packit Service 82fcde
    Returns:
Packit Service 82fcde
        int: The number of the line in which 'string' appears, starting from 1.
Packit Service 82fcde
    """
Packit Service 82fcde
    number = -1
Packit Service 82fcde
Packit Service 82fcde
    with open(file_name) as src_file:
Packit Service 82fcde
        for i, line in enumerate(src_file):
Packit Service 82fcde
            if string in line:
Packit Service 82fcde
                number = i + 1
Packit Service 82fcde
                break
Packit Service 82fcde
Packit Service 82fcde
    if number == -1:
Packit Service 82fcde
        raise NoLineError(file_name, string)
Packit Service 82fcde
Packit Service 82fcde
    return number
Packit Service 82fcde
Packit Service 82fcde
def break_at(file_name, string, temporary=True, thread=None):
Packit Service 82fcde
    """Places a breakpoint on the first line in 'file_name' containing 'string'.
Packit Service 82fcde
Packit Service 82fcde
    'string' is usually a comment like "Stop here".  Notice this may fail unless
Packit Service 82fcde
    the comment is placed inline next to actual code, e.g.:
Packit Service 82fcde
Packit Service 82fcde
        ...
Packit Service 82fcde
        /* Stop here */
Packit Service 82fcde
        ...
Packit Service 82fcde
Packit Service 82fcde
    may fail, while:
Packit Service 82fcde
Packit Service 82fcde
        ...
Packit Service 82fcde
        some_func(); /* Stop here */
Packit Service 82fcde
        ...
Packit Service 82fcde
Packit Service 82fcde
    will succeed.
Packit Service 82fcde
Packit Service 82fcde
    If 'thread' isn't None, the breakpoint will be set for all the threads.
Packit Service 82fcde
    Otherwise, it'll be set only for 'thread'.
Packit Service 82fcde
Packit Service 82fcde
    Args:
Packit Service 82fcde
        file_name (string): The name of the file we'll place the breakpoint in.
Packit Service 82fcde
        string (string): A string we'll look for inside the file.
Packit Service 82fcde
            We'll place a breakpoint on the line which contains it.
Packit Service 82fcde
        temporary (bool): Whether the breakpoint should be automatically deleted
Packit Service 82fcde
            after we reach it.
Packit Service 82fcde
        thread (int): The number of the thread we'll place the breakpoint for,
Packit Service 82fcde
            as seen by gdb.  If specified, it should be greater than zero.
Packit Service 82fcde
    """
Packit Service 82fcde
Packit Service 82fcde
    if not thread:
Packit Service 82fcde
        thread_str = ''
Packit Service 82fcde
    else:
Packit Service 82fcde
        thread_str = 'thread {0}'.format(thread)
Packit Service 82fcde
Packit Service 82fcde
    if temporary:
Packit Service 82fcde
        command = 'tbreak'
Packit Service 82fcde
        break_type = 'Temporary breakpoint'
Packit Service 82fcde
    else:
Packit Service 82fcde
        command = 'break'
Packit Service 82fcde
        break_type = 'Breakpoint'
Packit Service 82fcde
Packit Service 82fcde
    line_number = str(get_line_number(file_name, string))
Packit Service 82fcde
Packit Service 82fcde
    test('{0} {1}:{2} {3}'.format(command, file_name, line_number, thread_str),
Packit Service 82fcde
         r'{0} [0-9]+ at 0x[a-f0-9]+: file {1}, line {2}\.'.format(break_type,
Packit Service 82fcde
                                                                   file_name,
Packit Service 82fcde
                                                                   line_number))
Packit Service 82fcde
Packit Service 82fcde
def continue_cmd(thread=None):
Packit Service 82fcde
    """Executes a gdb 'continue' command.
Packit Service 82fcde
Packit Service 82fcde
    If 'thread' isn't None, the command will be applied to all the threads.
Packit Service 82fcde
    Otherwise, it'll be applied only to 'thread'.
Packit Service 82fcde
Packit Service 82fcde
    Args:
Packit Service 82fcde
        thread (int): The number of the thread we'll apply the command to,
Packit Service 82fcde
            as seen by gdb.  If specified, it should be greater than zero.
Packit Service 82fcde
    """
Packit Service 82fcde
Packit Service 82fcde
    if not thread:
Packit Service 82fcde
        command = 'continue'
Packit Service 82fcde
    else:
Packit Service 82fcde
        command = 'thread apply {0} continue'.format(thread)
Packit Service 82fcde
Packit Service 82fcde
    test(command)
Packit Service 82fcde
Packit Service 82fcde
def next_cmd(count=1, thread=None):
Packit Service 82fcde
    """Executes a gdb 'next' command.
Packit Service 82fcde
Packit Service 82fcde
    If 'thread' isn't None, the command will be applied to all the threads.
Packit Service 82fcde
    Otherwise, it'll be applied only to 'thread'.
Packit Service 82fcde
Packit Service 82fcde
    Args:
Packit Service 82fcde
        count (int): The 'count' argument of the 'next' command.
Packit Service 82fcde
        thread (int): The number of the thread we'll apply the command to,
Packit Service 82fcde
            as seen by gdb.  If specified, it should be greater than zero.
Packit Service 82fcde
    """
Packit Service 82fcde
Packit Service 82fcde
    if not thread:
Packit Service 82fcde
        command = 'next'
Packit Service 82fcde
    else:
Packit Service 82fcde
        command = 'thread apply {0} next'
Packit Service 82fcde
Packit Service 82fcde
    test('{0} {1}'.format(command, count))
Packit Service 82fcde
Packit Service 82fcde
def select_thread(thread):
Packit Service 82fcde
    """Selects the thread indicated by 'thread'.
Packit Service 82fcde
Packit Service 82fcde
    Args:
Packit Service 82fcde
        thread (int): The number of the thread we'll switch to, as seen by gdb.
Packit Service 82fcde
            This should be greater than zero.
Packit Service 82fcde
    """
Packit Service 82fcde
Packit Service 82fcde
    if thread > 0:
Packit Service 82fcde
        test('thread {0}'.format(thread))
Packit Service 82fcde
Packit Service 82fcde
def get_current_thread_lwpid():
Packit Service 82fcde
    """Gets the current thread's Lightweight Process ID.
Packit Service 82fcde
Packit Service 82fcde
    Returns:
Packit Service 82fcde
        string: The current thread's LWP ID.
Packit Service 82fcde
    """
Packit Service 82fcde
Packit Service 82fcde
    # It's easier to get the LWP ID through the Python API than the gdb CLI.
Packit Service 82fcde
    command = 'python print(gdb.selected_thread().ptid[1])'
Packit Service 82fcde
Packit Service 82fcde
    return test(command, r'[0-9]+')
Packit Service 82fcde
Packit Service 82fcde
def set_scheduler_locking(mode):
Packit Service 82fcde
    """Executes the gdb 'set scheduler-locking' command.
Packit Service 82fcde
Packit Service 82fcde
    Args:
Packit Service 82fcde
        mode (bool): Whether the scheduler locking mode should be 'on'.
Packit Service 82fcde
    """
Packit Service 82fcde
    modes = {
Packit Service 82fcde
        True: 'on',
Packit Service 82fcde
        False: 'off'
Packit Service 82fcde
    }
Packit Service 82fcde
Packit Service 82fcde
    test('set scheduler-locking {0}'.format(modes[mode]))
Packit Service 82fcde
Packit Service 82fcde
def test_printer(var, to_string, children=None, is_ptr=True):
Packit Service 82fcde
    """ Tests the output of a pretty printer.
Packit Service 82fcde
Packit Service 82fcde
    For a variable called 'var', this tests whether its associated printer
Packit Service 82fcde
    outputs the expected 'to_string' and children (if any).
Packit Service 82fcde
Packit Service 82fcde
    Args:
Packit Service 82fcde
        var (string): The name of the variable we'll print.
Packit Service 82fcde
        to_string (raw string): The expected output of the printer's 'to_string'
Packit Service 82fcde
            method.
Packit Service 82fcde
        children (map {raw string->raw string}): A map with the expected output
Packit Service 82fcde
            of the printer's children' method.
Packit Service 82fcde
        is_ptr (bool): Whether 'var' is a pointer, and thus should be
Packit Service 82fcde
            dereferenced.
Packit Service 82fcde
    """
Packit Service 82fcde
Packit Service 82fcde
    if is_ptr:
Packit Service 82fcde
        var = '*{0}'.format(var)
Packit Service 82fcde
Packit Service 82fcde
    test('print {0}'.format(var), to_string)
Packit Service 82fcde
Packit Service 82fcde
    if children:
Packit Service 82fcde
        for name, value in children.items():
Packit Service 82fcde
            # Children are shown as 'name = value'.
Packit Service 82fcde
            test('print {0}'.format(var), r'{0} = {1}'.format(name, value))
Packit Service 82fcde
Packit Service 82fcde
def check_debug_symbol(symbol):
Packit Service 82fcde
    """ Tests whether a given debugging symbol exists.
Packit Service 82fcde
Packit Service 82fcde
    If the symbol doesn't exist, raises a DebugError.
Packit Service 82fcde
Packit Service 82fcde
    Args:
Packit Service 82fcde
        symbol (string): The symbol we're going to check for.
Packit Service 82fcde
    """
Packit Service 82fcde
Packit Service 82fcde
    try:
Packit Service 82fcde
        test('ptype {0}'.format(symbol), r'type = {0}'.format(symbol))
Packit Service 82fcde
Packit Service 82fcde
    except pexpect.TIMEOUT:
Packit Service 82fcde
        # The symbol doesn't exist.
Packit Service 82fcde
        raise DebugError(symbol)