Skip to content

Commit

Permalink
fix: lcov report indexeerror for some Jinja2 files. #1553
Browse files Browse the repository at this point in the history
  • Loading branch information
nedbat committed May 14, 2023
1 parent aefde53 commit 610a56f
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 0 deletions.
6 changes: 6 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,16 @@ development at the same time, such as 4.5.x and 5.0.
Unreleased
----------

- Fix: the ``lcov`` command could raise an IndexError exception if a file is
translated to Python but then executed under its own name. Jinja2 does this
when rendering templates. Fixes `issue 1553`_.

- Python 3.12 beta 1 now inlines comprehensions. Previously they were compiled
as invisible functions and coverage.py would warn you if they weren't
completely executed. This no longer happens under Python 3.12.

.. _issue 1553: https://github.com/nedbat/coveragepy/issues/1553


.. scriv-start-here
Expand Down
2 changes: 2 additions & 0 deletions coverage/lcovreport.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ def get_lcov(self, fr: FileReporter, analysis: Analysis, outfile: IO[str]) -> No
# characters of the encoding ("==") are removed from the hash to
# allow genhtml to run on the resulting lcov file.
if source_lines:
if covered-1 >= len(source_lines):
break
line = source_lines[covered-1]
else:
line = ""
Expand Down
125 changes: 125 additions & 0 deletions tests/test_report_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import textwrap

import coverage
from coverage import env
from coverage.files import abs_file

from tests.coveragetest import CoverageTest
Expand Down Expand Up @@ -156,3 +157,127 @@ def test_map_paths_during_lcov_report(self) -> None:
cov.lcov_report()
contains("coverage.lcov", os_sep("src/program.py"))
doesnt_contain("coverage.lcov", os_sep("ver1/program.py"), os_sep("ver2/program.py"))


class ReportWithJinjaTest(CoverageTest):
"""Tests of Jinja-like behavior.
Jinja2 compiles a template into Python code, and then runs the Python code
to render the template. But during rendering, it uses the template name
(for example, "template.j2") as the file name, not the Python code file
name. Then during reporting, we will try to parse template.j2 as Python
code.
If the file can be parsed, it's included in the report (as a Python file!).
If it can't be parsed, then it's not included in the report.
These tests confirm that code doesn't raise an exception (as reported in
#1553), and that the current (incorrect) behavior remains stable. Ideally,
good.j2 wouldn't be listed at all, since we can't report on it accurately.
See https://github.com/nedbat/coveragepy/issues/1553 for more detail, and
https://github.com/nedbat/coveragepy/issues/1623 for an issue about this
behavior.
"""

def make_files(self) -> None:
"""Create test files: two Jinja templates, and data from rendering them."""
# A Jinja2 file that is syntactically acceptable Python (though it wont run).
self.make_file("good.j2", """\
{{ data }}
line2
line3
""")
# A Jinja2 file that is a Python syntax error.
self.make_file("bad.j2", """\
This is data: {{ data }}.
line 2
line 3
""")
self.make_data_file(
lines={
abs_file("good.j2"): [1, 3, 5, 7, 9],
abs_file("bad.j2"): [1, 3, 5, 7, 9],
}
)

def test_report(self) -> None:
self.make_files()
cov = coverage.Coverage()
cov.load()
cov.report(show_missing=True)
expected = textwrap.dedent("""\
Name Stmts Miss Cover Missing
---------------------------------------
good.j2 3 1 67% 2
---------------------------------------
TOTAL 3 1 67%
""")
assert expected == self.stdout()

def test_html(self) -> None:
self.make_files()
cov = coverage.Coverage()
cov.load()
cov.html_report()
contains("htmlcov/index.html", """\
<tbody>
<tr class="file">
<td class="name left"><a href="good_j2.html">good.j2</a></td>
<td>3</td>
<td>1</td>
<td>0</td>
<td class="right" data-ratio="2 3">67%</td>
</tr>
</tbody>"""
)
doesnt_contain("htmlcov/index.html", "bad.j2")

def test_xml(self) -> None:
self.make_files()
cov = coverage.Coverage()
cov.load()
cov.xml_report()
contains("coverage.xml", 'filename="good.j2"')
if env.PYVERSION >= (3, 8): # Py3.7 puts attributes in the other order.
contains("coverage.xml",
'<line number="1" hits="1"/>',
'<line number="2" hits="0"/>',
'<line number="3" hits="1"/>',
)
doesnt_contain("coverage.xml", 'filename="bad.j2"')
if env.PYVERSION >= (3, 8): # Py3.7 puts attributes in the other order.
doesnt_contain("coverage.xml", '<line number="4"',)

def test_json(self) -> None:
self.make_files()
cov = coverage.Coverage()
cov.load()
cov.json_report()
contains("coverage.json",
# Notice the .json report claims lines in good.j2 executed that
# don't even exist in good.j2...
'"files": {"good.j2": {"executed_lines": [1, 3, 5, 7, 9], ' +
'"summary": {"covered_lines": 2, "num_statements": 3',
)
doesnt_contain("coverage.json", "bad.j2")

def test_lcov(self) -> None:
self.make_files()
cov = coverage.Coverage()
cov.load()
cov.lcov_report()
with open("coverage.lcov") as lcov:
actual = lcov.read()
expected = textwrap.dedent("""\
TN:
SF:good.j2
DA:1,1,FHs1rDakj9p/NAzMCu3Kgw
DA:3,1,DGOyp8LEgI+3CcdFYw9uKQ
DA:2,0,5iUbzxp9w7peeTPjJbvmBQ
LF:3
LH:2
end_of_record
""")
assert expected == actual

0 comments on commit 610a56f

Please sign in to comment.