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