Thursday, December 17, 2020

pytest apparently modifies calls to range

 As I work making Friendly-traceback provide more useful information regarding the cause of an exception, I sometimes encounter weird "corner cases" about either Python itself [1] or occasionally about pytest [2]. Today, it was pytest's turn to give me a new puzzle to solve.

Consider the following:

range(1.0)

If you run this, Python will give you a TypeError: 'float' object cannot be interpreted as an integer

Using Friendly-traceback's console [3], I get something slightly more informative.


Actually, I can get even more information using "explain()":


Time to add this new working case to the unit test suite. I create a barebone one for the purpose of this blog, without capturing the output and comparing with what is expected.

import friendly_traceback

def test():
    try:
        range(1.0)
    except:
        friendly_traceback.explain_traceback()

if __name__ == '__main__':
    test()

Here's what happens if I run this using Python:


(1) is a "hint" that gets added to the traceback shown by Friendly-traceback. (2) is part of the more detailed explanation, available by typing "why()" in a Friendly console. So far, everything looks as expected.

In order to provide this kind of explanation, Friendly-traceback looks at the content of the frame where the exception was raised and does its best to deduce what needs to be changed. For example, if we modify the above example to use a variable called "end" instead of using a literal, here's what we get.

When writing unit tests, I make sure that such highlighted hints are reproduced exactly in the output that is captured. So, what happens if we run the previous example using pytest? ...


A new float variable, "start", has suddenly appeared, seemingly out of nowhere. Note that this pytest oddity is not revealed if we use a string instead of a float as the wrong type of argument.


Time to move on to handling other cases ...

[1] See this blogpost.

[2] An issue that I filed about a previous case seems to have disappeared and all that remains is this question on Stack Overflow.

[3] This is my local development version; the example shown here will be handled by versions 0.2.8 and later, to be released on pypi.

No comments: