In doctests, I often need a temporary directory for the duration of the test. In the test setup/teardown code, I create it and clean it up afterwards. So the test setup/teardown methods looks something like:
import tempfile
import shutil
def setup(test):
test.tempdir = tempfile.mkdtemp()
# other stuff
test.globs.update({'tempdir': test.tempdir})
def teardown(test):
shutil.rmtree(test.tempdir)
In the doctests, I can now use the tempdir
variable that got injected into
the test globs (=globals):
The test setup created a temp directory for us:
>>> print tempdir
/tmp/kdf34klsdj3200880f/
This directory is placed inside the default
temporary directory on our os, which is:
>>> import tempfile
>>> tempfile.gettempdir()
'/tmp'
Well, not quite. The name of the tempdir is different all the time. And
/tmp
can be /var/tmp
. And on osx it is something like
/var/folders/qC/qC6d69l0EDe-sx-yyKchqU+++TI/-Tmp-
and sometimes
/private/var/folders/qC/qC6d69l0EDe-sx-yyKchqU+++TI/-Tmp-
.
So you can opt to use ...
, the doctest standard placeholder for
“something”. But that can hide something:
The ... matches a lot:
>>> delete_files_but_not_too_much()
deleted ...
deleted ...
So we think we just deleted two files, but the ... also matches
complete sets of lines, so the following test could also pass:
>>> delete_files_but_not_too_much()
deleted ...
deleted ...
deleted ...
deleted ...
Oops.
You can pass a doctest output normalizer to your testrunner. For instance:
from zope.testing import renormalizing
checker = renormalizing.RENormalizing([
# tempfile.gettempdir() is the OS's base tempdir
(re.compile(re.escape(tempfile.gettempdir())),
'TMPDIR')])
# Don't forget to pass the checker to the testrunner.
This grabs the default base temporary directory where tempfile creates its
items. So tempfile.mkdtemp()
creates a temporary directory inside the
tempfile.gettempdir()
folder. Your doctests can be more explicit this
way:
TMPDIR is the base temporary directory
>>> delete_files_but_not_too_much() deleted TMPDIR/... deleted TMPDIR/...
You can do one better by also normalizing the actual created temporary
directory. The trick here is to call mkdtemp()
with a prefix option:
...
def setup(test):
test.tempdir = tempfile.mkdtemp(prefix='mytest')
...
This creates directories like /tmp/mytest888d987f3uewer/
. We cannot
directly use the created tempdir’s name in a normalizer regex as the tempdir
is normally created in the setup method and the normalizer method is outside
it. The prefix helps us however in doing it anyway:
checker = renormalizing.RENormalizing([
# Normalize tempdirs. For this to work reliably, we need
# to use a prefix in all tempfile.mkdtemp() calls. We
# look for the base tempdir, followed by the prefix,
# followed by several non-slash characters.
(re.compile(
'%s/mytest[^/]+' % re.escape(tempfile.gettempdir())),
'MYTEST'),
# We probably also still need TMPDIR. Place it after
# the more specific checks otherwise it matches first...
(re.compile(re.escape(tempfile.gettempdir())),
'TMPDIR')])
Tadah, the doctest can now be really specific with no chance of erroneous hiding of lines:
MYTEST is the tempdir the setup prepared for us
>>> delete_files_but_not_too_much()
deleted MYTEST/file1.txt
deleted MYTEST/file4.txt
In rare cases, osx “prepends” /private
to the normal /var...
base
tempdir. Mostly when you call the python executable from within your
doctest. So I had to add an extra normalizer to also detect that variant…
My name is Reinout van Rees and I program in Python, I live in the Netherlands, I cycle recumbent bikes and I have a model railway.
Most of my website content is in my weblog. You can keep up to date by subscribing to the automatic feeds (for instance with Google reader):