# 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""" 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.summary import SummaryReporter 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): """Make the mycode.py file when needed.""" self.make_file("mycode.py", """\ import covmod1 import covmodzip1 a = 1 print('done') """) def test_report(self): 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): # 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): # Try reporting using wildcards to get the modules. self.make_mycode() # 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): # 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): # 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_run_source_vs_report_include(self): # 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, []): cov.start() cov.stop() # pragma: nested def test_run_omit_vs_report_omit(self): # 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.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): 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): 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% 3-4, 10 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): 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): 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): 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): 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): 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): 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) == 6, report squeezed = self.squeezed_lines(report) assert squeezed[5] == "1 file skipped due to complete coverage." def test_report_skip_covered_longfilename(self): 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) == 6, report lines = self.report_lines(report) assert lines[0] == "Name Stmts Miss Branch BrPart Cover" squeezed = self.squeezed_lines(report) assert squeezed[5] == "1 file skipped due to complete coverage." def test_report_skip_covered_no_data(self): cov = coverage.Coverage() cov.load() with pytest.raises(NoDataError, match="No data to report."): self.get_report(cov, skip_covered=True) def test_report_skip_empty(self): 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): 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 # ------------------------------------ # # 1 empty file skipped. assert self.line_count(report) == 6, report squeezed = self.squeezed_lines(report) assert squeezed[3] == "TOTAL 0 0 100%" assert squeezed[5] == "1 empty file skipped." def test_report_precision(self): 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) # 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_dotpy_not_python(self): # 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): # 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 @pytest.mark.skipif(env.JYTHON, reason="Jython doesn't like accented file names") def test_accenteddotpy_not_python(self): # 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): # 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): # 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", "