From 833e3b2c31755e0376cf51cca7291cf82cde3ace Mon Sep 17 00:00:00 2001 From: Dag Sverre Seljebotn Date: Thu, 1 Oct 2009 13:55:32 +0200 Subject: [PATCH] doctesthack directive --- Cython/Compiler/AnalysedTreeTransforms.py | 51 ++++++++++++++++------- Cython/Compiler/Visitor.py | 31 ++++++++++++++ tests/run/doctesthack.pyx | 37 ++++++++-------- tests/run/doctesthack_skip.pyx | 24 +++++++++++ 4 files changed, 112 insertions(+), 31 deletions(-) create mode 100644 tests/run/doctesthack_skip.pyx diff --git a/Cython/Compiler/AnalysedTreeTransforms.py b/Cython/Compiler/AnalysedTreeTransforms.py index dce53d5f..d91fb4ac 100644 --- a/Cython/Compiler/AnalysedTreeTransforms.py +++ b/Cython/Compiler/AnalysedTreeTransforms.py @@ -1,4 +1,4 @@ -from Cython.Compiler.Visitor import VisitorTransform, CythonTransform, TreeVisitor +from Cython.Compiler.Visitor import VisitorTransform, ScopeTrackingTransform, TreeVisitor from Nodes import StatListNode, SingleAssignmentNode from ExprNodes import (DictNode, DictItemNode, NameNode, UnicodeNode, NoneNode, ExprNode, AttributeNode) @@ -7,12 +7,20 @@ from Builtin import dict_type from StringEncoding import EncodedString import Naming -class DoctestHackTransform(CythonTransform): +class DoctestHackTransform(ScopeTrackingTransform): # Handles doctesthack directive def visit_ModuleNode(self, node): + self.scope_type = 'module' + self.scope_node = node if self.current_directives['doctesthack']: assert isinstance(node.body, StatListNode) + + # First see if __test__ is already created + if u'__test__' in node.scope.entries: + # Do nothing + return node + pos = node.pos self.tests = [] @@ -32,26 +40,41 @@ class DoctestHackTransform(CythonTransform): return node - def add_test(self, testpos, name, funcname): + def add_test(self, testpos, name, func_ref_node): + # func_ref_node must evaluate to the function object containing + # the docstring, BUT it should not be the function itself (which + # would lead to a new *definition* of the function) pos = self.testspos keystr = u'%s (line %d)' % (name, testpos[1]) key = UnicodeNode(pos, value=EncodedString(keystr)) - getfunc = AttributeNode(pos, obj=ModuleRefNode(pos), - attribute=funcname, - type=py_object_type, - is_py_attr=True, - is_temp=True) - - value = DocstringRefNode(pos, getfunc) + value = DocstringRefNode(pos, func_ref_node) self.tests.append(DictItemNode(pos, key=key, value=value)) - def visit_ClassDefNode(self, node): - return node - def visit_FuncDefNode(self, node): if node.doc: - self.add_test(node.pos, node.entry.name, node.entry.name) + pos = self.testspos + if self.scope_type == 'module': + parent = ModuleRefNode(pos) + name = node.entry.name + elif self.scope_type in ('pyclass', 'cclass'): + mod = ModuleRefNode(pos) + if self.scope_type == 'pyclass': + clsname = self.scope_node.name + else: + clsname = self.scope_node.class_name + parent = AttributeNode(pos, obj=mod, + attribute=clsname, + type=py_object_type, + is_py_attr=True, + is_temp=True) + name = "%s.%s" % (clsname, node.entry.name) + getfunc = AttributeNode(pos, obj=parent, + attribute=node.entry.name, + type=py_object_type, + is_py_attr=True, + is_temp=True) + self.add_test(node.pos, name, getfunc) return node diff --git a/Cython/Compiler/Visitor.py b/Cython/Compiler/Visitor.py index 573d2def..636d9d05 100644 --- a/Cython/Compiler/Visitor.py +++ b/Cython/Compiler/Visitor.py @@ -275,6 +275,37 @@ class CythonTransform(VisitorTransform): self.visitchildren(node) return node +class ScopeTrackingTransform(CythonTransform): + # Keeps track of type of scopes + scope_type = None # can be either of 'module', 'function', 'cclass', 'pyclass' + scope_node = None + + def visit_ModuleNode(self, node): + self.scope_type = 'module' + self.scope_node = node + self.visitchildren(node) + return node + + def visit_scope(self, node, scope_type): + prev = self.scope_type, self.scope_node + self.scope_type = scope_type + self.scope_node = node + self.visitchildren(node) + self.scope_type, self.scope_node = prev + return node + + def visit_CClassDefNode(self, node): + return self.visit_scope(node, 'cclass') + + def visit_PyClassDefNode(self, node): + return self.visit_scope(node, 'pyclass') + + def visit_FuncDefNode(self, node): + return self.visit_scope(node, 'function') + + def visit_CStructOrUnionDefNode(self, node): + return self.visit_scope(node, 'struct') + diff --git a/tests/run/doctesthack.pyx b/tests/run/doctesthack.pyx index fbbdc9c0..2a97b556 100644 --- a/tests/run/doctesthack.pyx +++ b/tests/run/doctesthack.pyx @@ -12,18 +12,20 @@ all_tests_run() is executed which does final validation. >>> items.sort() >>> for key, value in items: ... print key, ';', value -mycpdeffunc (line 40) ; >>> add_log("cpdef") -myfunc (line 34) ; >>> add_log("def") +MyCdefClass.method (line 67) ; >>> add_log("cdef class method") +MyClass.method (line 57) ; >>> add_log("class method") +doc_without_test (line 39) ; Some docs +mycpdeffunc (line 45) ; >>> add_log("cpdef") +myfunc (line 36) ; >>> add_log("def") """ log = [] -#__test__ = {'a':'445', 'b':'3'} def all_tests_run(): log.sort() - assert log == [u'cpdef', u'def'], log + assert log == [u'cdef class method', u'class method', u'cpdef', u'def'], log def add_log(s): log.append(unicode(s)) @@ -34,6 +36,9 @@ def add_log(s): def myfunc(): """>>> add_log("def")""" +def doc_without_test(): + """Some docs""" + def nodocstring(): pass @@ -50,17 +55,15 @@ class MyClass: """ def method(self): - """ - >>> True - False - """ - -## cdef class MyCdefClass: -## """ -## >>> add_log("cdef class") -## """ -## def method(self): -## """ -## >>> add_log("cdef class method") -## """ + """>>> add_log("class method")""" + +cdef class MyCdefClass: + """ + Needs no hack + + >>> True + True + """ + def method(self): + """>>> add_log("cdef class method")""" diff --git a/tests/run/doctesthack_skip.pyx b/tests/run/doctesthack_skip.pyx new file mode 100644 index 00000000..8770b666 --- /dev/null +++ b/tests/run/doctesthack_skip.pyx @@ -0,0 +1,24 @@ +#cython: doctesthack=True +""" +Tests that doctesthack doesn't come into effect when +a __test__ is defined manually. + +If this doesn't work, then the function doctest should fail. + +>>> True +True +""" + + +def func(): + """ + >>> True + False + """ + +__test__ = { + u"one" : """ +>>> True +True +""" +} -- 2.26.2