diff --git a/docs/project/changelog.md b/docs/project/changelog.md index 578948a9a2b..b3a45e37d72 100644 --- a/docs/project/changelog.md +++ b/docs/project/changelog.md @@ -69,6 +69,9 @@ myst: - {{ Fix }} Fix sessionStorage-related crash when running in sandboxed iframe. {pr}`5186` +- {{ Fix }} `JsFinder.find_spec()` no longer crashes when called during pytest test collection. + {pr}`5170` + ### Packages - Upgraded `crc32c` to 2.7.1 {pr}`5169` diff --git a/src/py/_pyodide/_importhook.py b/src/py/_pyodide/_importhook.py index 2b1fe3af15e..77804c16689 100644 --- a/src/py/_pyodide/_importhook.py +++ b/src/py/_pyodide/_importhook.py @@ -25,6 +25,9 @@ def find_spec( try: parent_module = sys.modules[parent] except KeyError: + # Note: This will never happen when we're called from importlib, + # but pytest hits this codepath. See + # `test_importhook_called_from_pytest`. return None if not isinstance(parent_module, JsProxy): # Not one of us. diff --git a/src/tests/test_pyodide.py b/src/tests/test_pyodide.py index 89b44f5d0ce..a9305d8e4a4 100644 --- a/src/tests/test_pyodide.py +++ b/src/tests/test_pyodide.py @@ -1516,6 +1516,27 @@ def test_module_not_found_note(selenium_standalone): assert len(e.value.__notes__) == 1 +@run_in_pyodide +def test_importhook_called_from_pytest(selenium): + """ + Whenever importlib itself resolves `import a.b`, it splits on the . and + first imports `a` and then `a.b`. However, pytest does not, it calls + `find_spec("a.b")` directly here: + https://github.com/pytest-dev/pytest/blob/ea0fa639445ae08616edd2c15189a1a76168f018/src/_pytest/pathlib.py#L693-L698 + + This previously could lead to crashes in `JsFinder`. + """ + import sys + + def _import_module_using_spec(module_name): + """Modeled on a fragment of _pytest.pathlib._import_module_using_spec""" + for meta_importer in sys.meta_path: + spec = meta_importer.find_spec(module_name, path=[]) + + # Assertion: This should not raise KeyError. + _import_module_using_spec("a.b") + + def test_args(selenium_standalone_noload): selenium = selenium_standalone_noload assert selenium.run_js(