Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to determine whether code is running in a doctest?

Tags:

python

doctest

How can I make my (Python 2.7) code aware whether it is running in a doctest?

The scenario is as follows: I have a function that print()s some output to a file descriptor passed in as an argument, something like this:

from __future__ import print_function

def printing_func(inarg, file=sys.stdout):
    # (do some stuff...)
    print(result, file=file)

But when I try to use printing_func() in a doctest, the test fails; because of my specification of the keyword argument file when invoking print(), the output actually goes to sys.stdout rather than whatever default output redirection is set by the doctest module, and doctest never sees the output.

So how can I make printing_func() aware whether it is running inside a doctest, so that it knows not to pass the file keyword argument when calling print()?

like image 587
mshroyer Avatar asked Nov 14 '11 00:11

mshroyer


2 Answers

Niten's version of inside_doctest seems too broad. It isn't that unusual to redefine sys.stdout, either for logging or when testing in a framework other than doctest, so it would give false positives.

A narrower test looks like this:

import sys

def in_doctest():
    """
Determined by observation
    """
    if '_pytest.doctest' in sys.modules:
        return True
    ##
    if hasattr(sys.modules['__main__'], '_SpoofOut'):
        return True
    ##
    if sys.modules['__main__'].__dict__.get('__file__', '').endswith('/pytest'):
        return True
    ##
    return False


def test():
    """
    >>> print 'inside comments, running in doctest?', in_doctest()
    inside comments, running in doctest? True
    """
    print 'outside comments, running in doctest?', in_doctest()

if __name__ == '__main__':
    test()

in_doctest tests for the _SpoofOut class doctest uses to replace sys.stdout. There are other attributes of the doctest module that could be verified the same way. Not that you can prevent another module from reusing a name, but this name isn't common, so probably a decent test.

Put the above in test.py. Running it in non-doctest mode, python test.py yields:

outside comments, running in doctest? False

Running in doctest verbose mode, python -m doctest test.py -v yields:

Trying:
    print 'inside comments, running in doctest?', in_doctest()
Expecting:
    inside comments, running in doctest? True
ok

I agree with others' comments that making code aware of doctest is generally a bad idea. I've only done it in somewhat exotic circumstances -- when I needed to create test cases via a code generator because there were too many to efficiently craft manually. But if you need to do it, the above is a decent test for it.

like image 147
Chris Johnson Avatar answered Nov 18 '22 16:11

Chris Johnson


I figured out the answer after reading doctest.py; posting here for posterity...

Doctest redirects standard output by assigning a new file descriptor to sys.stdout. The problem was that my function description was closing over the value of the original sys.stdout file descriptor prior to doctest's redefinition.

Instead, if I do the following:

def printing_func(inarg, file=None):
    # (do some stuff...)

    if file is None:
        file = sys.stdout

    print(result, file=file)

then printing_func() will capture the sys module rather than sys.stdout, and when it runs it will retrieve doctest's redefined stdout attribute from sys if running inside a test.

EDIT: This also yields an easy way to check whether we are running inside a doctest:

def inside_doctest(original_stdout=sys.stdout):
    return original_stdout != sys.stdout
like image 1
mshroyer Avatar answered Nov 18 '22 17:11

mshroyer