|  | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 | 
|  | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt | 
|  |  | 
|  | """Test LCOV-based summary reporting for coverage.py.""" | 
|  |  | 
|  | from __future__ import annotations | 
|  |  | 
|  | import math | 
|  | import textwrap | 
|  |  | 
|  | import coverage | 
|  |  | 
|  | from tests.coveragetest import CoverageTest | 
|  |  | 
|  |  | 
|  | class LcovTest(CoverageTest): | 
|  | """Tests of the LCOV reports from coverage.py.""" | 
|  |  | 
|  | def create_initial_files(self) -> None: | 
|  | """ | 
|  | Helper for tests that handles the common ceremony so the tests can | 
|  | show the consequences of changes in the setup. | 
|  | """ | 
|  | self.make_file( | 
|  | "main_file.py", | 
|  | """\ | 
|  | def cuboid_volume(l): | 
|  | return (l*l*l) | 
|  |  | 
|  | def IsItTrue(): | 
|  | return True | 
|  | """, | 
|  | ) | 
|  |  | 
|  | self.make_file( | 
|  | "test_file.py", | 
|  | """\ | 
|  | from main_file import cuboid_volume | 
|  | import unittest | 
|  |  | 
|  | class TestCuboid(unittest.TestCase): | 
|  | def test_volume(self): | 
|  | self.assertAlmostEqual(cuboid_volume(2),8) | 
|  | self.assertAlmostEqual(cuboid_volume(1),1) | 
|  | self.assertAlmostEqual(cuboid_volume(0),0) | 
|  | self.assertAlmostEqual(cuboid_volume(5.5),166.375) | 
|  | """, | 
|  | ) | 
|  |  | 
|  | def get_lcov_report_content(self, filename: str = "coverage.lcov") -> str: | 
|  | """Return the content of an LCOV report.""" | 
|  | with open(filename, encoding="utf-8") as file: | 
|  | return file.read() | 
|  |  | 
|  | def test_lone_file(self) -> None: | 
|  | # For a single file with a couple of functions, the lcov should cover | 
|  | # the function definitions themselves, but not the returns. | 
|  | self.make_file( | 
|  | "main_file.py", | 
|  | """\ | 
|  | def cuboid_volume(l): | 
|  | return (l*l*l) | 
|  |  | 
|  | def IsItTrue(): | 
|  | return True | 
|  | """, | 
|  | ) | 
|  | expected_result = textwrap.dedent("""\ | 
|  | SF:main_file.py | 
|  | DA:1,1 | 
|  | DA:2,0 | 
|  | DA:4,1 | 
|  | DA:5,0 | 
|  | LF:4 | 
|  | LH:2 | 
|  | FN:1,2,cuboid_volume | 
|  | FNDA:0,cuboid_volume | 
|  | FN:4,5,IsItTrue | 
|  | FNDA:0,IsItTrue | 
|  | FNF:2 | 
|  | FNH:0 | 
|  | end_of_record | 
|  | """) | 
|  | self.assert_doesnt_exist(".coverage") | 
|  | cov = coverage.Coverage(source=["."]) | 
|  | self.start_import_stop(cov, "main_file") | 
|  | pct = cov.lcov_report() | 
|  | assert pct == 50.0 | 
|  | actual_result = self.get_lcov_report_content() | 
|  | assert expected_result == actual_result | 
|  |  | 
|  | def test_line_checksums(self) -> None: | 
|  | self.make_file( | 
|  | "main_file.py", | 
|  | """\ | 
|  | def cuboid_volume(l): | 
|  | return (l*l*l) | 
|  |  | 
|  | def IsItTrue(): | 
|  | return True | 
|  | """, | 
|  | ) | 
|  | self.make_file(".coveragerc", "[lcov]\nline_checksums = true\n") | 
|  | self.assert_doesnt_exist(".coverage") | 
|  | cov = coverage.Coverage(source=["."]) | 
|  | self.start_import_stop(cov, "main_file") | 
|  | pct = cov.lcov_report() | 
|  | assert pct == 50.0 | 
|  | expected_result = textwrap.dedent("""\ | 
|  | SF:main_file.py | 
|  | DA:1,1,7URou3io0zReBkk69lEb/Q | 
|  | DA:2,0,Xqj6H1iz/nsARMCAbE90ng | 
|  | DA:4,1,ilhb4KUfytxtEuClijZPlQ | 
|  | DA:5,0,LWILTcvARcydjFFyo9qM0A | 
|  | LF:4 | 
|  | LH:2 | 
|  | FN:1,2,cuboid_volume | 
|  | FNDA:0,cuboid_volume | 
|  | FN:4,5,IsItTrue | 
|  | FNDA:0,IsItTrue | 
|  | FNF:2 | 
|  | FNH:0 | 
|  | end_of_record | 
|  | """) | 
|  | actual_result = self.get_lcov_report_content() | 
|  | assert expected_result == actual_result | 
|  |  | 
|  | def test_simple_line_coverage_two_files(self) -> None: | 
|  | # Test that line coverage is created when coverage is run, | 
|  | # and matches the output of the file below. | 
|  | self.create_initial_files() | 
|  | self.assert_doesnt_exist(".coverage") | 
|  | self.make_file(".coveragerc", "[lcov]\noutput = data.lcov\n") | 
|  | cov = coverage.Coverage(source=".") | 
|  | self.start_import_stop(cov, "test_file") | 
|  | pct = cov.lcov_report() | 
|  | assert pct == 50.0 | 
|  | self.assert_exists("data.lcov") | 
|  | expected_result = textwrap.dedent("""\ | 
|  | SF:main_file.py | 
|  | DA:1,1 | 
|  | DA:2,0 | 
|  | DA:4,1 | 
|  | DA:5,0 | 
|  | LF:4 | 
|  | LH:2 | 
|  | FN:1,2,cuboid_volume | 
|  | FNDA:0,cuboid_volume | 
|  | FN:4,5,IsItTrue | 
|  | FNDA:0,IsItTrue | 
|  | FNF:2 | 
|  | FNH:0 | 
|  | end_of_record | 
|  | SF:test_file.py | 
|  | DA:1,1 | 
|  | DA:2,1 | 
|  | DA:4,1 | 
|  | DA:5,1 | 
|  | DA:6,0 | 
|  | DA:7,0 | 
|  | DA:8,0 | 
|  | DA:9,0 | 
|  | LF:8 | 
|  | LH:4 | 
|  | FN:5,9,TestCuboid.test_volume | 
|  | FNDA:0,TestCuboid.test_volume | 
|  | FNF:1 | 
|  | FNH:0 | 
|  | end_of_record | 
|  | """) | 
|  | actual_result = self.get_lcov_report_content(filename="data.lcov") | 
|  | assert expected_result == actual_result | 
|  |  | 
|  | def test_branch_coverage_one_file(self) -> None: | 
|  | # Test that the reporter produces valid branch coverage. | 
|  | self.make_file( | 
|  | "main_file.py", | 
|  | """\ | 
|  | def is_it_x(x): | 
|  | if x == 3: | 
|  | return x | 
|  | else: | 
|  | return False | 
|  | """, | 
|  | ) | 
|  | self.assert_doesnt_exist(".coverage") | 
|  | cov = coverage.Coverage(branch=True, source=".") | 
|  | self.start_import_stop(cov, "main_file") | 
|  | pct = cov.lcov_report() | 
|  | assert math.isclose(pct, 16.666666666666668) | 
|  | self.assert_exists("coverage.lcov") | 
|  | expected_result = textwrap.dedent("""\ | 
|  | SF:main_file.py | 
|  | DA:1,1 | 
|  | DA:2,0 | 
|  | DA:3,0 | 
|  | DA:5,0 | 
|  | LF:4 | 
|  | LH:1 | 
|  | FN:1,5,is_it_x | 
|  | FNDA:0,is_it_x | 
|  | FNF:1 | 
|  | FNH:0 | 
|  | BRDA:2,0,jump to line 3,- | 
|  | BRDA:2,0,jump to line 5,- | 
|  | BRF:2 | 
|  | BRH:0 | 
|  | end_of_record | 
|  | """) | 
|  | actual_result = self.get_lcov_report_content() | 
|  | assert expected_result == actual_result | 
|  |  | 
|  | def test_branch_coverage_two_files(self) -> None: | 
|  | # Test that valid branch coverage is generated | 
|  | # in the case of two files. | 
|  | self.make_file( | 
|  | "main_file.py", | 
|  | """\ | 
|  | def is_it_x(x): | 
|  | if x == 3: | 
|  | return x | 
|  | else: | 
|  | return False | 
|  | """, | 
|  | ) | 
|  |  | 
|  | self.make_file( | 
|  | "test_file.py", | 
|  | """\ | 
|  | from main_file import * | 
|  | import unittest | 
|  |  | 
|  | class TestIsItX(unittest.TestCase): | 
|  | def test_is_it_x(self): | 
|  | self.assertEqual(is_it_x(3), 3) | 
|  | self.assertEqual(is_it_x(4), False) | 
|  | """, | 
|  | ) | 
|  | self.assert_doesnt_exist(".coverage") | 
|  | cov = coverage.Coverage(branch=True, source=".") | 
|  | self.start_import_stop(cov, "test_file") | 
|  | pct = cov.lcov_report() | 
|  | assert math.isclose(pct, 41.666666666666664) | 
|  | self.assert_exists("coverage.lcov") | 
|  | expected_result = textwrap.dedent("""\ | 
|  | SF:main_file.py | 
|  | DA:1,1 | 
|  | DA:2,0 | 
|  | DA:3,0 | 
|  | DA:5,0 | 
|  | LF:4 | 
|  | LH:1 | 
|  | FN:1,5,is_it_x | 
|  | FNDA:0,is_it_x | 
|  | FNF:1 | 
|  | FNH:0 | 
|  | BRDA:2,0,jump to line 3,- | 
|  | BRDA:2,0,jump to line 5,- | 
|  | BRF:2 | 
|  | BRH:0 | 
|  | end_of_record | 
|  | SF:test_file.py | 
|  | DA:1,1 | 
|  | DA:2,1 | 
|  | DA:4,1 | 
|  | DA:5,1 | 
|  | DA:6,0 | 
|  | DA:7,0 | 
|  | LF:6 | 
|  | LH:4 | 
|  | FN:5,7,TestIsItX.test_is_it_x | 
|  | FNDA:0,TestIsItX.test_is_it_x | 
|  | FNF:1 | 
|  | FNH:0 | 
|  | end_of_record | 
|  | """) | 
|  | actual_result = self.get_lcov_report_content() | 
|  | assert expected_result == actual_result | 
|  |  | 
|  | def test_half_covered_branch(self) -> None: | 
|  | # Test that for a given branch that is only half covered, | 
|  | # the block numbers remain the same, and produces valid lcov. | 
|  | self.make_file( | 
|  | "main_file.py", | 
|  | """\ | 
|  | something = True | 
|  |  | 
|  | if something: | 
|  | print("Yes, something") | 
|  | else: | 
|  | print("No, nothing") | 
|  | """, | 
|  | ) | 
|  | self.assert_doesnt_exist(".coverage") | 
|  | cov = coverage.Coverage(branch=True, source=".") | 
|  | self.start_import_stop(cov, "main_file") | 
|  | pct = cov.lcov_report() | 
|  | assert math.isclose(pct, 66.66666666666667) | 
|  | self.assert_exists("coverage.lcov") | 
|  | expected_result = textwrap.dedent("""\ | 
|  | SF:main_file.py | 
|  | DA:1,1 | 
|  | DA:3,1 | 
|  | DA:4,1 | 
|  | DA:6,0 | 
|  | LF:4 | 
|  | LH:3 | 
|  | BRDA:3,0,jump to line 4,1 | 
|  | BRDA:3,0,jump to line 6,0 | 
|  | BRF:2 | 
|  | BRH:1 | 
|  | end_of_record | 
|  | """) | 
|  | actual_result = self.get_lcov_report_content() | 
|  | assert expected_result == actual_result | 
|  |  | 
|  | def test_empty_init_files(self) -> None: | 
|  | # Test that an empty __init__.py still generates a (vacuous) | 
|  | # coverage record. | 
|  | self.make_file("__init__.py", "") | 
|  | self.assert_doesnt_exist(".coverage") | 
|  | cov = coverage.Coverage(branch=True, source=".") | 
|  | self.start_import_stop(cov, "__init__") | 
|  | pct = cov.lcov_report() | 
|  | assert pct == 0.0 | 
|  | self.assert_exists("coverage.lcov") | 
|  | expected_result = textwrap.dedent("""\ | 
|  | SF:__init__.py | 
|  | end_of_record | 
|  | """) | 
|  | actual_result = self.get_lcov_report_content() | 
|  | assert expected_result == actual_result | 
|  |  | 
|  | def test_empty_init_file_skipped(self) -> None: | 
|  | # Test that the lcov reporter honors skip_empty.  Because skip_empty | 
|  | # keys off the overall number of lines of code, the result in this | 
|  | # case will be the same regardless of the age of the Python interpreter. | 
|  | self.make_file("__init__.py", "") | 
|  | self.make_file(".coveragerc", "[report]\nskip_empty = True\n") | 
|  | self.assert_doesnt_exist(".coverage") | 
|  | cov = coverage.Coverage(branch=True, source=".") | 
|  | self.start_import_stop(cov, "__init__") | 
|  | pct = cov.lcov_report() | 
|  | assert pct == 0.0 | 
|  | self.assert_exists("coverage.lcov") | 
|  | expected_result = "" | 
|  | actual_result = self.get_lcov_report_content() | 
|  | assert expected_result == actual_result | 
|  |  | 
|  | def test_excluded_lines(self) -> None: | 
|  | self.make_file( | 
|  | ".coveragerc", | 
|  | """\ | 
|  | [report] | 
|  | exclude_lines = foo | 
|  | """, | 
|  | ) | 
|  | self.make_file( | 
|  | "runme.py", | 
|  | """\ | 
|  | s = "Hello 1" | 
|  | t = "foo is ignored 2" | 
|  | if s.upper() == "BYE 3": | 
|  | i_am_missing_4() | 
|  | foo_is_missing_5() | 
|  | print("Done 6") | 
|  | # foo 7 | 
|  | # line 8 | 
|  | """, | 
|  | ) | 
|  | cov = coverage.Coverage(source=".", branch=True) | 
|  | self.start_import_stop(cov, "runme") | 
|  | cov.lcov_report() | 
|  | expected_result = textwrap.dedent("""\ | 
|  | SF:runme.py | 
|  | DA:1,1 | 
|  | DA:3,1 | 
|  | DA:4,0 | 
|  | DA:6,1 | 
|  | LF:4 | 
|  | LH:3 | 
|  | BRDA:3,0,jump to line 4,0 | 
|  | BRDA:3,0,jump to line 6,1 | 
|  | BRF:2 | 
|  | BRH:1 | 
|  | end_of_record | 
|  | """) | 
|  | actual_result = self.get_lcov_report_content() | 
|  | assert expected_result == actual_result | 
|  |  | 
|  | def test_exit_branches(self) -> None: | 
|  | self.make_file( | 
|  | "runme.py", | 
|  | """\ | 
|  | def foo(a): | 
|  | if a: | 
|  | print(f"{a!r} is truthy") | 
|  | foo(True) | 
|  | foo(False) | 
|  | foo([]) | 
|  | foo([0]) | 
|  | """, | 
|  | ) | 
|  | cov = coverage.Coverage(source=".", branch=True) | 
|  | self.start_import_stop(cov, "runme") | 
|  | cov.lcov_report() | 
|  | expected_result = textwrap.dedent("""\ | 
|  | SF:runme.py | 
|  | DA:1,1 | 
|  | DA:2,1 | 
|  | DA:3,1 | 
|  | DA:4,1 | 
|  | DA:5,1 | 
|  | DA:6,1 | 
|  | DA:7,1 | 
|  | LF:7 | 
|  | LH:7 | 
|  | FN:1,3,foo | 
|  | FNDA:1,foo | 
|  | FNF:1 | 
|  | FNH:1 | 
|  | BRDA:2,0,jump to line 3,1 | 
|  | BRDA:2,0,return from function 'foo',1 | 
|  | BRF:2 | 
|  | BRH:2 | 
|  | end_of_record | 
|  | """) | 
|  | actual_result = self.get_lcov_report_content() | 
|  | assert expected_result == actual_result | 
|  |  | 
|  | def test_genexpr_exit_arcs_pruned_full_coverage(self) -> None: | 
|  | self.make_file( | 
|  | "runme.py", | 
|  | """\ | 
|  | def foo(a): | 
|  | if any(x > 0 for x in a): | 
|  | print(f"{a!r} has positives") | 
|  | foo([]) | 
|  | foo([0]) | 
|  | foo([0,1]) | 
|  | foo([0,-1]) | 
|  | """, | 
|  | ) | 
|  | cov = coverage.Coverage(source=".", branch=True) | 
|  | self.start_import_stop(cov, "runme") | 
|  | cov.lcov_report() | 
|  | expected_result = textwrap.dedent("""\ | 
|  | SF:runme.py | 
|  | DA:1,1 | 
|  | DA:2,1 | 
|  | DA:3,1 | 
|  | DA:4,1 | 
|  | DA:5,1 | 
|  | DA:6,1 | 
|  | DA:7,1 | 
|  | LF:7 | 
|  | LH:7 | 
|  | FN:1,3,foo | 
|  | FNDA:1,foo | 
|  | FNF:1 | 
|  | FNH:1 | 
|  | BRDA:2,0,jump to line 3,1 | 
|  | BRDA:2,0,return from function 'foo',1 | 
|  | BRF:2 | 
|  | BRH:2 | 
|  | end_of_record | 
|  | """) | 
|  | actual_result = self.get_lcov_report_content() | 
|  | assert expected_result == actual_result | 
|  |  | 
|  | def test_genexpr_exit_arcs_pruned_never_true(self) -> None: | 
|  | self.make_file( | 
|  | "runme.py", | 
|  | """\ | 
|  | def foo(a): | 
|  | if any(x > 0 for x in a): | 
|  | print(f"{a!r} has positives") | 
|  | foo([]) | 
|  | foo([0]) | 
|  | """, | 
|  | ) | 
|  | cov = coverage.Coverage(source=".", branch=True) | 
|  | self.start_import_stop(cov, "runme") | 
|  | cov.lcov_report() | 
|  | expected_result = textwrap.dedent("""\ | 
|  | SF:runme.py | 
|  | DA:1,1 | 
|  | DA:2,1 | 
|  | DA:3,0 | 
|  | DA:4,1 | 
|  | DA:5,1 | 
|  | LF:5 | 
|  | LH:4 | 
|  | FN:1,3,foo | 
|  | FNDA:1,foo | 
|  | FNF:1 | 
|  | FNH:1 | 
|  | BRDA:2,0,jump to line 3,0 | 
|  | BRDA:2,0,return from function 'foo',1 | 
|  | BRF:2 | 
|  | BRH:1 | 
|  | end_of_record | 
|  | """) | 
|  | actual_result = self.get_lcov_report_content() | 
|  | assert expected_result == actual_result | 
|  |  | 
|  | def test_genexpr_exit_arcs_pruned_always_true(self) -> None: | 
|  | self.make_file( | 
|  | "runme.py", | 
|  | """\ | 
|  | def foo(a): | 
|  | if any(x > 0 for x in a): | 
|  | print(f"{a!r} has positives") | 
|  | foo([1]) | 
|  | foo([1,2]) | 
|  | """, | 
|  | ) | 
|  | cov = coverage.Coverage(source=".", branch=True) | 
|  | self.start_import_stop(cov, "runme") | 
|  | cov.lcov_report() | 
|  | expected_result = textwrap.dedent("""\ | 
|  | SF:runme.py | 
|  | DA:1,1 | 
|  | DA:2,1 | 
|  | DA:3,1 | 
|  | DA:4,1 | 
|  | DA:5,1 | 
|  | LF:5 | 
|  | LH:5 | 
|  | FN:1,3,foo | 
|  | FNDA:1,foo | 
|  | FNF:1 | 
|  | FNH:1 | 
|  | BRDA:2,0,jump to line 3,1 | 
|  | BRDA:2,0,return from function 'foo',0 | 
|  | BRF:2 | 
|  | BRH:1 | 
|  | end_of_record | 
|  | """) | 
|  | actual_result = self.get_lcov_report_content() | 
|  | assert expected_result == actual_result | 
|  |  | 
|  | def test_genexpr_exit_arcs_pruned_not_reached(self) -> None: | 
|  | self.make_file( | 
|  | "runme.py", | 
|  | """\ | 
|  | def foo(a): | 
|  | if any(x > 0 for x in a): | 
|  | print(f"{a!r} has positives") | 
|  | """, | 
|  | ) | 
|  | cov = coverage.Coverage(source=".", branch=True) | 
|  | self.start_import_stop(cov, "runme") | 
|  | cov.lcov_report() | 
|  | expected_result = textwrap.dedent("""\ | 
|  | SF:runme.py | 
|  | DA:1,1 | 
|  | DA:2,0 | 
|  | DA:3,0 | 
|  | LF:3 | 
|  | LH:1 | 
|  | FN:1,3,foo | 
|  | FNDA:0,foo | 
|  | FNF:1 | 
|  | FNH:0 | 
|  | BRDA:2,0,jump to line 3,- | 
|  | BRDA:2,0,return from function 'foo',- | 
|  | BRF:2 | 
|  | BRH:0 | 
|  | end_of_record | 
|  | """) | 
|  | actual_result = self.get_lcov_report_content() | 
|  | assert expected_result == actual_result | 
|  |  | 
|  | def test_always_raise(self) -> None: | 
|  | self.make_file( | 
|  | "always_raise.py", | 
|  | """\ | 
|  | try: | 
|  | if not_defined: | 
|  | print("Yes") | 
|  | else: | 
|  | print("No") | 
|  | except Exception: | 
|  | pass | 
|  | """, | 
|  | ) | 
|  | cov = coverage.Coverage(source=".", branch=True) | 
|  | self.start_import_stop(cov, "always_raise") | 
|  | cov.lcov_report() | 
|  | expected_result = textwrap.dedent("""\ | 
|  | SF:always_raise.py | 
|  | DA:1,1 | 
|  | DA:2,1 | 
|  | DA:3,0 | 
|  | DA:5,0 | 
|  | DA:6,1 | 
|  | DA:7,1 | 
|  | LF:6 | 
|  | LH:4 | 
|  | BRDA:2,0,jump to line 3,- | 
|  | BRDA:2,0,jump to line 5,- | 
|  | BRF:2 | 
|  | BRH:0 | 
|  | end_of_record | 
|  | """) | 
|  | actual_result = self.get_lcov_report_content() | 
|  | assert expected_result == actual_result | 
|  |  | 
|  | def test_multiline_conditions(self) -> None: | 
|  | self.make_file( | 
|  | "multi.py", | 
|  | """\ | 
|  | def fun(x): | 
|  | if ( | 
|  | x | 
|  | ): | 
|  | print("got here") | 
|  | """, | 
|  | ) | 
|  | cov = coverage.Coverage(source=".", branch=True) | 
|  | self.start_import_stop(cov, "multi") | 
|  | cov.lcov_report() | 
|  | lcov = self.get_lcov_report_content() | 
|  | assert "BRDA:2,0,return from function 'fun',-" in lcov | 
|  |  | 
|  | def test_module_exit(self) -> None: | 
|  | self.make_file( | 
|  | "modexit.py", | 
|  | """\ | 
|  | #! /usr/bin/env python | 
|  | def foo(): | 
|  | return bar( | 
|  | ) | 
|  | if "x" == "y":  # line 5 | 
|  | foo() | 
|  | """, | 
|  | ) | 
|  | cov = coverage.Coverage(source=".", branch=True) | 
|  | self.start_import_stop(cov, "modexit") | 
|  | cov.lcov_report() | 
|  | lcov = self.get_lcov_report_content() | 
|  | print(lcov) | 
|  | assert "BRDA:5,0,exit the module,1" in lcov |