diff --git a/pcapkit/utilities/warnings.py b/pcapkit/utilities/warnings.py index 9beea5758e..1364fd812b 100644 --- a/pcapkit/utilities/warnings.py +++ b/pcapkit/utilities/warnings.py @@ -483,7 +483,10 @@ def _importer_stacklevel() -> 'int': level then runs past the outermost frame, and the warning is attributed to ``:0``. This counts frames as :func:`warnings.warn` does, and :mod:`logging` does from Python 3.11; on earlier versions the log record is - attributed to a frame inside the import. + attributed to a frame inside the import. GraalPy names the bootstrap's frames + ```` and ````, + which its :func:`warnings.warn` leaves out too, so those are not counted + either (:issue:`1963`). """ pcapkit = f'{os.path.sep}pcapkit{os.path.sep}' @@ -493,7 +496,8 @@ def _importer_stacklevel() -> 'int': frame = frame.f_back if frame is not None else None # the caller, level 1 while frame is not None: filename = frame.f_code.co_filename - if not ('importlib' in filename and '_bootstrap' in filename): + graalpy_bootstrap = filename.startswith(' 'dict[str, str]': @unittest.skipUnless(importlib.util.find_spec('xdist') is not None, 'pytest-xdist not installed') +@unittest.skipIf(sys.implementation.name == 'graalpy', SKIP_NESTED_ON_GRAALPY) class XdistSubprocessTests(unittest.TestCase): """End-to-end reproduction: a real violation, a real ``pytest`` subprocess. @@ -300,6 +307,7 @@ def pytest_runtest_setup(item): @unittest.skipUnless(importlib.util.find_spec('xdist') is not None, 'pytest-xdist not installed') +@unittest.skipIf(sys.implementation.name == 'graalpy', SKIP_NESTED_ON_GRAALPY) class ProbeWorkerCrashTests(unittest.TestCase): """A worker dying mid-probe fails that one test, not the session (#1756). diff --git a/tests/utilities/test_compat.py b/tests/utilities/test_compat.py index c6bae26df8..d5f1a9e69b 100644 --- a/tests/utilities/test_compat.py +++ b/tests/utilities/test_compat.py @@ -158,6 +158,11 @@ #: assertion is aimed there. OWN_MODULE_PREFIX = 'pcapkit' +#: Whether a :exc:`UnicodeDecodeError` row's message is compared past the octet +#: and position it names. GraalPy's codecs word the reason their own way -- +#: ``malformed input`` for the rows below -- so there the reason is left out (#1963). +DECODE_REASON_COMPARED = sys.implementation.name != 'graalpy' + #: ``(value, encoding, errors, outcome)`` for ``compat.unquote``, each as Python #: 3.9's :func:`urllib.parse.unquote` has it: the text returned, or the exception #: type and message raised. ``None`` keywords leave the defaults in place. Before @@ -340,7 +345,11 @@ def assert_unquotes_as_python39(self, unquote: 'Callable[..., str]') -> None: else: with self.assertRaises(outcome[0]) as raised: unquote(value, **kwargs) - self.assertEqual(str(raised.exception), outcome[1]) + message, expected = str(raised.exception), outcome[1] + if outcome[0] is UnicodeDecodeError and not DECODE_REASON_COMPARED: + message = message.rpartition(': ')[0] + expected = expected.rpartition(': ')[0] + self.assertEqual(message, expected) def test_unquote_decodes_bytes_as_python39_does(self) -> None: """From 3.9 the name is :func:`urllib.parse.unquote` itself (#1693).""" diff --git a/tests/utilities/test_config_unit.py b/tests/utilities/test_config_unit.py index 78d0b833ea..ab01446e9c 100644 --- a/tests/utilities/test_config_unit.py +++ b/tests/utilities/test_config_unit.py @@ -590,5 +590,47 @@ def test_an_error_filter_makes_the_import_raise(self) -> None: self.assertIn(f"ConfigWarning: PCAPKIT_{name}={text!r}", result.stderr) +class ImporterStacklevelTests(unittest.TestCase): + """GitHub issue #1963: the importer's level leaves out the bootstrap, however it is named. + + CPython names :mod:`importlib`'s bootstrap frames ```` + and ````, GraalPy ```` + and ````, and each one's :func:`warnings.warn` + leaves its own out. Counting GraalPy's ran the level past the outermost frame, + so the import-time warnings above named ``:0`` there. + + """ + + def level_under(self, bootstrap: str) -> int: + """The level a :mod:`pcapkit` frame gets, imported through ``bootstrap`` frames. + + The chain is that of an import: this test, a bootstrap frame, a package + body, a bootstrap frame, then the reporting module. Leaving the bootstrap + out, the test is the third frame. + + """ + pcapkit_warnings = importlib.import_module('pcapkit.utilities.warnings') + namespace = {'_importer_stacklevel': getattr(pcapkit_warnings, '_importer_stacklevel')} + package = os.path.join(os.sep, 'site-packages', 'pcapkit', 'probe_1963.py') + reporter = ('def report():\n return _importer_stacklevel()\n' + 'def body():\n return load_inner()\n') + importer = ('def load_inner():\n return report()\n' + 'def load_outer():\n return body()\n') + exec(compile(reporter, package, 'exec'), namespace) # nosec: B102 # pylint: disable=exec-used + exec(compile(importer, bootstrap, 'exec'), namespace) # nosec: B102 # pylint: disable=exec-used + return namespace['load_outer']() + + def test_each_interpreters_bootstrap_frames_are_left_out(self) -> None: + """Both CPython's names and GraalPy's leave the test the third frame.""" + for bootstrap in ('', '', + '', ''): + with self.subTest(bootstrap=bootstrap): + self.assertEqual(self.level_under(bootstrap), 3) + + def test_any_other_frame_is_counted(self) -> None: + """The control: frames named otherwise are counted, so the level is one further out.""" + self.assertEqual(self.level_under(''), 4) + + if __name__ == '__main__': unittest.main()