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

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

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

Python: accessing exception message of original exception

I have the following two functions:
>>> def spam():
... raise ValueError('hello')
...
>>> def catch():
... try:
... spam()
... except ValueError:
... raise ValueError('test')
Trying to catch the second ValueError exception works just fine and prints the exception's error message:
>>> try:
... catch()
... except ValueError as e:
... print(e)
...
test
Is there however any way to access the original exception's error message (i.e. 'hello')? I know I can print the full traceback with:
>>> try:
... catch()
... except ValueError as e:
... import traceback
... print(traceback.format_exc())
...
Traceback (most recent call last):
File "<stdin>", line 3, in catch
File "<stdin>", line 2, in spam
ValueError: hello
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 5, in catch
ValueError: test
but I don't exactly want to parse the hello from that string. Is there a way to access the list of exceptions and their respective messages, from which I would simply take the first one?
Figured it out: the original exception is available via e.__cause__.

Show only first and last line from traceback

I've build a little internal DSL with python. I'm using assert vor validation. If the end user types a wrong parameter the dsl should report whats wrong. At the moment this looks like this:
Traceback (most recent call last):
File "tests/maskedtimefilter_test/FilterDSL_test.py", line 63, in test_dsl_validation
input(0): self.regular
File "/Users/sh/reetz/pythonpath/maskedtimedata/maskedtimefilter.py", line 885, in __lshift__
kwargs = self.dsl_validation(kwargs)
File "/Users/sh/reetz/pythonpath/maskedtimedata/maskedtimefilter.py", line 1483, in dsl_validation
check_if_valid(parameter)
File "/Users/sh/reetz/pythonpath/maskedtimedata/dsl.py", line 47, in kernel_a
def kernel_a (x): assert isinstance(x, (list, tuple, np.ndarray)), "kernel must be a list."
AssertionError: kernel must be a list.
But the end users are engineers and no computer scientists. Therefore a minimal Traceback is handy. Is it possible to shrink the Traceback to the essential information (where is the failure and what's the cause) like this?:
Traceback (most recent call last):
File "tests/maskedtimefilter_test/FilterDSL_test.py", line 63, in test_dsl_validation
input(0): self.regular
AssertionError: kernel must be a list.
Reluctantly I'd like to use normal prints!
Why not return the traceback data as an array and just work back from that?
import traceback
try:
codethatwillthrowanexception()
except:
exceptiondata = traceback.format_exc().splitlines()
exceptionarray = [exceptiondata[-1]] + exceptiondata[1:-1]
Somewhere in your call stack you can do this:
try:
my_function_that_can_raise_exception()
except Exception: #or AssertionError
import traceback
traceback.print_exc(limit=1)
limit is the depth of how many stack trace entries you want to show. (in your case, 1)
demo:
In [21]: def f():
...: assert False == True, 'Assertion!'
...:
In [22]: try:
...: f()
...: except Exception:
...: import traceback
...: traceback.print_exc(limit=1)
...:
Traceback (most recent call last):
File "<ipython-input-22-78f9db8d5188>", line 2, in <module>
f()
AssertionError: Assertion!
read more at traceback docs.
As the first part of the traceback is just what you called, you could so something simple, like:
try:
input(0): self.regular # base call
except AssertionError as ae: # catch the exception
print(ae) # print out the message
You can print out whatever you think would be useful in the except block.
Alternatively, you could do something more complex with the traceback module.

Can you check that an exception is thrown with doctest in 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

Categories

Resources