Search code examples
pythonpython-unittestsuppress-warningsdoctest

How to suppress ResourceWarning()s in doctest running under unittest


My Python doctests open some files that it never closes. This causes no problems; they're automatically closed when the object is destroyed and adding logic to ensure they're expicitly closed would unnecessarily complicate my documentation.

However, when the doctests run inside of unittest, they start issuing ResourceWarning()s, adding unhelpful noise to my output.

For example, given leak.py:

def hello(f):
    """ 
    >>> a = open("test-file","w")
    >>> hello(a)
    >>> open("test-file").read()
    'hello'
    """
    f.write("hello")
    f.flush()

def load_tests(loader, tests, ignore):
    import doctest
    tests.addTests(doctest.DocTestSuite())
    return tests

Running it under doctest and unittest with Python 3.6.9 generates:

$ python3 --version
Python 3.6.9
$ python3 -m doctest leak.py -v
[...] 
3 passed and 0 failed.
Test passed.
$ python3 -m unittest leak
/tmp/fileleak/leak.py:1: ResourceWarning: unclosed file <_io.TextIOWrapper name='test-file' mode='r' encoding='UTF-8'>
  def hello(f):
/usr/lib/python3.6/doctest.py:2175: ResourceWarning: unclosed file <_io.TextIOWrapper name='test-file' mode='w' encoding='UTF-8'>
  test.globs.clear()
.
----------------------------------------------------------------------
Ran 1 test in 0.003s

OK

There are a few ways to to clean this up inside the doctests, but they all add complexity that would be distracting in the documentation. This incudes explict calls to a.close(), using with open("test-file") as a: (which also pushes the tested output below the with block, or outright discarding warnings with warnings.simplefilter("ignore").

How can I get doctests running under unittest to suppress the ResourceWarning()s like doctest does?


Solution

  • doctest is not suppressing these warnings. unittest is enabling them. We probably want unittest to enable them for our more traditional unittests, so we don't want to suppress these globally.

    I'm already using load_tests to add the doctests to unittest, so we've got a good place to put it. We can't just call warnings.filterwarnings() directly in load_tests as the filters are reset before our test is run. We can use the setUp argument to doctest.DocTestSuite to provide a function to do the job for us.

    def load_tests(loader, tests, ignore):
        import doctest
        import warnings
        def setup(doc_test_obj):
            for module in (__name__, 'doctest'):
                warnings.filterwarnings("ignore",
                        message= r'unclosed file <_io.TextIOWrapper',
                        category=ResourceWarning,
                        module=module+"$")
        tests.addTests(doctest.DocTestSuite(setUp=setup))
        return tests
    

    We need the filters anywhere where an object we created might be destroyed and a ResourceWarning generated. This includes our own module (__name__), but it also includes doctest as some globals aren't destroyed until DocTestCase.tearDown.

    By carefully specifying what to filter by category, message, and modules, this should limit the risk of suppressing designed warnings, but it's not risk free.