From 3683722bcbc0facc8fe695c7ece286f26fc6234f Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Wed, 5 Apr 2023 21:48:24 -0400 Subject: [PATCH] FormattedExcinfo.get_source: avoid crash when line number is out-of-bounds/negative pytest could crash given pathological AST position attributes, which shouldn't happen when testing real Python code, but could happen when testing AST produced by e.g. Hylang. Another example of the failure is in the nightly CI for the JAX project: https://github.com/google/jax/actions/runs/4607513902/jobs/8142126075 Co-authored-by: Bruno Oliveira Co-authored-by: Jake VanderPlas --- AUTHORS | 1 + changelog/10840.improvement.rst | 1 + src/_pytest/_code/code.py | 8 +++++--- testing/code/test_excinfo.py | 18 ++++++++++++++++++ 4 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 changelog/10840.improvement.rst diff --git a/AUTHORS b/AUTHORS index 058236461..1aa5265e6 100644 --- a/AUTHORS +++ b/AUTHORS @@ -163,6 +163,7 @@ Ionuț Turturică Itxaso Aizpurua Iwan Briquemont Jaap Broekhuizen +Jake VanderPlas Jakob van Santen Jakub Mitoraj James Bourbeau diff --git a/changelog/10840.improvement.rst b/changelog/10840.improvement.rst new file mode 100644 index 000000000..17be0d07b --- /dev/null +++ b/changelog/10840.improvement.rst @@ -0,0 +1 @@ +pytest should no longer crash on AST with pathological position attributes, for example testing AST produced by `Hylang __`. diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index e375fb70c..032b83beb 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -743,11 +743,13 @@ class FormattedExcinfo: ) -> List[str]: """Return formatted and marked up source lines.""" lines = [] - if source is None or line_index >= len(source.lines): + if source is not None and line_index < 0: + line_index += len(source) + if source is None or line_index >= len(source.lines) or line_index < 0: + # `line_index` could still be outside `range(len(source.lines))` if + # we're processing AST with pathological position attributes. source = Source("???") line_index = 0 - if line_index < 0: - line_index += len(source) space_prefix = " " if short: lines.append(space_prefix + source.lines[line_index].strip()) diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index 3607501a8..918c97276 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -461,6 +461,24 @@ class TestFormattedExcinfo: assert lines[0] == "| def f(x):" assert lines[1] == " pass" + def test_repr_source_out_of_bounds(self): + pr = FormattedExcinfo() + source = _pytest._code.Source( + """\ + def f(x): + pass + """ + ).strip() + pr.flow_marker = "|" # type: ignore[misc] + + lines = pr.get_source(source, 100) + assert len(lines) == 1 + assert lines[0] == "| ???" + + lines = pr.get_source(source, -100) + assert len(lines) == 1 + assert lines[0] == "| ???" + def test_repr_source_excinfo(self) -> None: """Check if indentation is right.""" try: