pytest-dev / pytest

The pytest framework makes it easy to write small tests, yet scales to support complex functional testing
https://pytest.org
MIT License
11.93k stars 2.65k forks source link

`INTERNALERROR> tokenize.TokenError: 'unexpected EOF in multi-line statement'` when trying to report a missing fixture in Hy #12846

Open Kodiologist opened 1 day ago

Kodiologist commented 1 day ago

In this example, pytest hits an internal error when trying to report "fixture 'f' not found". I don't know how to reproduce it without Hy; I suspect the problem is that _pytest.fixtures.formatrepr calls inspect.getsourcelines without any provisions for the possibility that there isn't real Python code in the source file. I notice that the internal error doesn't occur if you replace the [ at the beginning of the program with x.

$ pip list
Package       Version
------------- -------
funcparserlib 1.0.1
hy            1.0.0
iniconfig     2.0.0
packaging     24.1
pip           24.2
pluggy        1.5.0
pytest        8.3.3
$ echo 'import hy, pytest; pytest_collect_file = lambda file_path, parent: pytest.Module.from_parent(parent, path = file_path)' >conftest.py
$ python3 -c 'print(";[\n(defn test-foo [f])")' >example.hy
$ pytest --assert=plain example.hy
============================= test session starts ==============================
platform linux -- Python 3.12.3, pytest-8.3.3, pluggy-1.5.0
rootdir: /tmp/example
collected 1 item                                                               

example.hy 
INTERNALERROR> Traceback (most recent call last):
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/_pytest/main.py", line 283, in wrap_session
INTERNALERROR>     session.exitstatus = doit(config, session) or 0
INTERNALERROR>                          ^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/_pytest/main.py", line 337, in _main
INTERNALERROR>     config.hook.pytest_runtestloop(session=session)
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/pluggy/_hooks.py", line 513, in __call__
INTERNALERROR>     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
INTERNALERROR>            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/pluggy/_manager.py", line 120, in _hookexec
INTERNALERROR>     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
INTERNALERROR>            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/pluggy/_callers.py", line 139, in _multicall
INTERNALERROR>     raise exception.with_traceback(exception.__traceback__)
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/pluggy/_callers.py", line 122, in _multicall
INTERNALERROR>     teardown.throw(exception)  # type: ignore[union-attr]
INTERNALERROR>     ^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/_pytest/logging.py", line 803, in pytest_runtestloop
INTERNALERROR>     return (yield)  # Run all the tests.
INTERNALERROR>             ^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/pluggy/_callers.py", line 122, in _multicall
INTERNALERROR>     teardown.throw(exception)  # type: ignore[union-attr]
INTERNALERROR>     ^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/_pytest/terminal.py", line 673, in pytest_runtestloop
INTERNALERROR>     result = yield
INTERNALERROR>              ^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/pluggy/_callers.py", line 103, in _multicall
INTERNALERROR>     res = hook_impl.function(*args)
INTERNALERROR>           ^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/_pytest/main.py", line 362, in pytest_runtestloop
INTERNALERROR>     item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/pluggy/_hooks.py", line 513, in __call__
INTERNALERROR>     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
INTERNALERROR>            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/pluggy/_manager.py", line 120, in _hookexec
INTERNALERROR>     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
INTERNALERROR>            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/pluggy/_callers.py", line 139, in _multicall
INTERNALERROR>     raise exception.with_traceback(exception.__traceback__)
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/pluggy/_callers.py", line 122, in _multicall
INTERNALERROR>     teardown.throw(exception)  # type: ignore[union-attr]
INTERNALERROR>     ^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/_pytest/warnings.py", line 112, in pytest_runtest_protocol
INTERNALERROR>     return (yield)
INTERNALERROR>             ^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/pluggy/_callers.py", line 122, in _multicall
INTERNALERROR>     teardown.throw(exception)  # type: ignore[union-attr]
INTERNALERROR>     ^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/_pytest/assertion/__init__.py", line 176, in pytest_runtest_protocol
INTERNALERROR>     return (yield)
INTERNALERROR>             ^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/pluggy/_callers.py", line 122, in _multicall
INTERNALERROR>     teardown.throw(exception)  # type: ignore[union-attr]
INTERNALERROR>     ^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/_pytest/unittest.py", line 429, in pytest_runtest_protocol
INTERNALERROR>     res = yield
INTERNALERROR>           ^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/pluggy/_callers.py", line 122, in _multicall
INTERNALERROR>     teardown.throw(exception)  # type: ignore[union-attr]
INTERNALERROR>     ^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/_pytest/faulthandler.py", line 88, in pytest_runtest_protocol
INTERNALERROR>     return (yield)
INTERNALERROR>             ^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/pluggy/_callers.py", line 103, in _multicall
INTERNALERROR>     res = hook_impl.function(*args)
INTERNALERROR>           ^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/_pytest/runner.py", line 113, in pytest_runtest_protocol
INTERNALERROR>     runtestprotocol(item, nextitem=nextitem)
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/_pytest/runner.py", line 126, in runtestprotocol
INTERNALERROR>     rep = call_and_report(item, "setup", log)
INTERNALERROR>           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/_pytest/runner.py", line 244, in call_and_report
INTERNALERROR>     report: TestReport = ihook.pytest_runtest_makereport(item=item, call=call)
INTERNALERROR>                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/pluggy/_hooks.py", line 513, in __call__
INTERNALERROR>     return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
INTERNALERROR>            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/pluggy/_manager.py", line 120, in _hookexec
INTERNALERROR>     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
INTERNALERROR>            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/pluggy/_callers.py", line 139, in _multicall
INTERNALERROR>     raise exception.with_traceback(exception.__traceback__)
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/pluggy/_callers.py", line 122, in _multicall
INTERNALERROR>     teardown.throw(exception)  # type: ignore[union-attr]
INTERNALERROR>     ^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/_pytest/tmpdir.py", line 318, in pytest_runtest_makereport
INTERNALERROR>     rep = yield
INTERNALERROR>           ^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/pluggy/_callers.py", line 122, in _multicall
INTERNALERROR>     teardown.throw(exception)  # type: ignore[union-attr]
INTERNALERROR>     ^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/_pytest/skipping.py", line 269, in pytest_runtest_makereport
INTERNALERROR>     rep = yield
INTERNALERROR>           ^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/pluggy/_callers.py", line 103, in _multicall
INTERNALERROR>     res = hook_impl.function(*args)
INTERNALERROR>           ^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/_pytest/runner.py", line 368, in pytest_runtest_makereport
INTERNALERROR>     return TestReport.from_item_and_call(item, call)
INTERNALERROR>            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/_pytest/reports.py", line 378, in from_item_and_call
INTERNALERROR>     longrepr = item._repr_failure_py(
INTERNALERROR>                ^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/_pytest/nodes.py", line 421, in _repr_failure_py
INTERNALERROR>     return excinfo.value.formatrepr()
INTERNALERROR>            ^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/tmp/example/.venv/lib/python3.12/site-packages/_pytest/fixtures.py", line 815, in formatrepr
INTERNALERROR>     lines, _ = inspect.getsourcelines(get_real_func(function))
INTERNALERROR>                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/usr/lib/python3.12/inspect.py", line 1270, in getsourcelines
INTERNALERROR>     return getblock(lines[lnum:]), lnum + 1
INTERNALERROR>            ^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/usr/lib/python3.12/inspect.py", line 1237, in getblock
INTERNALERROR>     for _token in tokens:
INTERNALERROR>   File "/usr/lib/python3.12/tokenize.py", line 582, in _generate_tokens_from_c_tokenizer
INTERNALERROR>     raise TokenError(msg, (e.lineno, e.offset)) from None
INTERNALERROR> tokenize.TokenError: ('unexpected EOF in multi-line statement', (2, 0))

============================ no tests ran in 0.01s =============================
The-Compiler commented 1 day ago

Given that inspect.getsourcelines() from the Python stdlib fails, that seems like something that's unrelated to pytest and should be fixed by Hy - looks like an issue is open already:

We do already catch various exceptions there:

https://github.com/pytest-dev/pytest/blob/6486c3f3a858a0c8043f5c3f7c24297b82a0abe4/src/_pytest/fixtures.py#L814-L818

If it's as easy as catching tokenize.TokenError there too, I suppose we could do that - but I wouldn't want to go much further than that if Hy breaks more Python stdlib stuff.

Kodiologist commented 1 day ago

I'm not sure how much of the inspect module can ever be made to work with Hy. pytest has historically worked quite well with small changes, and pytest guarding against another exception type that inspect.getsourcelines can raise makes sense to me.