1212import sysconfig
1313import textwrap
1414import unittest
15+ from unittest import mock
1516
1617# Is this Python configured to support threads?
1718try :
@@ -85,7 +86,8 @@ def cet_protection():
8586def run_gdb (* args , ** env_vars ):
8687 """Runs gdb in --batch mode with the additional arguments given by *args.
8788
88- Returns its (stdout, stderr) decoded from utf-8 using the replace handler.
89+ Returns a CompletedProcess with stdout and stderr decoded from utf-8
90+ using the replace handler.
8991 """
9092 if env_vars :
9193 env = os .environ .copy ()
@@ -97,7 +99,8 @@ def run_gdb(*args, **env_vars):
9799 base_cmd = ('gdb' , '--batch' , '-nx' )
98100 if (gdb_major_version , gdb_minor_version ) >= (7 , 4 ):
99101 base_cmd += ('-iex' , 'add-auto-load-safe-path ' + checkout_hook_path )
100- proc = subprocess .Popen (base_cmd + args ,
102+ command = base_cmd + args
103+ proc = subprocess .Popen (command ,
101104 # Redirect stdin to prevent GDB from messing with
102105 # the terminal settings
103106 stdin = subprocess .PIPE ,
@@ -106,23 +109,25 @@ def run_gdb(*args, **env_vars):
106109 env = env )
107110 with proc :
108111 out , err = proc .communicate ()
109- return out .decode ('utf-8' , 'replace' ), err .decode ('utf-8' , 'replace' )
112+ return subprocess .CompletedProcess (command , proc .returncode ,
113+ out .decode ('utf-8' , 'replace' ),
114+ err .decode ('utf-8' , 'replace' ))
110115
111116# Verify that "gdb" was built with the embedded python support enabled:
112- gdbpy_version , _ = run_gdb ("--eval-command=python import sys; print(sys.version_info)" )
117+ gdbpy_version = run_gdb ("--eval-command=python import sys; print(sys.version_info)" ). stdout
113118if not gdbpy_version :
114119 raise unittest .SkipTest ("gdb not built with embedded python support" )
115120
116121# Verify that "gdb" can load our custom hooks, as OS security settings may
117122# disallow this without a customized .gdbinit.
118- _ , gdbpy_errors = run_gdb ('--args' , sys .executable )
123+ gdbpy_errors = run_gdb ('--args' , sys .executable ). stderr
119124if "auto-loading has been declined" in gdbpy_errors :
120125 msg = "gdb security settings prevent use of custom hooks: "
121126 raise unittest .SkipTest (msg + gdbpy_errors .rstrip ())
122127
123128def gdb_has_frame_select ():
124129 # Does this build of gdb have gdb.Frame.select ?
125- stdout , _ = run_gdb ("--eval-command=python print(dir(gdb.Frame))" )
130+ stdout = run_gdb ("--eval-command=python print(dir(gdb.Frame))" ). stdout
126131 m = re .match (r'.*\[(.*)\].*' , stdout )
127132 if not m :
128133 raise unittest .SkipTest ("Unable to parse output from gdb.Frame.select test" )
@@ -221,7 +226,8 @@ def get_stack_trace(self, source=None, script=None,
221226 # print (' '.join(args))
222227
223228 # Use "args" to invoke gdb, capturing stdout, stderr:
224- out , err = run_gdb (* args , PYTHONHASHSEED = PYTHONHASHSEED )
229+ result = run_gdb (* args , PYTHONHASHSEED = PYTHONHASHSEED )
230+ out , err = result .stdout , result .stderr
225231
226232 errlines = err .splitlines ()
227233 unexpected_errlines = []
@@ -252,8 +258,11 @@ def get_stack_trace(self, source=None, script=None,
252258 if not line .startswith (ignore_patterns ):
253259 unexpected_errlines .append (line )
254260
255- # Ensure no unexpected error messages:
256- self .assertEqual (unexpected_errlines , [])
261+ # Keep the full diagnostic, including warning lines filtered above.
262+ diagnostic = ('GDB command: {!r}\n GDB exit status: {}\n '
263+ 'GDB stdout:\n {}\n GDB stderr:\n {}' ).format (
264+ result .args , result .returncode , out , err )
265+ self .assertEqual (unexpected_errlines , [], diagnostic )
257266 return out
258267
259268 def get_gdb_repr (self , source ,
@@ -964,12 +973,43 @@ def test_locals_after_up(self):
964973 self .assertMultilineMatches (bt ,
965974 r".*\na = 1\nb = 2\nc = 3\n.*" )
966975
976+ class GdbInvocationTests (unittest .TestCase ):
977+ def test_run_gdb_preserves_process_details (self ):
978+ with mock .patch .object (subprocess , 'Popen' ) as popen :
979+ proc = popen .return_value
980+ proc .communicate .return_value = (b'output\xff ' , b'warning: detail\xfe ' )
981+ proc .returncode = 7
982+ result = run_gdb ('--eval-command=quit' )
983+ self .assertEqual (result .args , popen .call_args [0 ][0 ])
984+ self .assertIn ('--eval-command=quit' , result .args )
985+ self .assertEqual (result .returncode , 7 )
986+ self .assertEqual (result .stdout , 'output\ufffd ' )
987+ self .assertEqual (result .stderr , 'warning: detail\ufffd ' )
988+
989+ def test_failure_includes_unfiltered_output (self ):
990+ stderr = ('warning: original diagnostic\n '
991+ 'This normally should not happen, please file a bug report.\n ' )
992+ for returncode in (0 , 7 ):
993+ with self .subTest (returncode = returncode ):
994+ result = subprocess .CompletedProcess (
995+ ('gdb' , '--batch' ), returncode , 'raw backtrace\n ' , stderr )
996+ with mock .patch (__name__ + '.run_gdb' , return_value = result ):
997+ with self .assertRaises (AssertionError ) as cm :
998+ DebuggerTests ().get_stack_trace (source = 'id(42)' )
999+ message = str (cm .exception )
1000+ self .assertIn ('GDB command: {!r}' .format (result .args ), message )
1001+ self .assertIn ('GDB exit status: {}' .format (returncode ), message )
1002+ self .assertIn ('GDB stdout:\n ' + result .stdout , message )
1003+ self .assertIn ('GDB stderr:\n ' + stderr , message )
1004+
1005+
9671006def test_main ():
9681007 if support .verbose :
9691008 print ("GDB version %s.%s:" % (gdb_major_version , gdb_minor_version ))
9701009 for line in gdb_version .splitlines ():
9711010 print (" " * 4 + line )
972- run_unittest (PrettyPrintTests ,
1011+ run_unittest (GdbInvocationTests ,
1012+ PrettyPrintTests ,
9731013 PyListTests ,
9741014 StackNavigationTests ,
9751015 PyBtTests ,
0 commit comments