277 lines
8.3 KiB
Python
277 lines
8.3 KiB
Python
|
|
||
|
import os
|
||
|
import re
|
||
|
import sys
|
||
|
import shutil
|
||
|
import warnings
|
||
|
import textwrap
|
||
|
import unittest
|
||
|
import tempfile
|
||
|
import subprocess
|
||
|
#import distutils.core
|
||
|
#from distutils import sysconfig
|
||
|
from distutils import ccompiler
|
||
|
|
||
|
import runtests
|
||
|
import Cython.Distutils.extension
|
||
|
import Cython.Distutils.old_build_ext as build_ext
|
||
|
from Cython.Debugger import Cygdb as cygdb
|
||
|
|
||
|
root = os.path.dirname(os.path.abspath(__file__))
|
||
|
codefile = os.path.join(root, 'codefile')
|
||
|
cfuncs_file = os.path.join(root, 'cfuncs.c')
|
||
|
|
||
|
with open(codefile) as f:
|
||
|
source_to_lineno = dict((line.strip(), i + 1) for i, line in enumerate(f))
|
||
|
|
||
|
|
||
|
have_gdb = None
|
||
|
def test_gdb():
|
||
|
global have_gdb
|
||
|
if have_gdb is not None:
|
||
|
return have_gdb
|
||
|
|
||
|
have_gdb = False
|
||
|
try:
|
||
|
p = subprocess.Popen(['gdb', '-nx', '--version'], stdout=subprocess.PIPE)
|
||
|
except OSError:
|
||
|
# gdb not found
|
||
|
gdb_version = None
|
||
|
else:
|
||
|
stdout, _ = p.communicate()
|
||
|
# Based on Lib/test/test_gdb.py
|
||
|
regex = r"GNU gdb [^\d]*(\d+)\.(\d+)"
|
||
|
gdb_version = re.match(regex, stdout.decode('ascii', 'ignore'))
|
||
|
|
||
|
if gdb_version:
|
||
|
gdb_version_number = list(map(int, gdb_version.groups()))
|
||
|
if gdb_version_number >= [7, 2]:
|
||
|
have_gdb = True
|
||
|
with tempfile.NamedTemporaryFile(mode='w+') as python_version_script:
|
||
|
python_version_script.write(
|
||
|
'python import sys; print("%s %s" % sys.version_info[:2])')
|
||
|
python_version_script.flush()
|
||
|
p = subprocess.Popen(['gdb', '-batch', '-x', python_version_script.name],
|
||
|
stdout=subprocess.PIPE)
|
||
|
stdout, _ = p.communicate()
|
||
|
try:
|
||
|
internal_python_version = list(map(int, stdout.decode('ascii', 'ignore').split()))
|
||
|
if internal_python_version < [2, 7]:
|
||
|
have_gdb = False
|
||
|
except ValueError:
|
||
|
have_gdb = False
|
||
|
|
||
|
if not have_gdb:
|
||
|
warnings.warn('Skipping gdb tests, need gdb >= 7.2 with Python >= 2.7')
|
||
|
|
||
|
return have_gdb
|
||
|
|
||
|
|
||
|
class DebuggerTestCase(unittest.TestCase):
|
||
|
|
||
|
def setUp(self):
|
||
|
"""
|
||
|
Run gdb and have cygdb import the debug information from the code
|
||
|
defined in TestParseTreeTransforms's setUp method
|
||
|
"""
|
||
|
if not test_gdb():
|
||
|
return
|
||
|
|
||
|
self.tempdir = tempfile.mkdtemp()
|
||
|
self.destfile = os.path.join(self.tempdir, 'codefile.pyx')
|
||
|
self.debug_dest = os.path.join(self.tempdir,
|
||
|
'cython_debug',
|
||
|
'cython_debug_info_codefile')
|
||
|
self.cfuncs_destfile = os.path.join(self.tempdir, 'cfuncs')
|
||
|
|
||
|
self.cwd = os.getcwd()
|
||
|
try:
|
||
|
os.chdir(self.tempdir)
|
||
|
|
||
|
shutil.copy(codefile, self.destfile)
|
||
|
shutil.copy(cfuncs_file, self.cfuncs_destfile + '.c')
|
||
|
shutil.copy(cfuncs_file.replace('.c', '.h'),
|
||
|
self.cfuncs_destfile + '.h')
|
||
|
|
||
|
compiler = ccompiler.new_compiler()
|
||
|
compiler.compile(['cfuncs.c'], debug=True, extra_postargs=['-fPIC'])
|
||
|
|
||
|
opts = dict(
|
||
|
test_directory=self.tempdir,
|
||
|
module='codefile',
|
||
|
module_path=self.destfile,
|
||
|
)
|
||
|
|
||
|
optimization_disabler = build_ext.Optimization()
|
||
|
|
||
|
cython_compile_testcase = runtests.CythonCompileTestCase(
|
||
|
workdir=self.tempdir,
|
||
|
# we clean up everything (not only compiled files)
|
||
|
cleanup_workdir=False,
|
||
|
tags=runtests.parse_tags(codefile),
|
||
|
**opts
|
||
|
)
|
||
|
|
||
|
|
||
|
new_stderr = open(os.devnull, 'w')
|
||
|
|
||
|
stderr = sys.stderr
|
||
|
sys.stderr = new_stderr
|
||
|
|
||
|
optimization_disabler.disable_optimization()
|
||
|
try:
|
||
|
cython_compile_testcase.run_cython(
|
||
|
targetdir=self.tempdir,
|
||
|
incdir=None,
|
||
|
annotate=False,
|
||
|
extra_compile_options={
|
||
|
'gdb_debug':True,
|
||
|
'output_dir':self.tempdir,
|
||
|
},
|
||
|
**opts
|
||
|
)
|
||
|
|
||
|
cython_compile_testcase.run_distutils(
|
||
|
test_directory=opts['test_directory'],
|
||
|
module=opts['module'],
|
||
|
workdir=opts['test_directory'],
|
||
|
incdir=None,
|
||
|
extra_extension_args={'extra_objects':['cfuncs.o']},
|
||
|
)
|
||
|
finally:
|
||
|
optimization_disabler.restore_state()
|
||
|
sys.stderr = stderr
|
||
|
new_stderr.close()
|
||
|
|
||
|
# ext = Cython.Distutils.extension.Extension(
|
||
|
# 'codefile',
|
||
|
# ['codefile.pyx'],
|
||
|
# cython_gdb=True,
|
||
|
# extra_objects=['cfuncs.o'])
|
||
|
#
|
||
|
# distutils.core.setup(
|
||
|
# script_args=['build_ext', '--inplace'],
|
||
|
# ext_modules=[ext],
|
||
|
# cmdclass=dict(build_ext=Cython.Distutils.build_ext)
|
||
|
# )
|
||
|
|
||
|
except:
|
||
|
os.chdir(self.cwd)
|
||
|
raise
|
||
|
|
||
|
def tearDown(self):
|
||
|
if not test_gdb():
|
||
|
return
|
||
|
os.chdir(self.cwd)
|
||
|
shutil.rmtree(self.tempdir)
|
||
|
|
||
|
|
||
|
class GdbDebuggerTestCase(DebuggerTestCase):
|
||
|
|
||
|
def setUp(self):
|
||
|
if not test_gdb():
|
||
|
return
|
||
|
|
||
|
super(GdbDebuggerTestCase, self).setUp()
|
||
|
|
||
|
prefix_code = textwrap.dedent('''\
|
||
|
python
|
||
|
|
||
|
import os
|
||
|
import sys
|
||
|
import traceback
|
||
|
|
||
|
def excepthook(type, value, tb):
|
||
|
traceback.print_exception(type, value, tb)
|
||
|
sys.stderr.flush()
|
||
|
sys.stdout.flush()
|
||
|
os._exit(1)
|
||
|
|
||
|
sys.excepthook = excepthook
|
||
|
|
||
|
# Have tracebacks end up on sys.stderr (gdb replaces sys.stderr
|
||
|
# with an object that calls gdb.write())
|
||
|
sys.stderr = sys.__stderr__
|
||
|
|
||
|
end
|
||
|
''')
|
||
|
|
||
|
code = textwrap.dedent('''\
|
||
|
python
|
||
|
|
||
|
from Cython.Debugger.Tests import test_libcython_in_gdb
|
||
|
test_libcython_in_gdb.main(version=%r)
|
||
|
|
||
|
end
|
||
|
''' % (sys.version_info[:2],))
|
||
|
|
||
|
self.gdb_command_file = cygdb.make_command_file(self.tempdir,
|
||
|
prefix_code)
|
||
|
|
||
|
with open(self.gdb_command_file, 'a') as f:
|
||
|
f.write(code)
|
||
|
|
||
|
args = ['gdb', '-batch', '-x', self.gdb_command_file, '-n', '--args',
|
||
|
sys.executable, '-c', 'import codefile']
|
||
|
|
||
|
paths = []
|
||
|
path = os.environ.get('PYTHONPATH')
|
||
|
if path:
|
||
|
paths.append(path)
|
||
|
paths.append(os.path.dirname(os.path.dirname(
|
||
|
os.path.abspath(Cython.__file__))))
|
||
|
env = dict(os.environ, PYTHONPATH=os.pathsep.join(paths))
|
||
|
|
||
|
self.p = subprocess.Popen(
|
||
|
args,
|
||
|
stdout=subprocess.PIPE,
|
||
|
stderr=subprocess.PIPE,
|
||
|
env=env)
|
||
|
|
||
|
def tearDown(self):
|
||
|
if not test_gdb():
|
||
|
return
|
||
|
|
||
|
try:
|
||
|
super(GdbDebuggerTestCase, self).tearDown()
|
||
|
if self.p:
|
||
|
try: self.p.stdout.close()
|
||
|
except: pass
|
||
|
try: self.p.stderr.close()
|
||
|
except: pass
|
||
|
self.p.wait()
|
||
|
finally:
|
||
|
os.remove(self.gdb_command_file)
|
||
|
|
||
|
|
||
|
class TestAll(GdbDebuggerTestCase):
|
||
|
|
||
|
def test_all(self):
|
||
|
if not test_gdb():
|
||
|
return
|
||
|
|
||
|
out, err = self.p.communicate()
|
||
|
out = out.decode('UTF-8')
|
||
|
err = err.decode('UTF-8')
|
||
|
|
||
|
exit_status = self.p.returncode
|
||
|
|
||
|
if exit_status == 1:
|
||
|
sys.stderr.write(out)
|
||
|
sys.stderr.write(err)
|
||
|
elif exit_status >= 2:
|
||
|
border = u'*' * 30
|
||
|
start = u'%s v INSIDE GDB v %s' % (border, border)
|
||
|
stderr = u'%s v STDERR v %s' % (border, border)
|
||
|
end = u'%s ^ INSIDE GDB ^ %s' % (border, border)
|
||
|
errmsg = u'\n%s\n%s%s\n%s%s' % (start, out, stderr, err, end)
|
||
|
|
||
|
sys.stderr.write(errmsg)
|
||
|
|
||
|
# FIXME: re-enable this to make the test fail on internal failures
|
||
|
#self.assertEqual(exit_status, 0)
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
unittest.main()
|