summaryrefslogtreecommitdiffstats
path: root/test_gdb.py
blob: 127f19acbe9db01ba8b796c9421d78239d194a1a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# Verify that gdb can pretty-print the various PyObject* types
#
# The code for testing gdb was adapted from similar work in Unladen Swallow's
# Lib/test/test_jit_gdb.py

import os
import re
import subprocess
import sys
import unittest

from test.test_support import run_unittest, TestSkipped

try:
    gdb_version, _ = subprocess.Popen(["gdb", "--version"],
                                      stdout=subprocess.PIPE).communicate()
except OSError:
    # This is what "no gdb" looks like.  There may, however, be other
    # errors that manifest this way too.
    raise TestSkipped("Couldn't find gdb on the path")
gdb_version_number = re.search(r"^GNU gdb [^\d]*(\d+)\.", gdb_version)
if int(gdb_version_number.group(1)) < 7:
    raise TestSkipped("gdb versions before 7.0 didn't support python embedding."
                      " Saw:\n" + gdb_version)

# Verify that "gdb" was built with the embedded python support enabled:
p = subprocess.Popen(["gdb", "--batch",
                      "--eval-command=python import sys; print sys.version_info"],
                     stdout=subprocess.PIPE)
gdbpy_version, _ = p.communicate()
if gdbpy_version == '':
    raise TestSkipped("gdb not built with embedded python support")

class DebuggerTests(unittest.TestCase):

    """Test that the debugger can debug Python."""

    def run_gdb(self, *args):
        """Runs gdb with the command line given by *args. Returns its stdout.

        Forwards stderr to the current process's stderr.
        """
        # err winds up empty.
        out, err = subprocess.Popen(
            args, stdout=subprocess.PIPE,
            stderr=None,  # Forward stderr to the current process's stderr.
            ).communicate()
        return out

    def get_stack_trace(self, source, breakpoint='PyObject_Print'):
        '''
        Run 'python -c SOURCE' under gdb with a breakpoint.

        Returns the stdout from gdb
        '''
        # We use "set breakpoint pending yes" to avoid blocking with a:
        #   Function "foo" not defined.
        #   Make breakpoint pending on future shared library load? (y or [n])
        # error, which typically happens python is dynamically linked (the
        # breakpoints of interest are to be found in the shared library)
        # When this happens, we still get:
        #   Function "PyObject_Print" not defined.
        # emitted to stderr each time, alas.

        # Initially I had "--eval-command=continue" here, but removed it to
        # avoid repeated print breakpoints when traversing hierarchical data
        # structures
        gdb_output = self.run_gdb("gdb", "--batch",
                                  "--eval-command=set breakpoint pending yes",
                                  "--eval-command=break %s" % breakpoint,
                                  
                                  "--eval-command=run",
                                  "--eval-command=backtrace",
                                  "--args",
                                  sys.executable, "-S", "-c", source)
        return gdb_output

    def get_gdb_repr(self, in_repr):
        # Given an input python source representation of data,
        # run "python -c'print DATA'" under gdb with a breakpoint on
        # PyObject_Print and scrape out gdb's representation of the "op"
        # parameter, and verify that the gdb displays the same string
        #
        # For a nested structure, the first time we hit the breakpoint will
        # give us the top-level structure
        gdb_output = self.get_stack_trace('print ' + in_repr)
        m = re.match('.*Breakpoint 1, PyObject_Print \(op\=(.*?), fp=.*\).*', gdb_output, re.DOTALL)
        # print m.groups()
        return m.group(1), gdb_output

    def test_getting_backtrace(self):
        gdb_output = self.get_stack_trace('print 42')
        self.assertTrue('PyObject_Print' in gdb_output)

    def assertGdbRepr(self, val):
        # Ensure that gdb's rendering of the value in a debugged process
        # matches repr(value) in this process:
        gdb_repr, gdb_output = self.get_gdb_repr(repr(val))
        self.assertEquals(gdb_repr, repr(val), gdb_output)
        
    def test_int(self):
        self.assertGdbRepr(42)
        self.assertGdbRepr(0)
        self.assertGdbRepr(-7)
        self.assertGdbRepr(sys.maxint)
        self.assertGdbRepr(-sys.maxint)

    def test_long(self):
        self.assertGdbRepr(0L)
        self.assertGdbRepr(1000000000000L)
        self.assertGdbRepr(-1L)
        self.assertGdbRepr(-1000000000000000L)

    def test_singletons(self):
        self.assertGdbRepr(True)
        self.assertGdbRepr(False)
        self.assertGdbRepr(None)

    def test_dicts(self):
        self.assertGdbRepr({})
        self.assertGdbRepr({'foo':'bar'})

    def test_lists(self):
        self.assertGdbRepr([])
        self.assertGdbRepr(range(5))

    def test_strings(self):
        self.assertGdbRepr('')
        self.assertGdbRepr('And now for something hopefully the same')

    def test_tuples(self):
        self.assertGdbRepr( tuple() )
        self.assertGdbRepr( (1,) )

    def test_unicode(self):
        self.assertGdbRepr( u'hello world', )
        self.assertGdbRepr( u'\u2620')

    # TODO:
    #   old-style classes
    #   new-style classes
    #   frames

def test_main():
    run_unittest(DebuggerTests)


if __name__ == "__main__":
    test_main()