Can you check that an exception is thrown with doctest in Python? - python

Is it possible to write a doctest unit test that will check that an exception is raised?
For example, if I have a function foo(x) that is supposed to raise an exception if x < 0, how would I write the doctest for that?

Yes. You can do it. The doctest module documentation and Wikipedia has an example of it.
>>> x
Traceback (most recent call last):
...
NameError: name 'x' is not defined

>>> scope # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
NameError: name 'scope' is not defined
Don't know why the previous answers don't have the IGNORE_EXCEPTION_DETAIL. I need this for it to work. Py versioin: 3.7.3.

>>> import math
>>> math.log(-2)
Traceback (most recent call last):
...
ValueError: math domain error
ellipsis flag # doctest: +ELLIPSIS is not required to use ... in Traceback doctest

Related

Running `python -m unittest` changes the way exceptions with overriden `__name__` are printed in the traceback

I have a piece of code that dynamically creates Exceptions. Every exception class created in this way gets its __name__ overwritten:
def exception_injector(name, parent, module_dict):
class product_exception(parent):
pass
product_exception.__name__ = name
product_exception.__module__ = module_dict["__name__"]
module_dict[name] = product_exception
When I use it regularly it prints everything out just fine:
>>> namespace = {"__name__": "some.module"}
>>> exception_injector("TestError", Exception, namespace)
>>> raise namespace["TestError"]("What's going on?")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
some.module.TestError: What's going on?
But when I use the unittest module the original __name__ is printed:
>>> import unittest
>>> class DemoTestCase(unittest.TestCase):
... def test_raise(self):
... namespace = {"__name__": "some.module"}
... exception_injector("TestError", Exception, namespace)
... namespace["TestError"]("What's going on?")
...
>>> unittest.main(defaultTest="DemoTestCase", argv=["demo"], exit=False)
E
======================================================================
ERROR: test_raise (__main__.DemoTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "<stdin>", line 3, in test_raise
some.module.exception_injector.<locals>.product_exception: What's going on?
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (errors=1)
<unittest.main.TestProgram object at 0x1017c3eb8>
From where on the exception object could the unittest module be getting this original information?
Unittests use the traceback module to format exceptions. You can reproduce your output by doing the same:
>>> import traceback
>>> try:
... raise namespace["TestError"]("What's going on?")
... except Exception as e:
... tb = traceback.TracebackException.from_exception(e)
... print(*tb.format())
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
some.module.exception_injector.<locals>.product_exception: What's going on?
What is being printed is the object __module__ attribute value*, with object.__qualname__ attribute appended (with a dot separator), rather than __module__ plus __name__:
>>> namespace["TestError"].__qualname__
'exception_injector.<locals>.product_exception'
The qualified name includes the full scope where the class was created (function names here, but this could also include class names).
If your aim is to add exceptions to the global namespaces of a module, you can just set it to the same value as name:
>>> namespace["TestError"].__qualname__ = "TestError"
>>> try:
... raise namespace["TestError"]("What's going on?")
... except Exception as e:
... tb = traceback.TracebackException.from_exception(e)
... print(*tb.format())
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
some.module.TestError: What's going on?
or in the context of your code:
def exception_injector(name, parent, module_dict):
class product_exception(parent, details=args):
pass
product_exception.__name__ = name
product_exception.__qualname__ = name
product_exception.__module__ = module_dict["__name__"]
module_dict[name] = product_exception
* The traceback module omits the exception module if the module name is either __main__ or builtins.

Why does the Python linecache affect the traceback module but not regular tracebacks?

Consider the following Python program:
code = """
def test():
1/0
"""
filename = "<test>"
c = compile(code, filename, 'exec')
exec(c)
import linecache
linecache.cache[filename] = (len(code), None, code.splitlines(keepends=True), filename)
import traceback
print("Traceback from the traceback module:")
print()
try:
test()
except:
traceback.print_exc()
print()
print("Regular traceback:")
print()
test()
I am dynamically defining a function that raises an exception and adding it to the linecache. The output of the code is
Traceback from the traceback module:
Traceback (most recent call last):
File "test.py", line 20, in <module>
test()
File "<test>", line 3, in test
1/0
ZeroDivisionError: division by zero
Regular traceback:
Traceback (most recent call last):
File "test.py", line 28, in <module>
test()
File "<test>", line 3, in test
ZeroDivisionError: division by zero
If I then get a traceback from that function using the traceback module, the line of code from the function is shown (the 1/0 part of the first traceback). But if I just let the code raise an exception and get the regular traceback from the interpreter, it doesn't show the code.
Why doesn't the regular interpreter traceback use the linecache? Is there a way to make the code appear in regular tracebacks?
The default sys.excepthook uses a separate, C-level implementation of traceback printing, not the traceback module. (Perhaps this is so it still works even if the system is too borked to use traceback.py.) The C implementation doesn't try to use linecache. You can see the code it uses to retrieve source lines in _Py_DisplaySourceLine.
If you want tracebacks to use the traceback module's implementation, you could replace sys.excepthook with traceback.print_exception:
import sys
import traceback
sys.excepthook = traceback.print_exception

Why isn't IPython giving me a full traceback for a module I've written?

I'm confused about why, when an error is raised in a function within a module I've written, IPython doesn't show me a full traceback with the line in the function that caused the error.
Note: I'm not confused about the cause of this particular error, but about why IPython isn't showing me the cause.
My module is called module.py and it contains the function function, underneath which is written an if __name__ == '__main__' block. (Module and function names have been changed to protect the identities of the innocent -- or maybe not so innocent.)
Here's the traceback I get when an error is raised. (Note the lack of information about which line in function caused the error.)
In [1]: import module as m
In [2]: call = m.function('hello')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-2-ec0c1e40ec8c> in <module>()
----> 1 call = m.function('hello')
/home/module.py in function(greeting)
TypeError: join() takes exactly one argument (2 given)
Did you try with %xmode ?
In [2]: %xmode?
Type: Magic function
Definition: %xmode(self, parameter_s='')
Docstring:
Switch modes for the exception handlers.
Valid modes: Plain, Context and Verbose.
If called without arguments, acts as a toggle.
if you look carefully the 2 following example are different, but difference is more visible with long tracebacks :
In [8]: raise ValueError('Foo')
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-8-05e81bf5c607> in <module>()
----> 1 raise ValueError('Foo')
global ValueError = undefined
ValueError: Foo
Plain mode
In [9]: xmode
Exception reporting mode: Plain
In [10]: raise ValueError('Foo')
Traceback (most recent call last):
File "<ipython-input-10-05e81bf5c607>", line 1, in <module>
raise ValueError('Foo')
ValueError: Foo

How to test exceptions with doctest in Python 2.x and 3.x?

I defined an exception class SpamException in a module spam. Now I want to test a function spam_function, that raises this exception. So I wrote the following doctest.
>>> spam_function()
Traceback (most recent call last):
....
SpamException
The test succeeds on Python 2.x, but on Python 3.x the test fails. The following test works on Python 3.x.
>>> spam_function()
Traceback (most recent call last):
....
spam.SpamException
The notable difference here is the inclusion of the module name in the exception name. So how can I write a doctest that works on both Python 2.x and 3.x?
I would turn on the doctest.IGNORE_EXCEPTION_DETAIL directive, like this:
>>> spam_function() # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last)
...
SpamException: 'lovely spam'
But note that IGNORE_EXCEPTION_DETAIL doesn't work for plain exception objects (with no associated arguments). In particular, the following example isn't portable to Python 3, because there's nothing following the exception name:
>>> spam_function() # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last)
...
SpamException

How can I handle variable errors in python's doctest?

I have a doctest which expects an IOError when a file is not found.
>>> configParser('conffig.ini') # should not exist
Traceback (most recent call last):
...
IOError: No such file: /homes/ndeklein/workspace/MS/PyMS/conffig.ini
However, if I want to test this from a different pc, or someone else wants to test it, the path isn't going to be /homes/ndeklein/workspace/MS/PyMS/. I would want to do
>>> configParser('conffig.ini') # should not exist
Traceback (most recent call last):
...
IOError: No such file: os.path.abspath(conffig.ini)
but because it is in the docstring it sees os.path.abspath( as part of the result.
How can I make the result of the docstring test variable?
Do you actually need to match against the pathname? If not then just use ellipsis to skip that part of the output:
>>> configParser('conffig.ini') # should not exist
Traceback (most recent call last):
...
IOError: No such file: ...
If you do then you'll need to catch the error and test the value by hand. Something like:
>>> try:
... configParser('conffig.ini') # should not exist
... except IOError as e:
... print('ok' if str(e).endswith(os.path.abspath('conffig.ini')) else 'fail')
ok

Categories

Resources