hjl / source-git / glibc

Forked from source-git/glibc 3 years ago
Clone

Blame scripts/test_printers_common.py

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