| |
|
| | import os |
| | import re |
| | import sys |
| | import shutil |
| | import warnings |
| | import textwrap |
| | import unittest |
| | import tempfile |
| | import subprocess |
| | |
| | |
| | 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_version = None |
| | else: |
| | stdout, _ = p.communicate() |
| | |
| | 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, 6]: |
| | have_gdb = False |
| | except ValueError: |
| | have_gdb = False |
| |
|
| | if not have_gdb: |
| | warnings.warn('Skipping gdb tests, need gdb >= 7.2 with Python >= 2.6') |
| |
|
| | 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', |
| | ) |
| |
|
| | optimization_disabler = build_ext.Optimization() |
| |
|
| | cython_compile_testcase = runtests.CythonCompileTestCase( |
| | workdir=self.tempdir, |
| | |
| | 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( |
| | incdir=None, |
| | workdir=self.tempdir, |
| | extra_extension_args={'extra_objects':['cfuncs.o']}, |
| | **opts |
| | ) |
| | finally: |
| | optimization_disabler.restore_state() |
| | sys.stderr = stderr |
| | new_stderr.close() |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | 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) |
| |
|
| | |
| | |
| |
|
| |
|
| | if __name__ == '__main__': |
| | unittest.main() |
| |
|