| # 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 text-based summary reporting for coverage.py""" |
| |
| from __future__ import annotations |
| |
| import glob |
| import io |
| import math |
| import os |
| import os.path |
| import py_compile |
| import re |
| |
| |
| import pytest |
| |
| import coverage |
| from coverage import env |
| from coverage.control import Coverage |
| from coverage.data import CoverageData |
| from coverage.exceptions import ConfigError, NoDataError, NotPython |
| from coverage.files import abs_file |
| from coverage.report import SummaryReporter |
| from coverage.types import TConfigValueIn |
| |
| from tests.coveragetest import CoverageTest, TESTS_DIR, UsingModulesMixin |
| from tests.helpers import assert_coverage_warnings |
| |
| |
| class SummaryTest(UsingModulesMixin, CoverageTest): |
| """Tests of the text summary reporting for coverage.py.""" |
| |
| def make_mycode(self) -> None: |
| """Make the mycode.py file when needed.""" |
| self.make_file("mycode.py", """\ |
| import covmod1 |
| import covmodzip1 |
| a = 1 |
| print('done') |
| """) |
| |
| def test_report(self) -> None: |
| self.make_mycode() |
| cov = coverage.Coverage() |
| self.start_import_stop(cov, "mycode") |
| assert self.stdout() == 'done\n' |
| report = self.get_report(cov) |
| |
| # Name Stmts Miss Cover |
| # ------------------------------------------------------------------ |
| # c:/ned/coverage/tests/modules/covmod1.py 2 0 100% |
| # c:/ned/coverage/tests/zipmods.zip/covmodzip1.py 2 0 100% |
| # mycode.py 4 0 100% |
| # ------------------------------------------------------------------ |
| # TOTAL 8 0 100% |
| |
| assert "/coverage/__init__/" not in report |
| assert "/tests/modules/covmod1.py " in report |
| assert "/tests/zipmods.zip/covmodzip1.py " in report |
| assert "mycode.py " in report |
| assert self.last_line_squeezed(report) == "TOTAL 8 0 100%" |
| |
| def test_report_just_one(self) -> None: |
| # Try reporting just one module |
| self.make_mycode() |
| cov = coverage.Coverage() |
| self.start_import_stop(cov, "mycode") |
| report = self.get_report(cov, morfs=["mycode.py"]) |
| |
| # Name Stmts Miss Cover |
| # ------------------------------- |
| # mycode.py 4 0 100% |
| # ------------------------------- |
| # TOTAL 4 0 100% |
| assert self.line_count(report) == 5 |
| assert "/coverage/" not in report |
| assert "/tests/modules/covmod1.py " not in report |
| assert "/tests/zipmods.zip/covmodzip1.py " not in report |
| assert "mycode.py " in report |
| assert self.last_line_squeezed(report) == "TOTAL 4 0 100%" |
| |
| def test_report_wildcard(self) -> None: |
| # Try reporting using wildcards to get the modules. |
| self.make_mycode() |
| self.add_test_modules_to_pythonpath() |
| # Wildcard is handled by shell or cmdline.py, so use real commands |
| self.run_command("coverage run mycode.py") |
| report = self.report_from_command("coverage report my*.py") |
| |
| # Name Stmts Miss Cover |
| # ------------------------------- |
| # mycode.py 4 0 100% |
| # ------------------------------- |
| # TOTAL 4 0 100% |
| |
| assert self.line_count(report) == 5 |
| assert "/coverage/" not in report |
| assert "/tests/modules/covmod1.py " not in report |
| assert "/tests/zipmods.zip/covmodzip1.py " not in report |
| assert "mycode.py " in report |
| assert self.last_line_squeezed(report) == "TOTAL 4 0 100%" |
| |
| def test_report_omitting(self) -> None: |
| # Try reporting while omitting some modules |
| self.make_mycode() |
| cov = coverage.Coverage() |
| self.start_import_stop(cov, "mycode") |
| report = self.get_report(cov, omit=[f"{TESTS_DIR}/*", "*/site-packages/*"]) |
| |
| # Name Stmts Miss Cover |
| # ------------------------------- |
| # mycode.py 4 0 100% |
| # ------------------------------- |
| # TOTAL 4 0 100% |
| |
| assert self.line_count(report) == 5 |
| assert "/coverage/" not in report |
| assert "/tests/modules/covmod1.py " not in report |
| assert "/tests/zipmods.zip/covmodzip1.py " not in report |
| assert "mycode.py " in report |
| assert self.last_line_squeezed(report) == "TOTAL 4 0 100%" |
| |
| def test_report_including(self) -> None: |
| # Try reporting while including some modules |
| self.make_mycode() |
| cov = coverage.Coverage() |
| self.start_import_stop(cov, "mycode") |
| report = self.get_report(cov, include=["mycode*"]) |
| |
| # Name Stmts Miss Cover |
| # ------------------------------- |
| # mycode.py 4 0 100% |
| # ------------------------------- |
| # TOTAL 4 0 100% |
| |
| assert self.line_count(report) == 5 |
| assert "/coverage/" not in report |
| assert "/tests/modules/covmod1.py " not in report |
| assert "/tests/zipmods.zip/covmodzip1.py " not in report |
| assert "mycode.py " in report |
| assert self.last_line_squeezed(report) == "TOTAL 4 0 100%" |
| |
| def test_report_include_relative_files_and_path(self) -> None: |
| """ |
| Test that when relative_files is True and a relative path to a module |
| is included, coverage is reported for the module. |
| |
| Ref: https://github.com/nedbat/coveragepy/issues/1604 |
| """ |
| self.make_mycode() |
| self.make_file(".coveragerc", """\ |
| [run] |
| relative_files = true |
| """) |
| self.make_file("submodule/mycode.py", "import mycode") |
| |
| cov = coverage.Coverage() |
| self.start_import_stop(cov, "submodule/mycode") |
| report = self.get_report(cov, include="submodule/mycode.py") |
| |
| # Name Stmts Miss Cover |
| # --------------------------------------- |
| # submodule/mycode.py 1 0 100% |
| # --------------------------------------- |
| # TOTAL 1 0 100% |
| |
| assert "submodule/mycode.py " in report |
| assert self.last_line_squeezed(report) == "TOTAL 1 0 100%" |
| |
| def test_report_include_relative_files_and_wildcard_path(self) -> None: |
| self.make_mycode() |
| self.make_file(".coveragerc", """\ |
| [run] |
| relative_files = true |
| """) |
| self.make_file("submodule/mycode.py", "import nested.submodule.mycode") |
| self.make_file("nested/submodule/mycode.py", "import mycode") |
| |
| cov = coverage.Coverage() |
| self.start_import_stop(cov, "submodule/mycode") |
| report = self.get_report(cov, include="*/submodule/mycode.py") |
| |
| # Name Stmts Miss Cover |
| # ------------------------------------------------- |
| # nested/submodule/mycode.py 1 0 100% |
| # submodule/mycode.py 1 0 100% |
| # ------------------------------------------------- |
| # TOTAL 2 0 100% |
| |
| reported_files = [line.split()[0] for line in report.splitlines()[2:4]] |
| assert reported_files == [ |
| "nested/submodule/mycode.py", |
| "submodule/mycode.py", |
| ] |
| |
| def test_omit_files_here(self) -> None: |
| # https://github.com/nedbat/coveragepy/issues/1407 |
| self.make_file("foo.py", "") |
| self.make_file("bar/bar.py", "") |
| self.make_file("tests/test_baz.py", """\ |
| def test_foo(): |
| assert True |
| test_foo() |
| """) |
| self.run_command("coverage run --source=. --omit='./*.py' -m tests.test_baz") |
| report = self.report_from_command("coverage report") |
| |
| # Name Stmts Miss Cover |
| # --------------------------------------- |
| # tests/test_baz.py 3 0 100% |
| # --------------------------------------- |
| # TOTAL 3 0 100% |
| |
| assert self.line_count(report) == 5 |
| assert "foo" not in report |
| assert "bar" not in report |
| assert "tests/test_baz.py" in report |
| assert self.last_line_squeezed(report) == "TOTAL 3 0 100%" |
| |
| def test_run_source_vs_report_include(self) -> None: |
| # https://github.com/nedbat/coveragepy/issues/621 |
| self.make_file(".coveragerc", """\ |
| [run] |
| source = . |
| |
| [report] |
| include = mod/*,tests/* |
| """) |
| # It should be OK to use that configuration. |
| cov = coverage.Coverage() |
| with self.assert_warnings(cov, []): |
| with cov.collect(): |
| pass |
| |
| def test_run_omit_vs_report_omit(self) -> None: |
| # https://github.com/nedbat/coveragepy/issues/622 |
| # report:omit shouldn't clobber run:omit. |
| self.make_mycode() |
| self.make_file(".coveragerc", """\ |
| [run] |
| omit = */covmodzip1.py |
| |
| [report] |
| omit = */covmod1.py |
| """) |
| self.add_test_modules_to_pythonpath() |
| self.run_command("coverage run mycode.py") |
| |
| # Read the data written, to see that the right files have been omitted from running. |
| covdata = CoverageData() |
| covdata.read() |
| files = [os.path.basename(p) for p in covdata.measured_files()] |
| assert "covmod1.py" in files |
| assert "covmodzip1.py" not in files |
| |
| def test_report_branches(self) -> None: |
| self.make_file("mybranch.py", """\ |
| def branch(x): |
| if x: |
| print("x") |
| return x |
| branch(1) |
| """) |
| cov = coverage.Coverage(source=["."], branch=True) |
| self.start_import_stop(cov, "mybranch") |
| assert self.stdout() == 'x\n' |
| report = self.get_report(cov) |
| |
| # Name Stmts Miss Branch BrPart Cover |
| # ----------------------------------------------- |
| # mybranch.py 5 0 2 1 86% |
| # ----------------------------------------------- |
| # TOTAL 5 0 2 1 86% |
| assert self.line_count(report) == 5 |
| assert "mybranch.py " in report |
| assert self.last_line_squeezed(report) == "TOTAL 5 0 2 1 86%" |
| |
| def test_report_show_missing(self) -> None: |
| self.make_file("mymissing.py", """\ |
| def missing(x, y): |
| if x: |
| print("x") |
| return x |
| if y: |
| print("y") |
| try: |
| print("z") |
| 1/0 |
| print("Never!") |
| except ZeroDivisionError: |
| pass |
| return x |
| missing(0, 1) |
| """) |
| cov = coverage.Coverage(source=["."]) |
| self.start_import_stop(cov, "mymissing") |
| assert self.stdout() == 'y\nz\n' |
| report = self.get_report(cov, show_missing=True) |
| |
| # Name Stmts Miss Cover Missing |
| # -------------------------------------------- |
| # mymissing.py 14 3 79% 3-4, 10 |
| # -------------------------------------------- |
| # TOTAL 14 3 79% |
| |
| assert self.line_count(report) == 5 |
| squeezed = self.squeezed_lines(report) |
| assert squeezed[2] == "mymissing.py 14 3 79% 3-4, 10" |
| assert squeezed[4] == "TOTAL 14 3 79%" |
| |
| def test_report_show_missing_branches(self) -> None: |
| self.make_file("mybranch.py", """\ |
| def branch(x, y): |
| if x: |
| print("x") |
| if y: |
| print("y") |
| branch(1, 1) |
| """) |
| cov = coverage.Coverage(branch=True) |
| self.start_import_stop(cov, "mybranch") |
| assert self.stdout() == 'x\ny\n' |
| report = self.get_report(cov, show_missing=True) |
| |
| # Name Stmts Miss Branch BrPart Cover Missing |
| # ---------------------------------------------------------- |
| # mybranch.py 6 0 4 2 80% 2->4, 4->exit |
| # ---------------------------------------------------------- |
| # TOTAL 6 0 4 2 80% |
| |
| assert self.line_count(report) == 5 |
| squeezed = self.squeezed_lines(report) |
| assert squeezed[2] == "mybranch.py 6 0 4 2 80% 2->4, 4->exit" |
| assert squeezed[4] == "TOTAL 6 0 4 2 80%" |
| |
| def test_report_show_missing_branches_and_lines(self) -> None: |
| self.make_file("main.py", """\ |
| import mybranch |
| """) |
| self.make_file("mybranch.py", """\ |
| def branch(x, y, z): |
| if x: |
| print("x") |
| if y: |
| print("y") |
| if z: |
| if x and y: |
| print("z") |
| return x |
| branch(1, 1, 0) |
| """) |
| cov = coverage.Coverage(branch=True) |
| self.start_import_stop(cov, "main") |
| assert self.stdout() == 'x\ny\n' |
| report_lines = self.get_report(cov, squeeze=False, show_missing=True).splitlines() |
| |
| expected = [ |
| 'Name Stmts Miss Branch BrPart Cover Missing', |
| '---------------------------------------------------------', |
| 'main.py 1 0 0 0 100%', |
| 'mybranch.py 10 2 8 3 61% 2->4, 4->6, 7-8', |
| '---------------------------------------------------------', |
| 'TOTAL 11 2 8 3 63%', |
| ] |
| assert expected == report_lines |
| |
| def test_report_skip_covered_no_branches(self) -> None: |
| self.make_file("main.py", """\ |
| import not_covered |
| |
| def normal(): |
| print("z") |
| normal() |
| """) |
| self.make_file("not_covered.py", """\ |
| def not_covered(): |
| print("n") |
| """) |
| # --fail-under is handled by cmdline.py, use real commands. |
| out = self.run_command("coverage run main.py") |
| assert out == "z\n" |
| report = self.report_from_command("coverage report --skip-covered --fail-under=70") |
| |
| # Name Stmts Miss Cover |
| # ------------------------------------ |
| # not_covered.py 2 1 50% |
| # ------------------------------------ |
| # TOTAL 6 1 83% |
| # |
| # 1 file skipped due to complete coverage. |
| |
| assert self.line_count(report) == 7, report |
| squeezed = self.squeezed_lines(report) |
| assert squeezed[2] == "not_covered.py 2 1 50%" |
| assert squeezed[4] == "TOTAL 6 1 83%" |
| assert squeezed[6] == "1 file skipped due to complete coverage." |
| assert self.last_command_status == 0 |
| |
| def test_report_skip_covered_branches(self) -> None: |
| self.make_file("main.py", """\ |
| import not_covered, covered |
| |
| def normal(z): |
| if z: |
| print("z") |
| normal(True) |
| normal(False) |
| """) |
| self.make_file("not_covered.py", """\ |
| def not_covered(n): |
| if n: |
| print("n") |
| not_covered(True) |
| """) |
| self.make_file("covered.py", """\ |
| def foo(): |
| pass |
| foo() |
| """) |
| cov = coverage.Coverage(branch=True) |
| self.start_import_stop(cov, "main") |
| assert self.stdout() == "n\nz\n" |
| report = self.get_report(cov, skip_covered=True) |
| |
| # Name Stmts Miss Branch BrPart Cover |
| # -------------------------------------------------- |
| # not_covered.py 4 0 2 1 83% |
| # -------------------------------------------------- |
| # TOTAL 13 0 4 1 94% |
| # |
| # 2 files skipped due to complete coverage. |
| |
| assert self.line_count(report) == 7, report |
| squeezed = self.squeezed_lines(report) |
| assert squeezed[2] == "not_covered.py 4 0 2 1 83%" |
| assert squeezed[4] == "TOTAL 13 0 4 1 94%" |
| assert squeezed[6] == "2 files skipped due to complete coverage." |
| |
| def test_report_skip_covered_branches_with_totals(self) -> None: |
| self.make_file("main.py", """\ |
| import not_covered |
| import also_not_run |
| |
| def normal(z): |
| if z: |
| print("z") |
| normal(True) |
| normal(False) |
| """) |
| self.make_file("not_covered.py", """\ |
| def not_covered(n): |
| if n: |
| print("n") |
| not_covered(True) |
| """) |
| self.make_file("also_not_run.py", """\ |
| def does_not_appear_in_this_film(ni): |
| print("Ni!") |
| """) |
| cov = coverage.Coverage(branch=True) |
| self.start_import_stop(cov, "main") |
| assert self.stdout() == "n\nz\n" |
| report = self.get_report(cov, skip_covered=True) |
| |
| # Name Stmts Miss Branch BrPart Cover |
| # -------------------------------------------------- |
| # also_not_run.py 2 1 0 0 50% |
| # not_covered.py 4 0 2 1 83% |
| # -------------------------------------------------- |
| # TOTAL 13 1 4 1 88% |
| # |
| # 1 file skipped due to complete coverage. |
| |
| assert self.line_count(report) == 8, report |
| squeezed = self.squeezed_lines(report) |
| assert squeezed[2] == "also_not_run.py 2 1 0 0 50%" |
| assert squeezed[3] == "not_covered.py 4 0 2 1 83%" |
| assert squeezed[5] == "TOTAL 13 1 4 1 88%" |
| assert squeezed[7] == "1 file skipped due to complete coverage." |
| |
| def test_report_skip_covered_all_files_covered(self) -> None: |
| self.make_file("main.py", """\ |
| def foo(): |
| pass |
| foo() |
| """) |
| cov = coverage.Coverage(source=["."], branch=True) |
| self.start_import_stop(cov, "main") |
| assert self.stdout() == "" |
| report = self.get_report(cov, skip_covered=True) |
| |
| # Name Stmts Miss Branch BrPart Cover |
| # ----------------------------------------- |
| # TOTAL 3 0 0 0 100% |
| # |
| # 1 file skipped due to complete coverage. |
| |
| assert self.line_count(report) == 5, report |
| squeezed = self.squeezed_lines(report) |
| assert squeezed[4] == "1 file skipped due to complete coverage." |
| |
| report = self.get_report(cov, squeeze=False, skip_covered=True, output_format="markdown") |
| |
| # | Name | Stmts | Miss | Branch | BrPart | Cover | |
| # |---------- | -------: | -------: | -------: | -------: | -------: | |
| # | **TOTAL** | **3** | **0** | **0** | **0** | **100%** | |
| # |
| # 1 file skipped due to complete coverage. |
| |
| assert self.line_count(report) == 5, report |
| assert report.split("\n")[0] == ( |
| '| Name | Stmts | Miss | Branch | BrPart | Cover |' |
| ) |
| assert report.split("\n")[1] == ( |
| '|---------- | -------: | -------: | -------: | -------: | -------: |' |
| ) |
| assert report.split("\n")[2] == ( |
| '| **TOTAL** | **3** | **0** | **0** | **0** | **100%** |' |
| ) |
| squeezed = self.squeezed_lines(report) |
| assert squeezed[4] == "1 file skipped due to complete coverage." |
| |
| total = self.get_report(cov, output_format="total", skip_covered=True) |
| assert total == "100\n" |
| |
| def test_report_skip_covered_longfilename(self) -> None: |
| self.make_file("long_______________filename.py", """\ |
| def foo(): |
| pass |
| foo() |
| """) |
| cov = coverage.Coverage(source=["."], branch=True) |
| self.start_import_stop(cov, "long_______________filename") |
| assert self.stdout() == "" |
| report = self.get_report(cov, squeeze=False, skip_covered=True) |
| |
| # Name Stmts Miss Branch BrPart Cover |
| # ----------------------------------------- |
| # TOTAL 3 0 0 0 100% |
| # |
| # 1 file skipped due to complete coverage. |
| |
| assert self.line_count(report) == 5, report |
| lines = self.report_lines(report) |
| assert lines[0] == "Name Stmts Miss Branch BrPart Cover" |
| squeezed = self.squeezed_lines(report) |
| assert squeezed[4] == "1 file skipped due to complete coverage." |
| |
| def test_report_skip_covered_no_data(self) -> None: |
| cov = coverage.Coverage() |
| cov.load() |
| with pytest.raises(NoDataError, match="No data to report."): |
| self.get_report(cov, skip_covered=True) |
| self.assert_doesnt_exist(".coverage") |
| |
| def test_report_skip_empty(self) -> None: |
| self.make_file("main.py", """\ |
| import submodule |
| |
| def normal(): |
| print("z") |
| normal() |
| """) |
| self.make_file("submodule/__init__.py", "") |
| cov = coverage.Coverage() |
| self.start_import_stop(cov, "main") |
| assert self.stdout() == "z\n" |
| report = self.get_report(cov, skip_empty=True) |
| |
| # Name Stmts Miss Cover |
| # ------------------------------------ |
| # main.py 4 0 100% |
| # ------------------------------------ |
| # TOTAL 4 0 100% |
| # |
| # 1 empty file skipped. |
| |
| assert self.line_count(report) == 7, report |
| squeezed = self.squeezed_lines(report) |
| assert squeezed[2] == "main.py 4 0 100%" |
| assert squeezed[4] == "TOTAL 4 0 100%" |
| assert squeezed[6] == "1 empty file skipped." |
| |
| def test_report_skip_empty_no_data(self) -> None: |
| self.make_file("__init__.py", "") |
| cov = coverage.Coverage() |
| self.start_import_stop(cov, "__init__") |
| assert self.stdout() == "" |
| report = self.get_report(cov, skip_empty=True) |
| |
| # Name Stmts Miss Cover |
| # ------------------------------------ |
| # TOTAL 0 0 100% |
| # |
| # 1 empty file skipped. |
| |
| assert self.line_count(report) == 5, report |
| assert report.split("\n")[2] == "TOTAL 0 0 100%" |
| assert report.split("\n")[4] == "1 empty file skipped." |
| |
| def test_report_precision(self) -> None: |
| self.make_file(".coveragerc", """\ |
| [report] |
| precision = 3 |
| omit = */site-packages/* |
| """) |
| self.make_file("main.py", """\ |
| import not_covered, covered |
| |
| def normal(z): |
| if z: |
| print("z") |
| normal(True) |
| normal(False) |
| """) |
| self.make_file("not_covered.py", """\ |
| def not_covered(n): |
| if n: |
| print("n") |
| not_covered(True) |
| """) |
| self.make_file("covered.py", """\ |
| def foo(): |
| pass |
| foo() |
| """) |
| cov = coverage.Coverage(branch=True) |
| self.start_import_stop(cov, "main") |
| assert self.stdout() == "n\nz\n" |
| report = self.get_report(cov, squeeze=False) |
| |
| # Name Stmts Miss Branch BrPart Cover |
| # ------------------------------------------------------ |
| # covered.py 3 0 0 0 100.000% |
| # main.py 6 0 2 0 100.000% |
| # not_covered.py 4 0 2 1 83.333% |
| # ------------------------------------------------------ |
| # TOTAL 13 0 4 1 94.118% |
| |
| assert self.line_count(report) == 7, report |
| squeezed = self.squeezed_lines(report) |
| assert squeezed[2] == "covered.py 3 0 0 0 100.000%" |
| assert squeezed[4] == "not_covered.py 4 0 2 1 83.333%" |
| assert squeezed[6] == "TOTAL 13 0 4 1 94.118%" |
| |
| def test_report_precision_all_zero(self) -> None: |
| self.make_file("not_covered.py", """\ |
| def not_covered(n): |
| if n: |
| print("n") |
| """) |
| self.make_file("empty.py", "") |
| cov = coverage.Coverage(source=["."]) |
| self.start_import_stop(cov, "empty") |
| report = self.get_report(cov, precision=6, squeeze=False) |
| |
| # Name Stmts Miss Cover |
| # ----------------------------------------- |
| # empty.py 0 0 100.000000% |
| # not_covered.py 3 3 0.000000% |
| # ----------------------------------------- |
| # TOTAL 3 3 0.000000% |
| |
| assert self.line_count(report) == 6, report |
| assert "empty.py 0 0 100.000000%" in report |
| assert "not_covered.py 3 3 0.000000%" in report |
| assert "TOTAL 3 3 0.000000%" in report |
| |
| def test_report_module_docstrings(self) -> None: |
| self.make_file("main.py", """\ |
| # Line 1 |
| '''Line 2 docstring.''' |
| import other |
| a = 4 |
| """) |
| self.make_file("other.py", """\ |
| '''Line 1''' |
| a = 2 |
| """) |
| cov = coverage.Coverage() |
| self.start_import_stop(cov, "main") |
| report = self.get_report(cov) |
| |
| # Name Stmts Miss Cover |
| # ------------------------------ |
| # main.py 2 0 100% |
| # other.py 1 0 100% |
| # ------------------------------ |
| # TOTAL 3 0 100% |
| |
| assert self.line_count(report) == 6, report |
| squeezed = self.squeezed_lines(report) |
| assert squeezed[2] == "main.py 2 0 100%" |
| assert squeezed[3] == "other.py 1 0 100%" |
| assert squeezed[5] == "TOTAL 3 0 100%" |
| |
| def test_dotpy_not_python(self) -> None: |
| # We run a .py file, and when reporting, we can't parse it as Python. |
| # We should get an error message in the report. |
| |
| self.make_data_file(lines={"mycode.py": [1]}) |
| self.make_file("mycode.py", "This isn't python at all!") |
| cov = coverage.Coverage() |
| cov.load() |
| msg = r"Couldn't parse '.*[/\\]mycode.py' as Python source: '.*' at line 1" |
| with pytest.raises(NotPython, match=msg): |
| self.get_report(cov, morfs=["mycode.py"]) |
| |
| def test_accented_directory(self) -> None: |
| # Make a file with a non-ascii character in the directory name. |
| self.make_file("\xe2/accented.py", "print('accented')") |
| self.make_data_file(lines={abs_file("\xe2/accented.py"): [1]}) |
| report_expected = ( |
| "Name Stmts Miss Cover\n" + |
| "-----------------------------------\n" + |
| "\xe2/accented.py 1 0 100%\n" + |
| "-----------------------------------\n" + |
| "TOTAL 1 0 100%\n" |
| ) |
| cov = coverage.Coverage() |
| cov.load() |
| output = self.get_report(cov, squeeze=False) |
| assert output == report_expected |
| |
| def test_accenteddotpy_not_python(self) -> None: |
| # We run a .py file with a non-ascii name, and when reporting, we can't |
| # parse it as Python. We should get an error message in the report. |
| |
| self.make_data_file(lines={"accented\xe2.py": [1]}) |
| self.make_file("accented\xe2.py", "This isn't python at all!") |
| cov = coverage.Coverage() |
| cov.load() |
| msg = r"Couldn't parse '.*[/\\]accented\xe2.py' as Python source: '.*' at line 1" |
| with pytest.raises(NotPython, match=msg): |
| self.get_report(cov, morfs=["accented\xe2.py"]) |
| |
| def test_dotpy_not_python_ignored(self) -> None: |
| # We run a .py file, and when reporting, we can't parse it as Python, |
| # but we've said to ignore errors, so there's no error reported, |
| # though we still get a warning. |
| self.make_file("mycode.py", "This isn't python at all!") |
| self.make_data_file(lines={"mycode.py": [1]}) |
| cov = coverage.Coverage() |
| cov.load() |
| with pytest.raises(NoDataError, match="No data to report."): |
| with pytest.warns(Warning) as warns: |
| self.get_report(cov, morfs=["mycode.py"], ignore_errors=True) |
| assert_coverage_warnings( |
| warns, |
| re.compile(r"Couldn't parse Python file '.*[/\\]mycode.py' \(couldnt-parse\)"), |
| ) |
| |
| def test_dothtml_not_python(self) -> None: |
| # We run a .html file, and when reporting, we can't parse it as |
| # Python. Since it wasn't .py, no error is reported. |
| |
| # Pretend to run an html file. |
| self.make_file("mycode.html", "<h1>This isn't python at all!</h1>") |
| self.make_data_file(lines={"mycode.html": [1]}) |
| cov = coverage.Coverage() |
| cov.load() |
| with pytest.raises(NoDataError, match="No data to report."): |
| self.get_report(cov, morfs=["mycode.html"]) |
| |
| def test_report_no_extension(self) -> None: |
| self.make_file("xxx", """\ |
| # This is a python file though it doesn't look like it, like a main script. |
| a = b = c = d = 0 |
| a = 3 |
| b = 4 |
| if not b: |
| c = 6 |
| d = 7 |
| print(f"xxx: {a} {b} {c} {d}") |
| """) |
| self.make_data_file(lines={abs_file("xxx"): [2, 3, 4, 5, 7, 8]}) |
| cov = coverage.Coverage() |
| cov.load() |
| report = self.get_report(cov) |
| assert self.last_line_squeezed(report) == "TOTAL 7 1 86%" |
| |
| def test_report_with_chdir(self) -> None: |
| self.make_file("chdir.py", """\ |
| import os |
| print("Line One") |
| os.chdir("subdir") |
| print("Line Two") |
| print(open("something").read()) |
| """) |
| self.make_file("subdir/something", "hello") |
| out = self.run_command("coverage run --source=. chdir.py") |
| assert out == "Line One\nLine Two\nhello\n" |
| report = self.report_from_command("coverage report") |
| assert self.last_line_squeezed(report) == "TOTAL 5 0 100%" |
| report = self.report_from_command("coverage report --format=markdown") |
| assert self.last_line_squeezed(report) == "| **TOTAL** | **5** | **0** | **100%** |" |
| |
| def test_bug_156_file_not_run_should_be_zero(self) -> None: |
| # https://github.com/nedbat/coveragepy/issues/156 |
| self.make_file("mybranch.py", """\ |
| def branch(x): |
| if x: |
| print("x") |
| return x |
| branch(1) |
| """) |
| self.make_file("main.py", """\ |
| print("y") |
| """) |
| cov = coverage.Coverage(branch=True, source=["."]) |
| self.start_import_stop(cov, "main") |
| report = self.get_report(cov).splitlines() |
| assert "mybranch.py 5 5 2 0 0%" in report |
| |
| def run_TheCode_and_report_it(self) -> str: |
| """A helper for the next few tests.""" |
| cov = coverage.Coverage() |
| self.start_import_stop(cov, "TheCode") |
| return self.get_report(cov) |
| |
| def test_bug_203_mixed_case_listed_twice_with_rc(self) -> None: |
| self.make_file("TheCode.py", "a = 1\n") |
| self.make_file(".coveragerc", "[run]\nsource = .\n") |
| |
| report = self.run_TheCode_and_report_it() |
| assert "TheCode" in report |
| assert "thecode" not in report |
| |
| def test_bug_203_mixed_case_listed_twice(self) -> None: |
| self.make_file("TheCode.py", "a = 1\n") |
| |
| report = self.run_TheCode_and_report_it() |
| |
| assert "TheCode" in report |
| assert "thecode" not in report |
| |
| @pytest.mark.skipif(not env.WINDOWS, reason=".pyw files are only on Windows.") |
| def test_pyw_files(self) -> None: |
| # https://github.com/nedbat/coveragepy/issues/261 |
| self.make_file("start.pyw", """\ |
| import mod |
| print("In start.pyw") |
| """) |
| self.make_file("mod.pyw", """\ |
| print("In mod.pyw") |
| """) |
| cov = coverage.Coverage() |
| # start_import_stop can't import the .pyw file, so use the long form. |
| with cov.collect(): |
| import start # pylint: disable=import-error, unused-import |
| |
| report = self.get_report(cov) |
| assert "NoSource" not in report |
| report_lines = report.splitlines() |
| assert "start.pyw 2 0 100%" in report_lines |
| assert "mod.pyw 1 0 100%" in report_lines |
| |
| def test_tracing_pyc_file(self) -> None: |
| # Create two Python files. |
| self.make_file("mod.py", "a = 1\n") |
| self.make_file("main.py", "import mod\n") |
| |
| # Make one into a .pyc. |
| py_compile.compile("mod.py") |
| |
| # Run the program. |
| cov = coverage.Coverage() |
| self.start_import_stop(cov, "main") |
| |
| report_lines = self.get_report(cov).splitlines() |
| assert "mod.py 1 0 100%" in report_lines |
| report = self.get_report(cov, squeeze=False, output_format="markdown") |
| assert report.split("\n")[3] == "| mod.py | 1 | 0 | 100% |" |
| assert report.split("\n")[4] == "| **TOTAL** | **2** | **0** | **100%** |" |
| |
| def test_missing_py_file_during_run(self) -> None: |
| # Create two Python files. |
| self.make_file("mod.py", "a = 1\n") |
| self.make_file("main.py", "import mod\n") |
| |
| # Make one into a .pyc, and remove the .py. |
| py_compile.compile("mod.py") |
| os.remove("mod.py") |
| |
| # Python 3 puts the .pyc files in a __pycache__ directory, and will |
| # not import from there without source. It will import a .pyc from |
| # the source location though. |
| pycs = glob.glob("__pycache__/mod.*.pyc") |
| assert len(pycs) == 1 |
| os.rename(pycs[0], "mod.pyc") |
| |
| # Run the program. |
| cov = coverage.Coverage() |
| self.start_import_stop(cov, "main") |
| |
| # Put back the missing Python file. |
| self.make_file("mod.py", "a = 1\n") |
| report = self.get_report(cov).splitlines() |
| assert "mod.py 1 0 100%" in report |
| |
| def test_empty_files(self) -> None: |
| # Shows that empty files like __init__.py are listed as having zero |
| # statements, not one statement. |
| cov = coverage.Coverage(branch=True) |
| with cov.collect(): |
| import usepkgs # pylint: disable=import-error, unused-import |
| report = self.get_report(cov) |
| assert "tests/modules/pkg1/__init__.py 1 0 0 0 100%" in report |
| assert "tests/modules/pkg2/__init__.py 0 0 0 0 100%" in report |
| report = self.get_report(cov, squeeze=False, output_format="markdown") |
| # get_report() escapes backslash so we expect forward slash escaped |
| # underscore |
| assert "tests/modules/pkg1//_/_init/_/_.py " in report |
| assert "| 1 | 0 | 0 | 0 | 100% |" in report |
| assert "tests/modules/pkg2//_/_init/_/_.py " in report |
| assert "| 0 | 0 | 0 | 0 | 100% |" in report |
| |
| def test_markdown_with_missing(self) -> None: |
| self.make_file("mymissing.py", """\ |
| def missing(x, y): |
| if x: |
| print("x") |
| return x |
| if y: |
| print("y") |
| try: |
| print("z") |
| 1/0 |
| print("Never!") |
| except ZeroDivisionError: |
| pass |
| return x |
| missing(0, 1) |
| """) |
| cov = coverage.Coverage(source=["."]) |
| self.start_import_stop(cov, "mymissing") |
| assert self.stdout() == 'y\nz\n' |
| report = self.get_report(cov, squeeze=False, output_format="markdown", show_missing=True) |
| |
| # | Name | Stmts | Miss | Cover | Missing | |
| # |------------- | -------: | -------: | ------: | --------: | |
| # | mymissing.py | 14 | 3 | 79% | 3-4, 10 | |
| # | **TOTAL** | **14** | **3** | **79%** | | |
| assert self.line_count(report) == 4 |
| report_lines = report.split("\n") |
| assert report_lines[2] == "| mymissing.py | 14 | 3 | 79% | 3-4, 10 |" |
| assert report_lines[3] == "| **TOTAL** | **14** | **3** | **79%** | |" |
| |
| assert self.get_report(cov, output_format="total") == "79\n" |
| assert self.get_report(cov, output_format="total", precision=2) == "78.57\n" |
| assert self.get_report(cov, output_format="total", precision=4) == "78.5714\n" |
| |
| def test_bug_1524(self) -> None: |
| self.make_file("bug1524.py", """\ |
| class Mine: |
| @property |
| def thing(self) -> int: |
| return 17 |
| |
| print(Mine().thing) |
| """) |
| cov = coverage.Coverage() |
| self.start_import_stop(cov, "bug1524") |
| assert self.stdout() == "17\n" |
| report = self.get_report(cov) |
| report_lines = report.splitlines() |
| assert report_lines[2] == "bug1524.py 5 0 100%" |
| |
| |
| class ReportingReturnValueTest(CoverageTest): |
| """Tests of reporting functions returning values.""" |
| |
| def run_coverage(self) -> Coverage: |
| """Run coverage on doit.py and return the coverage object.""" |
| self.make_file("doit.py", """\ |
| a = 1 |
| b = 2 |
| c = 3 |
| d = 4 |
| if a > 10: |
| f = 6 |
| g = 7 |
| """) |
| |
| cov = coverage.Coverage() |
| self.start_import_stop(cov, "doit") |
| return cov |
| |
| def test_report(self) -> None: |
| cov = self.run_coverage() |
| val = cov.report(include="*/doit.py") |
| assert math.isclose(val, 6 / 7 * 100) |
| |
| def test_html(self) -> None: |
| cov = self.run_coverage() |
| val = cov.html_report(include="*/doit.py") |
| assert math.isclose(val, 6 / 7 * 100) |
| |
| def test_xml(self) -> None: |
| cov = self.run_coverage() |
| val = cov.xml_report(include="*/doit.py") |
| assert math.isclose(val, 6 / 7 * 100) |
| |
| |
| class SummaryReporterConfigurationTest(CoverageTest): |
| """Tests of SummaryReporter.""" |
| |
| def make_rigged_file(self, filename: str, stmts: int, miss: int) -> None: |
| """Create a file that will have specific results. |
| |
| `stmts` and `miss` are ints, the number of statements, and |
| missed statements that should result. |
| """ |
| run = stmts - miss - 1 |
| dont_run = miss |
| source = "" |
| source += "a = 1\n" * run |
| source += "if a == 99:\n" |
| source += " a = 2\n" * dont_run |
| self.make_file(filename, source) |
| |
| def get_summary_text(self, *options: tuple[str, TConfigValueIn]) -> str: |
| """Get text output from the SummaryReporter. |
| |
| The arguments are tuples: (name, value) for Coverage.set_option. |
| """ |
| self.make_rigged_file("file1.py", 339, 155) |
| self.make_rigged_file("file2.py", 13, 3) |
| self.make_rigged_file("file10.py", 234, 228) |
| self.make_file("doit.py", "import file1, file2, file10") |
| |
| cov = Coverage(source=["."], omit=["doit.py"]) |
| self.start_import_stop(cov, "doit") |
| for name, value in options: |
| cov.set_option(name, value) |
| printer = SummaryReporter(cov) |
| destination = io.StringIO() |
| printer.report([], destination) |
| return destination.getvalue() |
| |
| def test_test_data(self) -> None: |
| # We use our own test files as test data. Check that our assumptions |
| # about them are still valid. We want the three columns of numbers to |
| # sort in three different orders. |
| report = self.get_summary_text() |
| # Name Stmts Miss Cover |
| # ------------------------------ |
| # file1.py 339 155 54% |
| # file2.py 13 3 77% |
| # file10.py 234 228 3% |
| # ------------------------------ |
| # TOTAL 586 386 34% |
| lines = report.splitlines()[2:-2] |
| assert len(lines) == 3 |
| nums = [list(map(int, l.replace('%', '').split()[1:])) for l in lines] |
| # [ |
| # [339, 155, 54], |
| # [ 13, 3, 77], |
| # [234, 228, 3] |
| # ] |
| assert nums[1][0] < nums[2][0] < nums[0][0] |
| assert nums[1][1] < nums[0][1] < nums[2][1] |
| assert nums[2][2] < nums[0][2] < nums[1][2] |
| |
| def test_defaults(self) -> None: |
| """Run the report with no configuration options.""" |
| report = self.get_summary_text() |
| assert 'Missing' not in report |
| assert 'Branch' not in report |
| |
| def test_print_missing(self) -> None: |
| """Run the report printing the missing lines.""" |
| report = self.get_summary_text(('report:show_missing', True)) |
| assert 'Missing' in report |
| assert 'Branch' not in report |
| |
| def assert_ordering(self, text: str, *words: str) -> None: |
| """Assert that the `words` appear in order in `text`.""" |
| indexes = list(map(text.find, words)) |
| assert -1 not in indexes |
| msg = f"The words {words!r} don't appear in order in {text!r}" |
| assert indexes == sorted(indexes), msg |
| |
| def test_default_sort_report(self) -> None: |
| # Sort the text report by the default (Name) column. |
| report = self.get_summary_text() |
| self.assert_ordering(report, "file1.py", "file2.py", "file10.py") |
| |
| def test_sort_report_by_name(self) -> None: |
| # Sort the text report explicitly by the Name column. |
| report = self.get_summary_text(('report:sort', 'Name')) |
| self.assert_ordering(report, "file1.py", "file2.py", "file10.py") |
| |
| def test_sort_report_by_stmts(self) -> None: |
| # Sort the text report by the Stmts column. |
| report = self.get_summary_text(('report:sort', 'Stmts')) |
| self.assert_ordering(report, "file2.py", "file10.py", "file1.py") |
| |
| def test_sort_report_by_missing(self) -> None: |
| # Sort the text report by the Missing column. |
| report = self.get_summary_text(('report:sort', 'Miss')) |
| self.assert_ordering(report, "file2.py", "file1.py", "file10.py") |
| |
| def test_sort_report_by_cover(self) -> None: |
| # Sort the text report by the Cover column. |
| report = self.get_summary_text(('report:sort', 'Cover')) |
| self.assert_ordering(report, "file10.py", "file1.py", "file2.py") |
| |
| def test_sort_report_by_cover_plus(self) -> None: |
| # Sort the text report by the Cover column, including the explicit + sign. |
| report = self.get_summary_text(('report:sort', '+Cover')) |
| self.assert_ordering(report, "file10.py", "file1.py", "file2.py") |
| |
| def test_sort_report_by_cover_reversed(self) -> None: |
| # Sort the text report by the Cover column reversed. |
| report = self.get_summary_text(('report:sort', '-Cover')) |
| self.assert_ordering(report, "file2.py", "file1.py", "file10.py") |
| |
| def test_sort_report_by_invalid_option(self) -> None: |
| # Sort the text report by a nonsense column. |
| msg = "Invalid sorting option: 'Xyzzy'" |
| with pytest.raises(ConfigError, match=msg): |
| self.get_summary_text(('report:sort', 'Xyzzy')) |
| |
| def test_report_with_invalid_format(self) -> None: |
| # Ask for an invalid format. |
| msg = "Unknown report format choice: 'xyzzy'" |
| with pytest.raises(ConfigError, match=msg): |
| self.get_summary_text(('report:format', 'xyzzy')) |