xref: /llvm-project/libcxx/test/libcxx/gdb/gdb_pretty_printer_test.py (revision 473510abf5c6a2f9d74a0c19f0f5d8b483596e05)
1# ===----------------------------------------------------------------------===##
2#
3# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4# See https://llvm.org/LICENSE.txt for license information.
5# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6#
7# ===----------------------------------------------------------------------===##
8"""Commands used to automate testing gdb pretty printers.
9
10This script is part of a larger framework to test gdb pretty printers. It
11runs the program, detects test cases, checks them, and prints results.
12
13See gdb_pretty_printer_test.sh.cpp on how to write a test case.
14
15"""
16
17from __future__ import print_function
18import json
19import re
20import gdb
21import sys
22
23test_failures = 0
24# Sometimes the inital run command can fail to trace the process.
25# (e.g. you don't have ptrace permissions)
26# In these cases gdb still sends us an exited event so we cannot
27# see what "run" printed to check for a warning message, since
28# we get taken to our exit handler before we can look.
29# Instead check that at least one test has been run by the time
30# we exit.
31has_run_tests = False
32
33has_execute_mi = tuple(map(int, gdb.VERSION.split("."))) >= (14, 2)
34
35class CheckResult(gdb.Command):
36    def __init__(self):
37        super(CheckResult, self).__init__("print_and_compare", gdb.COMMAND_DATA)
38
39    def invoke(self, arg, from_tty):
40        global has_run_tests
41
42        try:
43            has_run_tests = True
44
45            # Stack frame is:
46            # 0. StopForDebugger
47            # 1. CompareListChildrenToChars, ComparePrettyPrintToChars or ComparePrettyPrintToRegex
48            # 2. TestCase
49            compare_frame = gdb.newest_frame().older()
50            testcase_frame = compare_frame.older()
51            test_loc = testcase_frame.find_sal()
52            test_loc_str = test_loc.symtab.filename + ":" + str(test_loc.line)
53            # Use interactive commands in the correct context to get the pretty
54            # printed version
55
56            frame_name = compare_frame.name()
57            if frame_name.startswith("CompareListChildren"):
58                if has_execute_mi:
59                    value = self._get_children(compare_frame)
60                else:
61                    print("SKIPPED: " + test_loc_str)
62                    return
63            else:
64                value = self._get_value(compare_frame, testcase_frame)
65
66            gdb.newest_frame().select()
67            expectation_val = compare_frame.read_var("expectation")
68            check_literal = expectation_val.string(encoding="utf-8")
69            if "PrettyPrintToRegex" in frame_name:
70                test_fails = not re.search(check_literal, value)
71            else:
72                test_fails = value != check_literal
73
74            if test_fails:
75                global test_failures
76                print("FAIL: " + test_loc_str)
77                print("GDB printed:")
78                print("   " + repr(value))
79                print("Value should match:")
80                print("   " + repr(check_literal))
81                test_failures += 1
82            else:
83                print("PASS: " + test_loc_str)
84
85        except RuntimeError as e:
86            # At this point, lots of different things could be wrong, so don't try to
87            # recover or figure it out. Don't exit either, because then it's
88            # impossible to debug the framework itself.
89            print("FAIL: Something is wrong in the test framework.")
90            print(str(e))
91            test_failures += 1
92
93    def _get_children(self, compare_frame):
94        compare_frame.select()
95        gdb.execute_mi("-var-create", "value", "*", "value")
96        r = gdb.execute_mi("-var-list-children", "--simple-values", "value")
97        gdb.execute_mi("-var-delete", "value")
98        children = r["children"]
99        if r["displayhint"] == "map":
100            r = [
101                {
102                    "key": json.loads(children[2 * i]["value"]),
103                    "value": json.loads(children[2 * i + 1]["value"]),
104                }
105                for i in range(len(children) // 2)
106            ]
107        else:
108            r = [json.loads(el["value"]) for el in children]
109        return json.dumps(r, sort_keys=True)
110
111    def _get_value(self, compare_frame, testcase_frame):
112        compare_frame.select()
113        frame_name = compare_frame.name()
114        if frame_name.startswith("ComparePrettyPrint"):
115            s = gdb.execute("p value", to_string=True)
116        else:
117            value_str = str(compare_frame.read_var("value"))
118            clean_expression_str = value_str.strip("'\"")
119            testcase_frame.select()
120            s = gdb.execute("p " + clean_expression_str, to_string=True)
121        if sys.version_info.major == 2:
122            s = s.decode("utf-8")
123
124        # Ignore the convenience variable name and newline
125        return s[s.find("= ") + 2 : -1]
126
127
128def exit_handler(event=None):
129    global test_failures
130    global has_run_tests
131
132    if not has_run_tests:
133        print("FAILED test program did not run correctly, check gdb warnings")
134        test_failures = -1
135    elif test_failures:
136        print("FAILED %d cases" % test_failures)
137    exit(test_failures)
138
139
140# Start code executed at load time
141
142# Disable terminal paging
143gdb.execute("set height 0")
144gdb.execute("set python print-stack full")
145
146if has_execute_mi:
147    gdb.execute_mi("-enable-pretty-printing")
148
149test_failures = 0
150CheckResult()
151test_bp = gdb.Breakpoint("StopForDebugger")
152test_bp.enabled = True
153test_bp.silent = True
154test_bp.commands = "print_and_compare\ncontinue"
155# "run" won't return if the program exits; ensure the script regains control.
156gdb.events.exited.connect(exit_handler)
157gdb.execute("run")
158# If the program didn't exit, something went wrong, but we don't
159# know what. Fail on exit.
160test_failures += 1
161exit_handler(None)
162