TestLibCython.py 8.1 KB

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