Commit 4dd72eb9 authored by Stefan Behnel's avatar Stefan Behnel

test support for parse tree path assertions

parent d0722d34
...@@ -163,9 +163,14 @@ class Context(object): ...@@ -163,9 +163,14 @@ class Context(object):
module_node.scope.utility_code_list.extend(scope.utility_code_list) module_node.scope.utility_code_list.extend(scope.utility_code_list)
return module_node return module_node
test_support = []
if options.evaluate_tree_assertions:
from Cython.TestUtils import TreeAssertVisitor
test_support.append(TreeAssertVisitor())
return ([ return ([
create_parse(self), create_parse(self),
] + self.create_pipeline(pxd=False, py=py) + [ ] + self.create_pipeline(pxd=False, py=py) + test_support + [
inject_pxd_code, inject_pxd_code,
abort_on_errors, abort_on_errors,
generate_pyx_code, generate_pyx_code,
...@@ -592,6 +597,7 @@ class CompilationOptions(object): ...@@ -592,6 +597,7 @@ class CompilationOptions(object):
verbose boolean Always print source names being compiled verbose boolean Always print source names being compiled
quiet boolean Don't print source names in recursive mode quiet boolean Don't print source names in recursive mode
compiler_directives dict Overrides for pragma options (see Options.py) 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: Following options are experimental and only used on MacOSX:
...@@ -780,6 +786,7 @@ default_options = dict( ...@@ -780,6 +786,7 @@ default_options = dict(
verbose = 0, verbose = 0,
quiet = 0, quiet = 0,
compiler_directives = {}, compiler_directives = {},
evaluate_tree_assertions = False,
emit_linenums = False, emit_linenums = False,
) )
if sys.platform == "mac": if sys.platform == "mac":
......
...@@ -68,7 +68,11 @@ option_defaults = { ...@@ -68,7 +68,11 @@ option_defaults = {
'c99_complex' : False, # Don't use macro wrappers for complex arith, not sure what to name this... 'c99_complex' : False, # Don't use macro wrappers for complex arith, not sure what to name this...
'callspec' : "", 'callspec' : "",
'profile': False, 'profile': False,
'doctesthack': False 'doctesthack': False,
# test support
'testAssertPathExists' : [],
'testFailIfPathExists' : [],
} }
# Override types possibilities above, if needed # Override types possibilities above, if needed
...@@ -80,7 +84,9 @@ for key, val in option_defaults.items(): ...@@ -80,7 +84,9 @@ for key, val in option_defaults.items():
option_scopes = { # defaults to available everywhere option_scopes = { # defaults to available everywhere
# 'module', 'function', 'class', 'with statement' # 'module', 'function', 'class', 'with statement'
'doctesthack' : ('module',) 'doctesthack' : ('module',),
'testAssertPathExists' : ('function',),
'testFailIfPathExists' : ('function',),
} }
def parse_option_value(name, value): def parse_option_value(name, value):
......
...@@ -457,6 +457,11 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations): ...@@ -457,6 +457,11 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
raise PostParseError(dec.function.pos, raise PostParseError(dec.function.pos,
'The %s option takes no prepositional arguments' % optname) 'The %s option takes no prepositional arguments' % optname)
return optname, dict([(key.value, value) for key, value in kwds.key_value_pairs]) 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: else:
assert False assert False
...@@ -499,10 +504,16 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations): ...@@ -499,10 +504,16 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
legal_scopes = Options.option_scopes.get(name, None) legal_scopes = Options.option_scopes.get(name, None)
if not self.check_directive_scope(node.pos, name, 'function'): if not self.check_directive_scope(node.pos, name, 'function'):
continue continue
if name in optdict and isinstance(optdict[name], dict): if name in optdict:
# only keywords can be merged, everything else old_value = optdict[name]
# overrides completely # keywords and arg lists can be merged, everything
optdict[name].update(value) # 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: else:
optdict[name] = value optdict[name] = value
body = StatListNode(node.pos, stats=[node]) body = StatListNode(node.pos, stats=[node])
......
...@@ -4,7 +4,8 @@ import unittest ...@@ -4,7 +4,8 @@ import unittest
from Cython.Compiler.ModuleNode import ModuleNode from Cython.Compiler.ModuleNode import ModuleNode
import Cython.Compiler.Main as Main import Cython.Compiler.Main as Main
from Cython.Compiler.TreeFragment import TreeFragment, strip_common_indent 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): class NodeTypeWriter(TreeVisitor):
def __init__(self): def __init__(self):
...@@ -74,6 +75,10 @@ class CythonTest(unittest.TestCase): ...@@ -74,6 +75,10 @@ class CythonTest(unittest.TestCase):
self.assertEqual(len(result_lines), len(expected_lines), self.assertEqual(len(result_lines), len(expected_lines),
"Unmatched lines. Got:\n%s\nExpected:\n%s" % ("\n".join(result_lines), expected)) "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=[]): def fragment(self, code, pxds={}, pipeline=[]):
"Simply create a tree fragment using the name of the test-case in parse errors." "Simply create a tree fragment using the name of the test-case in parse errors."
name = self.id() name = self.id()
...@@ -136,3 +141,28 @@ class TransformTest(CythonTest): ...@@ -136,3 +141,28 @@ class TransformTest(CythonTest):
tree = T(tree) tree = T(tree)
return 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
...@@ -279,7 +279,9 @@ class CythonCompileTestCase(unittest.TestCase): ...@@ -279,7 +279,9 @@ class CythonCompileTestCase(unittest.TestCase):
annotate = annotate, annotate = annotate,
use_listing_file = False, use_listing_file = False,
cplus = self.language == 'cpp', cplus = self.language == 'cpp',
generate_pxi = False) generate_pxi = False,
evaluate_tree_assertions = True,
)
cython_compile(source, options=options, cython_compile(source, options=options,
full_module_name=module) full_module_name=module)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment