0

Maybe I am going about this the wrong way, because my search turned up nothing useful.

Adding the -b (-bb) option when calling the python interpreter will warn (raise) whenever an implicit bytes to string or bytes to int conversion takes place:

Issue a warning when comparing bytes or bytearray with str or bytes with int. Issue an error when the option is given twice (-bb).

I would like to write a unit test around this using pytest. I.e., I'd like to do

# foo.py

import pytest

def test_foo():
    with pytest.raises(BytesWarning):
        print(b"This is a bytes string.")

When calling the above as pytest foo.py the test will fail (no BytesWarning raised). When I call the above test as python -bb -m pytest foo.py it will pass, because BytesWarning is raised as an exception. So far so good.

What I can't work out (nor do I seem to be able to find anything useful on the internet), is if/how it is possible to configure pytest to do this automatically so that I can run pytest --some_arg foo.py and it will do the intended thing. Is this possible?

FirefoxMetzger
  • 2,880
  • 1
  • 18
  • 32
  • Actually, what would be even more desirable is to have it happen only for specific tests. This might trigger warnings/exceptions inside upstream (3d party) code, too, and I am not worried about testing those here. – FirefoxMetzger Jan 20 '22 at 08:11

1 Answers1

2

When you execute pytest foo.py, your shell will look for the pytest program. You can know which one will be executed with the command which pytest. For me, it's /home/stack_overflow/venv/bin/pytest which looks like that :

#!/home/stack_overflow/venv/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from pytest import console_main
if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
    sys.exit(console_main())

It just calls console_main() from the pytest library.

If you add a print(sys.argv), you can see how it was called. For me, it is ['/home/stack_overflow/venv/bin/pytest', 'so70782647.py']. It matches the path from the first line, which is called a shebang. It instructs how your program should be invoked. Here, it indicates to run the file using the python from my venv.
If I modify the line :

#!/home/stack_overflow/venv/bin/python -bb
#                                     ^^^^

now your test passes.
It may be a solution, although not very elegant.

You may notice that, even now, the -bb do not appear when printing sys.argv. The reason is explained in the doc you linked yourself :

An interface option terminates the list of options consumed by the interpreter, all consecutive arguments will end up in sys.argv [...]

So it is not possible to check if it was activated using sys.argv.
I found a question about how to retrieve them from the interpreter's internal state, in case you are interested to check it as pre-condition for your test : Retrieve the command line arguments of the Python interpreter. Although, checking for sys.flags.bytes_warning is simpler in our case ({0: None, 1: '-b', 2: '-bb'}).

Continuing on your question, how to run pytest with the -bb interpreter option ?
You already have a solution : python -bb -m pytest foo.py.

If you prefer, it is possible to create a file pytest whose content is just python -bb -m pytest $@ (don't forget to make it executable). Run it with ./pytest foo.py. Or make it an alias.

You can't tell pytest which Python interpreter options you want, because pytest would already be running in the interpreter itself, which would have already handled its own options.

As far as I know, these options are really not easy to change. I guess if you could write into the PyConfig C struct, it would have the desired effect. For example see function bytes_richcompare which does :

        if (_Py_GetConfig()->bytes_warning && (op == Py_EQ || op == Py_NE)) {
            if (PyUnicode_Check(a) || PyUnicode_Check(b)) {
                if (PyErr_WarnEx(PyExc_BytesWarning,
                                 "Comparison between bytes and string", 1))
                    return NULL;
            }

then you could activate it from within your test, as in :

def test_foo():
    if sys.flags.bytes_warning < 2:
        # change PyConfig.bytes_warning
    assert sys.flags.bytes_warning == 2
    with pytest.raises(BytesWarning):
        print(b"This is a bytes string.")
    # change back the PyConfig.bytes_warning

But I think how to do that should be another question.

As a workaround, you can use pytest.warns like so :

def test_foo():
    with pytest.warns(BytesWarning):
        print(b"This is a bytes string.")

and it only requires the -b option (although -bb works too).

Lenormju
  • 4,078
  • 2
  • 8
  • 22