From: Stefan Behnel Date: Sun, 4 Oct 2009 14:38:40 +0000 (+0200) Subject: test support for parse tree path assertions X-Git-Tag: 0.12.alpha0~182^2~6 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=4dd72eb9bdb8a0af41682475ae081a0fef8a0a4d;p=cython.git test support for parse tree path assertions --- diff --git a/Cython/Compiler/Main.py b/Cython/Compiler/Main.py index 05bde200..8134f589 100644 --- a/Cython/Compiler/Main.py +++ b/Cython/Compiler/Main.py @@ -163,9 +163,14 @@ class Context(object): module_node.scope.utility_code_list.extend(scope.utility_code_list) return module_node + test_support = [] + if options.evaluate_tree_assertions: + from Cython.TestUtils import TreeAssertVisitor + test_support.append(TreeAssertVisitor()) + return ([ create_parse(self), - ] + self.create_pipeline(pxd=False, py=py) + [ + ] + self.create_pipeline(pxd=False, py=py) + test_support + [ inject_pxd_code, abort_on_errors, generate_pyx_code, @@ -592,6 +597,7 @@ class CompilationOptions(object): verbose boolean Always print source names being compiled quiet boolean Don't print source names in recursive mode compiler_directives dict Overrides for pragma options (see Options.py) + evaluate_tree_assertions boolean Test support: evaluate parse tree assertions Following options are experimental and only used on MacOSX: @@ -780,6 +786,7 @@ default_options = dict( verbose = 0, quiet = 0, compiler_directives = {}, + evaluate_tree_assertions = False, emit_linenums = False, ) if sys.platform == "mac": diff --git a/Cython/Compiler/Options.py b/Cython/Compiler/Options.py index 0739f249..9d1423d3 100644 --- a/Cython/Compiler/Options.py +++ b/Cython/Compiler/Options.py @@ -68,7 +68,11 @@ option_defaults = { 'c99_complex' : False, # Don't use macro wrappers for complex arith, not sure what to name this... 'callspec' : "", 'profile': False, - 'doctesthack': False + 'doctesthack': False, + +# test support + 'testAssertPathExists' : [], + 'testFailIfPathExists' : [], } # Override types possibilities above, if needed @@ -80,7 +84,9 @@ for key, val in option_defaults.items(): option_scopes = { # defaults to available everywhere # 'module', 'function', 'class', 'with statement' - 'doctesthack' : ('module',) + 'doctesthack' : ('module',), + 'testAssertPathExists' : ('function',), + 'testFailIfPathExists' : ('function',), } def parse_option_value(name, value): diff --git a/Cython/Compiler/ParseTreeTransforms.py b/Cython/Compiler/ParseTreeTransforms.py index 13142396..a2b20d53 100644 --- a/Cython/Compiler/ParseTreeTransforms.py +++ b/Cython/Compiler/ParseTreeTransforms.py @@ -457,6 +457,11 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations): raise PostParseError(dec.function.pos, 'The %s option takes no prepositional arguments' % optname) return optname, dict([(key.value, value) for key, value in kwds.key_value_pairs]) + elif optiontype is list: + if kwds and len(kwds) != 0: + raise PostParseError(dec.function.pos, + 'The %s option takes no keyword arguments' % optname) + return optname, [ str(arg.value) for arg in args ] else: assert False @@ -499,10 +504,16 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations): legal_scopes = Options.option_scopes.get(name, None) if not self.check_directive_scope(node.pos, name, 'function'): continue - if name in optdict and isinstance(optdict[name], dict): - # only keywords can be merged, everything else - # overrides completely - optdict[name].update(value) + if name in optdict: + old_value = optdict[name] + # keywords and arg lists can be merged, everything + # else overrides completely + if isinstance(old_value, dict): + old_value.update(value) + elif isinstance(old_value, list): + old_value.extend(value) + else: + optdict[name] = value else: optdict[name] = value body = StatListNode(node.pos, stats=[node]) diff --git a/Cython/TestUtils.py b/Cython/TestUtils.py index cd888dc0..a25933c8 100644 --- a/Cython/TestUtils.py +++ b/Cython/TestUtils.py @@ -4,7 +4,8 @@ import unittest from Cython.Compiler.ModuleNode import ModuleNode import Cython.Compiler.Main as Main from Cython.Compiler.TreeFragment import TreeFragment, strip_common_indent -from Cython.Compiler.Visitor import TreeVisitor +from Cython.Compiler.Visitor import TreeVisitor, VisitorTransform +from Cython.Compiler import TreePath class NodeTypeWriter(TreeVisitor): def __init__(self): @@ -74,6 +75,10 @@ class CythonTest(unittest.TestCase): self.assertEqual(len(result_lines), len(expected_lines), "Unmatched lines. Got:\n%s\nExpected:\n%s" % ("\n".join(result_lines), expected)) + def assertNodeExists(self, path, result_tree): + self.assertNotEqual(TreePath.find_first(result_tree, path), None, + "Path '%s' not found in result tree" % path) + def fragment(self, code, pxds={}, pipeline=[]): "Simply create a tree fragment using the name of the test-case in parse errors." name = self.id() @@ -136,3 +141,28 @@ class TransformTest(CythonTest): tree = T(tree) return tree + +class TreeAssertVisitor(VisitorTransform): + # actually, a TreeVisitor would be enough, but this needs to run + # as part of the compiler pipeline + + def visit_CompilerDirectivesNode(self, node): + directives = node.directives + if 'testAssertPathExists' in directives: + for path in directives['testAssertPathExists']: + if TreePath.find_first(node, path) is None: + Errors.error( + node.pos, + "Expected path '%s' not found in result tree of node %r" % ( + path, node.body)) + if 'testFailIfPathExists' in directives: + for path in directives['testFailIfPathExists']: + if TreePath.find_first(node, path) is not None: + Errors.error( + node.pos, + "Unexpected path '%s' found in result tree of node %r" % ( + path, node.body)) + self.visitchildren(node) + return node + + visit_Node = VisitorTransform.recurse_to_children diff --git a/runtests.py b/runtests.py index 74a84b6a..ce1cc7bd 100644 --- a/runtests.py +++ b/runtests.py @@ -279,7 +279,9 @@ class CythonCompileTestCase(unittest.TestCase): annotate = annotate, use_listing_file = False, cplus = self.language == 'cpp', - generate_pxi = False) + generate_pxi = False, + evaluate_tree_assertions = True, + ) cython_compile(source, options=options, full_module_name=module)