[Tutor] problems with doctest: apparent interferance between tests
(LONG)
Brian van den Broek
bvande at po-box.mcgill.ca
Sun Apr 10 03:45:48 CEST 2005
Hi all,
I must apologize for the length of the post. I have explained my
problem as tersely as able, and have done my level-best to produce the
minimum code snippet illustrative of my difficulties. But, as (I hope)
will become clear, any substantial attempts to further cull it made
the problem go away. (For what it is worth, this is the product of
several hours of investigating and attempting to cull.)
The quick story:
I have apparent interference between doctests embedded in the
docstrings of different methods, and this interference also appears to
be influenced by seemingly irrelevant things such as whether the
module has a (non-doctest-containing) docstring or not. I'm flummoxed
as to why there is this apparent interference. The long code snippet
below is the minimal snippet I was able to make which still exhibited
the problems; it is commented with indications of the dependencies of
the tests.
Background:
(Perhaps the the reader could skip this `Background' and jump down to
`The Problem', returning here if need be.) I have cut enough of the
original code that the point of what remains may be obscure. (The
original code includes complete documentation, more methods, etc.) A
bit of explanation is perhaps needed to give the context of my problem.
I've been writing a module to allow convenient wall clock timings and
generate a report of same on demand. The idea is to import it, make an
instance of the Wall_clock class, and then call Wall_clock methods at
places of interest in the code, so as to add _check_point's and start
and stop _intervals. (Never mind if this isn't too useful or
duplicates functionality available elsewhere--it is largely a learning
exercise to explore various Python features, many of which are not
reflected in the snippet below.)
All the timings are relative to the creation of the Wall_clock
instance. To prevent confusion in the reports, my original design idea
was to make the Wall_clock class `Singleton-like' in that there could
only be one instance alive at a time, and any attempt to create
another concurrent instance would raise an exception. (I've since
decided to opt for using the Borg pattern
<http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66531>, with
a class attribute storing the time of first instantiation, but my
issues depend on the first approach I explored.)
I made my `Singleton-like' class by having a class attribute
is_instanced, set to True by the class __init__ method, and False by
its __del__. The __init__ method raises an error if the attribute is
True, thus preventing concurrent instances. (See the final code
snippet for a working model with tests that pass as expected.)
So far, so good.
The Problem:
doctest is one of the aspects of Python that I've been trying to learn
with this project. I have 2 problems using doctest on my module:
1) There is a test neat the start of Wall_clock.check_point's
docstring that attempts to instantiate the Wall_clock class. It fails,
in the manner an attempt to create a *second* concurrent instance of
Wall_clock should fail. But, since the relevant test is not preceded
in the docstring by any tests which create a Wall_clock instance, I
don't understand how this could be happening. I had theorized that
perhaps there was interference in that previously run tests might
leave Wall_clock.is_instanced == True. The first test in
Wall_clock.check_point appears to bare this out. Yet this doesn't make
sense either. And it won't explain:
2) In my attempts to produce the minimal working sample that
illustrated my problem, I found that removing any of:
a) the Wall_clock class docstring (which contains no tests),
b) the entire Wall_clock.stop_interval method (this method
is not called anywhere in the code in the snippet, nor
does it contain any tests), or
c) a portion of the Wall_clock.interval method's doctests
(this portion does not do anything to affect affect the
Wall_clock.is_instanced class attribute)
all caused the failing test to pass. (These are all identified by
comments in the code.) I cannot understand why these things, not in
Wall_clock.check_point's doctest, should affect whether its tests pass
or fail. I am utterly perplexed. I *am* sure there is a bug, but also
think it much more likely to be mine than Tim Peters' :-)
In addition to the culled-code where I originally observed this
problem, I have produced a similar bit of skeleton code which works as
expected. I don't understand why they operate differently. First, my
culled code, then the skeleton that works.
The Culled Code:
When I run this, the Wall_clock.interval method is tested first, then
the Wall_clock.check_point method. (Assessed by the commented out
tests that were intentional failures.) It is the
Wall_clock.check_point docstring that is the locus of the problem.
(The heavy edit on the code has robbed the tests and much of the code
of their sense and relevance; please ignore that.)
<culled code>
import time
class Wall_clockInstanceError(Exception):
def __init__(self):
Exception.__init__(self)
class Interval_name_conflictError(Exception):
def __init__(self):
Exception.__init__(self)
class Wall_clock(object):
'''A class for making wall clock timings.'''
# If I remove the docstring above, the test passes. WHY?
is_instanced = False
def __init__(self):
if self.__class__.is_instanced:
raise Wall_clockInstanceError
else:
self.__class__.is_instanced = True
self.intervals = []
self.data = []
self.check_point_names = set()
self.interval_names = set()
self.start_time = time.time()
_Check_point.offset = _Interval.offset = self.start_time
_Check_point.count = _Interval.count = 0
def __del__(self):
self.__class__.is_instanced = False
def check_point(self, check_point_name = None):
'''Creates a new _Check_point instance; appends it to .data.
>>> # print "Testing check_point (intentional fail)."
>>> print Wall_clock.is_instanced
True
>>> # Why should it be True at this point?
>>> # Note, too, that any of the commented changes
>>> # make the most recent test fail!
>>> wclock = Wall_clock() # Failing test WHY?
>>> del(wclock)
>>> new_wclock = Wall_clock() # This passes
'''
# The test commented WHY? in this docstring fails unless
# I make any of the changes elsewhere commented. I don't
# understand why it fails in the present code, nor why the
# elsewhere commented changes make it pass.
# Since the marked test (and the subsequent del() test
# fail, I don't understand why the final test passes.
pass
def interval(self, interval_name = None):
'''
>>> # print "Testing interval (intentional fail)."
>>> wc = Wall_clock()
>>> del(wc)
>>> wclock = Wall_clock()
>>> # If I omit the rest of the tests here, no failure
>>> # of the test of interest in Wall_clock.check_point
>>> an_interval = wclock.interval('F')
>>> same_name = wclock.interval('F')
Traceback (most recent call last):
...
Interval_name_conflictError
'''
# The last few lines are key! (See comments in tests.) WHY?
if interval_name:
if type(interval_name) == int:
interval_name = str(interval_name)
if not interval_name.startswith(_Interval.name_form %''):
interval_name = _Interval.name_form %interval_name
if interval_name in self.interval_names:
raise Interval_name_conflictError
new_interval = _Interval(interval_name)
self.intervals.append(new_interval)
self.data.append(new_interval)
self.interval_names.add(str(new_interval.name))
return new_interval
# None of the code in the snippet references the method below.
# But, if I remove the method, the test of interest in
# Wall_clock.check_point passes. WTF!?!?!
def stop_interval(self, interval=None):
try:
if None == interval:
interval = self.intervals.pop()
interval.stop()
else:
self.intervals.remove(interval)
interval.stop()
except IndexError, ValueError:
raise "A custom error class my orig. code defined"
class _Check_point(object):
count = None # Wall_clock.__init__ will set these 2.
offset = None # Assignments here to make the existence of
# the attributes obvious.
name_form = 'Check point %s'
def __init__(self, name = None):
_Check_point.count += 1
if not name:
name = _Check_point.name_form % _Check_point.count
self.name = name
self.data = time.time() - _Check_point.offset
class _Interval(object):
count = None # Wall_clock.__init__ will set these 2.
offset = None # Assignments here to make the existence of
# the attributes obvious.
name_form = 'Interval %s'
def __init__(self, name = None):
_Interval.count += 1
self.running = True
if not name:
name = _Interval.name_form % _Interval.count
self.name = name
def _test():
import doctest, sys
doctest.testmod(sys.modules[__name__], report = True,
optionflags = doctest.ELLIPSIS)
if __name__ == '__main__':
_test()
print "\nIf you got this far in my post, my sincerest thanks!"
</culled code>
The Skeleton Code:
All tests pass in this, and it is, as far as I can tell, employing the
same broad structure as my problematic code above, save that there are
no `interfering' elements akin the the class docstring, etc.
<skeleton code>
'''
>>> an_instance = Singleton_like()
>>> another_instance = Singleton_like()
Traceback (most recent call last):
...
NameError: global name 'Concurrent_instancesError' is not defined
>>> del(an_instance)
>>> another_instance = Singleton_like()
'''
class Singleton_like(object):
is_instanced = False
def __init__(self):
if self.__class__.is_instanced:
raise Concurrent_instancesError
self.__class__.is_instanced = True
def __del__(self):
self.__class__.is_instanced = False
def a_method(self):
'''>>> an_instance = Singleton_like()'''
pass
def another_method(self):
'''>>> another_instance = Singleton_like()'''
pass
if __name__ == '__main__':
import doctest, sys
doctest.testmod(sys.modules[__name__])
</skeleton code>
I, and what hair I've not yet torn out, would be most grateful for any
suggestions.
Best to
More information about the Tutor
mailing list