Home | History | Annotate | Download | only in Tests
      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