OLD | NEW |
(Empty) | |
| 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_scri
pt.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() |
OLD | NEW |