1 2 import os 3 import re 4 import sys 5 import shutil 6 import warnings 7 import textwrap 8 import unittest 9 import tempfile 10 import subprocess 11 #import distutils.core 12 #from distutils import sysconfig 13 from distutils import ccompiler 14 15 import runtests 16 import Cython.Distutils.extension 17 import Cython.Distutils.build_ext 18 from Cython.Debugger import Cygdb as cygdb 19 20 root = os.path.dirname(os.path.abspath(__file__)) 21 codefile = os.path.join(root, 'codefile') 22 cfuncs_file = os.path.join(root, 'cfuncs.c') 23 24 f = open(codefile) 25 try: 26 source_to_lineno = dict([ (line.strip(), i + 1) for i, line in enumerate(f) ]) 27 finally: 28 f.close() 29 30 # Cython.Distutils.__init__ imports build_ext from build_ext which means we 31 # can't access the module anymore. Get it from sys.modules instead. 32 build_ext = sys.modules['Cython.Distutils.build_ext'] 33 34 35 have_gdb = None 36 def test_gdb(): 37 global have_gdb 38 if have_gdb is not None: 39 return have_gdb 40 41 try: 42 p = subprocess.Popen(['gdb', '-v'], stdout=subprocess.PIPE) 43 have_gdb = True 44 except OSError: 45 # gdb was not installed 46 have_gdb = False 47 else: 48 gdb_version = p.stdout.read().decode('ascii', 'ignore') 49 p.wait() 50 p.stdout.close() 51 52 if have_gdb: 53 # Based on Lib/test/test_gdb.py 54 regex = "^GNU gdb [^\d]*(\d+)\.(\d+)" 55 gdb_version_number = list(map(int, re.search(regex, gdb_version).groups())) 56 57 if gdb_version_number >= [7, 2]: 58 python_version_script = tempfile.NamedTemporaryFile(mode='w+') 59 try: 60 python_version_script.write( 61 'python import sys; print("%s %s" % sys.version_info[:2])') 62 python_version_script.flush() 63 p = subprocess.Popen(['gdb', '-batch', '-x', python_version_script.name], 64 stdout=subprocess.PIPE) 65 try: 66 python_version = p.stdout.read().decode('ascii') 67 p.wait() 68 finally: 69 p.stdout.close() 70 try: 71 python_version_number = list(map(int, python_version.split())) 72 except ValueError: 73 have_gdb = False 74 finally: 75 python_version_script.close() 76 77 # Be Python 3 compatible 78 if (not have_gdb 79 or gdb_version_number < [7, 2] 80 or python_version_number < [2, 6]): 81 warnings.warn( 82 'Skipping gdb tests, need gdb >= 7.2 with Python >= 2.6') 83 have_gdb = False 84 85 return have_gdb 86 87 88 class DebuggerTestCase(unittest.TestCase): 89 90 def setUp(self): 91 """ 92 Run gdb and have cygdb import the debug information from the code 93 defined in TestParseTreeTransforms's setUp method 94 """ 95 if not test_gdb(): 96 return 97 98 self.tempdir = tempfile.mkdtemp() 99 self.destfile = os.path.join(self.tempdir, 'codefile.pyx') 100 self.debug_dest = os.path.join(self.tempdir, 101 'cython_debug', 102 'cython_debug_info_codefile') 103 self.cfuncs_destfile = os.path.join(self.tempdir, 'cfuncs') 104 105 self.cwd = os.getcwd() 106 try: 107 os.chdir(self.tempdir) 108 109 shutil.copy(codefile, self.destfile) 110 shutil.copy(cfuncs_file, self.cfuncs_destfile + '.c') 111 112 compiler = ccompiler.new_compiler() 113 compiler.compile(['cfuncs.c'], debug=True, extra_postargs=['-fPIC']) 114 115 opts = dict( 116 test_directory=self.tempdir, 117 module='codefile', 118 ) 119 120 optimization_disabler = build_ext.Optimization() 121 122 cython_compile_testcase = runtests.CythonCompileTestCase( 123 workdir=self.tempdir, 124 # we clean up everything (not only compiled files) 125 cleanup_workdir=False, 126 tags=runtests.parse_tags(codefile), 127 **opts 128 ) 129 130 131 new_stderr = open(os.devnull, 'w') 132 133 stderr = sys.stderr 134 sys.stderr = new_stderr 135 136 optimization_disabler.disable_optimization() 137 try: 138 cython_compile_testcase.run_cython( 139 targetdir=self.tempdir, 140 incdir=None, 141 annotate=False, 142 extra_compile_options={ 143 'gdb_debug':True, 144 'output_dir':self.tempdir, 145 }, 146 **opts 147 ) 148 149 cython_compile_testcase.run_distutils( 150 incdir=None, 151 workdir=self.tempdir, 152 extra_extension_args={'extra_objects':['cfuncs.o']}, 153 **opts 154 ) 155 finally: 156 optimization_disabler.restore_state() 157 sys.stderr = stderr 158 new_stderr.close() 159 160 # ext = Cython.Distutils.extension.Extension( 161 # 'codefile', 162 # ['codefile.pyx'], 163 # cython_gdb=True, 164 # extra_objects=['cfuncs.o']) 165 # 166 # distutils.core.setup( 167 # script_args=['build_ext', '--inplace'], 168 # ext_modules=[ext], 169 # cmdclass=dict(build_ext=Cython.Distutils.build_ext) 170 # ) 171 172 except: 173 os.chdir(self.cwd) 174 raise 175 176 def tearDown(self): 177 if not test_gdb(): 178 return 179 os.chdir(self.cwd) 180 shutil.rmtree(self.tempdir) 181 182 183 class GdbDebuggerTestCase(DebuggerTestCase): 184 185 def setUp(self): 186 if not test_gdb(): 187 return 188 189 super(GdbDebuggerTestCase, self).setUp() 190 191 prefix_code = textwrap.dedent('''\ 192 python 193 194 import os 195 import sys 196 import traceback 197 198 def excepthook(type, value, tb): 199 traceback.print_exception(type, value, tb) 200 os._exit(1) 201 202 sys.excepthook = excepthook 203 204 # Have tracebacks end up on sys.stderr (gdb replaces sys.stderr 205 # with an object that calls gdb.write()) 206 sys.stderr = sys.__stderr__ 207 208 end 209 ''') 210 211 code = textwrap.dedent('''\ 212 python 213 214 from Cython.Debugger.Tests import test_libcython_in_gdb 215 test_libcython_in_gdb.main(version=%r) 216 217 end 218 ''' % (sys.version_info[:2],)) 219 220 self.gdb_command_file = cygdb.make_command_file(self.tempdir, 221 prefix_code) 222 223 f = open(self.gdb_command_file, 'a') 224 try: 225 f.write(code) 226 finally: 227 f.close() 228 229 args = ['gdb', '-batch', '-x', self.gdb_command_file, '-n', '--args', 230 sys.executable, '-c', 'import codefile'] 231 232 paths = [] 233 path = os.environ.get('PYTHONPATH') 234 if path: 235 paths.append(path) 236 paths.append(os.path.dirname(os.path.dirname( 237 os.path.abspath(Cython.__file__)))) 238 env = dict(os.environ, PYTHONPATH=os.pathsep.join(paths)) 239 240 self.p = subprocess.Popen( 241 args, 242 stdout=open(os.devnull, 'w'), 243 stderr=subprocess.PIPE, 244 env=env) 245 246 def tearDown(self): 247 if not test_gdb(): 248 return 249 250 try: 251 super(GdbDebuggerTestCase, self).tearDown() 252 if self.p: 253 try: self.p.stdout.close() 254 except: pass 255 try: self.p.stderr.close() 256 except: pass 257 self.p.wait() 258 finally: 259 os.remove(self.gdb_command_file) 260 261 262 class TestAll(GdbDebuggerTestCase): 263 264 def test_all(self): 265 if not test_gdb(): 266 return 267 268 out, err = self.p.communicate() 269 err = err.decode('UTF-8') 270 271 exit_status = self.p.returncode 272 273 if exit_status == 1: 274 sys.stderr.write(err) 275 elif exit_status >= 2: 276 border = u'*' * 30 277 start = u'%s v INSIDE GDB v %s' % (border, border) 278 end = u'%s ^ INSIDE GDB ^ %s' % (border, border) 279 errmsg = u'\n%s\n%s%s' % (start, err, end) 280 281 sys.stderr.write(errmsg) 282 283 284 if __name__ == '__main__': 285 unittest.main() 286