test_printers_common.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. # Common functions and variables for testing the Python pretty printers.
  2. #
  3. # Copyright (C) 2016-2026 Free Software Foundation, Inc.
  4. # This file is part of the GNU C Library.
  5. #
  6. # The GNU C Library is free software; you can redistribute it and/or
  7. # modify it under the terms of the GNU Lesser General Public
  8. # License as published by the Free Software Foundation; either
  9. # version 2.1 of the License, or (at your option) any later version.
  10. #
  11. # The GNU C Library is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. # Lesser General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU Lesser General Public
  17. # License along with the GNU C Library; if not, see
  18. # <https://www.gnu.org/licenses/>.
  19. """These tests require PExpect 4.0 or newer.
  20. Exported constants:
  21. PASS, FAIL, UNSUPPORTED (int): Test exit codes, as per evaluate-test.sh.
  22. """
  23. import os
  24. import re
  25. from test_printers_exceptions import *
  26. PASS = 0
  27. FAIL = 1
  28. UNSUPPORTED = 77
  29. gdb_bin = 'gdb'
  30. gdb_options = '-q -nx'
  31. gdb_invocation = '{0} {1}'.format(gdb_bin, gdb_options)
  32. pexpect_min_version = 4
  33. gdb_min_version = (7, 8)
  34. encoding = 'utf-8'
  35. try:
  36. import pexpect
  37. except ImportError:
  38. print('PExpect 4.0 or newer must be installed to test the pretty printers.')
  39. exit(UNSUPPORTED)
  40. pexpect_version = pexpect.__version__.split('.')[0]
  41. if int(pexpect_version) < pexpect_min_version:
  42. print('PExpect 4.0 or newer must be installed to test the pretty printers.')
  43. exit(UNSUPPORTED)
  44. if not pexpect.which(gdb_bin):
  45. print('gdb 7.8 or newer must be installed to test the pretty printers.')
  46. exit(UNSUPPORTED)
  47. timeout = 5
  48. TIMEOUTFACTOR = os.environ.get('TIMEOUTFACTOR')
  49. if TIMEOUTFACTOR:
  50. timeout = int(TIMEOUTFACTOR)
  51. # Otherwise GDB is run in interactive mode and readline may send escape
  52. # sequences confusing output for pexpect.
  53. os.environ["TERM"]="dumb"
  54. try:
  55. # Check the gdb version.
  56. version_cmd = '{0} --version'.format(gdb_invocation, timeout=timeout)
  57. gdb_version_out = pexpect.run(version_cmd, encoding=encoding)
  58. # The gdb version string is "GNU gdb <PKGVERSION><version>", where
  59. # PKGVERSION can be any text. We assume that there'll always be a space
  60. # between PKGVERSION and the version number for the sake of the regexp.
  61. version_match = re.search(r'GNU gdb .* ([1-9][0-9]*)\.([0-9]+)',
  62. gdb_version_out)
  63. if not version_match:
  64. print('The gdb version string (gdb -v) is incorrectly formatted.')
  65. exit(UNSUPPORTED)
  66. gdb_version = (int(version_match.group(1)), int(version_match.group(2)))
  67. if gdb_version < gdb_min_version:
  68. print('gdb 7.8 or newer must be installed to test the pretty printers.')
  69. exit(UNSUPPORTED)
  70. # Check if gdb supports Python.
  71. gdb_python_cmd = '{0} -ex "python import os" -batch'.format(gdb_invocation,
  72. timeout=timeout)
  73. gdb_python_error = pexpect.run(gdb_python_cmd, encoding=encoding)
  74. if gdb_python_error:
  75. print('gdb must have python support to test the pretty printers.')
  76. print('gdb output: {!r}'.format(gdb_python_error))
  77. exit(UNSUPPORTED)
  78. # If everything's ok, spawn the gdb process we'll use for testing.
  79. gdb = pexpect.spawn(gdb_invocation, echo=False, timeout=timeout,
  80. encoding=encoding)
  81. gdb_prompt = u'\\(gdb\\)'
  82. gdb.expect(gdb_prompt)
  83. except pexpect.ExceptionPexpect as exception:
  84. print('Error: {0}'.format(exception))
  85. exit(FAIL)
  86. def test(command, pattern=None):
  87. """Sends 'command' to gdb and expects the given 'pattern'.
  88. If 'pattern' is None, simply consumes everything up to and including
  89. the gdb prompt.
  90. Args:
  91. command (string): The command we'll send to gdb.
  92. pattern (raw string): A pattern the gdb output should match.
  93. Returns:
  94. string: The string that matched 'pattern', or an empty string if
  95. 'pattern' was None.
  96. """
  97. match = ''
  98. gdb.sendline(command)
  99. if pattern:
  100. # PExpect does a non-greedy match for '+' and '*'. Since it can't look
  101. # ahead on the gdb output stream, if 'pattern' ends with a '+' or a '*'
  102. # we may end up matching only part of the required output.
  103. # To avoid this, we'll consume 'pattern' and anything that follows it
  104. # up to and including the gdb prompt, then extract 'pattern' later.
  105. index = gdb.expect([u'{0}.+{1}'.format(pattern, gdb_prompt),
  106. pexpect.TIMEOUT])
  107. if index == 0:
  108. # gdb.after now contains the whole match. Extract the text that
  109. # matches 'pattern'.
  110. match = re.match(pattern, gdb.after, re.DOTALL).group()
  111. elif index == 1:
  112. # We got a timeout exception. Print information on what caused it
  113. # and bail out.
  114. error = ('Response does not match the expected pattern.\n'
  115. 'Command: {0}\n'
  116. 'Expected pattern: {1}\n'
  117. 'Response: {2}'.format(command, pattern, gdb.before))
  118. raise pexpect.TIMEOUT(error)
  119. else:
  120. # Consume just the the gdb prompt.
  121. gdb.expect(gdb_prompt)
  122. return match
  123. def init_test(test_bin, printer_files, printer_names):
  124. """Loads the test binary file and the required pretty printers to gdb.
  125. Args:
  126. test_bin (string): The name of the test binary file.
  127. pretty_printers (list of strings): A list with the names of the pretty
  128. printer files.
  129. """
  130. # Disable debuginfod to avoid GDB messages like:
  131. #
  132. # This GDB supports auto-downloading debuginfo from the following URLs:
  133. # https://debuginfod.fedoraproject.org/
  134. # Enable debuginfod for this session? (y or [n])
  135. #
  136. try:
  137. test('set debuginfod enabled off')
  138. except Exception:
  139. pass
  140. # Load all the pretty printer files. We're assuming these are safe.
  141. for printer_file in printer_files:
  142. test('source {0}'.format(printer_file))
  143. # Disable all the pretty printers.
  144. test('disable pretty-printer', r'0 of [0-9]+ printers enabled')
  145. # Enable only the required printers.
  146. for printer in printer_names:
  147. test('enable pretty-printer {0}'.format(printer),
  148. r'[1-9][0-9]* of [1-9]+ printers enabled')
  149. # Finally, load the test binary.
  150. test('file {0}'.format(test_bin))
  151. def go_to_main():
  152. """Executes a gdb 'start' command, which takes us to main."""
  153. test('start', r'main')
  154. def get_line_number(file_name, string):
  155. """Returns the number of the line in which 'string' appears within a file.
  156. Args:
  157. file_name (string): The name of the file we'll search through.
  158. string (string): The string we'll look for.
  159. Returns:
  160. int: The number of the line in which 'string' appears, starting from 1.
  161. """
  162. number = -1
  163. with open(file_name) as src_file:
  164. for i, line in enumerate(src_file):
  165. if string in line:
  166. number = i + 1
  167. break
  168. if number == -1:
  169. raise NoLineError(file_name, string)
  170. return number
  171. def break_at(file_name, string, temporary=True, thread=None):
  172. """Places a breakpoint on the first line in 'file_name' containing 'string'.
  173. 'string' is usually a comment like "Stop here". Notice this may fail unless
  174. the comment is placed inline next to actual code, e.g.:
  175. ...
  176. /* Stop here */
  177. ...
  178. may fail, while:
  179. ...
  180. some_func(); /* Stop here */
  181. ...
  182. will succeed.
  183. If 'thread' isn't None, the breakpoint will be set for all the threads.
  184. Otherwise, it'll be set only for 'thread'.
  185. Args:
  186. file_name (string): The name of the file we'll place the breakpoint in.
  187. string (string): A string we'll look for inside the file.
  188. We'll place a breakpoint on the line which contains it.
  189. temporary (bool): Whether the breakpoint should be automatically deleted
  190. after we reach it.
  191. thread (int): The number of the thread we'll place the breakpoint for,
  192. as seen by gdb. If specified, it should be greater than zero.
  193. """
  194. if not thread:
  195. thread_str = ''
  196. else:
  197. thread_str = 'thread {0}'.format(thread)
  198. if temporary:
  199. command = 'tbreak'
  200. break_type = 'Temporary breakpoint'
  201. else:
  202. command = 'break'
  203. break_type = 'Breakpoint'
  204. line_number = str(get_line_number(file_name, string))
  205. test('{0} {1}:{2} {3}'.format(command, file_name, line_number, thread_str),
  206. r'{0} [0-9]+ at 0x[a-f0-9]+: file {1}, line {2}\.'.format(break_type,
  207. file_name,
  208. line_number))
  209. def continue_cmd(thread=None):
  210. """Executes a gdb 'continue' command.
  211. If 'thread' isn't None, the command will be applied to all the threads.
  212. Otherwise, it'll be applied only to 'thread'.
  213. Args:
  214. thread (int): The number of the thread we'll apply the command to,
  215. as seen by gdb. If specified, it should be greater than zero.
  216. """
  217. if not thread:
  218. command = 'continue'
  219. else:
  220. command = 'thread apply {0} continue'.format(thread)
  221. test(command)
  222. def next_cmd(count=1, thread=None):
  223. """Executes a gdb 'next' command.
  224. If 'thread' isn't None, the command will be applied to all the threads.
  225. Otherwise, it'll be applied only to 'thread'.
  226. Args:
  227. count (int): The 'count' argument of the 'next' command.
  228. thread (int): The number of the thread we'll apply the command to,
  229. as seen by gdb. If specified, it should be greater than zero.
  230. """
  231. if not thread:
  232. command = 'next'
  233. else:
  234. command = 'thread apply {0} next'
  235. test('{0} {1}'.format(command, count))
  236. def select_thread(thread):
  237. """Selects the thread indicated by 'thread'.
  238. Args:
  239. thread (int): The number of the thread we'll switch to, as seen by gdb.
  240. This should be greater than zero.
  241. """
  242. if thread > 0:
  243. test('thread {0}'.format(thread))
  244. def get_current_thread_lwpid():
  245. """Gets the current thread's Lightweight Process ID.
  246. Returns:
  247. string: The current thread's LWP ID.
  248. """
  249. # It's easier to get the LWP ID through the Python API than the gdb CLI.
  250. command = 'python print(gdb.selected_thread().ptid[1])'
  251. return test(command, r'[0-9]+')
  252. def set_scheduler_locking(mode):
  253. """Executes the gdb 'set scheduler-locking' command.
  254. Args:
  255. mode (bool): Whether the scheduler locking mode should be 'on'.
  256. """
  257. modes = {
  258. True: 'on',
  259. False: 'off'
  260. }
  261. test('set scheduler-locking {0}'.format(modes[mode]))
  262. def test_printer(var, to_string, children=None, is_ptr=True):
  263. """ Tests the output of a pretty printer.
  264. For a variable called 'var', this tests whether its associated printer
  265. outputs the expected 'to_string' and children (if any).
  266. Args:
  267. var (string): The name of the variable we'll print.
  268. to_string (raw string): The expected output of the printer's 'to_string'
  269. method.
  270. children (map {raw string->raw string}): A map with the expected output
  271. of the printer's children' method.
  272. is_ptr (bool): Whether 'var' is a pointer, and thus should be
  273. dereferenced.
  274. """
  275. if is_ptr:
  276. var = '*{0}'.format(var)
  277. test('print {0}'.format(var), to_string)
  278. if children:
  279. for name, value in children.items():
  280. # Children are shown as 'name = value'.
  281. test('print {0}'.format(var), r'{0} = {1}'.format(name, value))
  282. def check_debug_symbol(symbol):
  283. """ Tests whether a given debugging symbol exists.
  284. If the symbol doesn't exist, raises a DebugError.
  285. Args:
  286. symbol (string): The symbol we're going to check for.
  287. """
  288. try:
  289. test('ptype {0}'.format(symbol), r'type = {0}'.format(symbol))
  290. except pexpect.TIMEOUT:
  291. # The symbol doesn't exist.
  292. raise DebugError(symbol)