Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UnicodeDecodeError pytest-html with pytest-pylint on Windows #342

Closed
R-Broadley opened this issue Oct 9, 2020 · 7 comments
Closed

UnicodeDecodeError pytest-html with pytest-pylint on Windows #342

R-Broadley opened this issue Oct 9, 2020 · 7 comments
Assignees
Labels
bug This issue/PR relates to a bug. windows This issue/PR is specifically for the windows ecosystem wontfix This will not be worked on

Comments

@R-Broadley
Copy link

R-Broadley commented Oct 9, 2020

I am using the pytest-html (2.1.1) and pytest-pylint(0.17.0) plugins (pytest version 6.1.1) on a windows host (python 3.7.7).

The line that is causing the issue is this one plugin.py#L156.

class TestResult:
    def __init__(self, outcome, report, logfile, config):
        self.test_id = report.nodeid.encode("utf-8").decode("unicode_escape")  # <- This line
        if getattr(report, "when", "call") != "call":
            self.test_id = "::".join([report.nodeid, report.when])

I put a break point in to see what report.nodeid contained: this is a basic example:

'package\\__init__.py::C:\\Users\\<my_username>\\repos\\package\\package\\__init__.py::PYLINT'

It is the \U which causes the issue.

Should there be some mechanism to standardise all paths to use / or at least handle \\ properly?

Here is the full traceback:

INTERNALERROR> Traceback (most recent call last):
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\_pytest\main.py", line 257, in wrap_session
INTERNALERROR>     session.exitstatus = doit(config, session) or 0
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\_pytest\main.py", line 313, in _main
INTERNALERROR>     config.hook.pytest_runtestloop(session=session)
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\pluggy\hooks.py", line 286, in __call__
INTERNALERROR>     return self._hookexec(self, self.get_hookimpls(), kwargs)
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\pluggy\manager.py", line 93, in _hookexec
INTERNALERROR>     return self._inner_hookexec(hook, methods, kwargs)
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\pluggy\manager.py", line 87, in <lambda>
INTERNALERROR>     firstresult=hook.spec.opts.get("firstresult") if hook.spec else False,
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\pluggy\callers.py", line 208, in _multicall
INTERNALERROR>     return outcome.get_result()
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\pluggy\callers.py", line 80, in get_result
INTERNALERROR>     raise ex[1].with_traceback(ex[2])
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\pluggy\callers.py", line 187, in _multicall
INTERNALERROR>     res = hook_impl.function(*args)
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\_pytest\main.py", line 338, in pytest_runtestloop
INTERNALERROR>     item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\pluggy\hooks.py", line 286, in __call__
INTERNALERROR>     return self._hookexec(self, self.get_hookimpls(), kwargs)
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\pluggy\manager.py", line 93, in _hookexec
INTERNALERROR>     return self._inner_hookexec(hook, methods, kwargs)
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\pluggy\manager.py", line 87, in <lambda>
INTERNALERROR>     firstresult=hook.spec.opts.get("firstresult") if hook.spec else False,
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\pluggy\callers.py", line 208, in _multicall
INTERNALERROR>     return outcome.get_result()
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\pluggy\callers.py", line 80, in get_result
INTERNALERROR>     raise ex[1].with_traceback(ex[2])
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\pluggy\callers.py", line 187, in _multicall
INTERNALERROR>     res = hook_impl.function(*args)
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\_pytest\runner.py", line 110, in pytest_runtest_protocol
INTERNALERROR>     runtestprotocol(item, nextitem=nextitem)
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\_pytest\runner.py", line 121, in runtestprotocol
INTERNALERROR>     rep = call_and_report(item, "setup", log)
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\_pytest\runner.py", line 220, in call_and_report
INTERNALERROR>     hook.pytest_runtest_logreport(report=report)
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\pluggy\hooks.py", line 286, in __call__
INTERNALERROR>     return self._hookexec(self, self.get_hookimpls(), kwargs)
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\pluggy\manager.py", line 93, in _hookexec
INTERNALERROR>     return self._inner_hookexec(hook, methods, kwargs)
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\pluggy\manager.py", line 87, in <lambda>
INTERNALERROR>     firstresult=hook.spec.opts.get("firstresult") if hook.spec else False,
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\pluggy\callers.py", line 208, in _multicall
INTERNALERROR>     return outcome.get_result()
INTERNALERROR>     raise ex[1].with_traceback(ex[2])
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\pluggy\callers.py", line 187, in _multicall
INTERNALERROR>     res = hook_impl.function(*args)
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\pytest_html\plugin.py", line 602, in pytest_runtest_logreport
INTERNALERROR>     self.append_skipped(report)
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\pytest_html\plugin.py", line 380, in append_skipped
INTERNALERROR>     self._appendrow("Skipped", report)
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\pytest_html\plugin.py", line 340, in _appendrow
INTERNALERROR>     result = self.TestResult(outcome, report, self.logfile, self.config)
INTERNALERROR>   File "C:\Users\***\.venv\lib\site-packages\pytest_html\plugin.py", line 140, in __init__
INTERNALERROR>     self.test_id = report.nodeid.encode("utf-8").decode("unicode_escape")
INTERNALERROR> UnicodeDecodeError: 'unicodeescape' codec can't decode bytes in position 24-25: truncated \UXXXXXXXX escape
@gnikonorov gnikonorov added bug This issue/PR relates to a bug. windows This issue/PR is specifically for the windows ecosystem labels Oct 22, 2020
@gnikonorov gnikonorov self-assigned this Oct 30, 2020
@gnikonorov
Copy link
Member

I'm going to try and take a look at this now

@gnikonorov
Copy link
Member

Updating this issue with a minimal reproducer:

glebi@DESKTOP-2TKJKMV MINGW64 ~/Documents/programming_environment
$ cat my_tests/test_me.py
def test_me():
    assert 1 == 1(.venv)
glebi@DESKTOP-2TKJKMV MINGW64 ~/Documents/programming_environment
$

When I do not invoke pytest with the --pylint flag, all is well:

glebi@DESKTOP-2TKJKMV MINGW64 ~/Documents/programming_environment
$ python3 -m pytest --html=test_report.html my_tests/
============================================================================================================================ test session starts =============================================================================================================================
platform win32 -- Python 3.8.6, pytest-6.0.1.dev384+ga95da7a42, py-1.9.0, pluggy-0.13.1
rootdir: C:\Users\glebi\Documents\programming_environment
plugins: metadata-1.10.0, html-2.1.2.dev44+g8fd44c8, pylint-0.17.0
collected 1 item

my_tests\test_me.py .                                                                                                                                                                                                                                                   [100%]

--------------------------------------------------------------------------------------- generated html file: file://C:\Users\glebi\Documents\programming_environment\test_report.html ----------------------------------------------------------------------------------------
============================================================================================================================= 1 passed in 0.02s ==============================================================================================================================
(.venv)
glebi@DESKTOP-2TKJKMV MINGW64 ~/Documents/programming_environment
$

However when I add --pylint, I can reproduce the error in the issue description:

glebi@DESKTOP-2TKJKMV MINGW64 ~/Documents/programming_environment
$ python3 -m pytest --html=test_report.html --pylint my_tests/
....
UnicodeDecodeError: 'unicodeescape' codec can't decode bytes in position 23-24: truncated \UXXXXXXXX escape
(.venv)
glebi@DESKTOP-2TKJKMV MINGW64 ~/Documents/programming_environment
$

@gnikonorov
Copy link
Member

gnikonorov commented Nov 2, 2020

Upon further inspection it looks like this is an issue with the interaction between pytest-pylint and pytest-html. pytest_runtest_logreport receives the nodeid as an unescaped string:

glebi@DESKTOP-2TKJKMV MINGW64 ~/Documents/programming_environment
$ python3 -m pytest --html=test_report.html --pylint my_tests/test_me.py
============================================================================================================================ test session starts =============================================================================================================================
platform win32 -- Python 3.8.6, pytest-6.0.1.dev384+ga95da7a42, py-1.9.0, pluggy-0.13.1
rootdir: C:\Users\glebi\Documents\programming_environment
plugins: metadata-1.10.0, html-2.1.2.dev44+g8fd44c8.d20201102, pylint-0.17.0
collected 2 items
--------------------------------------------------------------------------------
Linting files
.
--------------------------------------------------------------------------------

my_tests\test_me.py
INTERNALERROR> Traceback (most recent call last):
....
INTERNALERROR>   File "c:\users\glebi\documents\programming_environment\pytest-html\pytest_html\plugin.py", line 694, in pytest_runtest_logreport
INTERNALERROR>     raise ValueError(report.nodeid)
INTERNALERROR> ValueError: my_tests/test_me.py::C:\Users\glebi\Documents\programming_environment\my_tests\test_me.py::PYLINT

=========================================================================================================================== no tests ran in 0.20s ============================================================================================================================
(.venv)
glebi@DESKTOP-2TKJKMV MINGW64 ~/Documents/programming_environment

Later on, this string is converted as indicated in the issue description, and the value we receive fails conversion since \U needs to be escaped on Windows. We can solve the issue described then by escaping the path, and this works. The difficulty arises when we handle characters that rely on that sequence for encoding. Consider the following:

import pytest
@pytest.mark.parametrize("utf8", [("测试用例名称")])
def test_pass(utf8):
    assert True

In this case, pytest_runtest_logreport receives the report name as:

glebi@DESKTOP-2TKJKMV MINGW64 ~/Documents/programming_environment
$ python3 -m pytest --html=test_report.html my_tests/test_utf8.py
============================================================================================================================ test session starts =============================================================================================================================
platform win32 -- Python 3.8.6, pytest-6.0.1.dev384+ga95da7a42, py-1.9.0, pluggy-0.13.1
rootdir: C:\Users\glebi\Documents\programming_environment
plugins: metadata-1.10.0, html-2.1.2.dev44+g8fd44c8.d20201102, pylint-0.17.0
collected 1 item

my_tests\test_utf8.py
INTERNALERROR> Traceback (most recent call last):
...
INTERNALERROR>   File "c:\users\glebi\documents\programming_environment\pytest-html\pytest_html\plugin.py", line 694, in pytest_runtest_logreport
INTERNALERROR>     raise ValueError(report.nodeid)
INTERNALERROR> ValueError: my_tests/test_utf8.py::test_pass[\u6d4b\u8bd5\u7528\u4f8b\u540d\u79f0]

=========================================================================================================================== no tests ran in 0.05s ============================================================================================================================
(.venv)
glebi@DESKTOP-2TKJKMV MINGW64 ~/Documents/programming_environment

If we were to escape the sequence \u6d4b\u8bd5\u7528\u4f8b\u540d\u79f0, we would end up displaying the wrong data. So the proper fix IMO is for pytest-pylint to properly escape this path if it is detected that we are running on a Windows machine since I don't think we can reliably determine what parts of the nodeid need to be escaped at all times.

@gnikonorov
Copy link
Member

Closing this issue since we can't reliably make a special case for the pytest-pylint nodeids . Please see carsongee/pytest-pylint#144 for my request to have the nodeid values properly escaped on Windows, @R-Broadley

@gnikonorov gnikonorov added the wontfix This will not be worked on label Nov 3, 2020
@gnikonorov
Copy link
Member

Hi @R-Broadley as per carsongee/pytest-pylint#144 (comment) this issue should be resolved now. Thank you for reporting it!

@R-Broadley
Copy link
Author

@gnikonorov Thanks for investigating. I will test this in the next day or so, but expect the two plugins should work nicely together on our Windows machines with the new release of pytest-pylint.

@gnikonorov
Copy link
Member

@gnikonorov Thanks for investigating. I will test this in the next day or so, but expect the two plugins should work nicely together on our Windows machines with the new release of pytest-pylint.

Anytime @R-Broadley! If you're still experiencing problems please don't hesitate to reopen this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug This issue/PR relates to a bug. windows This issue/PR is specifically for the windows ecosystem wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests

2 participants