test support for parse tree path assertions
authorStefan Behnel <scoder@users.berlios.de>
Sun, 4 Oct 2009 14:38:40 +0000 (16:38 +0200)
committerStefan Behnel <scoder@users.berlios.de>
Sun, 4 Oct 2009 14:38:40 +0000 (16:38 +0200)
Cython/Compiler/Main.py
Cython/Compiler/Options.py
Cython/Compiler/ParseTreeTransforms.py
Cython/TestUtils.py
runtests.py

index 05bde200be9c36f2f8299b6e6e9aba9d8c4f96eb..8134f589b2baa7804ffcbe02a90e2dad7096bfda 100644 (file)
@@ -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":
index 0739f2498012a383c949be401bdc2f6fd1faa7ba..9d1423d386445dbbabbb3176d720f3abd8492a80 100644 (file)
@@ -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):
index 131423963f35f333719ba1e6115a0d3cb53407e4..a2b20d539d2245072094a9b20dcbfb2f856396fe 100644 (file)
@@ -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])
index cd888dc0e6cbb6ecf0fcaa268d9582d7a1d536d5..a25933c802a818c17087c2c1a9e4aee038813a2f 100644 (file)
@@ -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
index 74a84b6a136042486524fa2b244b1d665cc58257..ce1cc7bdeb66f87fbe3ae365fdb399114e5a8410 100644 (file)
@@ -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)