From d07e7567b471ec4a4c42edd51290b6fd97b94c0c Mon Sep 17 00:00:00 2001 From: Mark Florisson Date: Sun, 31 Oct 2010 22:35:18 +0100 Subject: [PATCH] lots of tests --- .../Compiler/Tests/TestParseTreeTransforms.py | 8 +- Cython/Debugger/Tests/TestLibCython.py | 88 ++++---- Cython/Debugger/Tests/cfuncs.c | 8 + Cython/Debugger/Tests/codefile | 36 ++++ .../Debugger/Tests/test_libcython_in_gdb.py | 193 +++++++++++++----- Cython/Tests/TestStringIOTree.py | 67 ++++++ 6 files changed, 301 insertions(+), 99 deletions(-) create mode 100644 Cython/Debugger/Tests/cfuncs.c create mode 100644 Cython/Debugger/Tests/codefile create mode 100644 Cython/Tests/TestStringIOTree.py diff --git a/Cython/Compiler/Tests/TestParseTreeTransforms.py b/Cython/Compiler/Tests/TestParseTreeTransforms.py index 7857986c..6be5f423 100644 --- a/Cython/Compiler/Tests/TestParseTreeTransforms.py +++ b/Cython/Compiler/Tests/TestParseTreeTransforms.py @@ -197,10 +197,12 @@ class TestDebugTransform(TestLibCython.DebuggerTestCase): self.assertEqual(1, len(list(spam_arguments))) # test step-into functions - spam_stepinto = list(spam.find('StepIntoFunctions')) + step_into = spam.find('StepIntoFunctions') + spam_stepinto = [x.attrib['name'] for x in step_into] assert spam_stepinto - self.assertEqual(1, len(list(spam_stepinto))) - self.assertEqual('puts', list(spam_stepinto)[0].attrib['name']) + self.assertEqual(2, len(spam_stepinto)) + assert 'puts' in spam_stepinto + assert 'some_c_function' in spam_stepinto except: print open(self.debug_dest).read() raise diff --git a/Cython/Debugger/Tests/TestLibCython.py b/Cython/Debugger/Tests/TestLibCython.py index f83b1f84..25941d4b 100644 --- a/Cython/Debugger/Tests/TestLibCython.py +++ b/Cython/Debugger/Tests/TestLibCython.py @@ -9,10 +9,16 @@ import tempfile import subprocess import distutils.core from distutils import sysconfig +from distutils import ccompiler import Cython.Distutils.extension 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)) class DebuggerTestCase(unittest.TestCase): @@ -26,52 +32,26 @@ class DebuggerTestCase(unittest.TestCase): self.debug_dest = os.path.join(self.tempdir, 'cython_debug', 'cython_debug_info_codefile') - - code = textwrap.dedent(""" - cdef extern from "stdio.h": - int puts(char *s) - - cdef int c_var = 0 - python_var = 0 - - def spam(a=0): - cdef: - int b, c, d - - b = c = d = 0 - - b = 1 - c = 2 - d = 3 - int(10) - puts("spam") - - cdef ham(): - pass - - cpdef eggs(): - pass - - cdef class SomeClass(object): - def spam(self): - pass - - spam() - """) + self.cfuncs_destfile = os.path.join(self.tempdir, 'cfuncs') self.cwd = os.getcwd() os.chdir(self.tempdir) - open(self.destfile, 'w').write(code) + shutil.copy(codefile, self.destfile) + shutil.copy(cfuncs_file, self.cfuncs_destfile + '.c') + + compiler = ccompiler.new_compiler() + compiler.compile(['cfuncs.c'], debug=True) ext = Cython.Distutils.extension.Extension( 'codefile', ['codefile.pyx'], - pyrex_debug=True) + pyrex_debug=True, + extra_objects=['cfuncs.o']) distutils.core.setup( script_args=['build_ext', '--inplace'], - ext_modules=[ext], + ext_modules=[ext], cmdclass=dict(build_ext=Cython.Distutils.build_ext) ) @@ -84,12 +64,39 @@ class GdbDebuggerTestCase(DebuggerTestCase): def setUp(self): super(GdbDebuggerTestCase, self).setUp() - self.gdb_command_file = cygdb.make_command_file(self.tempdir) - with open(self.gdb_command_file, 'a') as f: - f.write('python ' - 'from Cython.Debugger.Tests import test_libcython_in_gdb;' - 'test_libcython_in_gdb.main()\n') + prefix_code = textwrap.dedent('''\ + python + import os + import sys + import traceback + + def excepthook(type, value, tb): + traceback.print_exception(type, value, tb) + 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() + + end + ''') + + self.gdb_command_file = cygdb.make_command_file(self.tempdir, + prefix_code) + open(self.gdb_command_file, 'a').write(code) + args = ['gdb', '-batch', '-x', self.gdb_command_file, '-n', '--args', sys.executable, '-c', 'import codefile'] @@ -100,6 +107,7 @@ class GdbDebuggerTestCase(DebuggerTestCase): 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=open(os.devnull, 'w'), diff --git a/Cython/Debugger/Tests/cfuncs.c b/Cython/Debugger/Tests/cfuncs.c new file mode 100644 index 00000000..35fb0290 --- /dev/null +++ b/Cython/Debugger/Tests/cfuncs.c @@ -0,0 +1,8 @@ +void +some_c_function(void) +{ + int a, b, c; + + a = 1; + b = 2; +} \ No newline at end of file diff --git a/Cython/Debugger/Tests/codefile b/Cython/Debugger/Tests/codefile new file mode 100644 index 00000000..e9c77c7d --- /dev/null +++ b/Cython/Debugger/Tests/codefile @@ -0,0 +1,36 @@ +cdef extern from "stdio.h": + int puts(char *s) + +cdef extern: + void some_c_function() + +import os + +cdef int c_var = 0 +python_var = 0 + +def spam(a=0): + cdef: + int b, c + + b = c = d = 0 + + b = 1 + c = 2 + int(10) + puts("spam") + os.path.join("foo", "bar") + some_c_function() + +cdef ham(): + pass + +cpdef eggs(): + pass + +cdef class SomeClass(object): + def spam(self): + pass + +spam() +print "bye!" diff --git a/Cython/Debugger/Tests/test_libcython_in_gdb.py b/Cython/Debugger/Tests/test_libcython_in_gdb.py index 5a065b68..c926b526 100644 --- a/Cython/Debugger/Tests/test_libcython_in_gdb.py +++ b/Cython/Debugger/Tests/test_libcython_in_gdb.py @@ -5,19 +5,9 @@ Note: debug information is already imported by the file generated by Cython.Debugger.Cygdb.make_command_file() """ -import sys - -# First, fix gdb's python. Make sure to do this before importing modules -# that bind output streams as default parameters - -# for some reason sys.argv is missing in gdb -sys.argv = ['gdb'] - -# Allow gdb to capture output, but have errors end up on stderr -# sys.stdout = sys.__stdout__ -sys.stderr = sys.__stderr__ - import os +import sys +import trace import warnings import unittest import traceback @@ -26,6 +16,12 @@ from test import test_support import gdb from Cython.Debugger import libcython +from Cython.Debugger import libpython +from Cython.Debugger.Tests import TestLibCython as test_libcython + +# for some reason sys.argv is missing in gdb +sys.argv = ['gdb'] + class DebugTestCase(unittest.TestCase): @@ -39,14 +35,35 @@ class DebugTestCase(unittest.TestCase): self.eggs_func = libcython.cy.functions_by_qualified_name[ 'codefile.eggs'] - def read_var(self, varname): - return gdb.parse_and_eval('$cy_cname("%s")' % varname) + def read_var(self, varname, cast_to=None): + result = gdb.parse_and_eval('$cy_cname("%s")' % varname) + if cast_to: + result = cast_to(result) + + return result def local_info(self): return gdb.execute('info locals', to_string=True) -class TestDebugInformationClasses(DebugTestCase): + def lineno_equals(self, source_line=None, lineno=None): + if source_line is not None: + lineno = test_libcython.source_to_lineno[source_line] + frame = gdb.selected_frame() + self.assertEqual(libcython.cy.step.lineno(frame), lineno) + def tearDown(self): + gdb.execute('delete breakpoints', to_string=True) + try: + gdb.execute('kill inferior 1', to_string=True) + except RuntimeError: + pass + + def break_and_run(self, source_line): + break_lineno = test_libcython.source_to_lineno[source_line] + gdb.execute('cy break codefile:%d' % break_lineno, to_string=True) + gdb.execute('run', to_string=True) + +class TestDebugInformationClasses(DebugTestCase): def test_CythonModule(self): "test that debug information was parsed properly into data structures" @@ -78,9 +95,11 @@ class TestDebugInformationClasses(DebugTestCase): self.assertEqual(self.ham_func.type, libcython.CObject) self.assertEqual(self.spam_func.arguments, ['a']) - self.assertEqual(self.spam_func.step_into_functions, set(['puts'])) + self.assertEqual(self.spam_func.step_into_functions, + set(['puts', 'some_c_function'])) - self.assertEqual(self.spam_func.lineno, 8) + expected_lineno = test_libcython.source_to_lineno['def spam(a=0):'] + self.assertEqual(self.spam_func.lineno, expected_lineno) self.assertEqual(sorted(self.spam_func.locals), list('abcd')) @@ -95,7 +114,8 @@ class TestParameters(unittest.TestCase): class TestBreak(DebugTestCase): def test_break(self): - result = gdb.execute('cy break codefile.spam', to_string=True) + result = libpython._execute('cy break codefile.spam', to_string=True) + print >>sys.stderr, repr(result) assert self.spam_func.cname in result self.assertEqual(len(gdb.breakpoints()), 1) @@ -104,48 +124,109 @@ class TestBreak(DebugTestCase): self.assertEqual(bp.location, self.spam_func.cname) assert bp.enabled - -class TestStep(DebugTestCase): + +class DebugStepperTestCase(DebugTestCase): - def test_step(self): - # Note: breakpoint for spam is still set - gdb.execute('run') + def step(self, varnames_and_values, source_line=None, lineno=None): + gdb.execute(self.command, to_string=True) + for varname, value in varnames_and_values: + self.assertEqual(self.read_var(varname), value, self.local_info()) - gdb.execute('cy step', to_string=True) # b = c = d = 0 - gdb.execute('cy step', to_string=True) # b = 1 - self.assertEqual(self.read_var('b'), 1, self.local_info()) - self.assertRaises(RuntimeError, self.read_var('b')) - gdb.execute('cont') + self.lineno_equals(source_line, lineno) -class TestNext(DebugTestCase): +class TestStep(DebugStepperTestCase): + """ + Test stepping. Stepping happens in the code found in + Cython/Debugger/Tests/codefile. + """ + def test_cython_step(self): + gdb.execute('cy break codefile.spam') + libcython.parameters.step_into_c_code.value = False + + gdb.execute('run', to_string=True) + self.lineno_equals('def spam(a=0):') + + gdb.execute('cy step', to_string=True) + self.lineno_equals('b = c = d = 0') + + self.command = 'cy step' + self.step([('b', 0)], source_line='b = 1') + self.step([('b', 1), ('c', 0)], source_line='c = 2') + self.step([('c', 2)], source_line='int(10)') + self.step([], source_line='puts("spam")') + self.step([], source_line='os.path.join("foo", "bar")') + + gdb.execute('cont', to_string=True) + self.assertEqual(len(gdb.inferiors()), 1) + self.assertEqual(gdb.inferiors()[0].pid, 0) - def test_next(self): - pass + def test_c_step(self): + libcython.parameters.step_into_c_code.value = True + self.break_and_run('some_c_function()') + gdb.execute('cy step', to_string=True) + self.assertEqual(gdb.selected_frame().name(), 'some_c_function') -def main(): + def test_python_step(self): + self.break_and_run('os.path.join("foo", "bar")') + + gdb.execute('cy step', to_string=True) + + curframe = gdb.selected_frame() + self.assertEqual(curframe.name(), 'PyEval_EvalFrameEx') + + pyframe = libpython.Frame(curframe).get_pyop() + self.assertEqual(str(pyframe.co_name), 'join') + + +class TestNext(DebugStepperTestCase): + + def test_cython_next(self): + libcython.parameters.step_into_c_code.value = True + self.break_and_run('c = 2') + + lines = ( + 'int(10)', + 'puts("spam")', + 'os.path.join("foo", "bar")', + 'some_c_function()', + ) + + for line in lines: + gdb.execute('cy next') + self.lineno_equals(line) + + +def _main(): + # unittest.main(module=__import__(__name__, fromlist=[''])) try: - # unittest.main(module=__import__(__name__, fromlist=[''])) - try: - gdb.lookup_type('PyModuleObject') - except RuntimeError: - msg = ("Unable to run tests, Python was not compiled with " - "debugging information. Either compile python with " - "-g or get a debug build (configure with --with-pydebug).") - warnings.warn(msg) - else: - tests = ( - TestDebugInformationClasses, - TestParameters, - TestBreak, - TestStep - ) - # test_support.run_unittest(tests) - test_loader = unittest.TestLoader() - suite = unittest.TestSuite( - [test_loader.loadTestsFromTestCase(cls) for cls in tests]) - - unittest.TextTestRunner(verbosity=1).run(suite) - except Exception: - traceback.print_exc() - os._exit(1) \ No newline at end of file + gdb.lookup_type('PyModuleObject') + except RuntimeError: + msg = ("Unable to run tests, Python was not compiled with " + "debugging information. Either compile python with " + "-g or get a debug build (configure with --with-pydebug).") + warnings.warn(msg) + else: + tests = ( + TestDebugInformationClasses, + TestParameters, + TestBreak, + TestStep, + TestNext, + ) + # test_support.run_unittest(tests) + test_loader = unittest.TestLoader() + suite = unittest.TestSuite( + [test_loader.loadTestsFromTestCase(cls) for cls in tests]) + + result = unittest.TextTestRunner(verbosity=1).run(suite) + if not result.wasSuccessful(): + os._exit(1) + +def main(trace_code=False): + if trace_code: + tracer = trace.Trace(count=False, trace=True, outfile=sys.stderr, + ignoredirs=[sys.prefix, sys.exec_prefix]) + tracer.runfunc(_main) + else: + _main() \ No newline at end of file diff --git a/Cython/Tests/TestStringIOTree.py b/Cython/Tests/TestStringIOTree.py new file mode 100644 index 00000000..41162ac3 --- /dev/null +++ b/Cython/Tests/TestStringIOTree.py @@ -0,0 +1,67 @@ +import unittest + +from Cython import StringIOTree as stringtree + +code = """ +cdef int spam # line 1 + +cdef ham(): + a = 1 + b = 2 + c = 3 + d = 4 + +def eggs(): + pass + +cpdef bacon(): + print spam + print 'scotch' + print 'tea?' + print 'or coffee?' # line 16 +""" + +linemap = dict(enumerate(code.splitlines())) + +class TestStringIOTree(unittest.TestCase): + + def setUp(self): + self.tree = stringtree.StringIOTree() + + def test_markers(self): + assert not self.tree.allmarkers() + + def test_insertion(self): + self.write_lines((1, 2, 3)) + line_4_to_6_insertion_point = self.tree.insertion_point() + self.write_lines((7, 8)) + line_9_to_13_insertion_point = self.tree.insertion_point() + self.write_lines((14, 15, 16)) + + line_4_insertion_point = line_4_to_6_insertion_point.insertion_point() + self.write_lines((5, 6), tree=line_4_to_6_insertion_point) + + line_9_to_12_insertion_point = ( + line_9_to_13_insertion_point.insertion_point()) + self.write_line(13, tree=line_9_to_13_insertion_point) + + self.write_line(4, tree=line_4_insertion_point) + self.write_line(9, tree=line_9_to_12_insertion_point) + line_10_insertion_point = line_9_to_12_insertion_point.insertion_point() + self.write_line(11, tree=line_9_to_12_insertion_point) + self.write_line(10, tree=line_10_insertion_point) + self.write_line(12, tree=line_9_to_12_insertion_point) + + self.assertEqual(self.tree.allmarkers(), range(1, 17)) + self.assertEqual(code.strip(), self.tree.getvalue().strip()) + + + def write_lines(self, linenos, tree=None): + for lineno in linenos: + self.write_line(lineno, tree=tree) + + def write_line(self, lineno, tree=None): + if tree is None: + tree = self.tree + tree.markers.append(lineno) + tree.write(linemap[lineno] + '\n') \ No newline at end of file -- 2.26.2