about summary refs log tree commit diff
path: root/pretty-printers/test_common.py
diff options
context:
space:
mode:
Diffstat (limited to 'pretty-printers/test_common.py')
-rw-r--r--pretty-printers/test_common.py315
1 files changed, 315 insertions, 0 deletions
diff --git a/pretty-printers/test_common.py b/pretty-printers/test_common.py
new file mode 100644
index 0000000000..de758f8036
--- /dev/null
+++ b/pretty-printers/test_common.py
@@ -0,0 +1,315 @@
+# Common functions and variables for testing the Python pretty printers.
+#
+# Copyright (C) 2016 Free Software Foundation, Inc.
+# This file is part of the GNU C Library.
+#
+# The GNU C Library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# The GNU C Library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with the GNU C Library; if not, see
+# <http://www.gnu.org/licenses/>.
+
+"""These tests require PExpect.
+
+Attributes:
+    PASS, FAIL, UNSUPPORTED (int): Test exit codes, as per evaluate-test.sh.
+    GDB (string): A string with the name of the gdb binary.
+    gdb (pexpect.spawn): The gdb process, as handled by PExpect.
+    gdb_prompt (raw string): A pattern for matching the gdb prompt.
+"""
+
+import os
+import re
+
+PASS = 0
+FAIL = 1
+UNSUPPORTED = 77
+GDB = 'gdb'
+
+try:
+    import pexpect
+except ImportError:
+    print('PExpect must be installed in order to test the pretty printers.')
+    exit(UNSUPPORTED)
+
+if not pexpect.which(GDB):
+    print('gdb must be installed in order to test the pretty printers.')
+    exit(UNSUPPORTED)
+
+class NoLineError(Exception):
+    """Custom exception which indicates that a test file doesn't contain
+    the requested string.
+    """
+
+    def __init__(self, file_name, string):
+        """Constructor.
+
+        Args:
+            file_name (string): The name of the test file.
+            string (string): The string that was requested.
+        """
+
+        super(NoLineError, self).__init__()
+        self.file_name = file_name
+        self.string = string
+
+    def __str__(self):
+        """Shows a readable representation of the exception."""
+
+        return ('File {0} has no line containing the following string: {1}'
+                .format(self.file_name, self.string))
+
+timeout = 1
+TIMEOUTFACTOR = os.environ.get('TIMEOUTFACTOR')
+
+if TIMEOUTFACTOR:
+    timeout = int(TIMEOUTFACTOR)
+
+gdb = pexpect.spawn(GDB, echo=False, timeout=timeout)
+
+# Set the gdb prompt to a custom one, so that user-defined prompts won't
+# interfere.  We assume the user won't have his prompt set to this.
+gdb_prompt = r'gdb-test% '
+gdb.sendline('set prompt {0}'.format(gdb_prompt))
+gdb.expect(gdb_prompt)
+
+def test(command, pattern):
+    """Sends 'command' to gdb and expects the given 'pattern'.
+
+    If 'pattern' is None, simply consumes everything up to and including
+    the gdb prompt.
+
+    Args:
+        command (string): The command we'll send to gdb.
+        pattern (raw string): A pattern the gdb output should match.
+
+    Returns:
+        string: The string that matched 'pattern', or an empty string if
+            'pattern' was None.
+    """
+
+    match = ''
+
+    gdb.sendline(command)
+
+    if pattern:
+        # PExpect does a non-greedy match for '+' and '*'.  Since it can't look
+        # ahead on the gdb output stream, if 'pattern' ends with a '+' or a '*'
+        # we may end up matching only part of the required output.
+        # To avoid this, we'll consume 'pattern' and anything that follows it
+        # up to and including the gdb prompt, then extract 'pattern' later.
+        index = gdb.expect([r'{0}.+{1}'.format(pattern, gdb_prompt),
+                            pexpect.TIMEOUT])
+
+        if index == 0:
+            # gdb.after now contains the whole match.  Extract the text that
+            # matches 'pattern'.
+            match = re.match(pattern, gdb.after, re.DOTALL).group()
+        elif index == 1:
+            # We got a timeout exception.  Print information on what caused it
+            # and bail out.
+            error = ('Response does not match the expected pattern.\n'
+                     'Command: {0}\n'
+                     'Expected pattern: {1}\n'
+                     'Response: {2}'.format(command, pattern, gdb.before))
+
+            raise pexpect.TIMEOUT(error)
+    else:
+        # Consume just the the gdb prompt.
+        gdb.expect(gdb_prompt)
+
+    return match
+
+def init_test(test_bin):
+    """Loads the test binary file to gdb.
+
+    Args:
+        test_bin (string): The name of the test binary file.
+    """
+
+    test('file {0}'.format(test_bin), None)
+
+def go_to_main():
+    """Executes a gdb 'start' command, which takes us to main."""
+
+    test('start', r'main')
+
+def get_line_number(file_name, string):
+    """Returns the number of the line in which 'string' appears within a file.
+
+    Args:
+        file_name (string): The name of the file we'll search through.
+        string (string): The string we'll look for.
+
+    Returns:
+        int: The number of the line in which 'string' appears, starting from 1.
+    """
+    number = -1
+
+    with open(file_name) as src_file:
+        for i, line in enumerate(src_file):
+            if string in line:
+                number = i + 1
+                break
+
+    if number == -1:
+        raise NoLineError(file_name, string)
+
+    return number
+
+def break_at(file_name, string, temporary=True, thread=None):
+    """Places a breakpoint on the first line in 'file_name' containing 'string'.
+
+    'string' is usually a comment like "Stop here".  Notice this may fail unless
+    the comment is placed inline next to actual code, e.g.:
+
+        ...
+        /* Stop here */
+        ...
+
+    may fail, while:
+
+        ...
+        some_func(); /* Stop here */
+        ...
+
+    will succeed.
+
+    If 'thread' isn't None, the breakpoint will be set for all the threads.
+    Otherwise, it'll be set only for 'thread'.
+
+    Args:
+        file_name (string): The name of the file we'll place the breakpoint in.
+        string (string): A string we'll look for inside the file.
+            We'll place a breakpoint on the line which contains it.
+        temporary (bool): Whether the breakpoint should be automatically deleted
+            after we reach it.
+        thread (int): The number of the thread we'll place the breakpoint for,
+            as seen by gdb.  If specified, it should be greater than zero.
+    """
+
+    if not thread:
+        thread_str = ''
+    else:
+        thread_str = 'thread {0}'.format(thread)
+
+    if temporary:
+        command = 'tbreak'
+        break_type = 'Temporary breakpoint'
+    else:
+        command = 'break'
+        break_type = 'Breakpoint'
+
+    line_number = str(get_line_number(file_name, string))
+
+    test('{0} {1}:{2} {3}'.format(command, file_name, line_number, thread_str),
+         r'{0} [0-9]+ at 0x[a-f0-9]+: file {1}, line {2}\.'.format(break_type,
+                                                                   file_name,
+                                                                   line_number))
+
+def continue_cmd(thread=None):
+    """Executes a gdb 'continue' command.
+
+    If 'thread' isn't None, the command will be applied to all the threads.
+    Otherwise, it'll be applied only to 'thread'.
+
+    Args:
+        thread (int): The number of the thread we'll apply the command to,
+            as seen by gdb.  If specified, it should be greater than zero.
+    """
+
+    if not thread:
+        command = 'continue'
+    else:
+        command = 'thread apply {0} continue'.format(thread)
+
+    test(command, None)
+
+def next_cmd(count=1, thread=None):
+    """Executes a gdb 'next' command.
+
+    If 'thread' isn't None, the command will be applied to all the threads.
+    Otherwise, it'll be applied only to 'thread'.
+
+    Args:
+        count (int): The 'count' argument of the 'next' command.
+        thread (int): The number of the thread we'll apply the command to,
+            as seen by gdb.  If specified, it should be greater than zero.
+    """
+
+    if not thread:
+        command = 'next'
+    else:
+        command = 'thread apply {0} next'
+
+    test('{0} {1}'.format(command, count), None)
+
+def select_thread(thread):
+    """Selects the thread indicated by 'thread'.
+
+    Args:
+        thread (int): The number of the thread we'll switch to, as seen by gdb.
+            This should be greater than zero.
+    """
+
+    if thread > 0:
+        test('thread {0}'.format(thread), None)
+
+def get_current_thread_lwpid():
+    """Gets the current thread's Lightweight Process ID.
+
+    Returns:
+        string: The current thread's LWP ID.
+    """
+
+    # It's easier to get the LWP ID through the Python API than the gdb CLI.
+    command = 'python print(gdb.selected_thread().ptid[1])'
+
+    return test(command, r'[0-9]+')
+
+def set_scheduler_locking(mode):
+    """Executes the gdb 'set scheduler-locking' command.
+
+    Args:
+        mode (bool): Whether the scheduler locking mode should be 'on'.
+    """
+    modes = {
+        True: 'on',
+        False: 'off'
+    }
+
+    test('set scheduler-locking {0}'.format(modes[mode]), None)
+
+def test_printer(var, to_string, children=None, is_ptr=True):
+    """ Tests the output of a pretty printer.
+
+    For a variable called 'var', this tests whether its associated printer
+    outputs the expected 'to_string' and children (if any).
+
+    Args:
+        var (string): The name of the variable we'll print.
+        to_string (raw string): The expected output of the printer's 'to_string'
+            method.
+        children (map {raw string->raw string}): A map with the expected output
+            of the printer's children' method.
+        is_ptr (bool): Whether 'var' is a pointer, and thus should be
+            dereferenced.
+    """
+
+    if is_ptr:
+        var = '*{0}'.format(var)
+
+    test('print {0}'.format(var), to_string)
+
+    if children:
+        for name, value in children.items():
+            # Children are shown as 'name = value'.
+            test('print {0}'.format(var), r'{0} = {1}'.format(name, value))