Skip to content

Instantly share code, notes, and snippets.

@zed
Created March 26, 2011 02:11
Show Gist options
  • Save zed/887961 to your computer and use it in GitHub Desktop.
Save zed/887961 to your computer and use it in GitHub Desktop.
doctest support for cython
#!/usr/bin/env python
"""
Cython-compatible wrapper for doctest.testmod().
Usage example, assuming a Cython module mymod.pyx is compiled.
This is run from the command line, passing a command to Python:
python -c "import cydoctest, mymod; cydoctest.testmod(mymod)"
(This still won't let a Cython module run its own doctests
when called with "python mymod.py", but it's pretty close.
Further options can be passed to testmod() as desired, e.g.
verbose=True.)
http://wiki.cython.org/FAQ#HowcanIrundoctestsinCythoncode.28pyxfiles.29.3F
Changed to support (with pyximport):
$ python -mcydoctest mymod.pyx
"""
try:
import pyximport; pyximport.install()
except ImportError:
pass # ignore
import doctest
import inspect
import os
import sys
def _from_module(module, object):
"""
Return true if the given object is defined in the given module.
"""
if module is None:
return True
elif inspect.getmodule(object) is not None:
return module is inspect.getmodule(object)
elif inspect.isfunction(object):
return module.__dict__ is object.func_globals
elif inspect.isclass(object):
return module.__name__ == object.__module__
elif hasattr(object, '__module__'):
return module.__name__ == object.__module__
elif isinstance(object, property):
return True # [XX] no way not be sure.
else:
raise ValueError("object must be a class or function")
def fix_module_doctest(module):
"""
Extract docstrings from cython functions, that would be skipped by doctest
otherwise.
"""
module.__test__ = {}
for name in dir(module):
value = getattr(module, name)
if inspect.isbuiltin(value) and isinstance(value.__doc__, str) and _from_module(module, value):
module.__test__[name] = value.__doc__
def testmod(m=None, *args, **kwargs):
"""
Fix a Cython module's doctests, then call doctest.testmod()
All other arguments are passed directly to doctest.testmod().
"""
fix_module_doctest(m)
return doctest.testmod(m, *args, **kwargs)
def _test():
"""adapted from stdlib's doctest.py"""
testfiles = [arg for arg in sys.argv[1:] if arg and arg[0] != '-']
if not testfiles:
name = os.path.basename(sys.argv[0])
if '__loader__' in globals(): # python -m
name, _ = os.path.splitext(name)
print("usage: {0} [-v] file ...".format(name))
return 2
for filename in testfiles:
if filename.endswith((".py", ".pyx")):
# It is a module -- insert its dir into sys.path and try to
# import it. If it is part of a package, that possibly
# won't work because of package imports.
dirname, filename = os.path.split(filename)
sys.path.insert(0, dirname)
_, ext = os.path.splitext(filename)
m = __import__(filename[:-len(ext)])
del sys.path[0]
failures, _ = testmod(m)
else:
failures, _ = doctest.testfile(filename, module_relative=False)
if failures:
return 1
return 0
if __name__ == "__main__":
sys.exit(_test())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment