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?
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.