├── tests ├── modules │ ├── plugins │ │ ├── __init__.py │ │ ├── a_plugin.py │ │ └── another.py │ ├── aa │ │ ├── __init__.py │ │ ├── bb │ │ │ ├── cc │ │ │ │ ├── __init__.py │ │ │ │ └── cfile.py │ │ │ ├── __init__.py │ │ │ ├── bfile.py │ │ │ └── bfile.odd.py │ │ ├── afile.py │ │ ├── zfile.py │ │ ├── afile.odd.py │ │ └── bb.odd │ │ │ └── bfile.py │ ├── ambiguous │ │ ├── __init__.py │ │ └── pkg1 │ │ │ ├── __init__.py │ │ │ └── ambiguous.py │ ├── pkg1 │ │ ├── sub │ │ │ ├── __init__.py │ │ │ ├── __main__.py │ │ │ ├── ps1a.py │ │ │ └── runmod3.py │ │ ├── __init__.py │ │ ├── __main__.py │ │ ├── p1b.py │ │ ├── p1c.py │ │ ├── runmod2.py │ │ └── p1a.py │ ├── process_test │ │ ├── __init__.py │ │ └── try_execfile.py │ ├── pkg2 │ │ ├── __init__.py │ │ ├── p2a.py │ │ └── p2b.py │ ├── namespace_420 │ │ └── sub1 │ │ │ └── __init__.py │ ├── covmod1.py │ ├── runmod1.py │ └── usepkgs.py ├── zipsrc │ └── zip1 │ │ ├── __init__.py │ │ └── zip1.py ├── moremodules │ ├── othermods │ │ ├── __init__.py │ │ ├── sub │ │ │ ├── __init__.py │ │ │ ├── osa.py │ │ │ └── osb.py │ │ ├── othera.py │ │ └── otherb.py │ └── namespace_420 │ │ └── sub2 │ │ └── __init__.py ├── gold │ ├── annotate │ │ ├── multi │ │ │ ├── a │ │ │ │ ├── __init__.py,cover │ │ │ │ └── a.py,cover │ │ │ ├── b │ │ │ │ ├── __init__.py,cover │ │ │ │ └── b.py,cover │ │ │ └── multi.py,cover │ │ ├── anno_dir │ │ │ ├── d_80084bf2fba02475___init__.py,cover │ │ │ ├── d_b039179a8a4ce2c2___init__.py,cover │ │ │ ├── multi.py,cover │ │ │ ├── d_b039179a8a4ce2c2_b.py,cover │ │ │ └── d_80084bf2fba02475_a.py,cover │ │ ├── encodings │ │ │ └── utf8.py,cover │ │ ├── mae │ │ │ └── mae.py,cover │ │ └── white │ │ │ └── white.py,cover │ ├── html │ │ ├── styled │ │ │ └── extra.css │ │ ├── support │ │ │ ├── favicon_32.png │ │ │ ├── keybd_open.png │ │ │ └── keybd_closed.png │ │ ├── Makefile │ │ └── a │ │ │ └── index.html │ ├── testing │ │ ├── xml │ │ │ └── output.xml │ │ └── getty │ │ │ └── gettysburg.txt │ ├── xml │ │ ├── x_xml │ │ │ └── coverage.xml │ │ └── y_xml_branch │ │ │ └── coverage.xml │ └── README.rst ├── __init__.py ├── covmodzip1.py ├── plugin_config.py ├── stress_phystoken_dos.tok ├── stress_phystoken.tok ├── js │ └── index.html ├── test_collector.py ├── test_version.py ├── test_setup.py ├── plugin1.py ├── test_python.py ├── plugin2.py ├── test_report.py ├── osinfo.py └── test_mixins.py ├── .github ├── FUNDING.yml ├── SECURITY.md ├── CODE_OF_CONDUCT.md ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── dependency-review.yml │ ├── codeql-analysis.yml │ ├── quality.yml │ ├── python-nightly.yml │ └── testsuite.yml ├── ci ├── README.txt ├── ghrel_template.md.j2 ├── trigger_build_kits.py ├── comment_on_fixes.py ├── download_gha_artifacts.py └── parse_relnotes.py ├── coverage ├── py.typed ├── htmlfiles │ ├── favicon_32.png │ ├── keybd_open.png │ └── keybd_closed.png ├── __main__.py ├── bytecode.py ├── ctracer │ ├── filedisp.h │ ├── stats.h │ ├── datastack.c │ ├── datastack.h │ ├── module.c │ ├── tracer.h │ ├── util.h │ └── filedisp.c ├── tracer.pyi ├── __init__.py ├── exceptions.py ├── version.py ├── disposition.py ├── fullcoverage │ └── encodings.py └── context.py ├── doc ├── media │ ├── sleepy-snake-600.png │ ├── sleepy-snake-circle-150.png │ ├── Tidelift_Logos_RGB_Tidelift_Shorthand_On-White.png │ └── Tidelift_Logos_RGB_Tidelift_Shorthand_On-White_small.png ├── sample_html │ ├── favicon_32.png │ ├── keybd_open.png │ ├── keybd_closed.png │ └── status.json ├── api_coverage.rst ├── api_coveragedata.rst ├── api_exceptions.rst ├── requirements.in ├── sleepy.rst ├── api_plugin.rst ├── api_module.rst ├── _static │ └── coverage.css ├── api.rst ├── trouble.rst ├── install.rst ├── plugins.rst └── dict.txt ├── lab ├── treetopy.sh ├── README.txt ├── show_ast.py ├── branch_trace.py ├── bpo_prelude.py ├── show_platform.py ├── parse_all.py ├── platform_info.py ├── benchmark │ ├── empty.py │ └── run.py ├── run_trace.py ├── find_class.py ├── new-data.js ├── coverage-03.dtd ├── coverage-04.dtd ├── compare_times.sh ├── select_contexts.py ├── extract_code.py ├── branches.py ├── hack_pyc.py ├── goals.py └── notes │ └── pypy-738-decorated-functions.txt ├── requirements ├── mypy.in ├── pip-tools.in ├── pip.in ├── light-threads.in ├── tox.in ├── kit.in ├── pytest.in ├── dev.in ├── pins.pip ├── pip.pip ├── pip-tools.pip ├── pytest.pip └── tox.pip ├── .git-blame-ignore-revs ├── .treerc ├── __main__.py ├── .readthedocs.yml ├── NOTICE.txt ├── .gitignore ├── .editorconfig ├── setup.cfg ├── pyproject.toml ├── MANIFEST.in ├── metacov.ini ├── CONTRIBUTORS.txt ├── howto.txt └── tox.ini /tests/modules/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/zipsrc/zip1/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/modules/aa/__init__.py: -------------------------------------------------------------------------------- 1 | # aa 2 | -------------------------------------------------------------------------------- /tests/modules/aa/bb/cc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/modules/ambiguous/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/modules/pkg1/sub/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/modules/process_test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/modules/aa/afile.py: -------------------------------------------------------------------------------- 1 | # afile.py 2 | -------------------------------------------------------------------------------- /tests/modules/aa/bb/__init__.py: -------------------------------------------------------------------------------- 1 | # bb 2 | -------------------------------------------------------------------------------- /tests/modules/aa/bb/bfile.py: -------------------------------------------------------------------------------- 1 | # bfile.py 2 | -------------------------------------------------------------------------------- /tests/modules/aa/zfile.py: -------------------------------------------------------------------------------- 1 | # zfile.py 2 | -------------------------------------------------------------------------------- /tests/moremodules/othermods/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/moremodules/othermods/sub/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/gold/annotate/multi/a/__init__.py,cover: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/gold/annotate/multi/b/__init__.py,cover: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/modules/aa/afile.odd.py: -------------------------------------------------------------------------------- 1 | # afile.odd.py 2 | -------------------------------------------------------------------------------- /tests/modules/aa/bb.odd/bfile.py: -------------------------------------------------------------------------------- 1 | # bfile.py 2 | -------------------------------------------------------------------------------- /tests/modules/aa/bb/cc/cfile.py: -------------------------------------------------------------------------------- 1 | # cfile.py 2 | -------------------------------------------------------------------------------- /tests/modules/aa/bb/bfile.odd.py: -------------------------------------------------------------------------------- 1 | # bfile.odd.py 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: nedbat 2 | tidelift: pypi/coverage 3 | -------------------------------------------------------------------------------- /ci/README.txt: -------------------------------------------------------------------------------- 1 | Files to support continuous integration systems. 2 | -------------------------------------------------------------------------------- /tests/gold/annotate/anno_dir/d_80084bf2fba02475___init__.py,cover: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/gold/annotate/anno_dir/d_b039179a8a4ce2c2___init__.py,cover: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/modules/ambiguous/pkg1/__init__.py: -------------------------------------------------------------------------------- 1 | print("Ambiguous pkg1") 2 | -------------------------------------------------------------------------------- /tests/modules/ambiguous/pkg1/ambiguous.py: -------------------------------------------------------------------------------- 1 | amb = 1 2 | amb = 2 3 | -------------------------------------------------------------------------------- /tests/gold/annotate/multi/b/b.py,cover: -------------------------------------------------------------------------------- 1 | > def b(x): 2 | > print "x is %s" % x 3 | -------------------------------------------------------------------------------- /coverage/py.typed: -------------------------------------------------------------------------------- 1 | # Marker file for PEP 561 to indicate that this package has type hints. 2 | -------------------------------------------------------------------------------- /tests/gold/html/styled/extra.css: -------------------------------------------------------------------------------- 1 | /* Doesn't matter what goes in here, it gets copied. */ 2 | -------------------------------------------------------------------------------- /tests/gold/annotate/multi/multi.py,cover: -------------------------------------------------------------------------------- 1 | > import a.a 2 | > import b.b 3 | 4 | > a.a.a(1) 5 | > b.b.b(2) 6 | -------------------------------------------------------------------------------- /tests/modules/pkg1/__init__.py: -------------------------------------------------------------------------------- 1 | # A simple package for testing with. 2 | print(f"pkg1.__init__: {__name__}") 3 | -------------------------------------------------------------------------------- /doc/media/sleepy-snake-600.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/george0st/coveragepy/HEAD/doc/media/sleepy-snake-600.png -------------------------------------------------------------------------------- /doc/sample_html/favicon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/george0st/coveragepy/HEAD/doc/sample_html/favicon_32.png -------------------------------------------------------------------------------- /doc/sample_html/keybd_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/george0st/coveragepy/HEAD/doc/sample_html/keybd_open.png -------------------------------------------------------------------------------- /tests/gold/annotate/anno_dir/multi.py,cover: -------------------------------------------------------------------------------- 1 | > import a.a 2 | > import b.b 3 | 4 | > a.a.a(1) 5 | > b.b.b(2) 6 | -------------------------------------------------------------------------------- /coverage/htmlfiles/favicon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/george0st/coveragepy/HEAD/coverage/htmlfiles/favicon_32.png -------------------------------------------------------------------------------- /coverage/htmlfiles/keybd_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/george0st/coveragepy/HEAD/coverage/htmlfiles/keybd_open.png -------------------------------------------------------------------------------- /doc/sample_html/keybd_closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/george0st/coveragepy/HEAD/doc/sample_html/keybd_closed.png -------------------------------------------------------------------------------- /tests/gold/annotate/anno_dir/d_b039179a8a4ce2c2_b.py,cover: -------------------------------------------------------------------------------- 1 | > def b(x): 2 | > msg = f"x is {x}" 3 | > print(msg) 4 | -------------------------------------------------------------------------------- /coverage/htmlfiles/keybd_closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/george0st/coveragepy/HEAD/coverage/htmlfiles/keybd_closed.png -------------------------------------------------------------------------------- /doc/media/sleepy-snake-circle-150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/george0st/coveragepy/HEAD/doc/media/sleepy-snake-circle-150.png -------------------------------------------------------------------------------- /tests/gold/html/support/favicon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/george0st/coveragepy/HEAD/tests/gold/html/support/favicon_32.png -------------------------------------------------------------------------------- /tests/gold/html/support/keybd_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/george0st/coveragepy/HEAD/tests/gold/html/support/keybd_open.png -------------------------------------------------------------------------------- /tests/gold/html/support/keybd_closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/george0st/coveragepy/HEAD/tests/gold/html/support/keybd_closed.png -------------------------------------------------------------------------------- /tests/modules/pkg1/__main__.py: -------------------------------------------------------------------------------- 1 | # Used in the tests for PyRunner 2 | import sys 3 | print("pkg1.__main__: passed %s" % sys.argv[1]) 4 | -------------------------------------------------------------------------------- /tests/gold/annotate/encodings/utf8.py,cover: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This comment has an accent: é 3 | 4 | > print("spam eggs") 5 | -------------------------------------------------------------------------------- /tests/modules/pkg1/sub/__main__.py: -------------------------------------------------------------------------------- 1 | # Used in the tests for PyRunner 2 | import sys 3 | print("pkg1.sub.__main__: passed %s" % sys.argv[1]) 4 | -------------------------------------------------------------------------------- /tests/gold/annotate/multi/a/a.py,cover: -------------------------------------------------------------------------------- 1 | > def a(x): 2 | > if x == 1: 3 | > print "x is 1" 4 | ! else: 5 | ! print "x is not 1" 6 | -------------------------------------------------------------------------------- /tests/modules/pkg2/__init__.py: -------------------------------------------------------------------------------- 1 | # This is an __init__.py file, with no executable statements in it. 2 | # This comment shouldn't confuse the parser. 3 | -------------------------------------------------------------------------------- /doc/media/Tidelift_Logos_RGB_Tidelift_Shorthand_On-White.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/george0st/coveragepy/HEAD/doc/media/Tidelift_Logos_RGB_Tidelift_Shorthand_On-White.png -------------------------------------------------------------------------------- /tests/gold/annotate/anno_dir/d_80084bf2fba02475_a.py,cover: -------------------------------------------------------------------------------- 1 | > def a(x): 2 | > if x == 1: 3 | > print("x is 1") 4 | ! else: 5 | ! print("x is not 1") 6 | -------------------------------------------------------------------------------- /doc/media/Tidelift_Logos_RGB_Tidelift_Shorthand_On-White_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/george0st/coveragepy/HEAD/doc/media/Tidelift_Logos_RGB_Tidelift_Shorthand_On-White_small.png -------------------------------------------------------------------------------- /tests/gold/testing/xml/output.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Goodbye 5 | 6 | 7 | -------------------------------------------------------------------------------- /lab/treetopy.sh: -------------------------------------------------------------------------------- 1 | # Turn a tree of Python files into a series of make_file calls. 2 | for f in **/*.py; do 3 | echo 'make_file("'$1$f'", """\\' 4 | sed -e 's/^/ /' <$f 5 | echo ' """)' 6 | done 7 | -------------------------------------------------------------------------------- /tests/modules/pkg1/p1b.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | x = 1 5 | y = 2 6 | z = 3 7 | -------------------------------------------------------------------------------- /tests/modules/pkg1/p1c.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | a = 1 5 | b = 2 6 | c = 3 7 | -------------------------------------------------------------------------------- /tests/modules/pkg2/p2a.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | q = 1 5 | r = 1 6 | s = 1 7 | -------------------------------------------------------------------------------- /tests/modules/pkg2/p2b.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | t = 1 5 | u = 1 6 | v = 1 7 | -------------------------------------------------------------------------------- /tests/modules/pkg1/sub/ps1a.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | d = 1 5 | e = 2 6 | f = 3 7 | -------------------------------------------------------------------------------- /tests/moremodules/othermods/othera.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | o = 1 5 | p = 2 6 | -------------------------------------------------------------------------------- /tests/moremodules/othermods/otherb.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | q = 3 5 | r = 4 6 | -------------------------------------------------------------------------------- /tests/moremodules/othermods/sub/osa.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | s = 5 5 | t = 6 6 | -------------------------------------------------------------------------------- /tests/moremodules/othermods/sub/osb.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | u = 7 5 | v = 8 6 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | """Automated tests. Run with pytest.""" 5 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Disclosures 2 | 3 | To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). 4 | Tidelift will coordinate the fix and disclosure with maintainers. 5 | -------------------------------------------------------------------------------- /tests/gold/annotate/mae/mae.py,cover: -------------------------------------------------------------------------------- 1 | > def f(x): 2 | > if x == 1: 3 | > print("1") 4 | > else: 5 | > print("2") 6 | 7 | > if f(1): 8 | ! print("nope") 9 | > if f(2): 10 | ! print("nope") 11 | -------------------------------------------------------------------------------- /tests/modules/namespace_420/sub1/__init__.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | sub1 = "namespace_420 sub1" 5 | -------------------------------------------------------------------------------- /tests/moremodules/namespace_420/sub2/__init__.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | sub2 = "namespace_420 sub2" 5 | -------------------------------------------------------------------------------- /tests/modules/covmod1.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | # covmod1.py: Simplest module for testing. 5 | i = 1 6 | i += 1 7 | -------------------------------------------------------------------------------- /tests/zipsrc/zip1/zip1.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | # My zip file! 5 | 6 | lighter = "Zippo" 7 | says = "coo-coo cachoo" 8 | -------------------------------------------------------------------------------- /tests/gold/testing/getty/gettysburg.txt: -------------------------------------------------------------------------------- 1 | Four score and seven years ago our fathers brought forth upon this continent, a 2 | new nation, conceived in Liberty, and dedicated to the proposition that all men 3 | are created equal. 4 | 11/19/1863, Gettysburg, Pennsylvania 5 | -------------------------------------------------------------------------------- /requirements/mypy.in: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | -c pins.pip 5 | 6 | # So that we have pytest types. 7 | -r pytest.pip 8 | 9 | mypy 10 | -------------------------------------------------------------------------------- /ci/ghrel_template.md.j2: -------------------------------------------------------------------------------- 1 | 2 | {{body}} 3 | 4 | :arrow_right:  PyPI page: [coverage {{version}}](https://pypi.org/project/coverage/{{version}}). 5 | :arrow_right:  To install: `python3 -m pip install coverage=={{version}}` 6 | -------------------------------------------------------------------------------- /tests/modules/runmod1.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | # Used in the tests for PyRunner 5 | import sys 6 | print("runmod1: passed %s" % sys.argv[1]) 7 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Commits to ignore when doing git-blame. 2 | 3 | # 2023-01-05 style: use good style for annotated defaults parameters 4 | 78444f4c06df6a634fa67dd99ee7c07b6b633d9e 5 | 6 | # 2023-01-06 style(perf): blacken lab/benchmark.py 7 | bf6c12f5da54db7c5c0cc47cbf22c70f686e8236 8 | -------------------------------------------------------------------------------- /requirements/pip-tools.in: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | -c pins.pip 5 | 6 | # "make upgrade" turns this into requirements/pip-tools.pip. 7 | 8 | pip-tools 9 | -------------------------------------------------------------------------------- /tests/modules/pkg1/runmod2.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | # Used in the tests for PyRunner 5 | import sys 6 | print("runmod2: passed %s" % sys.argv[1]) 7 | -------------------------------------------------------------------------------- /tests/modules/pkg1/sub/runmod3.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | # Used in the tests for PyRunner 5 | import sys 6 | print("runmod3: passed %s" % sys.argv[1]) 7 | -------------------------------------------------------------------------------- /lab/README.txt: -------------------------------------------------------------------------------- 1 | The lab directory is not part of the installed coverage.py code. These programs 2 | are tools I have used while diagnosing problems, investigating functionality, 3 | and so on. They are not guaranteed to work, or to be suitable for any given 4 | purpose. If you find them useful, enjoy! 5 | -------------------------------------------------------------------------------- /coverage/__main__.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | """Coverage.py's main entry point.""" 5 | 6 | import sys 7 | from coverage.cmdline import main 8 | sys.exit(main()) 9 | -------------------------------------------------------------------------------- /requirements/pip.in: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | -c pins.pip 5 | 6 | # "make upgrade" turns this into requirements/pip.pip. 7 | 8 | pip 9 | setuptools 10 | virtualenv 11 | -------------------------------------------------------------------------------- /tests/modules/pkg1/p1a.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | import os, sys 5 | 6 | # Invoke functions in os and sys so we can see if we measure code there. 7 | x = sys.getfilesystemencoding() 8 | y = os.getcwd() 9 | -------------------------------------------------------------------------------- /lab/show_ast.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | """Dump the AST of a file.""" 5 | 6 | import ast 7 | import sys 8 | 9 | from coverage.parser import ast_dump 10 | 11 | ast_dump(ast.parse(open(sys.argv[1], "rb").read())) 12 | -------------------------------------------------------------------------------- /lab/branch_trace.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | pairs = set() 4 | last = -1 5 | 6 | def trace(frame, event, arg): 7 | global last 8 | if event == "line": 9 | this = frame.f_lineno 10 | pairs.add((last, this)) 11 | last = this 12 | return trace 13 | 14 | code = open(sys.argv[1]).read() 15 | sys.settrace(trace) 16 | exec(code) 17 | print(sorted(pairs)) 18 | -------------------------------------------------------------------------------- /tests/modules/usepkgs.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | import pkg1.p1a, pkg1.p1b, pkg1.sub 5 | import pkg2.p2a, pkg2.p2b 6 | import othermods.othera, othermods.otherb 7 | import othermods.sub.osa, othermods.sub.osb 8 | import ambiguous, ambiguous.pkg1.ambiguous 9 | -------------------------------------------------------------------------------- /requirements/light-threads.in: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | -c pins.pip 5 | 6 | # The light-threads packages we test against. 7 | 8 | eventlet 9 | gevent 10 | greenlet 11 | 12 | # gevent needs cffi, but only on Windows, not sure why. 13 | cffi>=1.12.2 14 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Treat each other well 2 | 3 | Everyone participating in the coverage.py project, and in particular in the 4 | issue tracker, pull requests, and social media activity, is expected to treat 5 | other people with respect and to follow the guidelines articulated in the 6 | [Python Community Code of Conduct][psf_coc]. 7 | 8 | [psf_coc]: https://www.python.org/psf/codeofconduct/ 9 | -------------------------------------------------------------------------------- /.treerc: -------------------------------------------------------------------------------- 1 | # .treerc for coverage: controls what files get searched. 2 | [default] 3 | ignore = 4 | .treerc 5 | build 6 | htmlcov 7 | html0 8 | .tox* 9 | .coverage* .metacov 10 | *.min.js style.css 11 | gold 12 | sample_html sample_html_beta 13 | *.so *.pyd 14 | *.gz *.zip 15 | _build _spell 16 | *.egg *.egg-info 17 | .mypy_cache 18 | tmp 19 | -------------------------------------------------------------------------------- /tests/covmodzip1.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | # Module-level docstrings are counted differently in different versions of Python, 5 | # so don't add one here. 6 | # pylint: disable=missing-module-docstring 7 | 8 | # covmodzip.py: for putting into a zip file. 9 | j = 1 10 | j += 1 11 | -------------------------------------------------------------------------------- /doc/api_coverage.rst: -------------------------------------------------------------------------------- 1 | .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | .. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | .. _api_coverage: 5 | 6 | The Coverage class 7 | ------------------ 8 | 9 | .. module:: coverage 10 | :noindex: 11 | 12 | .. autoclass:: Coverage 13 | :members: 14 | :exclude-members: sys_info 15 | :special-members: __init__ 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # From: 2 | # https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/keeping-your-actions-up-to-date-with-dependabot 3 | # Set update schedule for GitHub Actions 4 | 5 | version: 2 6 | updates: 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | # Check for updates to GitHub Actions every weekday 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /doc/api_coveragedata.rst: -------------------------------------------------------------------------------- 1 | .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | .. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | .. _api_coveragedata: 5 | 6 | The CoverageData class 7 | ---------------------- 8 | 9 | .. versionadded:: 4.0 10 | 11 | .. module:: coverage 12 | :noindex: 13 | 14 | .. autoclass:: CoverageData 15 | :members: 16 | :special-members: __init__ 17 | -------------------------------------------------------------------------------- /lab/bpo_prelude.py: -------------------------------------------------------------------------------- 1 | import linecache, sys 2 | 3 | def trace(frame, event, arg): 4 | # The weird globals here is to avoid a NameError on shutdown... 5 | if frame.f_code.co_filename == globals().get("__file__"): 6 | lineno = frame.f_lineno 7 | line = linecache.getline(__file__, lineno).rstrip() 8 | print("{} {}: {}".format(event[:4], lineno, line)) 9 | return trace 10 | 11 | print(sys.version) 12 | sys.settrace(trace) 13 | 14 | -------------------------------------------------------------------------------- /__main__.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | """Be able to execute coverage.py by pointing Python at a working tree.""" 5 | 6 | import runpy 7 | import os 8 | 9 | PKG = 'coverage' 10 | 11 | run_globals = runpy.run_module(PKG, run_name='__main__', alter_sys=True) 12 | executed = os.path.splitext(os.path.basename(run_globals['__file__']))[0] 13 | -------------------------------------------------------------------------------- /tests/modules/plugins/a_plugin.py: -------------------------------------------------------------------------------- 1 | """A plugin for tests to reference.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import Any 6 | 7 | from coverage import CoveragePlugin 8 | from coverage.plugin_support import Plugins 9 | 10 | 11 | class Plugin(CoveragePlugin): 12 | pass 13 | 14 | 15 | def coverage_init( 16 | reg: Plugins, 17 | options: Any, # pylint: disable=unused-argument 18 | ) -> None: 19 | reg.add_file_tracer(Plugin()) 20 | -------------------------------------------------------------------------------- /doc/api_exceptions.rst: -------------------------------------------------------------------------------- 1 | .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | .. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | .. _api_exceptions: 5 | 6 | Coverage exceptions 7 | ------------------- 8 | 9 | .. module:: coverage.exceptions 10 | 11 | .. autoclass:: CoverageException 12 | 13 | .. automodule:: coverage.exceptions 14 | :noindex: 15 | :members: 16 | :exclude-members: CoverageException 17 | -------------------------------------------------------------------------------- /doc/requirements.in: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | # PyPI requirements input for building documentation for coverage.py 5 | # "make upgrade" turns this into doc/requirements.pip 6 | 7 | -c ../requirements/pins.pip 8 | 9 | cogapp 10 | #doc8 11 | pyenchant 12 | scriv # for writing GitHub releases 13 | sphinx 14 | sphinx-autobuild 15 | sphinx_rtd_theme 16 | #sphinx-tabs 17 | sphinxcontrib-restbuilder 18 | sphinxcontrib-spelling 19 | -------------------------------------------------------------------------------- /lab/show_platform.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | import platform 5 | import types 6 | 7 | for n in dir(platform): 8 | if n.startswith("_"): 9 | continue 10 | v = getattr(platform, n) 11 | if isinstance(v, types.ModuleType): 12 | continue 13 | if callable(v): 14 | try: 15 | v = v() 16 | n += "()" 17 | except: 18 | continue 19 | print(f"{n:>30}: {v!r}") 20 | -------------------------------------------------------------------------------- /requirements/tox.in: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | -c pins.pip 5 | 6 | # Just install tox, which will then install more things. 7 | # "make upgrade" turns this into requirements/tox.pip. 8 | 9 | tox 10 | tox-gh 11 | 12 | # Tox has a windows-only dependency on colorama: 13 | # https://github.com/tox-dev/tox/blob/master/setup.cfg#L44 14 | # colorama>=0.4.1 ;platform_system=="Windows" 15 | # We copy it here so it can get pinned. 16 | colorama>=0.4.1 17 | -------------------------------------------------------------------------------- /requirements/kit.in: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | -c pins.pip 5 | 6 | # Things needed to make distribution kits. 7 | # "make upgrade" turns this into requirements/kit.pip. 8 | 9 | auditwheel 10 | build 11 | cibuildwheel 12 | setuptools 13 | wheel 14 | 15 | # Build has a windows-only dependency on colorama: 16 | # https://github.com/pypa/build/blob/main/setup.cfg#L32 17 | # colorama;os_name == "nt" 18 | # We copy it here so it can get pinned. 19 | colorama 20 | -------------------------------------------------------------------------------- /requirements/pytest.in: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | -c pins.pip 5 | 6 | # The pytest specifics used by coverage.py 7 | # "make upgrade" turns this into requirements/pytest.pip. 8 | 9 | flaky 10 | hypothesis 11 | pytest 12 | pytest-xdist 13 | 14 | # Pytest has a windows-only dependency on colorama: 15 | # https://github.com/pytest-dev/pytest/blob/main/setup.cfg#L49 16 | # colorama;sys_platform=="win32" 17 | # We copy it here so it can get pinned. 18 | colorama 19 | -------------------------------------------------------------------------------- /lab/parse_all.py: -------------------------------------------------------------------------------- 1 | """Parse every Python file in a tree.""" 2 | 3 | import os 4 | import sys 5 | 6 | from coverage.parser import PythonParser 7 | 8 | for root, dirnames, filenames in os.walk(sys.argv[1]): 9 | for filename in filenames: 10 | if filename.endswith(".py"): 11 | filename = os.path.join(root, filename) 12 | print(f":: {filename}") 13 | try: 14 | par = PythonParser(filename=filename) 15 | par.parse_source() 16 | par.arcs() 17 | except Exception as exc: 18 | print(f" ** {exc}") 19 | -------------------------------------------------------------------------------- /tests/modules/plugins/another.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | """A plugin for tests to reference.""" 5 | 6 | from __future__ import annotations 7 | 8 | from typing import Any 9 | 10 | from coverage import CoveragePlugin 11 | from coverage.plugin_support import Plugins 12 | 13 | class Plugin(CoveragePlugin): 14 | pass 15 | 16 | 17 | def coverage_init( 18 | reg: Plugins, 19 | options: Any, # pylint: disable=unused-argument 20 | ) -> None: 21 | reg.add_file_tracer(Plugin()) 22 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | # 4 | # ReadTheDocs configuration. 5 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html 6 | 7 | version: 2 8 | 9 | sphinx: 10 | builder: html 11 | configuration: doc/conf.py 12 | 13 | # Build all the formats 14 | formats: 15 | - epub 16 | - htmlzip 17 | - pdf 18 | 19 | python: 20 | # PYVERSIONS 21 | version: 3.7 22 | install: 23 | - requirements: doc/requirements.pip 24 | - method: pip 25 | path: . 26 | system_packages: false 27 | -------------------------------------------------------------------------------- /requirements/dev.in: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | # Requirements input for doing local development work on coverage.py. 5 | # "make upgrade" turns this into requirements/dev.pip. 6 | 7 | -c pins.pip 8 | -r pip.pip 9 | 10 | # PyPI requirements for running tests. 11 | -r tox.pip 12 | -r pytest.pip 13 | 14 | # for linting. 15 | check-manifest 16 | cogapp 17 | greenlet 18 | pylint 19 | readme_renderer 20 | 21 | # for kitting. 22 | requests 23 | twine 24 | libsass 25 | 26 | # Just so I have a debugger if I want it. 27 | pudb 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | # https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository 5 | 6 | blank_issues_enabled: false 7 | contact_links: 8 | - name: Frequently Asked Questions 9 | url: https://coverage.readthedocs.io/en/latest/faq.html 10 | about: Some common problems are described here. 11 | - name: Tidelift security contact 12 | url: https://tidelift.com/security 13 | about: Please report security vulnerabilities here. 14 | -------------------------------------------------------------------------------- /requirements/pins.pip: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | # Version pins, for use as a constraints file. 5 | 6 | # docutils has been going through some turmoil. Different packages require it, 7 | # but have different pins. This seems to satisfy them all: 8 | #docutils>=0.17,<0.18 9 | 10 | # Setuptools became stricter about version number syntax. But it shouldn't be 11 | # checking the Python version like that, should it? 12 | # https://github.com/pypa/packaging/issues/678 13 | # https://github.com/nedbat/coveragepy/issues/1556 14 | setuptools<66.0.0 15 | -------------------------------------------------------------------------------- /tests/gold/annotate/white/white.py,cover: -------------------------------------------------------------------------------- 1 | # A test case sent to me by Steve White 2 | 3 | > def f(self): 4 | ! if self==1: 5 | ! pass 6 | ! elif self.m('fred'): 7 | ! pass 8 | ! elif (g==1) and (b==2): 9 | ! pass 10 | ! elif self.m('fred')==True: 11 | ! pass 12 | ! elif ((g==1) and (b==2))==True: 13 | ! pass 14 | ! else: 15 | ! pass 16 | 17 | > def g(x): 18 | > if x == 1: 19 | > a = 1 20 | ! else: 21 | ! a = 2 22 | 23 | > g(1) 24 | 25 | > def h(x): 26 | - if 0: #pragma: no cover 27 | - pass 28 | > if x == 1: 29 | ! a = 1 30 | > else: 31 | > a = 2 32 | 33 | > h(2) 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for coverage.py 4 | title: '' 5 | labels: enhancement, needs triage 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context about the feature request here. 21 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2001 Gareth Rees. All rights reserved. 2 | Copyright 2004-2023 Ned Batchelder. All rights reserved. 3 | 4 | Except where noted otherwise, this software is licensed under the Apache 5 | License, Version 2.0 (the "License"); you may not use this work except in 6 | compliance with the License. You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | -------------------------------------------------------------------------------- /doc/sleepy.rst: -------------------------------------------------------------------------------- 1 | .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | .. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | .. _sleepy: 5 | 6 | ============ 7 | Sleepy Snake 8 | ============ 9 | 10 | Coverage.py's mascot is Sleepy Snake, drawn by Ben Batchelder. Ben's art can 11 | be found on `Instagram`_ and at `artofbatch.com`_. Some details of Sleepy's 12 | creation are on `Ned's blog`__. 13 | 14 | __ https://nedbatchelder.com/blog/201912/sleepy_snake.html 15 | 16 | .. image:: media/sleepy-snake-600.png 17 | :alt: Sleepy Snake, cozy in his snake-shaped bed. 18 | 19 | 20 | .. _Instagram: https://instagram.com/artofbatch 21 | .. _artofbatch.com: https://artofbatch.com 22 | -------------------------------------------------------------------------------- /lab/platform_info.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | """Dump information so we can get a quick look at what's available.""" 5 | 6 | import platform 7 | import sys 8 | 9 | 10 | def whatever(f): 11 | try: 12 | return f() 13 | except: 14 | return f 15 | 16 | 17 | def dump_module(mod): 18 | print(f"\n### {mod.__name__} ---------------------------") 19 | for name in dir(mod): 20 | if name.startswith("_"): 21 | continue 22 | print(f"{name:30s}: {whatever(getattr(mod, name))!r:.100}") 23 | 24 | 25 | for mod in [platform, sys]: 26 | dump_module(mod) 27 | -------------------------------------------------------------------------------- /ci/trigger_build_kits.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | """Trigger the GitHub action to build our kits.""" 5 | 6 | import sys 7 | 8 | import requests 9 | 10 | repo_owner = sys.argv[1] 11 | 12 | # The GitHub URL makes no mention of which workflow to use. It's found based on 13 | # the event_type, which matches the types in the workflow: 14 | # 15 | # on: 16 | # repository_dispatch: 17 | # types: 18 | # - build-kits 19 | # 20 | 21 | resp = requests.post( 22 | f"https://api.github.com/repos/{repo_owner}/dispatches", 23 | json={"event_type": "build-kits"}, 24 | ) 25 | print(f"Status: {resp.status_code}") 26 | print(resp.text) 27 | -------------------------------------------------------------------------------- /doc/api_plugin.rst: -------------------------------------------------------------------------------- 1 | .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | .. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | .. _api_plugin: 5 | 6 | =============== 7 | Plug-in classes 8 | =============== 9 | 10 | .. automodule:: coverage.plugin 11 | 12 | .. module:: coverage 13 | :noindex: 14 | 15 | The CoveragePlugin class 16 | ------------------------ 17 | 18 | .. autoclass:: CoveragePlugin 19 | :members: 20 | :member-order: bysource 21 | 22 | The FileTracer class 23 | -------------------- 24 | 25 | .. autoclass:: FileTracer 26 | :members: 27 | :member-order: bysource 28 | 29 | The FileReporter class 30 | ---------------------- 31 | 32 | .. autoclass:: FileReporter 33 | :members: 34 | :member-order: bysource 35 | -------------------------------------------------------------------------------- /coverage/bytecode.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | """Bytecode manipulation for coverage.py""" 5 | 6 | from __future__ import annotations 7 | 8 | from types import CodeType 9 | from typing import Iterator 10 | 11 | 12 | def code_objects(code: CodeType) -> Iterator[CodeType]: 13 | """Iterate over all the code objects in `code`.""" 14 | stack = [code] 15 | while stack: 16 | # We're going to return the code object on the stack, but first 17 | # push its children for later returning. 18 | code = stack.pop() 19 | for c in code.co_consts: 20 | if isinstance(c, CodeType): 21 | stack.append(c) 22 | yield code 23 | -------------------------------------------------------------------------------- /coverage/ctracer/filedisp.h: -------------------------------------------------------------------------------- 1 | /* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */ 2 | /* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */ 3 | 4 | #ifndef _COVERAGE_FILEDISP_H 5 | #define _COVERAGE_FILEDISP_H 6 | 7 | #include "util.h" 8 | #include "structmember.h" 9 | 10 | typedef struct CFileDisposition { 11 | PyObject_HEAD 12 | 13 | PyObject * original_filename; 14 | PyObject * canonical_filename; 15 | PyObject * source_filename; 16 | PyObject * trace; 17 | PyObject * reason; 18 | PyObject * file_tracer; 19 | PyObject * has_dynamic_filename; 20 | } CFileDisposition; 21 | 22 | void CFileDisposition_dealloc(CFileDisposition *self); 23 | 24 | extern PyTypeObject CFileDispositionType; 25 | 26 | #endif /* _COVERAGE_FILEDISP_H */ 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files that can appear anywhere in the tree. 2 | *.pyc 3 | *.pyo 4 | *$py.class 5 | *.pyd 6 | *.so 7 | *.bak 8 | .coverage 9 | .coverage.* 10 | coverage.xml 11 | coverage.json 12 | .metacov 13 | .metacov.* 14 | *.swp 15 | 16 | # Stuff generated by editors. 17 | .idea/ 18 | .vscode/ 19 | .vimtags 20 | 21 | # Stuff in the root. 22 | build 23 | *.egg-info 24 | dist 25 | htmlcov 26 | MANIFEST 27 | setuptools-*.egg 28 | .tox 29 | .noseids 30 | .cache 31 | .pytest_cache 32 | .hypothesis 33 | .ruby-version 34 | .venv 35 | 36 | # Stuff in the test directory. 37 | covmain.zip 38 | zipmods.zip 39 | zip1.zip 40 | tests/actual 41 | 42 | # Stuff in the doc directory. 43 | doc/_build 44 | doc/_spell 45 | doc/sample_html_beta 46 | 47 | # Build intermediaries. 48 | tmp 49 | 50 | # OS junk 51 | .DS_Store 52 | 53 | !.github 54 | -------------------------------------------------------------------------------- /coverage/ctracer/stats.h: -------------------------------------------------------------------------------- 1 | /* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */ 2 | /* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */ 3 | 4 | #ifndef _COVERAGE_STATS_H 5 | #define _COVERAGE_STATS_H 6 | 7 | #include "util.h" 8 | 9 | #if COLLECT_STATS 10 | #define STATS(x) x 11 | #else 12 | #define STATS(x) 13 | #endif 14 | 15 | typedef struct Stats { 16 | unsigned int calls; /* Need at least one member, but the rest only if needed. */ 17 | #if COLLECT_STATS 18 | unsigned int lines; 19 | unsigned int returns; 20 | unsigned int others; 21 | unsigned int files; 22 | unsigned int stack_reallocs; 23 | unsigned int errors; 24 | unsigned int pycalls; 25 | unsigned int start_context_calls; 26 | #endif 27 | } Stats; 28 | 29 | #endif /* _COVERAGE_STATS_H */ 30 | -------------------------------------------------------------------------------- /lab/benchmark/empty.py: -------------------------------------------------------------------------------- 1 | from benchmark import * 2 | 3 | run_experiment( 4 | py_versions=[ 5 | Python(3, 9), 6 | Python(3, 11), 7 | ], 8 | cov_versions=[ 9 | Coverage("701", "coverage==7.0.1"), 10 | Coverage( 11 | "701.dynctx", "coverage==7.0.1", [("dynamic_context", "test_function")] 12 | ), 13 | Coverage("702", "coverage==7.0.2"), 14 | Coverage( 15 | "702.dynctx", "coverage==7.0.2", [("dynamic_context", "test_function")] 16 | ), 17 | ], 18 | projects=[ 19 | EmptyProject("empty", [1.2, 3.4]), 20 | EmptyProject("dummy", [6.9, 7.1]), 21 | ], 22 | rows=["proj", "pyver"], 23 | column="cov", 24 | ratios=[ 25 | (".2 vs .1", "702", "701"), 26 | (".1 dynctx cost", "701.dynctx", "701"), 27 | (".2 dynctx cost", "702.dynctx", "702"), 28 | ], 29 | ) 30 | -------------------------------------------------------------------------------- /lab/run_trace.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | """Run a simple trace function on a file of Python code.""" 5 | 6 | import os, sys 7 | 8 | nest = 0 9 | 10 | def trace(frame, event, arg): 11 | global nest 12 | 13 | if nest is None: 14 | # This can happen when Python is shutting down. 15 | return None 16 | 17 | print("%s%s %s %d @%d" % ( 18 | " " * nest, 19 | event, 20 | os.path.basename(frame.f_code.co_filename), 21 | frame.f_lineno, 22 | frame.f_lasti, 23 | )) 24 | 25 | if event == 'call': 26 | nest += 1 27 | if event == 'return': 28 | nest -= 1 29 | 30 | return trace 31 | 32 | print(sys.version) 33 | the_program = sys.argv[1] 34 | 35 | code = open(the_program).read() 36 | sys.settrace(trace) 37 | exec(code) 38 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Dependency Review Action 2 | # 3 | # This Action will scan dependency manifest files that change as part of a Pull Reqest, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging. 4 | # 5 | # Source repository: https://github.com/actions/dependency-review-action 6 | # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement 7 | name: 'Dependency Review' 8 | on: [pull_request] 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | dependency-review: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: 'Checkout Repository' 18 | uses: actions/checkout@v3 19 | - name: 'Dependency Review' 20 | uses: actions/dependency-review-action@v3 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a problem with coverage.py 4 | title: '' 5 | labels: bug, needs triage 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of the bug. 12 | 13 | **To Reproduce** 14 | How can we reproduce the problem? Please *be specific*. Don't link to a failing CI job. Answer the questions below: 15 | 1. What version of Python are you using? 16 | 1. What version of coverage.py shows the problem? The output of `coverage debug sys` is helpful. 17 | 1. What versions of what packages do you have installed? The output of `pip freeze` is helpful. 18 | 1. What code shows the problem? Give us a specific commit of a specific repo that we can check out. If you've already worked around the problem, please provide a commit before that fix. 19 | 1. What commands did you run? 20 | 21 | **Expected behavior** 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Additional context** 25 | Add any other context about the problem here. 26 | -------------------------------------------------------------------------------- /tests/gold/xml/x_xml/coverage.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | /private/var/folders/j2/gr3cj3jn63s5q8g3bjvw57hm0000gp/T/coverage_test/tests_test_xml_XmlGoldTest_test_a_xml_1_43316963 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/gold/html/Makefile: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | help: 5 | @echo "Available targets:" 6 | @grep '^[a-zA-Z]' $(MAKEFILE_LIST) | sort | awk -F ':.*?## ' 'NF==2 {printf " %-26s%s\n", $$1, $$2}' 7 | 8 | complete: ## Copy support files into directories so the HTML can be viewed properly. 9 | @for sub in *; do \ 10 | if [ -f "$$sub/index.html" ]; then \ 11 | echo Copying into $$sub ; \ 12 | cp -n support/* $$sub ; \ 13 | fi ; \ 14 | done ; \ 15 | true # because the for loop exits with 1 for some reason. 16 | 17 | clean: ## Remove the effects of this Makefile. 18 | @git clean -fq . 19 | 20 | update-gold: ## Copy output files from latest tests to gold files. 21 | @for sub in ../../actual/html/*; do \ 22 | rsync --verbose --existing --recursive $$sub/ $$(basename $$sub) ; \ 23 | done ; \ 24 | true 25 | 26 | update-support: ## Copy latest support files here for posterity. 27 | cp ../../../coverage/htmlfiles/*.{css,js,png} support 28 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | # This file is for unifying the coding style for different editors and IDEs. 5 | # More information at http://EditorConfig.org 6 | 7 | root = true 8 | 9 | [*] 10 | charset = utf-8 11 | end_of_line = lf 12 | indent_size = 4 13 | indent_style = space 14 | insert_final_newline = true 15 | max_line_length = 80 16 | trim_trailing_whitespace = true 17 | 18 | [*.py] 19 | max_line_length = 100 20 | 21 | [*.pyi] 22 | max_line_length = 100 23 | 24 | [*.c] 25 | max_line_length = 100 26 | 27 | [*.h] 28 | max_line_length = 100 29 | 30 | [*.yml] 31 | indent_size = 2 32 | 33 | [*.rst] 34 | max_line_length = 79 35 | 36 | [*.tok] 37 | trim_trailing_whitespace = false 38 | 39 | [*_dos.tok] 40 | end_of_line = crlf 41 | 42 | [Makefile] 43 | indent_style = tab 44 | indent_size = 8 45 | 46 | [*,cover] 47 | trim_trailing_whitespace = false 48 | 49 | [*.diff] 50 | trim_trailing_whitespace = false 51 | 52 | [.git/*] 53 | trim_trailing_whitespace = false 54 | -------------------------------------------------------------------------------- /lab/find_class.py: -------------------------------------------------------------------------------- 1 | class Parent: 2 | def meth(self): 3 | print("METH") 4 | 5 | class Child(Parent): 6 | pass 7 | 8 | def trace(frame, event, args): 9 | # Thanks to Aleksi Torhamo for code and idea. 10 | co = frame.f_code 11 | fname = co.co_name 12 | if not co.co_varnames: 13 | return 14 | locs = frame.f_locals 15 | first_arg = co.co_varnames[0] 16 | if co.co_argcount: 17 | self = locs[first_arg] 18 | elif co.co_flags & 0x04: # *args syntax 19 | self = locs[first_arg][0] 20 | else: 21 | return 22 | 23 | func = getattr(self, fname).__func__ 24 | if hasattr(func, '__qualname__'): 25 | qname = func.__qualname__ 26 | else: 27 | for cls in self.__class__.__mro__: 28 | f = cls.__dict__.get(fname, None) 29 | if f is None: 30 | continue 31 | if f is func: 32 | qname = cls.__name__ + "." + fname 33 | break 34 | print(f"{event}: {self}.{fname} {qname}") 35 | return trace 36 | 37 | import sys 38 | sys.settrace(trace) 39 | 40 | Child().meth() 41 | -------------------------------------------------------------------------------- /coverage/tracer.pyi: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | from typing import Any, Dict 5 | 6 | from coverage.types import TFileDisposition, TTraceData, TTraceFn, TTracer 7 | 8 | class CFileDisposition(TFileDisposition): 9 | canonical_filename: Any 10 | file_tracer: Any 11 | has_dynamic_filename: Any 12 | original_filename: Any 13 | reason: Any 14 | source_filename: Any 15 | trace: Any 16 | def __init__(self) -> None: ... 17 | 18 | class CTracer(TTracer): 19 | check_include: Any 20 | concur_id_func: Any 21 | data: TTraceData 22 | disable_plugin: Any 23 | file_tracers: Any 24 | should_start_context: Any 25 | should_trace: Any 26 | should_trace_cache: Any 27 | switch_context: Any 28 | trace_arcs: Any 29 | warn: Any 30 | def __init__(self) -> None: ... 31 | def activity(self) -> bool: ... 32 | def get_stats(self) -> Dict[str, int]: ... 33 | def reset_activity(self) -> Any: ... 34 | def start(self) -> TTraceFn: ... 35 | def stop(self) -> None: ... 36 | -------------------------------------------------------------------------------- /tests/plugin_config.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | """A configuring plugin for test_plugins.py to import.""" 5 | 6 | from __future__ import annotations 7 | 8 | from typing import Any, List, cast 9 | 10 | import coverage 11 | from coverage.plugin_support import Plugins 12 | from coverage.types import TConfigurable 13 | 14 | 15 | class Plugin(coverage.CoveragePlugin): 16 | """A configuring plugin for testing.""" 17 | def configure(self, config: TConfigurable) -> None: 18 | """Configure all the things!""" 19 | opt_name = "report:exclude_lines" 20 | exclude_lines = cast(List[str], config.get_option(opt_name)) 21 | exclude_lines.append(r"pragma: custom") 22 | exclude_lines.append(r"pragma: or whatever") 23 | config.set_option(opt_name, exclude_lines) 24 | 25 | 26 | def coverage_init( 27 | reg: Plugins, 28 | options: Any, # pylint: disable=unused-argument 29 | ) -> None: 30 | """Called by coverage to initialize the plugins here.""" 31 | reg.add_configurer(Plugin()) 32 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | [tool:pytest] 5 | addopts = -q -n auto -p no:legacypath --strict-markers --no-flaky-report -rfEX --failed-first 6 | python_classes = *Test 7 | markers = 8 | expensive: too slow to run during "make smoke" 9 | 10 | # How come these warnings are suppressed successfully here, but not in conftest.py?? 11 | filterwarnings = 12 | ignore:the imp module is deprecated in favour of importlib:DeprecationWarning 13 | ignore:distutils Version classes are deprecated:DeprecationWarning 14 | ignore:The distutils package is deprecated and slated for removal in Python 3.12:DeprecationWarning 15 | 16 | # xfail tests that pass should fail the test suite 17 | xfail_strict = true 18 | 19 | balanced_clumps = 20 | ; Because of expensive session-scoped fixture: 21 | VirtualenvTest 22 | ; Because of shared-file manipulations (~/tests/actual/testing): 23 | CompareTest 24 | ; No idea why this one fails if run on separate workers: 25 | GetZipBytesTest 26 | 27 | [metadata] 28 | license_files = LICENSE.txt 29 | -------------------------------------------------------------------------------- /tests/gold/README.rst: -------------------------------------------------------------------------------- 1 | .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | .. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | Gold files 5 | ========== 6 | 7 | These are files used in comparisons for some of the tests. Code to support 8 | these comparisons is in tests/goldtest.py. 9 | 10 | If gold tests are failing, you may need to update the gold files by copying the 11 | current output of the tests into the gold files. When a test fails, the actual 12 | output is in the tests/actual directory. Do not commit those files to git. 13 | 14 | You can run just the failed tests again with:: 15 | 16 | tox -e py39 -- -n 0 --lf 17 | 18 | The saved HTML files in the html directories can't be viewed properly without 19 | the supporting CSS and Javascript files. But we don't want to save copies of 20 | those files in every subdirectory. There's a Makefile in the html directory 21 | for working with the saved copies of the support files. 22 | 23 | If the output files are correct, you can update the gold files with "make 24 | update-gold". If there are version-specific gold files (for example, 25 | bom/2/\*), you'll need to update them manually. 26 | -------------------------------------------------------------------------------- /tests/gold/xml/y_xml_branch/coverage.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | /private/var/folders/j2/gr3cj3jn63s5q8g3bjvw57hm0000gp/T/coverage_test/tests_test_xml_XmlGoldTest_test_y_xml_branch_93378757 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | [build-system] 5 | requires = ['setuptools'] 6 | build-backend = 'setuptools.build_meta' 7 | 8 | [tool.mypy] 9 | check_untyped_defs = true 10 | disallow_any_generics = true 11 | disallow_incomplete_defs = true 12 | disallow_subclassing_any = true 13 | disallow_untyped_calls = true 14 | disallow_untyped_decorators = true 15 | disallow_untyped_defs = true 16 | follow_imports = "silent" 17 | ignore_missing_imports = true 18 | no_implicit_optional = true 19 | show_error_codes = true 20 | warn_redundant_casts = true 21 | warn_return_any = true 22 | warn_unreachable = true 23 | warn_unused_configs = true 24 | warn_unused_ignores = true 25 | 26 | exclude = """(?x)( 27 | ^coverage/fullcoverage/encodings\\.py$ # can't import things into it. 28 | | ^tests/balance_xdist_plugin\\.py$ # not part of our test suite. 29 | )""" 30 | 31 | [tool.scriv] 32 | # Changelog management: https://pypi.org/project/scriv/ 33 | format = "rst" 34 | output_file = "CHANGES.rst" 35 | insert_marker = "scriv-start-here" 36 | end_marker = "scriv-end-here" 37 | ghrel_template = "file: ci/ghrel_template.md.j2" 38 | -------------------------------------------------------------------------------- /doc/api_module.rst: -------------------------------------------------------------------------------- 1 | .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | .. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | .. _api_module: 5 | 6 | coverage module 7 | --------------- 8 | 9 | .. module:: coverage 10 | 11 | The most important thing in the coverage module is the 12 | :class:`coverage.Coverage` class, described in :ref:`api_coverage`, but there 13 | are a few other things also. 14 | 15 | 16 | .. data:: version_info 17 | 18 | A tuple of five elements, similar to :data:`sys.version_info 19 | `: *major*, *minor*, *micro*, *releaselevel*, and 20 | *serial*. All values except *releaselevel* are integers; the release level is 21 | ``'alpha'``, ``'beta'``, ``'candidate'``, or ``'final'``. Unlike 22 | :data:`sys.version_info `, the elements are not 23 | available by name. 24 | 25 | .. data:: __version__ 26 | 27 | A string with the version of coverage.py, for example, ``"5.0b2"``. 28 | 29 | .. autoclass:: CoverageException 30 | 31 | 32 | Starting coverage.py automatically 33 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 34 | 35 | This function is used to start coverage measurement automatically when Python 36 | starts. See :ref:`subprocess` for details. 37 | 38 | .. autofunction:: process_startup 39 | -------------------------------------------------------------------------------- /tests/stress_phystoken_dos.tok: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | # Here's some random Python so that test_tokenize_myself will have some 5 | # stressful stuff to try. This file is .tok instead of .py so pylint won't 6 | # complain about it, editors won't mess with it, etc. 7 | 8 | first_back = """\ 9 | hey there! 10 | """ 11 | 12 | other_back = """ 13 | hey \ 14 | there 15 | """ 16 | 17 | lots_of_back = """\ 18 | hey \ 19 | there 20 | """ 21 | # This next line is supposed to have trailing whitespace: 22 | fake_back = """\ 23 | ouch 24 | """ 25 | 26 | # Lots of difficulty happens with code like: 27 | # 28 | # fake_back = """\ 29 | # ouch 30 | # """ 31 | # 32 | # Ugh, the edge cases... 33 | 34 | # What about a comment like this\ 35 | "what's this string doing here?" 36 | 37 | class C(object): 38 | def there(): 39 | this = 5 + \ 40 | 7 41 | that = \ 42 | "a continued line" 43 | 44 | cont1 = "one line of text" + \ 45 | "another line of text" 46 | 47 | a_long_string = \ 48 | "part 1" \ 49 | "2" \ 50 | "3 is longer" 51 | 52 | def hello(): 53 | print("Hello world!") 54 | 55 | hello() 56 | -------------------------------------------------------------------------------- /coverage/__init__.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | """Code coverage measurement for Python. 5 | 6 | Ned Batchelder 7 | https://nedbatchelder.com/code/coverage 8 | 9 | """ 10 | 11 | import sys 12 | 13 | from coverage.version import __version__, __url__, version_info 14 | 15 | from coverage.control import Coverage, process_startup 16 | from coverage.data import CoverageData 17 | from coverage.exceptions import CoverageException 18 | from coverage.plugin import CoveragePlugin, FileTracer, FileReporter 19 | from coverage.pytracer import PyTracer 20 | 21 | # Backward compatibility. 22 | coverage = Coverage 23 | 24 | # On Windows, we encode and decode deep enough that something goes wrong and 25 | # the encodings.utf_8 module is loaded and then unloaded, I don't know why. 26 | # Adding a reference here prevents it from being unloaded. Yuk. 27 | import encodings.utf_8 # pylint: disable=wrong-import-position, wrong-import-order 28 | 29 | # Because of the "from coverage.control import fooey" lines at the top of the 30 | # file, there's an entry for coverage.coverage in sys.modules, mapped to None. 31 | # This makes some inspection tools (like pydoc) unable to find the class 32 | # coverage.coverage. So remove that entry. 33 | try: 34 | del sys.modules['coverage.coverage'] 35 | except KeyError: 36 | pass 37 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | # MANIFEST.in file for coverage.py 5 | 6 | # This file includes everything needed to recreate the entire project, even 7 | # though many of these files are not installed by setup.py. Unpacking the 8 | # .tar.gz source distribution would give you everything needed to continue 9 | # developing the project. "pip install" will not install many of these files. 10 | 11 | include CONTRIBUTORS.txt 12 | include CHANGES.rst 13 | include LICENSE.txt 14 | include MANIFEST.in 15 | include Makefile 16 | include NOTICE.txt 17 | include README.rst 18 | include __main__.py 19 | include howto.txt 20 | include igor.py 21 | include metacov.ini 22 | include pylintrc 23 | include setup.py 24 | include tox.ini 25 | include .editorconfig 26 | include .git-blame-ignore-revs 27 | include .readthedocs.yml 28 | 29 | recursive-include ci * 30 | recursive-include lab * 31 | recursive-include .github * 32 | 33 | recursive-include coverage *.pyi 34 | recursive-include coverage/fullcoverage *.py 35 | recursive-include coverage/ctracer *.c *.h 36 | 37 | recursive-include doc *.py *.in *.pip *.rst *.txt *.png 38 | recursive-include doc/_static * 39 | prune doc/_build 40 | prune doc/_spell 41 | 42 | recursive-include requirements *.in *.pip 43 | 44 | recursive-include tests *.py *.tok 45 | recursive-include tests/gold * 46 | recursive-include tests js/* qunit/* 47 | prune tests/eggsrc/build 48 | -------------------------------------------------------------------------------- /tests/stress_phystoken.tok: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | # Here's some random Python so that test_tokenize_myself will have some 5 | # stressful stuff to try. This file is .tok instead of .py so pylint won't 6 | # complain about it, editors won't mess with it, etc. 7 | # Some lines are here to reproduce fixed bugs in ast_dump also. 8 | 9 | first_back = """\ 10 | hey there! 11 | """ 12 | 13 | other_back = """ 14 | hey \ 15 | there 16 | """ 17 | 18 | lots_of_back = """\ 19 | hey \ 20 | there 21 | """ 22 | # This next line is supposed to have trailing whitespace: 23 | fake_back = """\ 24 | ouch 25 | """ 26 | 27 | # Lots of difficulty happens with code like: 28 | # 29 | # fake_back = """\ 30 | # ouch 31 | # """ 32 | # 33 | # Ugh, the edge cases... 34 | 35 | # What about a comment like this\ 36 | "what's this string doing here?" 37 | 38 | class C(object): 39 | def there(): 40 | this = 5 + \ 41 | 7 42 | that = \ 43 | "a continued line" 44 | 45 | cont1 = "one line of text" + \ 46 | "another line of text" 47 | 48 | a_long_string = \ 49 | "part 1" \ 50 | "2" \ 51 | "3 is longer" 52 | 53 | def hello(): 54 | global x # ast_dump bug 55 | print("Hello world!") 56 | 57 | hello() 58 | 59 | # ast dump bugs: 60 | weird = { 61 | **d, 62 | **{'c': 7}, 63 | 'd': 8, 64 | } 65 | self.hash.update(b'.') 66 | -------------------------------------------------------------------------------- /coverage/exceptions.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | """Exceptions coverage.py can raise.""" 5 | 6 | 7 | class _BaseCoverageException(Exception): 8 | """The base-base of all Coverage exceptions.""" 9 | pass 10 | 11 | 12 | class CoverageException(_BaseCoverageException): 13 | """The base class of all exceptions raised by Coverage.py.""" 14 | pass 15 | 16 | 17 | class ConfigError(_BaseCoverageException): 18 | """A problem with a config file, or a value in one.""" 19 | pass 20 | 21 | 22 | class DataError(CoverageException): 23 | """An error in using a data file.""" 24 | pass 25 | 26 | class NoDataError(CoverageException): 27 | """We didn't have data to work with.""" 28 | pass 29 | 30 | 31 | class NoSource(CoverageException): 32 | """We couldn't find the source for a module.""" 33 | pass 34 | 35 | 36 | class NoCode(NoSource): 37 | """We couldn't find any code at all.""" 38 | pass 39 | 40 | 41 | class NotPython(CoverageException): 42 | """A source file turned out not to be parsable Python.""" 43 | pass 44 | 45 | 46 | class PluginError(CoverageException): 47 | """A plugin misbehaved.""" 48 | pass 49 | 50 | 51 | class _ExceptionDuringRun(CoverageException): 52 | """An exception happened while running customer code. 53 | 54 | Construct it with three arguments, the values from `sys.exc_info`. 55 | 56 | """ 57 | pass 58 | 59 | 60 | class CoverageWarning(Warning): 61 | """A warning from Coverage.py.""" 62 | pass 63 | -------------------------------------------------------------------------------- /coverage/ctracer/datastack.c: -------------------------------------------------------------------------------- 1 | /* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */ 2 | /* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */ 3 | 4 | #include "util.h" 5 | #include "datastack.h" 6 | 7 | #define STACK_DELTA 20 8 | 9 | int 10 | DataStack_init(Stats *pstats, DataStack *pdata_stack) 11 | { 12 | pdata_stack->depth = -1; 13 | pdata_stack->stack = NULL; 14 | pdata_stack->alloc = 0; 15 | return RET_OK; 16 | } 17 | 18 | void 19 | DataStack_dealloc(Stats *pstats, DataStack *pdata_stack) 20 | { 21 | int i; 22 | 23 | for (i = 0; i < pdata_stack->alloc; i++) { 24 | Py_XDECREF(pdata_stack->stack[i].file_data); 25 | } 26 | PyMem_Free(pdata_stack->stack); 27 | } 28 | 29 | int 30 | DataStack_grow(Stats *pstats, DataStack *pdata_stack) 31 | { 32 | pdata_stack->depth++; 33 | if (pdata_stack->depth >= pdata_stack->alloc) { 34 | /* We've outgrown our data_stack array: make it bigger. */ 35 | int bigger = pdata_stack->alloc + STACK_DELTA; 36 | DataStackEntry * bigger_data_stack = PyMem_Realloc(pdata_stack->stack, bigger * sizeof(DataStackEntry)); 37 | STATS( pstats->stack_reallocs++; ) 38 | if (bigger_data_stack == NULL) { 39 | PyErr_NoMemory(); 40 | pdata_stack->depth--; 41 | return RET_ERROR; 42 | } 43 | /* Zero the new entries. */ 44 | memset(bigger_data_stack + pdata_stack->alloc, 0, STACK_DELTA * sizeof(DataStackEntry)); 45 | 46 | pdata_stack->stack = bigger_data_stack; 47 | pdata_stack->alloc = bigger; 48 | } 49 | return RET_OK; 50 | } 51 | -------------------------------------------------------------------------------- /coverage/ctracer/datastack.h: -------------------------------------------------------------------------------- 1 | /* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */ 2 | /* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */ 3 | 4 | #ifndef _COVERAGE_DATASTACK_H 5 | #define _COVERAGE_DATASTACK_H 6 | 7 | #include "util.h" 8 | #include "stats.h" 9 | 10 | /* An entry on the data stack. For each call frame, we need to record all 11 | * the information needed for CTracer_handle_line to operate as quickly as 12 | * possible. 13 | */ 14 | typedef struct DataStackEntry { 15 | /* The current file_data set. Owned. */ 16 | PyObject * file_data; 17 | 18 | /* The disposition object for this frame. A borrowed instance of CFileDisposition. */ 19 | PyObject * disposition; 20 | 21 | /* The FileTracer handling this frame, or None if it's Python. Borrowed. */ 22 | PyObject * file_tracer; 23 | 24 | /* The line number of the last line recorded, for tracing arcs. 25 | -1 means there was no previous line, as when entering a code object. 26 | */ 27 | int last_line; 28 | 29 | BOOL started_context; 30 | } DataStackEntry; 31 | 32 | /* A data stack is a dynamically allocated vector of DataStackEntry's. */ 33 | typedef struct DataStack { 34 | int depth; /* The index of the last-used entry in stack. */ 35 | int alloc; /* number of entries allocated at stack. */ 36 | /* The file data at each level, or NULL if not recording. */ 37 | DataStackEntry * stack; 38 | } DataStack; 39 | 40 | 41 | int DataStack_init(Stats * pstats, DataStack *pdata_stack); 42 | void DataStack_dealloc(Stats * pstats, DataStack *pdata_stack); 43 | int DataStack_grow(Stats * pstats, DataStack *pdata_stack); 44 | 45 | #endif /* _COVERAGE_DATASTACK_H */ 46 | -------------------------------------------------------------------------------- /coverage/version.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | """The version and URL for coverage.py""" 5 | # This file is exec'ed in setup.py, don't import anything! 6 | 7 | from __future__ import annotations 8 | 9 | # version_info: same semantics as sys.version_info. 10 | # _dev: the .devN suffix if any. 11 | version_info = (7, 2, 0, "alpha", 0) 12 | _dev = 1 13 | 14 | 15 | def _make_version( 16 | major: int, 17 | minor: int, 18 | micro: int, 19 | releaselevel: str = "final", 20 | serial: int = 0, 21 | dev: int = 0, 22 | ) -> str: 23 | """Create a readable version string from version_info tuple components.""" 24 | assert releaselevel in ['alpha', 'beta', 'candidate', 'final'] 25 | version = "%d.%d.%d" % (major, minor, micro) 26 | if releaselevel != 'final': 27 | short = {'alpha': 'a', 'beta': 'b', 'candidate': 'rc'}[releaselevel] 28 | version += f"{short}{serial}" 29 | if dev != 0: 30 | version += f".dev{dev}" 31 | return version 32 | 33 | 34 | def _make_url( 35 | major: int, 36 | minor: int, 37 | micro: int, 38 | releaselevel: str, 39 | serial: int = 0, 40 | dev: int = 0, 41 | ) -> str: 42 | """Make the URL people should start at for this version of coverage.py.""" 43 | url = "https://coverage.readthedocs.io" 44 | if releaselevel != "final" or dev != 0: 45 | # For pre-releases, use a version-specific URL. 46 | url += "/en/" + _make_version(major, minor, micro, releaselevel, serial, dev) 47 | return url 48 | 49 | 50 | __version__ = _make_version(*version_info, _dev) 51 | __url__ = _make_url(*version_info, _dev) 52 | -------------------------------------------------------------------------------- /lab/benchmark/run.py: -------------------------------------------------------------------------------- 1 | from benchmark import * 2 | 3 | if 0: 4 | run_experiment( 5 | py_versions=[ 6 | # Python(3, 11), 7 | AdHocPython("/usr/local/cpython/v3.10.5", "v3.10.5"), 8 | AdHocPython("/usr/local/cpython/v3.11.0b3", "v3.11.0b3"), 9 | AdHocPython("/usr/local/cpython/94231", "94231"), 10 | ], 11 | cov_versions=[ 12 | Coverage("6.4.1", "coverage==6.4.1"), 13 | ], 14 | projects=[ 15 | AdHocProject("/src/bugs/bug1339/bug1339.py"), 16 | SlipcoverBenchmark("bm_sudoku.py"), 17 | SlipcoverBenchmark("bm_spectral_norm.py"), 18 | ], 19 | rows=["cov", "proj"], 20 | column="pyver", 21 | ratios=[ 22 | ("3.11b3 vs 3.10", "v3.11.0b3", "v3.10.5"), 23 | ("94231 vs 3.10", "94231", "v3.10.5"), 24 | ], 25 | ) 26 | 27 | 28 | if 1: 29 | run_experiment( 30 | py_versions=[ 31 | Python(3, 9), 32 | Python(3, 11), 33 | ], 34 | cov_versions=[ 35 | Coverage("701", "coverage==7.0.1"), 36 | Coverage( 37 | "701.dynctx", "coverage==7.0.1", [("dynamic_context", "test_function")] 38 | ), 39 | Coverage("702", "coverage==7.0.2"), 40 | Coverage( 41 | "702.dynctx", "coverage==7.0.2", [("dynamic_context", "test_function")] 42 | ), 43 | ], 44 | projects=[ 45 | ProjectAttrs(), 46 | ], 47 | rows=["proj", "pyver"], 48 | column="cov", 49 | ratios=[ 50 | (".2 vs .1", "702", "701"), 51 | (".1 dynctx cost", "701.dynctx", "701"), 52 | (".2 dynctx cost", "702.dynctx", "702"), 53 | ], 54 | ) 55 | -------------------------------------------------------------------------------- /tests/js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Coverage.py Javascript Test Suite 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 26 | 27 | 30 | 31 | 34 | 35 | 36 | 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /tests/test_collector.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 3 | 4 | """Tests of coverage/collector.py and other collectors.""" 5 | 6 | from __future__ import annotations 7 | 8 | import os.path 9 | 10 | import coverage 11 | 12 | from tests.coveragetest import CoverageTest 13 | from tests.helpers import CheckUniqueFilenames 14 | 15 | 16 | class CollectorTest(CoverageTest): 17 | """Test specific aspects of the collection process.""" 18 | 19 | def test_should_trace_cache(self) -> None: 20 | # The tracers should only invoke should_trace once for each file name. 21 | 22 | # Make some files that invoke each other. 23 | self.make_file("f1.py", """\ 24 | def f1(x, f): 25 | return f(x) 26 | """) 27 | 28 | self.make_file("f2.py", """\ 29 | import f1 30 | 31 | def func(x): 32 | return f1.f1(x, otherfunc) 33 | 34 | def otherfunc(x): 35 | return x*x 36 | 37 | for i in range(10): 38 | func(i) 39 | """) 40 | 41 | # Trace one file, but not the other. CheckUniqueFilenames will assert 42 | # that _should_trace hasn't been called twice for the same file. 43 | cov = coverage.Coverage(include=["f1.py"]) 44 | should_trace_hook = CheckUniqueFilenames.hook(cov, '_should_trace') 45 | 46 | # Import the Python file, executing it. 47 | self.start_import_stop(cov, "f2") 48 | 49 | # Double-check that our files were checked. 50 | abs_files = {os.path.abspath(f) for f in should_trace_hook.filenames} 51 | assert os.path.abspath("f1.py") in abs_files 52 | assert os.path.abspath("f2.py") in abs_files 53 | -------------------------------------------------------------------------------- /doc/_static/coverage.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Georgia; 3 | } 4 | 5 | h1, h2, h3, h4, h5, h6 { 6 | font-family: Helvetica; 7 | } 8 | 9 | a:hover { 10 | text-decoration: underline; 11 | } 12 | 13 | img.tideliftlogo { 14 | border: 1px solid #888; 15 | margin-top: .5em !important; 16 | } 17 | 18 | .rst-content ol.arabic li { 19 | margin-bottom: 12px; 20 | } 21 | 22 | .rst-content h3, .rst-content h4, .rst-content h5, .rst-content h6 { 23 | /* This makes config.rst look a little better, but the paras are still too 24 | * spaced out. 25 | */ 26 | margin-bottom: 12px; 27 | } 28 | 29 | /* Tabs */ 30 | 31 | .ui.menu { 32 | font-family: Helvetica; 33 | min-height: 0; 34 | } 35 | 36 | .ui.tabular.menu .item { 37 | padding: .25em 1em; 38 | } 39 | 40 | .ui.menu .item { 41 | padding: 0; 42 | } 43 | 44 | .sphinx-tabs { 45 | margin-bottom: 1em; 46 | } 47 | 48 | .sig { 49 | font-family: Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace; 50 | } 51 | 52 | .sig-name, .sig-prename { 53 | font-size: 1.1em; 54 | font-weight: bold; 55 | color: black; 56 | } 57 | 58 | .rst-content dl dt.sig { 59 | font-weight: inherit; 60 | } 61 | 62 | /* .. parsed-literal:: isn't styled like other
 blocks!? */
63 | 
64 | .rst-content pre.literal-block {
65 |     white-space: pre;
66 |     padding: 12px 12px !important;
67 |     font-family: Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;
68 |     font-size: 12px;
69 |     display: block;
70 |     overflow: auto;
71 |     color: #404040;
72 |     background: #efc;
73 | }
74 | 


--------------------------------------------------------------------------------
/ci/comment_on_fixes.py:
--------------------------------------------------------------------------------
 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 3 | 
 4 | """Add a release comment to all the issues mentioned in the latest release."""
 5 | 
 6 | import json
 7 | import re
 8 | import sys
 9 | 
10 | import requests
11 | 
12 | with open("tmp/relnotes.json") as frn:
13 |     relnotes = json.load(frn)
14 | 
15 | latest = relnotes[0]
16 | version = latest["version"]
17 | comment = (
18 |     f"This is now released as part of [coverage {version}]" +
19 |     f"(https://pypi.org/project/coverage/{version})."
20 | )
21 | print(f"Comment will be:\n\n{comment}\n")
22 | 
23 | repo_owner = sys.argv[1]
24 | for m in re.finditer(rf"https://github.com/{repo_owner}/(issues|pull)/(\d+)", latest["text"]):
25 |     kind, number = m.groups()
26 |     do_comment = False
27 | 
28 |     if kind == "issues":
29 |         url = f"https://api.github.com/repos/{repo_owner}/issues/{number}"
30 |         issue_data = requests.get(url).json()
31 |         if issue_data["state"] == "closed":
32 |             do_comment = True
33 |         else:
34 |             print(f"Still open, comment manually: {m[0]}")
35 |     else:
36 |         url = f"https://api.github.com/repos/{repo_owner}/pulls/{number}"
37 |         pull_data = requests.get(url).json()
38 |         if pull_data["state"] == "closed":
39 |             if pull_data["merged"]:
40 |                 do_comment = True
41 |             else:
42 |                 print(f"Not merged, comment manually: {m[0]}")
43 |         else:
44 |             print(f"Still open, comment manually: {m[0]}")
45 | 
46 |     if do_comment:
47 |         print(f"Commenting on {m[0]}")
48 |         url = f"https://api.github.com/repos/{repo_owner}/issues/{number}/comments"
49 |         resp = requests.post(url, json={"body": comment})
50 |         print(resp)
51 | 


--------------------------------------------------------------------------------
/coverage/ctracer/module.c:
--------------------------------------------------------------------------------
 1 | /* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */
 2 | /* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */
 3 | 
 4 | #include "util.h"
 5 | #include "tracer.h"
 6 | #include "filedisp.h"
 7 | 
 8 | /* Module definition */
 9 | 
10 | #define MODULE_DOC PyDoc_STR("Fast coverage tracer.")
11 | 
12 | static PyModuleDef
13 | moduledef = {
14 |     PyModuleDef_HEAD_INIT,
15 |     "coverage.tracer",
16 |     MODULE_DOC,
17 |     -1,
18 |     NULL,       /* methods */
19 |     NULL,
20 |     NULL,       /* traverse */
21 |     NULL,       /* clear */
22 |     NULL
23 | };
24 | 
25 | 
26 | PyObject *
27 | PyInit_tracer(void)
28 | {
29 |     PyObject * mod = PyModule_Create(&moduledef);
30 |     if (mod == NULL) {
31 |         return NULL;
32 |     }
33 | 
34 |     if (CTracer_intern_strings() < 0) {
35 |         return NULL;
36 |     }
37 | 
38 |     /* Initialize CTracer */
39 |     CTracerType.tp_new = PyType_GenericNew;
40 |     if (PyType_Ready(&CTracerType) < 0) {
41 |         Py_DECREF(mod);
42 |         return NULL;
43 |     }
44 | 
45 |     Py_INCREF(&CTracerType);
46 |     if (PyModule_AddObject(mod, "CTracer", (PyObject *)&CTracerType) < 0) {
47 |         Py_DECREF(mod);
48 |         Py_DECREF(&CTracerType);
49 |         return NULL;
50 |     }
51 | 
52 |     /* Initialize CFileDisposition */
53 |     CFileDispositionType.tp_new = PyType_GenericNew;
54 |     if (PyType_Ready(&CFileDispositionType) < 0) {
55 |         Py_DECREF(mod);
56 |         Py_DECREF(&CTracerType);
57 |         return NULL;
58 |     }
59 | 
60 |     Py_INCREF(&CFileDispositionType);
61 |     if (PyModule_AddObject(mod, "CFileDisposition", (PyObject *)&CFileDispositionType) < 0) {
62 |         Py_DECREF(mod);
63 |         Py_DECREF(&CTracerType);
64 |         Py_DECREF(&CFileDispositionType);
65 |         return NULL;
66 |     }
67 | 
68 |     return mod;
69 | }
70 | 


--------------------------------------------------------------------------------
/doc/api.rst:
--------------------------------------------------------------------------------
 1 | .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 2 | .. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 3 | 
 4 | .. _api:
 5 | 
 6 | ===============
 7 | Coverage.py API
 8 | ===============
 9 | 
10 | There are a few different ways to use coverage.py programmatically.
11 | 
12 | The API to coverage.py is in a module called :mod:`coverage`.  Most of the
13 | interface is in the :class:`coverage.Coverage` class.  Methods on the Coverage
14 | object correspond roughly to operations available in the command line
15 | interface. For example, a simple use would be::
16 | 
17 |     import coverage
18 | 
19 |     cov = coverage.Coverage()
20 |     cov.start()
21 | 
22 |     # .. call your code ..
23 | 
24 |     cov.stop()
25 |     cov.save()
26 | 
27 |     cov.html_report()
28 | 
29 | Any of the methods can raise specialized exceptions described in
30 | :ref:`api_exceptions`.
31 | 
32 | Coverage.py supports plugins that can change its behavior, to collect
33 | information from non-Python files, or to perform complex configuration.  See
34 | :ref:`api_plugin` for details.
35 | 
36 | If you want to access the data that coverage.py has collected, the
37 | :class:`coverage.CoverageData` class provides an API to read coverage.py data
38 | files.
39 | 
40 | .. note::
41 | 
42 |     Only the documented portions of the API are supported. Other names you may
43 |     find in modules or objects can change their behavior at any time. Please
44 |     limit yourself to documented methods to avoid problems.
45 | 
46 | For more intensive data use, you might want to access the coverage.py database
47 | file directly.  The schema is subject to change, so this is for advanced uses
48 | only.  :ref:`dbschema` explains more.
49 | 
50 | .. toctree::
51 |     :maxdepth: 1
52 | 
53 |     api_coverage
54 |     api_exceptions
55 |     api_module
56 |     api_plugin
57 |     api_coveragedata
58 |     dbschema
59 | 


--------------------------------------------------------------------------------
/lab/new-data.js:
--------------------------------------------------------------------------------
 1 | {
 2 |     // As of now:
 3 |     "lines": {
 4 |         "a/b/c.py": [1, 2, 3, 4, 5],
 5 |         "a/b/d.py": [4, 5, 6, 7, 8],
 6 |     },
 7 |     "arcs": {
 8 |         "a/b/c.py: [[1, 2], [2, 3], [4, 5]],
 9 |     },
10 |     "file_tracers": {
11 |         "a/b/c.py": "fooey.plugin",
12 |     },
13 | 
14 |     // We used to do this, but it got too bulky, removed in 4.0.1:
15 |     "run" {
16 |         "collector": "coverage.py 4.0",
17 |         "config": {
18 |             "branch": true,
19 |             "source": ".",
20 |         },
21 |         "collected": "20150711T090600",
22 |     },
23 | 
24 |     // Maybe in the future?
25 |     "files": {
26 |         "a/b/c.py": {
27 |             "lines": [1, 2, 3, 4, 5],
28 |             "arcs": [
29 |                 [1, 2], [3, 4], [5, -1],
30 |             ],
31 | 
32 |             "plugin": "django.coverage",
33 | 
34 |             "lines": {
35 |                 "1": {
36 |                     "tests": [
37 |                         "foo/bar/test.py:TheTest.test_it",
38 |                         "asdasdasd",
39 |                         ],
40 |                     "tests": [17, 34, 23, 12389],
41 |                     },
42 |                 "2": {
43 |                     "count": 23,
44 |                     },
45 |                 "3": {},
46 |                 "4": {},
47 |                 "17": {},
48 |                 },
49 | 
50 |             "arcs": {
51 |                 "1.2": {},
52 |                 "2.3": {},
53 |                 "3.-1": {},
54 |             },
55 |         },
56 |     },
57 | 
58 |     "tests": [
59 |         {
60 |             "file": "a/b/c.py",
61 |             "test": "test_it",
62 |             },
63 |         {
64 |             "file": "a/b/d.py",
65 |             "test": "TheTest.test_it",
66 |             },
67 |     ],
68 | 
69 |     "runs": [
70 |         {
71 |             // info about each run?
72 |             },
73 |         { ... },
74 |     ],
75 | }
76 | 


--------------------------------------------------------------------------------
/tests/test_version.py:
--------------------------------------------------------------------------------
 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 3 | 
 4 | """Tests of version.py."""
 5 | 
 6 | from __future__ import annotations
 7 | 
 8 | import coverage
 9 | from coverage.version import _make_url, _make_version
10 | 
11 | from tests.coveragetest import CoverageTest
12 | 
13 | 
14 | class VersionTest(CoverageTest):
15 |     """Tests of version.py"""
16 | 
17 |     run_in_temp_dir = False
18 | 
19 |     def test_version_info(self) -> None:
20 |         # Make sure we didn't screw up the version_info tuple.
21 |         assert isinstance(coverage.version_info, tuple)
22 |         assert [type(d) for d in coverage.version_info] == [int, int, int, str, int]
23 |         assert coverage.version_info[3] in {'alpha', 'beta', 'candidate', 'final'}
24 | 
25 |     def test_make_version(self) -> None:
26 |         assert _make_version(4, 0, 0, 'alpha') == "4.0.0a0"
27 |         assert _make_version(4, 0, 0, 'alpha', 1) == "4.0.0a1"
28 |         assert _make_version(4, 0, 0, 'final') == "4.0.0"
29 |         assert _make_version(4, 1, 0) == "4.1.0"
30 |         assert _make_version(4, 1, 2, 'beta', 3) == "4.1.2b3"
31 |         assert _make_version(4, 1, 2) == "4.1.2"
32 |         assert _make_version(5, 10, 2, 'candidate', 7) == "5.10.2rc7"
33 |         assert _make_version(5, 10, 2, 'candidate', 7, 3) == "5.10.2rc7.dev3"
34 | 
35 |     def test_make_url(self) -> None:
36 |         assert _make_url(4, 0, 0, 'final') == "https://coverage.readthedocs.io"
37 |         expected = "https://coverage.readthedocs.io/en/4.1.2b3"
38 |         assert _make_url(4, 1, 2, 'beta', 3) == expected
39 |         expected = "https://coverage.readthedocs.io/en/4.1.2b3.dev17"
40 |         assert _make_url(4, 1, 2, 'beta', 3, 17) == expected
41 |         expected = "https://coverage.readthedocs.io/en/4.1.2.dev17"
42 |         assert _make_url(4, 1, 2, 'final', 0, 17) == expected
43 | 


--------------------------------------------------------------------------------
/lab/coverage-03.dtd:
--------------------------------------------------------------------------------
 1 | 
 6 | 
 7 | 
 8 | 
 9 | 
10 | 
11 | 
12 | 
13 | 
14 | 
15 | 
16 | 
17 | 
18 | 
19 | 
20 | 
21 | 
22 | 
23 | 
24 | 
25 | 
26 | 
27 | 
28 | 
29 | 
30 | 
31 | 
32 | 
33 | 
34 | 
35 | 
36 | 
37 | 
38 | 
39 | 
40 | 
41 | 
42 | 
43 | 
44 | 
45 | 
46 | 
47 | 
48 | 
49 | 
50 | 
51 | 
52 | 
53 | 
54 | 
55 | 
56 | 


--------------------------------------------------------------------------------
/doc/sample_html/status.json:
--------------------------------------------------------------------------------
1 | {"format":2,"version":"7.1.0","globals":"7055224a7bc2d5650caf977ebd61f5fe","files":{"d_7b071bdc2a35fa80___init___py":{"hash":"29cdbd59f3692c82f37e41536e3a2417","index":{"nums":[2,1,2,0,0,0,0,0],"html_filename":"d_7b071bdc2a35fa80___init___py.html","relative_filename":"cogapp/__init__.py"}},"d_7b071bdc2a35fa80___main___py":{"hash":"ffe6befa655d4d0b0b31eb0c73811311","index":{"nums":[2,1,3,0,3,0,0,0],"html_filename":"d_7b071bdc2a35fa80___main___py.html","relative_filename":"cogapp/__main__.py"}},"d_7b071bdc2a35fa80_backward_py":{"hash":"8f127f1e99243534806b5e7842d7bd7c","index":{"nums":[2,1,22,0,6,4,2,2],"html_filename":"d_7b071bdc2a35fa80_backward_py.html","relative_filename":"cogapp/backward.py"}},"d_7b071bdc2a35fa80_cogapp_py":{"hash":"659112bebf3e453082a54c29ddc9be18","index":{"nums":[2,1,510,1,230,216,30,144],"html_filename":"d_7b071bdc2a35fa80_cogapp_py.html","relative_filename":"cogapp/cogapp.py"}},"d_7b071bdc2a35fa80_makefiles_py":{"hash":"4cac5bcd4b2151cb0f865736ff610acc","index":{"nums":[2,1,27,0,20,14,0,14],"html_filename":"d_7b071bdc2a35fa80_makefiles_py.html","relative_filename":"cogapp/makefiles.py"}},"d_7b071bdc2a35fa80_test_cogapp_py":{"hash":"dab21c99d2584fd9dd1245ae7eb199cd","index":{"nums":[2,1,849,2,595,28,1,25],"html_filename":"d_7b071bdc2a35fa80_test_cogapp_py.html","relative_filename":"cogapp/test_cogapp.py"}},"d_7b071bdc2a35fa80_test_makefiles_py":{"hash":"150060801c7a23f407563647d09899ff","index":{"nums":[2,1,71,0,53,6,0,6],"html_filename":"d_7b071bdc2a35fa80_test_makefiles_py.html","relative_filename":"cogapp/test_makefiles.py"}},"d_7b071bdc2a35fa80_test_whiteutils_py":{"hash":"6c4e351912582b16a450ab46df5d390c","index":{"nums":[2,1,69,0,50,0,0,0],"html_filename":"d_7b071bdc2a35fa80_test_whiteutils_py.html","relative_filename":"cogapp/test_whiteutils.py"}},"d_7b071bdc2a35fa80_whiteutils_py":{"hash":"755965ecdf5d51b6b9350f179070494f","index":{"nums":[2,1,45,0,5,34,4,4],"html_filename":"d_7b071bdc2a35fa80_whiteutils_py.html","relative_filename":"cogapp/whiteutils.py"}}}}


--------------------------------------------------------------------------------
/tests/test_setup.py:
--------------------------------------------------------------------------------
 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 3 | 
 4 | """Tests of miscellaneous stuff."""
 5 | 
 6 | from __future__ import annotations
 7 | 
 8 | import sys
 9 | 
10 | from typing import List, cast
11 | 
12 | import coverage
13 | 
14 | from tests.coveragetest import CoverageTest
15 | 
16 | 
17 | class SetupPyTest(CoverageTest):
18 |     """Tests of setup.py"""
19 | 
20 |     run_in_temp_dir = False
21 | 
22 |     def setUp(self) -> None:
23 |         super().setUp()
24 |         # Force the most restrictive interpretation.
25 |         self.set_environ('LC_ALL', 'C')
26 | 
27 |     def test_metadata(self) -> None:
28 |         status, output = self.run_command_status(
29 |             "python setup.py --description --version --url --author"
30 |         )
31 |         assert status == 0
32 |         out = output.splitlines()
33 |         assert "measurement" in out[0]
34 |         assert coverage.__version__ == out[1]
35 |         assert "github.com/nedbat/coveragepy" in out[2]
36 |         assert "Ned Batchelder" in out[3]
37 | 
38 |     def test_more_metadata(self) -> None:
39 |         # Let's be sure we pick up our own setup.py
40 |         # CoverageTest restores the original sys.path for us.
41 |         sys.path.insert(0, '')
42 |         from setup import setup_args
43 | 
44 |         classifiers = cast(List[str], setup_args['classifiers'])
45 |         assert len(classifiers) > 7
46 |         assert classifiers[-1].startswith("Development Status ::")
47 |         assert "Programming Language :: Python :: %d" % sys.version_info[:1] in classifiers
48 |         assert "Programming Language :: Python :: %d.%d" % sys.version_info[:2] in classifiers
49 | 
50 |         long_description = cast(str, setup_args['long_description']).splitlines()
51 |         assert len(long_description) > 7
52 |         assert long_description[0].strip() != ""
53 |         assert long_description[-1].strip() != ""
54 | 


--------------------------------------------------------------------------------
/coverage/disposition.py:
--------------------------------------------------------------------------------
 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 3 | 
 4 | """Simple value objects for tracking what to do with files."""
 5 | 
 6 | from __future__ import annotations
 7 | 
 8 | from typing import Optional, Type, TYPE_CHECKING
 9 | 
10 | from coverage.types import TFileDisposition
11 | 
12 | if TYPE_CHECKING:
13 |     from coverage.plugin import FileTracer
14 | 
15 | 
16 | class FileDisposition:
17 |     """A simple value type for recording what to do with a file."""
18 | 
19 |     original_filename: str
20 |     canonical_filename: str
21 |     source_filename: Optional[str]
22 |     trace: bool
23 |     reason: str
24 |     file_tracer: Optional[FileTracer]
25 |     has_dynamic_filename: bool
26 | 
27 |     def __repr__(self) -> str:
28 |         return f""
29 | 
30 | 
31 | # FileDisposition "methods": FileDisposition is a pure value object, so it can
32 | # be implemented in either C or Python.  Acting on them is done with these
33 | # functions.
34 | 
35 | def disposition_init(cls: Type[TFileDisposition], original_filename: str) -> TFileDisposition:
36 |     """Construct and initialize a new FileDisposition object."""
37 |     disp = cls()
38 |     disp.original_filename = original_filename
39 |     disp.canonical_filename = original_filename
40 |     disp.source_filename = None
41 |     disp.trace = False
42 |     disp.reason = ""
43 |     disp.file_tracer = None
44 |     disp.has_dynamic_filename = False
45 |     return disp
46 | 
47 | 
48 | def disposition_debug_msg(disp: TFileDisposition) -> str:
49 |     """Make a nice debug message of what the FileDisposition is doing."""
50 |     if disp.trace:
51 |         msg = f"Tracing {disp.original_filename!r}"
52 |         if disp.original_filename != disp.source_filename:
53 |             msg += f" as {disp.source_filename!r}"
54 |         if disp.file_tracer:
55 |             msg += f": will be traced by {disp.file_tracer!r}"
56 |     else:
57 |         msg = f"Not tracing {disp.original_filename!r}: {disp.reason}"
58 |     return msg
59 | 


--------------------------------------------------------------------------------
/tests/plugin1.py:
--------------------------------------------------------------------------------
 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 3 | 
 4 | """A file tracer plugin for test_plugins.py to import."""
 5 | 
 6 | from __future__ import annotations
 7 | 
 8 | import os.path
 9 | 
10 | from types import FrameType
11 | from typing import Any, Optional, Set, Tuple, Union
12 | 
13 | from coverage import CoveragePlugin, FileReporter, FileTracer
14 | from coverage.plugin_support import Plugins
15 | from coverage.types import TLineNo
16 | 
17 | class Plugin(CoveragePlugin):
18 |     """A file tracer plugin to import, so that it isn't in the test's current directory."""
19 | 
20 |     def file_tracer(self, filename: str) -> Optional[FileTracer]:
21 |         """Trace only files named xyz.py"""
22 |         if "xyz.py" in filename:
23 |             return MyFileTracer(filename)
24 |         return None
25 | 
26 |     def file_reporter(self, filename: str) -> Union[FileReporter, str]:
27 |         return MyFileReporter(filename)
28 | 
29 | 
30 | class MyFileTracer(FileTracer):
31 |     """A FileTracer emulating a simple static plugin."""
32 | 
33 |     def __init__(self, filename: str) -> None:
34 |         """Claim that */*xyz.py was actually sourced from /src/*ABC.zz"""
35 |         self._filename = filename
36 |         self._source_filename = os.path.join(
37 |             "/src",
38 |             os.path.basename(filename.replace("xyz.py", "ABC.zz"))
39 |         )
40 | 
41 |     def source_filename(self) -> str:
42 |         return self._source_filename
43 | 
44 |     def line_number_range(self, frame: FrameType) -> Tuple[TLineNo, TLineNo]:
45 |         """Map the line number X to X05,X06,X07."""
46 |         lineno = frame.f_lineno
47 |         return lineno*100+5, lineno*100+7
48 | 
49 | 
50 | class MyFileReporter(FileReporter):
51 |     """Dead-simple FileReporter."""
52 |     def lines(self) -> Set[TLineNo]:
53 |         return {105, 106, 107, 205, 206, 207}
54 | 
55 | 
56 | def coverage_init(
57 |     reg: Plugins,
58 |     options: Any,       # pylint: disable=unused-argument
59 | ) -> None:
60 |     """Called by coverage to initialize the plugins here."""
61 |     reg.add_file_tracer(Plugin())
62 | 


--------------------------------------------------------------------------------
/lab/coverage-04.dtd:
--------------------------------------------------------------------------------
 1 | 
 6 | 
 7 | 
 8 | 
 9 | 
10 | 
11 | 
12 | 
13 | 
14 | 
15 | 
16 | 
17 | 
18 | 
19 | 
20 | 
21 | 
22 | 
23 | 
24 | 
25 | 
26 | 
27 | 
28 | 
29 | 
30 | 
31 | 
32 | 
33 | 
34 | 
35 | 
36 | 
37 | 
38 | 
39 | 
40 | 
41 | 
42 | 
43 | 
44 | 
45 | 
46 | 
47 | 
48 | 
49 | 
50 | 
51 | 
52 | 
53 | 
54 | 
55 | 
56 | 
57 | 
58 | 
59 | 
60 | 
61 | 


--------------------------------------------------------------------------------
/coverage/ctracer/tracer.h:
--------------------------------------------------------------------------------
 1 | /* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */
 2 | /* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */
 3 | 
 4 | #ifndef _COVERAGE_TRACER_H
 5 | #define _COVERAGE_TRACER_H
 6 | 
 7 | #include "util.h"
 8 | #include "structmember.h"
 9 | #include "frameobject.h"
10 | #include "opcode.h"
11 | 
12 | #include "datastack.h"
13 | 
14 | /* The CTracer type. */
15 | 
16 | typedef struct CTracer {
17 |     PyObject_HEAD
18 | 
19 |     /* Python objects manipulated directly by the Collector class. */
20 |     PyObject * should_trace;
21 |     PyObject * check_include;
22 |     PyObject * warn;
23 |     PyObject * concur_id_func;
24 |     PyObject * data;
25 |     PyObject * file_tracers;
26 |     PyObject * should_trace_cache;
27 |     PyObject * trace_arcs;
28 |     PyObject * should_start_context;
29 |     PyObject * switch_context;
30 |     PyObject * disable_plugin;
31 | 
32 |     /* Has the tracer been started? */
33 |     BOOL started;
34 |     /* Are we tracing arcs, or just lines? */
35 |     BOOL tracing_arcs;
36 |     /* Have we had any activity? */
37 |     BOOL activity;
38 |     /* The current dynamic context. */
39 |     PyObject * context;
40 | 
41 |     /*
42 |         The data stack is a stack of sets.  Each set collects
43 |         data for a single source file.  The data stack parallels the call stack:
44 |         each call pushes the new frame's file data onto the data stack, and each
45 |         return pops file data off.
46 | 
47 |         The file data is a set whose form depends on the tracing options.
48 |         If tracing arcs, the values are line number pairs.  If not tracing arcs,
49 |         the values are line numbers.
50 |     */
51 | 
52 |     DataStack data_stack;           /* Used if we aren't doing concurrency. */
53 | 
54 |     PyObject * data_stack_index;    /* Used if we are doing concurrency. */
55 |     DataStack * data_stacks;
56 |     int data_stacks_alloc;
57 |     int data_stacks_used;
58 |     DataStack * pdata_stack;
59 | 
60 |     /* The current file's data stack entry. */
61 |     DataStackEntry * pcur_entry;
62 | 
63 |     Stats stats;
64 | } CTracer;
65 | 
66 | int CTracer_intern_strings(void);
67 | 
68 | extern PyTypeObject CTracerType;
69 | 
70 | #endif /* _COVERAGE_TRACER_H */
71 | 


--------------------------------------------------------------------------------
/lab/compare_times.sh:
--------------------------------------------------------------------------------
 1 | #!/bin/bash
 2 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 3 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 4 | 
 5 | # A suggestion about how to get less hyperfine output:
 6 | #   https://github.com/sharkdp/hyperfine/issues/223
 7 | HYPERFINE='hyperfine -w 1 -s basic -r 10'
 8 | 
 9 | cat > sourcefile1.py << EOF
10 | import random
11 | 
12 | def get_random_number():
13 | 	return random.randint(5, 20)
14 | EOF
15 | 
16 | cat > test_file1.py << EOF
17 | import pytest
18 | import sourcefile1
19 | 
20 | tests = tuple(f'test{i}' for i in range(1000))
21 | 
22 | @pytest.mark.parametrize("input_str", tests)
23 | def test_speed(input_str):
24 |     print(input_str)
25 |     number = sourcefile1.get_random_number()
26 |     assert number <= 20
27 |     assert number >= 5
28 | EOF
29 | 
30 | rm -f .coveragerc
31 | 
32 | $HYPERFINE 'python -m pytest test_file1.py'
33 | 
34 | echo "Coverage 4.5.4"
35 | pip install -q coverage==4.5.4
36 | $HYPERFINE 'python -m coverage run -m pytest test_file1.py'
37 | $HYPERFINE 'python -m coverage run --branch -m pytest test_file1.py'
38 | $HYPERFINE 'python -m pytest --cov=. --cov-report= test_file1.py'
39 | $HYPERFINE 'python -m pytest --cov=. --cov-report= --cov-branch test_file1.py'
40 | 
41 | echo "Coverage 5.0a8, no contexts"
42 | pip install -q coverage==5.0a8
43 | $HYPERFINE 'python -m coverage run -m pytest test_file1.py'
44 | $HYPERFINE 'python -m coverage run --branch -m pytest test_file1.py'
45 | $HYPERFINE 'python -m pytest --cov=. --cov-report= test_file1.py'
46 | $HYPERFINE 'python -m pytest --cov=. --cov-report= --cov-branch test_file1.py'
47 | 
48 | echo "Coverage 5.0a8, with test contexts"
49 | cat > .coveragerc < None:
30 |         # See igor.py, do_zipmods, for the text of these files.
31 |         zip_file = "tests/zipmods.zip"
32 |         sys.path.append(zip_file)       # So we can import the files.
33 |         filename = zip_file + "/encoded_" + encoding + ".py"
34 |         filename = os_sep(filename)
35 |         zip_data = get_zip_bytes(filename)
36 |         assert zip_data is not None
37 |         zip_text = zip_data.decode(encoding)
38 |         assert 'All OK' in zip_text
39 |         # Run the code to see that we really got it encoded properly.
40 |         mod = __import__("encoded_"+encoding)
41 |         assert mod.encoding == encoding
42 | 
43 | 
44 | def test_source_for_file(tmp_path: pathlib.Path) -> None:
45 |     src = str(tmp_path / "a.py")
46 |     assert source_for_file(src) == src
47 |     assert source_for_file(src + 'c') == src
48 |     assert source_for_file(src + 'o') == src
49 |     unknown = src + 'FOO'
50 |     assert source_for_file(unknown) == unknown
51 | 
52 | 
53 | @pytest.mark.skipif(not env.WINDOWS, reason="not windows")
54 | def test_source_for_file_windows(tmp_path: pathlib.Path) -> None:
55 |     a_py = tmp_path / "a.py"
56 |     src = str(a_py)
57 | 
58 |     # On windows if a pyw exists, it is an acceptable source
59 |     path_windows = tmp_path / "a.pyw"
60 |     path_windows.write_text("")
61 |     assert str(path_windows) == source_for_file(src + 'c')
62 | 
63 |     # If both pyw and py exist, py is preferred
64 |     a_py.write_text("")
65 |     assert source_for_file(src + 'c') == src
66 | 


--------------------------------------------------------------------------------
/ci/download_gha_artifacts.py:
--------------------------------------------------------------------------------
 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 3 | 
 4 | """Use the GitHub API to download built artifacts."""
 5 | 
 6 | import datetime
 7 | import json
 8 | import os
 9 | import os.path
10 | import sys
11 | import time
12 | import zipfile
13 | 
14 | import requests
15 | 
16 | def download_url(url, filename):
17 |     """Download a file from `url` to `filename`."""
18 |     response = requests.get(url, stream=True)
19 |     if response.status_code == 200:
20 |         with open(filename, "wb") as f:
21 |             for chunk in response.iter_content(16*1024):
22 |                 f.write(chunk)
23 |     else:
24 |         raise RuntimeError(f"Fetching {url} produced: status={response.status_code}")
25 | 
26 | def unpack_zipfile(filename):
27 |     """Unpack a zipfile, using the names in the zip."""
28 |     with open(filename, "rb") as fzip:
29 |         z = zipfile.ZipFile(fzip)
30 |         for name in z.namelist():
31 |             print(f"  extracting {name}")
32 |             z.extract(name)
33 | 
34 | def utc2local(timestring):
35 |     """Convert a UTC time into local time in a more readable form.
36 | 
37 |     For example: '20201208T122900Z' to '2020-12-08 07:29:00'.
38 | 
39 |     """
40 |     dt = datetime.datetime
41 |     utc = dt.fromisoformat(timestring.rstrip("Z"))
42 |     epoch = time.mktime(utc.timetuple())
43 |     offset = dt.fromtimestamp(epoch) - dt.utcfromtimestamp(epoch)
44 |     local = utc + offset
45 |     return local.strftime("%Y-%m-%d %H:%M:%S")
46 | 
47 | dest = "dist"
48 | repo_owner = sys.argv[1]
49 | temp_zip = "artifacts.zip"
50 | 
51 | os.makedirs(dest, exist_ok=True)
52 | os.chdir(dest)
53 | 
54 | r = requests.get(f"https://api.github.com/repos/{repo_owner}/actions/artifacts")
55 | if r.status_code == 200:
56 |     dists = [a for a in r.json()["artifacts"] if a["name"] == "dist"]
57 |     if not dists:
58 |         print("No recent dists!")
59 |     else:
60 |         latest = max(dists, key=lambda a: a["created_at"])
61 |         print(f"Artifacts created at {utc2local(latest['created_at'])}")
62 |         download_url(latest["archive_download_url"], temp_zip)
63 |         unpack_zipfile(temp_zip)
64 |         os.remove(temp_zip)
65 | else:
66 |     print(f"Fetching artifacts returned status {r.status_code}:")
67 |     print(json.dumps(r.json(), indent=4))
68 |     sys.exit(1)
69 | 


--------------------------------------------------------------------------------
/lab/select_contexts.py:
--------------------------------------------------------------------------------
 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 3 | 
 4 | """\
 5 | Select certain contexts from a coverage.py data file.
 6 | """
 7 | 
 8 | import argparse
 9 | import re
10 | import sys
11 | 
12 | import coverage
13 | 
14 | 
15 | def main(argv):
16 |     parser = argparse.ArgumentParser(description=__doc__)
17 |     parser.add_argument("--include", type=str, help="Regex for contexts to keep")
18 |     parser.add_argument("--exclude", type=str, help="Regex for contexts to discard")
19 |     args = parser.parse_args(argv)
20 | 
21 |     print("** Note: this is a proof-of-concept. Support is not promised. **")
22 |     print("Feedback is appreciated: https://github.com/nedbat/coveragepy/issues/668")
23 | 
24 |     cov_in = coverage.Coverage()
25 |     cov_in.load()
26 |     data_in = cov_in.get_data()
27 |     print(f"Contexts in {data_in.data_filename()}:")
28 |     for ctx in sorted(data_in.measured_contexts()):
29 |         print(f"    {ctx}")
30 | 
31 |     if args.include is None and args.exclude is None:
32 |         print("Nothing to do, no output written.")
33 |         return
34 | 
35 |     out_file = "output.data"
36 |     file_names = data_in.measured_files()
37 |     print(f"{len(file_names)} measured files")
38 |     print(f"Writing to {out_file}")
39 |     cov_out = coverage.Coverage(data_file=out_file)
40 |     data_out = cov_out.get_data()
41 | 
42 |     for ctx in sorted(data_in.measured_contexts()):
43 |         if args.include is not None:
44 |             if not re.search(args.include, ctx):
45 |                 print(f"Skipping context {ctx}, not included")
46 |                 continue
47 |         if args.exclude is not None:
48 |             if re.search(args.exclude, ctx):
49 |                 print(f"Skipping context {ctx}, excluded")
50 |                 continue
51 |         print(f"Keeping context {ctx}")
52 |         data_in.set_query_context(ctx)
53 |         data_out.set_context(ctx)
54 |         if data_in.has_arcs():
55 |             data_out.add_arcs({f: data_in.arcs(f) for f in file_names})
56 |         else:
57 |             data_out.add_lines({f: data_in.lines(f) for f in file_names})
58 | 
59 |     for fname in file_names:
60 |         data_out.touch_file(fname, data_in.file_tracer(fname))
61 | 
62 |     cov_out.save()
63 | 
64 | 
65 | if __name__ == "__main__":
66 |     sys.exit(main(sys.argv[1:]))
67 | 


--------------------------------------------------------------------------------
/lab/extract_code.py:
--------------------------------------------------------------------------------
 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 3 | 
 4 | """
 5 | Use this to copy some indented code from the coverage.py test suite into a
 6 | standalone file for deeper testing, or writing bug reports.
 7 | 
 8 | Give it a file name and a line number, and it will find the indentend
 9 | multiline string containing that line number, and output the dedented
10 | contents of the string.
11 | 
12 | If tests/test_arcs.py has this (partial) content::
13 | 
14 |     1630	    def test_partial_generators(self):
15 |     1631	        # https://github.com/nedbat/coveragepy/issues/475
16 |     1632	        # Line 2 is executed completely.
17 |     1633	        # Line 3 is started but not finished, because zip ends before it finishes.
18 |     1634	        # Line 4 is never started.
19 |     1635	        cov = self.check_coverage('''\
20 |     1636	            def f(a, b):
21 |     1637	                c = (i for i in a)          # 2
22 |     1638	                d = (j for j in b)          # 3
23 |     1639	                e = (k for k in b)          # 4
24 |     1640	                return dict(zip(c, d))
25 |     1641
26 |     1642	            f(['a', 'b'], [1, 2, 3])
27 |     1643	            ''',
28 |     1644	            arcz=".1 17 7.  .2 23 34 45 5.  -22 2-2  -33 3-3  -44 4-4",
29 |     1645	            arcz_missing="3-3 -44 4-4",
30 |     1646	        )
31 | 
32 | then you can do::
33 | 
34 |     % python lab/extract_code.py tests/test_arcs.py 1637
35 |     def f(a, b):
36 |         c = (i for i in a)          # 2
37 |         d = (j for j in b)          # 3
38 |         e = (k for k in b)          # 4
39 |         return dict(zip(c, d))
40 | 
41 |     f(['a', 'b'], [1, 2, 3])
42 |     %
43 | 
44 | """
45 | 
46 | import sys
47 | import textwrap
48 | 
49 | if len(sys.argv) == 2:
50 |     fname, lineno = sys.argv[1].split(":")
51 | else:
52 |     fname, lineno = sys.argv[1:]
53 | lineno = int(lineno)
54 | 
55 | with open(fname) as code_file:
56 |     lines = ["", *code_file]
57 | 
58 | # Find opening triple-quote
59 | for start in range(lineno, 0, -1):
60 |     line = lines[start]
61 |     if "'''" in line or '"""' in line:
62 |         break
63 | 
64 | for end in range(lineno+1, len(lines)):
65 |     line = lines[end]
66 |     if "'''" in line or '"""' in line:
67 |         break
68 | 
69 | code = "".join(lines[start+1: end])
70 | code = textwrap.dedent(code)
71 | 
72 | print(code, end="")
73 | 


--------------------------------------------------------------------------------
/coverage/ctracer/util.h:
--------------------------------------------------------------------------------
 1 | /* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */
 2 | /* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */
 3 | 
 4 | #ifndef _COVERAGE_UTIL_H
 5 | #define _COVERAGE_UTIL_H
 6 | 
 7 | #include 
 8 | 
 9 | /* Compile-time debugging helpers */
10 | #undef WHAT_LOG         /* Define to log the WHAT params in the trace function. */
11 | #undef TRACE_LOG        /* Define to log our bookkeeping. */
12 | #undef COLLECT_STATS    /* Collect counters: stats are printed when tracer is stopped. */
13 | #undef DO_NOTHING       /* Define this to make the tracer do nothing. */
14 | 
15 | #if PY_VERSION_HEX >= 0x030B00A0
16 | // 3.11 moved f_lasti into an internal structure. This is totally the wrong way
17 | // to make this work, but it's all I've got until https://bugs.python.org/issue40421
18 | // is resolved.
19 | #include 
20 | #if PY_VERSION_HEX >= 0x030B00A7
21 | #define MyFrame_GetLasti(f)     (PyFrame_GetLasti(f))
22 | #else
23 | #define MyFrame_GetLasti(f)     ((f)->f_frame->f_lasti * 2)
24 | #endif
25 | #elif PY_VERSION_HEX >= 0x030A00A7
26 | // The f_lasti field changed meaning in 3.10.0a7. It had been bytes, but
27 | // now is instructions, so we need to adjust it to use it as a byte index.
28 | #define MyFrame_GetLasti(f)     ((f)->f_lasti * 2)
29 | #else
30 | #define MyFrame_GetLasti(f)     ((f)->f_lasti)
31 | #endif
32 | 
33 | // Access f_code should be done through a helper starting in 3.9.
34 | #if PY_VERSION_HEX >= 0x03090000
35 | #define MyFrame_GetCode(f)      (PyFrame_GetCode(f))
36 | #else
37 | #define MyFrame_GetCode(f)      ((f)->f_code)
38 | #endif
39 | 
40 | #if PY_VERSION_HEX >= 0x030B00B1
41 | #define MyCode_GetCode(co)      (PyCode_GetCode(co))
42 | #define MyCode_FreeCode(code)   Py_XDECREF(code)
43 | #elif PY_VERSION_HEX >= 0x030B00A7
44 | #define MyCode_GetCode(co)      (PyObject_GetAttrString((PyObject *)(co), "co_code"))
45 | #define MyCode_FreeCode(code)   Py_XDECREF(code)
46 | #else
47 | #define MyCode_GetCode(co)      ((co)->co_code)
48 | #define MyCode_FreeCode(code)
49 | #endif
50 | 
51 | /* The values returned to indicate ok or error. */
52 | #define RET_OK      0
53 | #define RET_ERROR   -1
54 | 
55 | /* Nicer booleans */
56 | typedef int BOOL;
57 | #define FALSE   0
58 | #define TRUE    1
59 | 
60 | #if SIZEOF_LONG_LONG < 8
61 | #error long long too small!
62 | #endif
63 | typedef unsigned long long uint64;
64 | 
65 | /* Only for extreme machete-mode debugging! */
66 | #define CRASH       { printf("*** CRASH! ***\n"); *((int*)1) = 1; }
67 | 
68 | #endif /* _COVERAGE_UTIL_H */
69 | 


--------------------------------------------------------------------------------
/tests/plugin2.py:
--------------------------------------------------------------------------------
 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 3 | 
 4 | """A file tracer plugin for test_plugins.py to import."""
 5 | 
 6 | from __future__ import annotations
 7 | 
 8 | import os.path
 9 | 
10 | from types import FrameType
11 | from typing import Any, Optional, Set, Tuple
12 | 
13 | from coverage import CoveragePlugin, FileReporter, FileTracer
14 | from coverage.plugin_support import Plugins
15 | from coverage.types import TLineNo
16 | 
17 | try:
18 |     import third.render                 # pylint: disable=unused-import
19 | except ImportError:
20 |     # This plugin is used in a few tests. One of them has the third.render
21 |     # module, but most don't. We need to import it but not use it, so just
22 |     # try importing it and it's OK if the module doesn't exist.
23 |     pass
24 | 
25 | 
26 | class Plugin(CoveragePlugin):
27 |     """A file tracer plugin for testing."""
28 |     def file_tracer(self, filename: str) -> Optional[FileTracer]:
29 |         if "render.py" in filename:
30 |             return RenderFileTracer()
31 |         return None
32 | 
33 |     def file_reporter(self, filename: str) -> FileReporter:
34 |         return MyFileReporter(filename)
35 | 
36 | 
37 | class RenderFileTracer(FileTracer):
38 |     """A FileTracer using information from the caller."""
39 | 
40 |     def has_dynamic_source_filename(self) -> bool:
41 |         return True
42 | 
43 |     def dynamic_source_filename(
44 |         self,
45 |         filename: str,
46 |         frame: FrameType,
47 |     ) -> Optional[str]:
48 |         if frame.f_code.co_name != "render":
49 |             return None
50 |         source_filename: str = os.path.abspath(frame.f_locals['filename'])
51 |         return source_filename
52 | 
53 |     def line_number_range(self, frame: FrameType) -> Tuple[TLineNo, TLineNo]:
54 |         lineno = frame.f_locals['linenum']
55 |         return lineno, lineno+1
56 | 
57 | 
58 | class MyFileReporter(FileReporter):
59 |     """A goofy file reporter."""
60 |     def lines(self) -> Set[TLineNo]:
61 |         # Goofy test arrangement: claim that the file has as many lines as the
62 |         # number in its name.
63 |         num = os.path.basename(self.filename).split(".")[0].split("_")[1]
64 |         return set(range(1, int(num)+1))
65 | 
66 | 
67 | def coverage_init(
68 |     reg: Plugins,
69 |     options: Any,       # pylint: disable=unused-argument
70 | ) -> None:
71 |     """Called by coverage to initialize the plugins here."""
72 |     reg.add_file_tracer(Plugin())
73 | 


--------------------------------------------------------------------------------
/tests/test_report.py:
--------------------------------------------------------------------------------
 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 3 | 
 4 | """Tests for helpers in report.py"""
 5 | 
 6 | from __future__ import annotations
 7 | 
 8 | from typing import IO, Iterable, List, Optional, Type
 9 | 
10 | import pytest
11 | 
12 | from coverage.exceptions import CoverageException
13 | from coverage.report import render_report
14 | from coverage.types import TMorf
15 | 
16 | from tests.coveragetest import CoverageTest
17 | 
18 | 
19 | class FakeReporter:
20 |     """A fake implementation of a one-file reporter."""
21 | 
22 |     report_type = "fake report file"
23 | 
24 |     def __init__(self, output: str = "", error: Optional[Type[Exception]] = None) -> None:
25 |         self.output = output
26 |         self.error = error
27 |         self.morfs: Optional[Iterable[TMorf]] = None
28 | 
29 |     def report(self, morfs: Optional[Iterable[TMorf]], outfile: IO[str]) -> float:
30 |         """Fake."""
31 |         self.morfs = morfs
32 |         outfile.write(self.output)
33 |         if self.error:
34 |             raise self.error("You asked for it!")
35 |         return 17.25
36 | 
37 | 
38 | class RenderReportTest(CoverageTest):
39 |     """Tests of render_report."""
40 | 
41 |     def test_stdout(self) -> None:
42 |         fake = FakeReporter(output="Hello!\n")
43 |         msgs: List[str] = []
44 |         res = render_report("-", fake, [pytest, "coverage"], msgs.append)
45 |         assert res == 17.25
46 |         assert fake.morfs == [pytest, "coverage"]
47 |         assert self.stdout() == "Hello!\n"
48 |         assert not msgs
49 | 
50 |     def test_file(self) -> None:
51 |         fake = FakeReporter(output="Gréètings!\n")
52 |         msgs: List[str] = []
53 |         res = render_report("output.txt", fake, [], msgs.append)
54 |         assert res == 17.25
55 |         assert self.stdout() == ""
56 |         with open("output.txt", "rb") as f:
57 |             assert f.read().rstrip() == b"Gr\xc3\xa9\xc3\xa8tings!"
58 |         assert msgs == ["Wrote fake report file to output.txt"]
59 | 
60 |     @pytest.mark.parametrize("error", [CoverageException, ZeroDivisionError])
61 |     def test_exception(self, error: Type[Exception]) -> None:
62 |         fake = FakeReporter(error=error)
63 |         msgs: List[str] = []
64 |         with pytest.raises(error, match="You asked for it!"):
65 |             render_report("output.txt", fake, [], msgs.append)
66 |         assert self.stdout() == ""
67 |         self.assert_doesnt_exist("output.txt")
68 |         assert not msgs
69 | 


--------------------------------------------------------------------------------
/coverage/fullcoverage/encodings.py:
--------------------------------------------------------------------------------
 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 3 | 
 4 | """Imposter encodings module that installs a coverage-style tracer.
 5 | 
 6 | This is NOT the encodings module; it is an imposter that sets up tracing
 7 | instrumentation and then replaces itself with the real encodings module.
 8 | 
 9 | If the directory that holds this file is placed first in the PYTHONPATH when
10 | using "coverage" to run Python's tests, then this file will become the very
11 | first module imported by the internals of Python 3.  It installs a
12 | coverage.py-compatible trace function that can watch Standard Library modules
13 | execute from the very earliest stages of Python's own boot process.  This fixes
14 | a problem with coverage.py - that it starts too late to trace the coverage of
15 | many of the most fundamental modules in the Standard Library.
16 | 
17 | DO NOT import other modules into here, it will interfere with the goal of this
18 | code executing before all imports.  This is why this file isn't type-checked.
19 | 
20 | """
21 | 
22 | import sys
23 | 
24 | class FullCoverageTracer:
25 |     def __init__(self):
26 |         # `traces` is a list of trace events.  Frames are tricky: the same
27 |         # frame object is used for a whole scope, with new line numbers
28 |         # written into it.  So in one scope, all the frame objects are the
29 |         # same object, and will eventually all will point to the last line
30 |         # executed.  So we keep the line numbers alongside the frames.
31 |         # The list looks like:
32 |         #
33 |         #   traces = [
34 |         #       ((frame, event, arg), lineno), ...
35 |         #       ]
36 |         #
37 |         self.traces = []
38 | 
39 |     def fullcoverage_trace(self, *args):
40 |         frame, event, arg = args
41 |         if frame.f_lineno is not None:
42 |             # https://bugs.python.org/issue46911
43 |             self.traces.append((args, frame.f_lineno))
44 |         return self.fullcoverage_trace
45 | 
46 | sys.settrace(FullCoverageTracer().fullcoverage_trace)
47 | 
48 | # Remove our own directory from sys.path; remove ourselves from
49 | # sys.modules; and re-import "encodings", which will be the real package
50 | # this time.  Note that the delete from sys.modules dictionary has to
51 | # happen last, since all of the symbols in this module will become None
52 | # at that exact moment, including "sys".
53 | 
54 | parentdir = max(filter(__file__.startswith, sys.path), key=len)
55 | sys.path.remove(parentdir)
56 | del sys.modules['encodings']
57 | import encodings
58 | 


--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
 1 | # For most projects, this workflow file will not need changing; you simply need
 2 | # to commit it to your repository.
 3 | #
 4 | # You may wish to alter this file to override the set of languages analyzed,
 5 | # or to provide custom queries or build logic.
 6 | #
 7 | # ******** NOTE ********
 8 | # We have attempted to detect the languages in your repository. Please check
 9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 | 
14 | on:
15 |   push:
16 |     branches:
17 |       - master
18 |   pull_request:
19 |     # The branches below must be a subset of the branches above
20 |     branches:
21 |       - master
22 |   schedule:
23 |     - cron: '30 20 * * 6'
24 | 
25 | permissions:
26 |   contents: read
27 | 
28 | jobs:
29 |   analyze:
30 |     name: Analyze
31 |     runs-on: ubuntu-latest
32 |     permissions:
33 |       actions: read
34 |       contents: read
35 |       security-events: write
36 | 
37 |     strategy:
38 |       fail-fast: false
39 |       matrix:
40 |         language:
41 |           - python
42 |           - javascript
43 |         # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
44 |         # Learn more about CodeQL language support at https://git.io/codeql-language-support
45 | 
46 |     steps:
47 |     - name: Checkout repository
48 |       uses: actions/checkout@v3
49 | 
50 |     # Initializes the CodeQL tools for scanning.
51 |     - name: Initialize CodeQL
52 |       uses: github/codeql-action/init@v2
53 |       with:
54 |         languages: ${{ matrix.language }}
55 |         # If you wish to specify custom queries, you can do so here or in a config file.
56 |         # By default, queries listed here will override any specified in a config file.
57 |         # Prefix the list here with "+" to use these queries and those in the config file.
58 |         # queries: ./path/to/local/query, your-org/your-repo/queries@main
59 | 
60 |     # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
61 |     # If this step fails, then you should remove it and run the build manually (see below)
62 |     - name: Autobuild
63 |       uses: github/codeql-action/autobuild@v2
64 | 
65 |     # ℹ️ Command-line programs to run using the OS shell.
66 |     # 📚 https://git.io/JvXDl
67 | 
68 |     # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
69 |     #    and modify them (or add more) to build your code if your project
70 |     #    uses a compiled language
71 | 
72 |     #- run: |
73 |     #   make bootstrap
74 |     #   make release
75 | 
76 |     - name: Perform CodeQL Analysis
77 |       uses: github/codeql-action/analyze@v2
78 | 


--------------------------------------------------------------------------------
/doc/trouble.rst:
--------------------------------------------------------------------------------
 1 | .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 2 | .. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 3 | 
 4 | .. _trouble:
 5 | 
 6 | =========================
 7 | Things that cause trouble
 8 | =========================
 9 | 
10 | Coverage.py works well, and I want it to properly measure any Python program,
11 | but there are some situations it can't cope with.  This page details some known
12 | problems, with possible courses of action, and links to coverage.py bug reports
13 | with more information.
14 | 
15 | I would love to :ref:`hear from you ` if you have information about
16 | any of these problems, even just to explain to me why you want them to start
17 | working properly.
18 | 
19 | If your problem isn't discussed here, you can of course search the `coverage.py
20 | bug tracker`_ directly to see if there is some mention of it.
21 | 
22 | .. _coverage.py bug tracker: https://github.com/nedbat/coveragepy/issues
23 | 
24 | 
25 | Things that don't work
26 | ----------------------
27 | 
28 | There are a few modules or functions that prevent coverage.py from working
29 | properly:
30 | 
31 | * `execv`_, or one of its variants.  These end the current program and replace
32 |   it with a new one.  This doesn't save the collected coverage data, so your
33 |   program that calls execv will not be fully measured.  A patch for coverage.py
34 |   is in `issue 43`_.
35 | 
36 | * `thread`_, in the Python standard library, is the low-level threading
37 |   interface.  Threads created with this module will not be traced.  Use the
38 |   higher-level `threading`_ module instead.
39 | 
40 | * `sys.settrace`_ is the Python feature that coverage.py uses to see what's
41 |   happening in your program.  If another part of your program is using
42 |   sys.settrace, then it will conflict with coverage.py, and it won't be
43 |   measured properly.
44 | 
45 | * `sys.setprofile`_ calls your code, but while running your code, does not fire
46 |   trace events.  This means that coverage.py can't see what's happening in that
47 |   code.
48 | 
49 | .. _execv: https://docs.python.org/3/library/os.html#os.execl
50 | .. _sys.settrace: https://docs.python.org/3/library/sys.html#sys.settrace
51 | .. _sys.setprofile: https://docs.python.org/3/library/sys.html#sys.setprofile
52 | .. _thread: https://docs.python.org/3/library/_thread.html
53 | .. _threading: https://docs.python.org/3/library/threading.html
54 | .. _issue 43: https://github.com/nedbat/coveragepy/issues/43
55 | 
56 | 
57 | Still having trouble?
58 | ---------------------
59 | 
60 | If your problem isn't mentioned here, and isn't already reported in the
61 | `coverage.py bug tracker`_, please :ref:`get in touch with me `,
62 | we'll figure out a solution.
63 | 


--------------------------------------------------------------------------------
/coverage/context.py:
--------------------------------------------------------------------------------
 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 3 | 
 4 | """Determine contexts for coverage.py"""
 5 | 
 6 | from __future__ import annotations
 7 | 
 8 | from types import FrameType
 9 | from typing import cast, Callable, Optional, Sequence
10 | 
11 | 
12 | def combine_context_switchers(
13 |     context_switchers: Sequence[Callable[[FrameType], Optional[str]]],
14 | ) -> Optional[Callable[[FrameType], Optional[str]]]:
15 |     """Create a single context switcher from multiple switchers.
16 | 
17 |     `context_switchers` is a list of functions that take a frame as an
18 |     argument and return a string to use as the new context label.
19 | 
20 |     Returns a function that composites `context_switchers` functions, or None
21 |     if `context_switchers` is an empty list.
22 | 
23 |     When invoked, the combined switcher calls `context_switchers` one-by-one
24 |     until a string is returned.  The combined switcher returns None if all
25 |     `context_switchers` return None.
26 |     """
27 |     if not context_switchers:
28 |         return None
29 | 
30 |     if len(context_switchers) == 1:
31 |         return context_switchers[0]
32 | 
33 |     def should_start_context(frame: FrameType) -> Optional[str]:
34 |         """The combiner for multiple context switchers."""
35 |         for switcher in context_switchers:
36 |             new_context = switcher(frame)
37 |             if new_context is not None:
38 |                 return new_context
39 |         return None
40 | 
41 |     return should_start_context
42 | 
43 | 
44 | def should_start_context_test_function(frame: FrameType) -> Optional[str]:
45 |     """Is this frame calling a test_* function?"""
46 |     co_name = frame.f_code.co_name
47 |     if co_name.startswith("test") or co_name == "runTest":
48 |         return qualname_from_frame(frame)
49 |     return None
50 | 
51 | 
52 | def qualname_from_frame(frame: FrameType) -> Optional[str]:
53 |     """Get a qualified name for the code running in `frame`."""
54 |     co = frame.f_code
55 |     fname = co.co_name
56 |     method = None
57 |     if co.co_argcount and co.co_varnames[0] == "self":
58 |         self = frame.f_locals.get("self", None)
59 |         method = getattr(self, fname, None)
60 | 
61 |     if method is None:
62 |         func = frame.f_globals.get(fname)
63 |         if func is None:
64 |             return None
65 |         return cast(str, func.__module__ + "." + fname)
66 | 
67 |     func = getattr(method, "__func__", None)
68 |     if func is None:
69 |         cls = self.__class__
70 |         return cast(str, cls.__module__ + "." + cls.__name__ + "." + fname)
71 | 
72 |     return cast(str, func.__module__ + "." + func.__qualname__)
73 | 


--------------------------------------------------------------------------------
/doc/install.rst:
--------------------------------------------------------------------------------
 1 | .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 2 | .. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 3 | 
 4 | .. _install:
 5 | 
 6 | ============
 7 | Installation
 8 | ============
 9 | 
10 | .. highlight:: console
11 | 
12 | .. _coverage_pypi: https://pypi.org/project/coverage/
13 | .. _setuptools: https://pypi.org/project/setuptools/
14 | 
15 | 
16 | You can install coverage.py in the usual ways. The simplest way is with pip::
17 | 
18 |     $ pip install coverage
19 | 
20 | .. ifconfig:: prerelease
21 | 
22 |     To install a pre-release version, you will need to specify ``--pre``::
23 | 
24 |         $ pip install --pre coverage
25 | 
26 |     or the exact version you want to install:
27 | 
28 |     .. parsed-literal::
29 | 
30 |         $ pip install |coverage-equals-release|
31 | 
32 | .. _install_extension:
33 | 
34 | C Extension
35 | -----------
36 | 
37 | Coverage.py includes a C extension for speed. It is strongly recommended to use
38 | this extension: it is much faster, and is needed to support a number of
39 | coverage.py features.  Most of the time, the C extension will be installed
40 | without any special action on your part.
41 | 
42 | You can determine if you are using the extension by looking at the output of
43 | ``coverage --version``:
44 | 
45 | .. parsed-literal::
46 | 
47 |     $ coverage --version
48 |     Coverage.py, version |release| with C extension
49 |     Documentation at |doc-url|
50 | 
51 | The first line will either say "with C extension," or "without C extension."
52 | 
53 | If you are missing the extension, first make sure you have the latest version
54 | of pip in use when installing coverage.
55 | 
56 | If you are installing on Linux, you may need to install the python-dev and gcc
57 | support files before installing coverage via pip.  The exact commands depend on
58 | which package manager you use, which Python version you are using, and the
59 | names of the packages for your distribution.  For example::
60 | 
61 |     $ sudo apt-get install python-dev gcc
62 |     $ sudo yum install python-devel gcc
63 | 
64 |     $ sudo apt-get install python3-dev gcc
65 |     $ sudo yum install python3-devel gcc
66 | 
67 | A few features of coverage.py aren't supported without the C extension, such
68 | as concurrency and plugins.
69 | 
70 | 
71 | Checking the installation
72 | -------------------------
73 | 
74 | If all went well, you should be able to open a command prompt, and see
75 | coverage.py installed properly:
76 | 
77 | .. parsed-literal::
78 | 
79 |     $ coverage --version
80 |     Coverage.py, version |release| with C extension
81 |     Documentation at |doc-url|
82 | 
83 | You can also invoke coverage.py as a module:
84 | 
85 | .. parsed-literal::
86 | 
87 |     $ python -m coverage --version
88 |     Coverage.py, version |release| with C extension
89 |     Documentation at |doc-url|
90 | 


--------------------------------------------------------------------------------
/doc/plugins.rst:
--------------------------------------------------------------------------------
 1 | .. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 2 | .. For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 3 | 
 4 | .. _plugins:
 5 | 
 6 | ========
 7 | Plug-ins
 8 | ========
 9 | 
10 | Coverage.py's behavior can be extended with third-party plug-ins.  A plug-in is
11 | a separately installed Python class that you register in your .coveragerc.
12 | Plugins can alter a number of aspects of coverage.py's behavior, including
13 | implementing coverage measurement for non-Python files.
14 | 
15 | Information about using plug-ins is on this page.  To write a plug-in, see
16 | :ref:`api_plugin`.
17 | 
18 | .. versionadded:: 4.0
19 | 
20 | 
21 | Using plug-ins
22 | --------------
23 | 
24 | To use a coverage.py plug-in, you install it and configure it.  For this
25 | example, let's say there's a Python package called ``something`` that provides
26 | a coverage.py plug-in called ``something.plugin``.
27 | 
28 | #. Install the plug-in's package as you would any other Python package:
29 | 
30 |    .. code-block:: sh
31 | 
32 |     $ pip install something
33 | 
34 | #. Configure coverage.py to use the plug-in.  You do this by editing (or
35 |    creating) your .coveragerc file, as described in :ref:`config`.  The
36 |    ``plugins`` setting indicates your plug-in.  It's a list of importable
37 |    module names of plug-ins:
38 | 
39 |    .. code-block:: ini
40 | 
41 |     [run]
42 |     plugins =
43 |         something.plugin
44 | 
45 | #. If the plug-in needs its own configuration, you can add those settings in
46 |    the .coveragerc file in a section named for the plug-in:
47 | 
48 |    .. code-block:: ini
49 | 
50 |     [something.plugin]
51 |     option1 = True
52 |     option2 = abc.foo
53 | 
54 |    Check the documentation for the plug-in for details on the options it takes.
55 | 
56 | #. Run your tests with coverage.py as you usually would.  If you get a message
57 |    like "Plugin file tracers (something.plugin) aren't supported with
58 |    PyTracer," then you don't have the :ref:`C extension `
59 |    installed.  The C extension is needed for certain plug-ins.
60 | 
61 | 
62 | Available plug-ins
63 | ------------------
64 | 
65 | Some coverage.py plug-ins you might find useful:
66 | 
67 | * `Django template coverage.py plug-in`__: for measuring coverage in Django
68 |   templates.
69 | 
70 |   .. __: https://pypi.org/project/django_coverage_plugin/
71 | 
72 | * `Conditional coverage plug-in`__: for measuring coverage based
73 |   on any rules you define!
74 |   Can exclude different lines of code that are only executed
75 |   on different platforms, python versions,
76 |   and with different dependencies installed.
77 | 
78 |   .. __: https://github.com/wemake-services/coverage-conditional-plugin
79 | 
80 | * `Mako template coverage plug-in`__: for measuring coverage in Mako templates.
81 |   Doesn't work yet, probably needs some changes in Mako itself.
82 | 
83 |   .. __: https://bitbucket-archive.softwareheritage.org/projects/ne/ned/coverage-mako-plugin.html
84 | 


--------------------------------------------------------------------------------
/lab/branches.py:
--------------------------------------------------------------------------------
 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 3 | 
 4 | # Demonstrate some issues with coverage.py branch testing.
 5 | 
 6 | def my_function(x):
 7 |     """This isn't real code, just snippets..."""
 8 | 
 9 |     # An infinite loop is structurally still a branch: it can next execute the
10 |     # first line of the loop, or the first line after the loop.  But
11 |     # "while True" will never jump to the line after the loop, so the line
12 |     # is shown as a partial branch:
13 | 
14 |     i = 0
15 |     while True:
16 |         print("In while True")
17 |         if i > 0:
18 |             break
19 |         i += 1
20 |     print("Left the True loop")
21 | 
22 |     # Notice that "while 1" also has this problem.  Even though the compiler
23 |     # knows there's no computation at the top of the loop, it's still expressed
24 |     # in bytecode as a branch with two possibilities.
25 | 
26 |     i = 0
27 |     while 1:
28 |         print("In while 1")
29 |         if i > 0:
30 |             break
31 |         i += 1
32 |     print("Left the 1 loop")
33 | 
34 |     # Coverage.py lets developers exclude lines that they know will not be
35 |     # executed.  So far, the branch coverage doesn't use all that information
36 |     # when deciding which lines are partially executed.
37 |     #
38 |     # Here, even though the else line is explicitly marked as never executed,
39 |     # the if line complains that it never branched to the else:
40 | 
41 |     if x < 1000:
42 |         # This branch is always taken
43 |         print("x is reasonable")
44 |     else:   # pragma: nocover
45 |         print("this never happens")
46 | 
47 |     # try-except structures are complex branches.  An except clause with a
48 |     # type is a three-way branch: there could be no exception, there could be
49 |     # a matching exception, and there could be a non-matching exception.
50 |     #
51 |     # Here we run the code twice: once with no exception, and once with a
52 |     # matching exception.  The "except" line is marked as partial because we
53 |     # never executed its third case: a non-matching exception.
54 | 
55 |     for y in (1, 2):
56 |         try:
57 |             if y % 2:
58 |                 raise ValueError("y is odd!")
59 |         except ValueError:
60 |             print("y must have been odd")
61 |         print("done with y")
62 |     print("done with 1, 2")
63 | 
64 |     # Another except clause, but this time all three cases are executed.  No
65 |     # partial lines are shown:
66 | 
67 |     for y in (0, 1, 2):
68 |         try:
69 |             if y % 2:
70 |                 raise ValueError("y is odd!")
71 |             if y == 0:
72 |                 raise Exception("zero!")
73 |         except ValueError:
74 |             print("y must have been odd")
75 |         except:
76 |             print("y is something else")
77 |         print("done with y")
78 |     print("done with 0, 1, 2")
79 | 
80 | 
81 | my_function(1)
82 | 


--------------------------------------------------------------------------------
/requirements/pip-tools.pip:
--------------------------------------------------------------------------------
 1 | #
 2 | # This file is autogenerated by pip-compile with Python 3.7
 3 | # by the following command:
 4 | #
 5 | #    make upgrade
 6 | #
 7 | build==0.10.0 \
 8 |     --hash=sha256:af266720050a66c893a6096a2f410989eeac74ff9a68ba194b3f6473e8e26171 \
 9 |     --hash=sha256:d5b71264afdb5951d6704482aac78de887c80691c52b88a9ad195983ca2c9269
10 |     # via pip-tools
11 | click==8.1.3 \
12 |     --hash=sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e \
13 |     --hash=sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48
14 |     # via pip-tools
15 | importlib-metadata==6.0.0 \
16 |     --hash=sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad \
17 |     --hash=sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d
18 |     # via
19 |     #   build
20 |     #   click
21 | packaging==23.0 \
22 |     --hash=sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2 \
23 |     --hash=sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97
24 |     # via build
25 | pip-tools==6.12.2 \
26 |     --hash=sha256:6a51f4fd67140d5e83703ebfa9610fb61398727151f56a1be02a972d062e4679 \
27 |     --hash=sha256:8b903696df4598b10d469026ef9995c5f9a874b416e88e7a214884ebe4a70245
28 |     # via -r requirements/pip-tools.in
29 | pyproject-hooks==1.0.0 \
30 |     --hash=sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8 \
31 |     --hash=sha256:f271b298b97f5955d53fb12b72c1fb1948c22c1a6b70b315c54cedaca0264ef5
32 |     # via build
33 | tomli==2.0.1 \
34 |     --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
35 |     --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
36 |     # via
37 |     #   build
38 |     #   pyproject-hooks
39 | typing-extensions==4.4.0 \
40 |     --hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \
41 |     --hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e
42 |     # via importlib-metadata
43 | wheel==0.38.4 \
44 |     --hash=sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac \
45 |     --hash=sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8
46 |     # via pip-tools
47 | zipp==3.13.0 \
48 |     --hash=sha256:23f70e964bc11a34cef175bc90ba2914e1e4545ea1e3e2f67c079671883f9cb6 \
49 |     --hash=sha256:e8b2a36ea17df80ffe9e2c4fda3f693c3dad6df1697d3cd3af232db680950b0b
50 |     # via importlib-metadata
51 | 
52 | # The following packages are considered to be unsafe in a requirements file:
53 | pip==23.0 \
54 |     --hash=sha256:aee438284e82c8def684b0bcc50b1f6ed5e941af97fa940e83e2e8ef1a59da9b \
55 |     --hash=sha256:b5f88adff801f5ef052bcdef3daa31b55eb67b0fccd6d0106c206fa248e0463c
56 |     # via pip-tools
57 | setuptools==65.7.0 \
58 |     --hash=sha256:4d3c92fac8f1118bb77a22181355e29c239cabfe2b9effdaa665c66b711136d7 \
59 |     --hash=sha256:8ab4f1dbf2b4a65f7eec5ad0c620e84c34111a68d3349833494b9088212214dd
60 |     # via
61 |     #   -c requirements/pins.pip
62 |     #   pip-tools
63 | 


--------------------------------------------------------------------------------
/.github/workflows/quality.yml:
--------------------------------------------------------------------------------
  1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
  2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
  3 | 
  4 | name: "Quality"
  5 | 
  6 | on:
  7 |   push:
  8 |     branches:
  9 |       - master
 10 |       - nedbat/*
 11 |   pull_request:
 12 |   workflow_dispatch:
 13 | 
 14 | defaults:
 15 |   run:
 16 |     shell: bash
 17 | 
 18 | env:
 19 |   PIP_DISABLE_PIP_VERSION_CHECK: 1
 20 | 
 21 | permissions:
 22 |   contents: read
 23 | 
 24 | concurrency:
 25 |   group: "${{ github.workflow }}-${{ github.ref }}"
 26 |   cancel-in-progress: true
 27 | 
 28 | jobs:
 29 |   lint:
 30 |     name: "Pylint etc"
 31 |     # Because pylint can report different things on different OS's (!)
 32 |     # (https://github.com/PyCQA/pylint/issues/3489), run this on Mac where local
 33 |     # pylint gets run.
 34 |     runs-on: macos-latest
 35 | 
 36 |     steps:
 37 |       - name: "Check out the repo"
 38 |         uses: "actions/checkout@v3"
 39 | 
 40 |       - name: "Install Python"
 41 |         uses: "actions/setup-python@v4"
 42 |         with:
 43 |           python-version: "3.7" # Minimum of PYVERSIONS
 44 |           cache: pip
 45 |           cache-dependency-path: 'requirements/*.pip'
 46 | 
 47 |       - name: "Install dependencies"
 48 |         run: |
 49 |           python -m pip install --require-hashes -r requirements/tox.pip
 50 | 
 51 |       - name: "Tox lint"
 52 |         run: |
 53 |           python -m tox -e lint
 54 | 
 55 |   mypy:
 56 |     name: "Check types"
 57 |     runs-on: ubuntu-latest
 58 | 
 59 |     steps:
 60 |       - name: "Check out the repo"
 61 |         uses: "actions/checkout@v3"
 62 | 
 63 |       - name: "Install Python"
 64 |         uses: "actions/setup-python@v4"
 65 |         with:
 66 |           python-version: "3.8" # Minimum of PYVERSIONS, but at least 3.8
 67 |           cache: pip
 68 |           cache-dependency-path: 'requirements/*.pip'
 69 | 
 70 |       - name: "Install dependencies"
 71 |         run: |
 72 |           # We run on 3.8, but the pins were made on 3.7, so don't insist on
 73 |           # hashes, which won't match.
 74 |           python -m pip install -r requirements/tox.pip
 75 | 
 76 |       - name: "Tox mypy"
 77 |         run: |
 78 |           python -m tox -e mypy
 79 | 
 80 |   doc:
 81 |     name: "Build docs"
 82 |     runs-on: ubuntu-latest
 83 | 
 84 |     steps:
 85 |       - name: "Check out the repo"
 86 |         uses: "actions/checkout@v3"
 87 | 
 88 |       - name: "Install Python"
 89 |         uses: "actions/setup-python@v4"
 90 |         with:
 91 |           python-version: "3.7" # Minimum of PYVERSIONS
 92 |           cache: pip
 93 |           cache-dependency-path: 'requirements/*.pip'
 94 | 
 95 |       - name: "Install dependencies"
 96 |         run: |
 97 |           set -xe
 98 |           python -VV
 99 |           python -m site
100 |           python -m pip install --require-hashes -r requirements/tox.pip
101 | 
102 |       - name: "Tox doc"
103 |         run: |
104 |           python -m tox -e doc
105 | 


--------------------------------------------------------------------------------
/.github/workflows/python-nightly.yml:
--------------------------------------------------------------------------------
 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 3 | 
 4 | name: "Python Nightly Tests"
 5 | 
 6 | on:
 7 |   push:
 8 |     branches:
 9 |       - "**/*nightly*"
10 |   schedule:
11 |     # Run at 2:22am early every morning Eastern time (6/7:22 UTC)
12 |     # so that we get tips of CPython development tested.
13 |     # https://crontab.guru/#22_7_%2a_%2a_%2a
14 |     - cron: "22 7 * * *"
15 |   workflow_dispatch:
16 | 
17 | defaults:
18 |   run:
19 |     shell: bash
20 | 
21 | env:
22 |   PIP_DISABLE_PIP_VERSION_CHECK: 1
23 |   COVERAGE_IGOR_VERBOSE: 1
24 | 
25 | permissions:
26 |   contents: read
27 | 
28 | concurrency:
29 |   group: "${{ github.workflow }}-${{ github.ref }}"
30 |   cancel-in-progress: true
31 | 
32 | jobs:
33 |   tests:
34 |     name: "Python ${{ matrix.python-version }}"
35 |     # Choose a recent Ubuntu that deadsnakes still builds all the versions for.
36 |     # For example, deadsnakes doesn't provide 3.10 nightly for 22.04 (jammy)
37 |     # because jammy ships 3.10, and deadsnakes doesn't want to clobber it.
38 |     # https://launchpad.net/~deadsnakes/+archive/ubuntu/nightly/+packages
39 |     # https://github.com/deadsnakes/issues/issues/234
40 |     runs-on: ubuntu-20.04
41 | 
42 |     strategy:
43 |       matrix:
44 |         python-version:
45 |           # When changing this list, be sure to check the [gh] list in
46 |           # tox.ini so that tox will run properly. PYVERSIONS
47 |           # Available versions:
48 |           # https://launchpad.net/~deadsnakes/+archive/ubuntu/nightly/+packages
49 |           - "3.10-dev"
50 |           - "3.11-dev"
51 |           - "3.12-dev"
52 |           # https://github.com/actions/setup-python#available-versions-of-pypy
53 |           - "pypy-3.7-nightly"
54 |           - "pypy-3.8-nightly"
55 |           - "pypy-3.9-nightly"
56 |       fail-fast: false
57 | 
58 |     steps:
59 |       - name: "Check out the repo"
60 |         uses: "actions/checkout@v3"
61 | 
62 |       - name: "Install ${{ matrix.python-version }} with deadsnakes"
63 |         uses: deadsnakes/action@e3117c2981fd8afe4af79f3e1be80066c82b70f5
64 |         if: "!startsWith(matrix.python-version, 'pypy-')"
65 |         with:
66 |           python-version: "${{ matrix.python-version }}"
67 | 
68 |       - name: "Install ${{ matrix.python-version }} with setup-python"
69 |         uses: "actions/setup-python@v4"
70 |         if: "startsWith(matrix.python-version, 'pypy-')"
71 |         with:
72 |           python-version: "${{ matrix.python-version }}"
73 | 
74 |       - name: "Show diagnostic info"
75 |         run: |
76 |           set -xe
77 |           python -VV
78 |           python -m site
79 |           python -m coverage debug sys
80 |           python -m coverage debug pybehave
81 | 
82 |       - name: "Install dependencies"
83 |         run: |
84 |           python -m pip install --require-hashes -r requirements/tox.pip
85 | 
86 |       - name: "Run tox"
87 |         run: |
88 |           python -m tox -- -rfsEX
89 | 


--------------------------------------------------------------------------------
/tests/osinfo.py:
--------------------------------------------------------------------------------
 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 3 | 
 4 | """OS information for testing."""
 5 | 
 6 | from __future__ import annotations
 7 | 
 8 | import sys
 9 | 
10 | 
11 | if sys.platform == "win32":
12 |     # Windows implementation
13 |     def process_ram() -> int:
14 |         """How much RAM is this process using? (Windows)"""
15 |         import ctypes
16 |         # From: http://lists.ubuntu.com/archives/bazaar-commits/2009-February/011990.html
17 |         class PROCESS_MEMORY_COUNTERS_EX(ctypes.Structure):
18 |             """Used by GetProcessMemoryInfo"""
19 |             _fields_ = [
20 |                 ('cb', ctypes.c_ulong),
21 |                 ('PageFaultCount', ctypes.c_ulong),
22 |                 ('PeakWorkingSetSize', ctypes.c_size_t),
23 |                 ('WorkingSetSize', ctypes.c_size_t),
24 |                 ('QuotaPeakPagedPoolUsage', ctypes.c_size_t),
25 |                 ('QuotaPagedPoolUsage', ctypes.c_size_t),
26 |                 ('QuotaPeakNonPagedPoolUsage', ctypes.c_size_t),
27 |                 ('QuotaNonPagedPoolUsage', ctypes.c_size_t),
28 |                 ('PagefileUsage', ctypes.c_size_t),
29 |                 ('PeakPagefileUsage', ctypes.c_size_t),
30 |                 ('PrivateUsage', ctypes.c_size_t),
31 |             ]
32 | 
33 |         mem_struct = PROCESS_MEMORY_COUNTERS_EX()
34 |         ret = ctypes.windll.psapi.GetProcessMemoryInfo(
35 |             ctypes.windll.kernel32.GetCurrentProcess(),
36 |             ctypes.byref(mem_struct),
37 |             ctypes.sizeof(mem_struct)
38 |         )
39 |         if not ret:                 # pragma: part covered
40 |             return 0                # pragma: cant happen
41 |         return mem_struct.PrivateUsage
42 | 
43 | elif sys.platform.startswith("linux"):
44 |     # Linux implementation
45 |     import os
46 | 
47 |     _scale = {'kb': 1024, 'mb': 1024*1024}
48 | 
49 |     def _VmB(key: str) -> int:
50 |         """Read the /proc/PID/status file to find memory use."""
51 |         try:
52 |             # Get pseudo file /proc//status
53 |             with open(f"/proc/{os.getpid()}/status") as t:
54 |                 v = t.read()
55 |         except OSError:             # pragma: cant happen
56 |             return 0    # non-Linux?
57 |         # Get VmKey line e.g. 'VmRSS:  9999  kB\n ...'
58 |         i = v.index(key)
59 |         vp = v[i:].split(None, 3)
60 |         if len(vp) < 3:             # pragma: part covered
61 |             return 0                # pragma: cant happen
62 |         # Convert Vm value to bytes.
63 |         return int(float(vp[1]) * _scale[vp[2].lower()])
64 | 
65 |     def process_ram() -> int:
66 |         """How much RAM is this process using? (Linux implementation)"""
67 |         return _VmB('VmRSS')
68 | 
69 | else:
70 |     # Generic implementation.
71 |     def process_ram() -> int:
72 |         """How much RAM is this process using? (stdlib implementation)"""
73 |         import resource
74 |         return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
75 | 


--------------------------------------------------------------------------------
/lab/hack_pyc.py:
--------------------------------------------------------------------------------
 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 3 | 
 4 | """ Wicked hack to get .pyc files to do bytecode tracing instead of
 5 |     line tracing.
 6 | """
 7 | 
 8 | import marshal, new, opcode, sys, types
 9 | 
10 | from lnotab import lnotab_numbers, lnotab_string
11 | 
12 | class PycFile:
13 |     def read(self, f):
14 |         if isinstance(f, basestring):
15 |             f = open(f, "rb")
16 |         self.magic = f.read(4)
17 |         self.modtime = f.read(4)
18 |         self.code = marshal.load(f)
19 | 
20 |     def write(self, f):
21 |         if isinstance(f, basestring):
22 |             f = open(f, "wb")
23 |         f.write(self.magic)
24 |         f.write(self.modtime)
25 |         marshal.dump(self.code, f)
26 | 
27 |     def hack_line_numbers(self):
28 |         self.code = hack_line_numbers(self.code)
29 | 
30 | def hack_line_numbers(code):
31 |     """ Replace a code object's line number information to claim that every
32 |         byte of the bytecode is a new source line.  Returns a new code
33 |         object.  Also recurses to hack the line numbers in nested code objects.
34 |     """
35 | 
36 |     # Create a new lnotab table.  Each opcode is claimed to be at
37 |     # 1000*lineno + (opcode number within line), so for example, the opcodes on
38 |     # source line 12 will be given new line numbers 12000, 12001, 12002, etc.
39 |     old_num = list(lnotab_numbers(code.co_lnotab, code.co_firstlineno))
40 |     n_bytes = len(code.co_code)
41 |     new_num = []
42 |     line = 0
43 |     opnum_in_line = 0
44 |     i_byte = 0
45 |     while i_byte < n_bytes:
46 |         if old_num and i_byte == old_num[0][0]:
47 |             line = old_num.pop(0)[1]
48 |             opnum_in_line = 0
49 |         new_num.append((i_byte, 100000000 + 1000*line + opnum_in_line))
50 |         if ord(code.co_code[i_byte]) >= opcode.HAVE_ARGUMENT:
51 |             i_byte += 3
52 |         else:
53 |             i_byte += 1
54 |         opnum_in_line += 1
55 | 
56 |     # new_num is a list of pairs, (byteoff, lineoff).  Turn it into an lnotab.
57 |     new_firstlineno = new_num[0][1]-1
58 |     new_lnotab = lnotab_string(new_num, new_firstlineno)
59 | 
60 |     # Recurse into code constants in this code object.
61 |     new_consts = []
62 |     for const in code.co_consts:
63 |         if type(const) == types.CodeType:
64 |             new_consts.append(hack_line_numbers(const))
65 |         else:
66 |             new_consts.append(const)
67 | 
68 |     # Create a new code object, just like the old one, except with new
69 |     # line numbers.
70 |     new_code = new.code(
71 |         code.co_argcount, code.co_nlocals, code.co_stacksize, code.co_flags,
72 |         code.co_code, tuple(new_consts), code.co_names, code.co_varnames,
73 |         code.co_filename, code.co_name, new_firstlineno, new_lnotab
74 |     )
75 | 
76 |     return new_code
77 | 
78 | def hack_file(f):
79 |     pyc = PycFile()
80 |     pyc.read(f)
81 |     pyc.hack_line_numbers()
82 |     pyc.write(f)
83 | 
84 | if __name__ == '__main__':
85 |     hack_file(sys.argv[1])
86 | 


--------------------------------------------------------------------------------
/metacov.ini:
--------------------------------------------------------------------------------
 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 3 | 
 4 | # Settings to use when using coverage.py to measure itself, known as
 5 | # meta-coverage.  This gets intricate because we need to keep the coverage
 6 | # measurement happening in the tests separate from our own coverage measurement
 7 | # of coverage.py itself.
 8 | 
 9 | [run]
10 | branch = true
11 | data_file = ${COVERAGE_METAFILE-.metacov}
12 | parallel = true
13 | relative_files = true
14 | source =
15 |     ${COVERAGE_HOME-.}/coverage
16 |     ${COVERAGE_HOME-.}/tests
17 | # $set_env.py: COVERAGE_DYNCTX - Set to 'test_function' for who-tests-what
18 | dynamic_context = ${COVERAGE_DYNCTX-none}
19 | # $set_env.py: COVERAGE_CONTEXT - Static context for this run (or $ENV_VAR like $TOX_ENV_NAME)
20 | context = ${COVERAGE_CONTEXT-none}
21 | 
22 | [report]
23 | # We set different pragmas so our code won't be confused with test code, and
24 | # we use different pragmas for different reasons that the lines won't be
25 | # measured.
26 | exclude_lines =
27 |     pragma: not covered
28 | 
29 |     # Lines in test code that aren't covered: we are nested inside ourselves.
30 |     # Sometimes this is used as a comment:
31 |     #
32 |     #       cov.start()
33 |     #       blah()              # pragma: nested
34 |     #       cov.stop()          # pragma: nested
35 |     #
36 |     # In order to exclude a series of lines, sometimes it's used as a constant
37 |     # condition, which might be too cute:
38 |     #
39 |     #       cov.start()
40 |     #       if "pragma: nested":
41 |     #           blah()
42 |     #           cov.stop()
43 |     #
44 |     pragma: nested
45 | 
46 |     # Lines that are only executed when we are debugging coverage.py.
47 |     def __repr__
48 |     pragma: debugging
49 | 
50 |     # Lines that are only executed when we are not testing coverage.py.
51 |     pragma: not testing
52 | 
53 |     # Lines that we can't run during metacov.
54 |     pragma: no metacov
55 |     pytest.mark.skipif\(env.METACOV
56 |     if not env.METACOV:
57 | 
58 |     # These lines only happen if tests fail.
59 |     raise AssertionError
60 |     pragma: only failure
61 | 
62 |     # Not-real code for type checking
63 |     if TYPE_CHECKING:
64 |     class .*\(Protocol\):
65 | 
66 |     # OS error conditions that we can't (or don't care to) replicate.
67 |     pragma: cant happen
68 | 
69 |     # Obscure bugs in specific versions of interpreters, and so probably no
70 |     # longer tested.
71 |     pragma: obscure
72 | 
73 | partial_branches =
74 |     pragma: part covered
75 |     # A for-loop that always hits its break statement
76 |     pragma: always breaks
77 |     pragma: part started
78 |     # If we're asserting that any() is true, it didn't finish.
79 |     assert any\(
80 |     if env.TESTING:
81 |     if env.METACOV:
82 | 
83 | precision = 3
84 | 
85 | [html]
86 | title = Coverage.py metacov
87 | 
88 | [paths]
89 | source =
90 |     .
91 |     */coverage/trunk
92 |     # GitHub Actions on Ubuntu uses /home/runner/work/coveragepy
93 |     # GitHub Actions on Mac uses /Users/runner/work/coveragepy
94 |     # GitHub Actions on Window uses D:\a\coveragepy\coveragepy
95 |     *\coveragepy
96 |     */coveragepy
97 | 


--------------------------------------------------------------------------------
/tests/test_mixins.py:
--------------------------------------------------------------------------------
 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 3 | 
 4 | """Tests of code in tests/mixins.py"""
 5 | 
 6 | from __future__ import annotations
 7 | 
 8 | import pytest
 9 | 
10 | from coverage.misc import import_local_file
11 | 
12 | from tests.mixins import TempDirMixin, RestoreModulesMixin
13 | 
14 | 
15 | class TempDirMixinTest(TempDirMixin):
16 |     """Test the methods in TempDirMixin."""
17 | 
18 |     def file_text(self, fname: str) -> str:
19 |         """Return the text read from a file."""
20 |         with open(fname, "rb") as f:
21 |             return f.read().decode('ascii')
22 | 
23 |     def test_make_file(self) -> None:
24 |         # A simple file.
25 |         self.make_file("fooey.boo", "Hello there")
26 |         assert self.file_text("fooey.boo") == "Hello there"
27 |         # A file in a sub-directory
28 |         self.make_file("sub/another.txt", "Another")
29 |         assert self.file_text("sub/another.txt") == "Another"
30 |         # A second file in that sub-directory
31 |         self.make_file("sub/second.txt", "Second")
32 |         assert self.file_text("sub/second.txt") == "Second"
33 |         # A deeper directory
34 |         self.make_file("sub/deeper/evenmore/third.txt")
35 |         assert self.file_text("sub/deeper/evenmore/third.txt") == ""
36 |         # Dedenting
37 |         self.make_file("dedented.txt", """\
38 |             Hello
39 |             Bye
40 |             """)
41 |         assert self.file_text("dedented.txt") == "Hello\nBye\n"
42 | 
43 |     def test_make_file_newline(self) -> None:
44 |         self.make_file("unix.txt", "Hello\n")
45 |         assert self.file_text("unix.txt") == "Hello\n"
46 |         self.make_file("dos.txt", "Hello\n", newline="\r\n")
47 |         assert self.file_text("dos.txt") == "Hello\r\n"
48 |         self.make_file("mac.txt", "Hello\n", newline="\r")
49 |         assert self.file_text("mac.txt") == "Hello\r"
50 | 
51 |     def test_make_file_non_ascii(self) -> None:
52 |         self.make_file("unicode.txt", "tablo: «ταБℓσ»")
53 |         with open("unicode.txt", "rb") as f:
54 |             text = f.read()
55 |         assert text == b"tablo: \xc2\xab\xcf\x84\xce\xb1\xd0\x91\xe2\x84\x93\xcf\x83\xc2\xbb"
56 | 
57 |     def test_make_bytes_file(self) -> None:
58 |         self.make_file("binary.dat", bytes=b"\x99\x33\x66hello\0")
59 |         with open("binary.dat", "rb") as f:
60 |             data = f.read()
61 |         assert data == b"\x99\x33\x66hello\0"
62 | 
63 | 
64 | class RestoreModulessMixinTest(TempDirMixin, RestoreModulesMixin):
65 |     """Tests of SysPathModulesMixin."""
66 | 
67 |     @pytest.mark.parametrize("val", [17, 42])
68 |     def test_module_independence(self, val: int) -> None:
69 |         self.make_file("xyzzy.py", f"A = {val}")
70 |         import xyzzy            # pylint: disable=import-error
71 |         assert xyzzy.A == val
72 | 
73 |     def test_cleanup_and_reimport(self) -> None:
74 |         self.make_file("xyzzy.py", "A = 17")
75 |         xyzzy = import_local_file("xyzzy")
76 |         assert xyzzy.A == 17
77 | 
78 |         self.clean_local_file_imports()
79 | 
80 |         self.make_file("xyzzy.py", "A = 42")
81 |         xyzzy = import_local_file("xyzzy")
82 |         assert xyzzy.A == 42
83 | 


--------------------------------------------------------------------------------
/.github/workflows/testsuite.yml:
--------------------------------------------------------------------------------
  1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
  2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
  3 | 
  4 | name: "Tests"
  5 | 
  6 | on:
  7 |   push:
  8 |     branches:
  9 |       - master
 10 |       - nedbat/*
 11 |   pull_request:
 12 |   workflow_dispatch:
 13 | 
 14 | defaults:
 15 |   run:
 16 |     shell: bash
 17 | 
 18 | env:
 19 |   PIP_DISABLE_PIP_VERSION_CHECK: 1
 20 |   COVERAGE_IGOR_VERBOSE: 1
 21 |   FORCE_COLOR: 1    # Get colored pytest output
 22 | 
 23 | permissions:
 24 |   contents: read
 25 | 
 26 | concurrency:
 27 |   group: "${{ github.workflow }}-${{ github.ref }}"
 28 |   cancel-in-progress: true
 29 | 
 30 | jobs:
 31 |   tests:
 32 |     name: "${{ matrix.python-version }} on ${{ matrix.os }}"
 33 |     runs-on: "${{ matrix.os }}-latest"
 34 | 
 35 |     strategy:
 36 |       matrix:
 37 |         os:
 38 |           - ubuntu
 39 |           - macos
 40 |           - windows
 41 |         python-version:
 42 |           # When changing this list, be sure to check the [gh] list in
 43 |           # tox.ini so that tox will run properly. PYVERSIONS
 44 |           # Available versions:
 45 |           # https://github.com/actions/python-versions/blob/main/versions-manifest.json
 46 |           # https://github.com/actions/setup-python/blob/main/docs/advanced-usage.md#available-versions-of-python-and-pypy
 47 |           - "3.7"
 48 |           - "3.8"
 49 |           - "3.9"
 50 |           - "3.10"
 51 |           - "3.11"
 52 |           - "pypy-3.7"
 53 |           - "pypy-3.9"
 54 |         exclude:
 55 |           # Windows PyPy-3.9 always gets killed.
 56 |           - os: windows
 57 |             python-version: "pypy-3.9"
 58 |       fail-fast: false
 59 | 
 60 |     steps:
 61 |       - name: "Check out the repo"
 62 |         uses: "actions/checkout@v3"
 63 | 
 64 |       - name: "Set up Python"
 65 |         uses: "actions/setup-python@v4"
 66 |         with:
 67 |           python-version: "${{ matrix.python-version }}"
 68 |           cache: pip
 69 |           cache-dependency-path: 'requirements/*.pip'
 70 | 
 71 |       - name: "Install dependencies"
 72 |         run: |
 73 |           set -xe
 74 |           python -VV
 75 |           python -m site
 76 |           python -m pip install --require-hashes -r requirements/tox.pip
 77 |           # For extreme debugging:
 78 |           # python -c "import urllib.request as r; exec(r.urlopen('https://bit.ly/pydoctor').read())"
 79 | 
 80 |       - name: "Run tox for ${{ matrix.python-version }}"
 81 |         run: |
 82 |           python -m tox -- -rfsEX
 83 | 
 84 |       - name: "Retry tox for ${{ matrix.python-version }}"
 85 |         if: failure()
 86 |         run: |
 87 |           # `exit 1` makes sure that the job remains red with flaky runs
 88 |           python -m tox -- -rfsEX --lf -vvvvv && exit 1
 89 | 
 90 |   # This job aggregates test results. It's the required check for branch protection.
 91 |   # https://github.com/marketplace/actions/alls-green#why
 92 |   # https://github.com/orgs/community/discussions/33579
 93 |   success:
 94 |     name: Tests successful
 95 |     if: always()
 96 |     needs:
 97 |       - tests
 98 |     runs-on: ubuntu-latest
 99 |     steps:
100 |       - name: Decide whether the needed jobs succeeded or failed
101 |         uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe
102 |         with:
103 |           jobs: ${{ toJSON(needs) }}
104 | 


--------------------------------------------------------------------------------
/coverage/ctracer/filedisp.c:
--------------------------------------------------------------------------------
 1 | /* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */
 2 | /* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */
 3 | 
 4 | #include "util.h"
 5 | #include "filedisp.h"
 6 | 
 7 | void
 8 | CFileDisposition_dealloc(CFileDisposition *self)
 9 | {
10 |     Py_XDECREF(self->original_filename);
11 |     Py_XDECREF(self->canonical_filename);
12 |     Py_XDECREF(self->source_filename);
13 |     Py_XDECREF(self->trace);
14 |     Py_XDECREF(self->reason);
15 |     Py_XDECREF(self->file_tracer);
16 |     Py_XDECREF(self->has_dynamic_filename);
17 | }
18 | 
19 | static PyMemberDef
20 | CFileDisposition_members[] = {
21 |     { "original_filename",      T_OBJECT, offsetof(CFileDisposition, original_filename), 0,
22 |             PyDoc_STR("") },
23 | 
24 |     { "canonical_filename",     T_OBJECT, offsetof(CFileDisposition, canonical_filename), 0,
25 |             PyDoc_STR("") },
26 | 
27 |     { "source_filename",        T_OBJECT, offsetof(CFileDisposition, source_filename), 0,
28 |             PyDoc_STR("") },
29 | 
30 |     { "trace",                  T_OBJECT, offsetof(CFileDisposition, trace), 0,
31 |             PyDoc_STR("") },
32 | 
33 |     { "reason",                 T_OBJECT, offsetof(CFileDisposition, reason), 0,
34 |             PyDoc_STR("") },
35 | 
36 |     { "file_tracer",            T_OBJECT, offsetof(CFileDisposition, file_tracer), 0,
37 |             PyDoc_STR("") },
38 | 
39 |     { "has_dynamic_filename",   T_OBJECT, offsetof(CFileDisposition, has_dynamic_filename), 0,
40 |             PyDoc_STR("") },
41 | 
42 |     { NULL }
43 | };
44 | 
45 | PyTypeObject
46 | CFileDispositionType = {
47 |     PyVarObject_HEAD_INIT(NULL, 0)
48 |     "coverage.CFileDispositionType",        /*tp_name*/
49 |     sizeof(CFileDisposition),  /*tp_basicsize*/
50 |     0,                         /*tp_itemsize*/
51 |     (destructor)CFileDisposition_dealloc, /*tp_dealloc*/
52 |     0,                         /*tp_print*/
53 |     0,                         /*tp_getattr*/
54 |     0,                         /*tp_setattr*/
55 |     0,                         /*tp_compare*/
56 |     0,                         /*tp_repr*/
57 |     0,                         /*tp_as_number*/
58 |     0,                         /*tp_as_sequence*/
59 |     0,                         /*tp_as_mapping*/
60 |     0,                         /*tp_hash */
61 |     0,                         /*tp_call*/
62 |     0,                         /*tp_str*/
63 |     0,                         /*tp_getattro*/
64 |     0,                         /*tp_setattro*/
65 |     0,                         /*tp_as_buffer*/
66 |     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
67 |     "CFileDisposition objects", /* tp_doc */
68 |     0,                         /* tp_traverse */
69 |     0,                         /* tp_clear */
70 |     0,                         /* tp_richcompare */
71 |     0,                         /* tp_weaklistoffset */
72 |     0,                         /* tp_iter */
73 |     0,                         /* tp_iternext */
74 |     0,                         /* tp_methods */
75 |     CFileDisposition_members,  /* tp_members */
76 |     0,                         /* tp_getset */
77 |     0,                         /* tp_base */
78 |     0,                         /* tp_dict */
79 |     0,                         /* tp_descr_get */
80 |     0,                         /* tp_descr_set */
81 |     0,                         /* tp_dictoffset */
82 |     0,                         /* tp_init */
83 |     0,                         /* tp_alloc */
84 |     0,                         /* tp_new */
85 | };
86 | 


--------------------------------------------------------------------------------
/CONTRIBUTORS.txt:
--------------------------------------------------------------------------------
  1 | Coverage.py was originally written by Gareth Rees, and since 2004 has been
  2 | extended and maintained by Ned Batchelder.
  3 | 
  4 | Other contributions, including writing code, updating docs, and submitting
  5 | useful bug reports, have been made by:
  6 | 
  7 | Abdeali Kothari
  8 | Adi Roiban
  9 | Agbonze O. Jeremiah
 10 | Albertas Agejevas
 11 | Aleksi Torhamo
 12 | Alex Gaynor
 13 | Alex Groce
 14 | Alex Sandro
 15 | Alexander Todorov
 16 | Alexander Walters
 17 | Ammar Askar
 18 | Andrew Hoos
 19 | Anthony Sottile
 20 | Arcadiy Ivanov
 21 | Aron Griffis
 22 | Artem Dayneko
 23 | Arthur Deygin
 24 | Arthur Rio
 25 | Ben Carlsson
 26 | Ben Finney
 27 | Benjamin Schubert
 28 | Bernát Gábor
 29 | Bill Hart
 30 | Bradley Burns
 31 | Brandon Rhodes
 32 | Brett Cannon
 33 | Bruno P. Kinoshita
 34 | Buck Evan
 35 | Calen Pennington
 36 | Carl Friedrich Bolz-Tereick
 37 | Carl Gieringer
 38 | Catherine Proulx
 39 | Chris Adams
 40 | Chris Jerdonek
 41 | Chris Rose
 42 | Chris Warrick
 43 | Christian Heimes
 44 | Christine Lytwynec
 45 | Christoph Blessing
 46 | Christoph Zwerschke
 47 | Clément Pit-Claudel
 48 | Conrad Ho
 49 | Cosimo Lupo
 50 | Dan Hemberger
 51 | Dan Riti
 52 | Dan Wandschneider
 53 | Danek Duvall
 54 | Daniel Hahler
 55 | Danny Allen
 56 | David Christian
 57 | David MacIver
 58 | David Stanek
 59 | David Szotten
 60 | Detlev Offenbach
 61 | Devin Jeanpierre
 62 | Dirk Thomas
 63 | Dmitry Shishov
 64 | Dmitry Trofimov
 65 | Eduardo Schettino
 66 | Edward Loper
 67 | Eli Skeggs
 68 | Emil Madsen
 69 | Éric Larivière
 70 | Federico Bond
 71 | Felix Horvat
 72 | Frazer McLean
 73 | Geoff Bache
 74 | George Paci
 75 | George Song
 76 | George-Cristian Bîrzan
 77 | Greg Rogers
 78 | Guido van Rossum
 79 | Guillaume Chazarain
 80 | Hugo van Kemenade
 81 | Ilia Meerovich
 82 | Imri Goldberg
 83 | Ionel Cristian Mărieș
 84 | Ivan Ciuvalschii
 85 | J. M. F. Tsang
 86 | JT Olds
 87 | Jerin Peter George
 88 | Jessamyn Smith
 89 | Joe Doherty
 90 | Joe Jevnik
 91 | Jon Chappell
 92 | Jon Dufresne
 93 | Joseph Tate
 94 | Josh Williams
 95 | Judson Neer
 96 | Julian Berman
 97 | Julien Voisin
 98 | Justas Sadzevičius
 99 | Kjell Braden
100 | Krystian Kichewko
101 | Kyle Altendorf
102 | Lars Hupfeldt Nielsen
103 | Leonardo Pistone
104 | Lex Berezhny
105 | Loïc Dachary
106 | Lorenzo Micò
107 | Marc Abramowitz
108 | Marc Legendre
109 | Marcelo Trylesinski
110 | Marcus Cobden
111 | Marius Gedminas
112 | Mark van der Wal
113 | Martin Fuzzey
114 | Matt Bachmann
115 | Matthew Boehm
116 | Matthew Desmarais
117 | Matus Valo
118 | Max Linke
119 | Michael Krebs
120 | Michał Bultrowicz
121 | Michał Górny
122 | Mickie Betz
123 | Mike Fiedler
124 | Naveen Yadav
125 | Nathan Land
126 | Nikita Bloshchanevich
127 | Nils Kattenbeck
128 | Noel O'Boyle
129 | Olivier Grisel
130 | Ori Avtalion
131 | Pankaj Pandey
132 | Pablo Carballo
133 | Patrick Mezard
134 | Peter Baughman
135 | Peter Ebden
136 | Peter Portante
137 | Reya B
138 | Rodrigue Cloutier
139 | Roger Hu
140 | Ross Lawley
141 | Roy Williams
142 | Russell Keith-Magee
143 | Salvatore Zagaria
144 | Sandra Martocchia
145 | Scott Belden
146 | Sebastián Ramírez
147 | Sergey B Kirpichev
148 | Sigve Tjora
149 | Simon Willison
150 | Stan Hu
151 | Stefan Behnel
152 | Stephan Deibel
153 | Stephan Richter
154 | Stephen Finucane
155 | Steve Dower
156 | Steve Leonard
157 | Steve Oswald
158 | Steve Peak
159 | Sviatoslav Sydorenko
160 | S. Y. Lee
161 | Teake Nutma
162 | Ted Wexler
163 | Thijs Triemstra
164 | Thomas Grainger
165 | Titus Brown
166 | Valentin Lab
167 | Vince Salvino
168 | Ville Skyttä
169 | Xie Yanbo
170 | Yilei "Dolee" Yang
171 | Yury Selivanov
172 | Zac Hatfield-Dodds
173 | Zooko Wilcox-O'Hearn
174 | 


--------------------------------------------------------------------------------
/doc/dict.txt:
--------------------------------------------------------------------------------
  1 | activestate
  2 | api
  3 | apache
  4 | API
  5 | args
  6 | argv
  7 | ascii
  8 | basename
  9 | basenames
 10 | bitbucket
 11 | BOM
 12 | bom
 13 | boolean
 14 | booleans
 15 | BTW
 16 | btw
 17 | builtin
 18 | builtins
 19 | bytecode
 20 | bytecodes
 21 | bytestring
 22 | callable
 23 | callables
 24 | canonicalize
 25 | canonicalized
 26 | canonicalizes
 27 | chdir'd
 28 | clickable
 29 | cmdline
 30 | Cobertura
 31 | codecs
 32 | colorsys
 33 | combinable
 34 | conditionalizing
 35 | config
 36 | configparser
 37 | configurability
 38 | configurability's
 39 | configurer
 40 | configurers
 41 | Consolas
 42 | cov
 43 | coveragepy
 44 | coveragerc
 45 | covhtml
 46 | CPython
 47 | css
 48 | CTracer
 49 | Cython
 50 | datetime
 51 | deallocating
 52 | dedent
 53 | defaultdict
 54 | deserialize
 55 | deserialized
 56 | dict
 57 | dict's
 58 | dicts
 59 | dirname
 60 | django
 61 | docstring
 62 | docstrings
 63 | doctest
 64 | doctests
 65 | DOCTYPE
 66 | DOM
 67 | encodable
 68 | encodings
 69 | endfor
 70 | endif
 71 | eventlet
 72 | exe
 73 | exec'd
 74 | exec'ing
 75 | execfile
 76 | executability
 77 | executable's
 78 | expr
 79 | extensibility
 80 | favicon
 81 | filename
 82 | filenames
 83 | filepath
 84 | filereporter
 85 | fname
 86 | fnmatch
 87 | fooey
 88 | formfeed
 89 | fpath
 90 | fullcoverage
 91 | gauge
 92 | getattr
 93 | gevent
 94 | gevent's
 95 | github
 96 | gitignore
 97 | globals
 98 | greenlet
 99 | hotkey
100 | hotkeys
101 | html
102 | HTML
103 | htmlcov
104 | http
105 | https
106 | importlib
107 | installable
108 | instancemethod
109 | int
110 | ints
111 | invariants
112 | iterable
113 | iterables
114 | Jinja
115 | jquery
116 | jQuery
117 | json
118 | jython
119 | kwargs
120 | lcov
121 | localStorage
122 | Mako
123 | manylinux
124 | matcher
125 | matchers
126 | merchantability
127 | metadata
128 | meth
129 | mischaracterize
130 | mischaracterized
131 | mixin
132 | modulename
133 | monkeypatch
134 | monkeypatching
135 | monospaced
136 | morf
137 | morfs
138 | multi
139 | mumbo
140 | mycode
141 | namespace
142 | namespaces
143 | nano
144 | nbsp
145 | ned
146 | nedbat
147 | nedbatchelder
148 | nosetests
149 | nullary
150 | num
151 | numbits
152 | numpy
153 | ok
154 | OK
155 | opcode
156 | opcodes
157 | optparse
158 | os
159 | outfile
160 | overridable
161 | parallelizing
162 | parsable
163 | parsers
164 | pathnames
165 | plugin
166 | plugins
167 | pragma
168 | pragmas
169 | pragma'd
170 | pre
171 | prepended
172 | prepending
173 | programmability
174 | programmatically
175 | py
176 | py's
177 | pyc
178 | pyexpat
179 | pylint
180 | pyproject
181 | pypy
182 | pytest
183 | pythonpath
184 | PYTHONPATH
185 | pyw
186 | rcfile
187 | readme
188 | readthedocs
189 | recordable
190 | refactored
191 | refactoring
192 | refactorings
193 | regex
194 | regexes
195 | reimplemented
196 | renderer
197 | runnable
198 | runtime
199 | scrollbar
200 | serializable
201 | settrace
202 | setuptools
203 | sigterm
204 | sitecustomize
205 | sortable
206 | src
207 | stackoverflow
208 | stderr
209 | stdlib
210 | stdout
211 | str
212 | subclasses
213 | subdirectory
214 | subprocess
215 | subprocesses
216 | symlink
217 | symlinks
218 | syntaxes
219 | sys
220 | templite
221 | templating
222 | testability
223 | Tidelift
224 | todo
225 | TODO
226 | tokenization
227 | tokenize
228 | tokenized
229 | tokenizer
230 | tokenizes
231 | tokenizing
232 | toml
233 | tomllib
234 | tox
235 | traceback
236 | tracebacks
237 | tuple
238 | tuples
239 | txt
240 | ubuntu
241 | undecodable
242 | unexecutable
243 | unicode
244 | uninstall
245 | unittest
246 | unparsable
247 | unrunnable
248 | unsubscriptable
249 | untokenizable
250 | username
251 | URL
252 | UTF
253 | utf
254 | vendored
255 | versionadded
256 | virtualenv
257 | wikipedia
258 | wildcard
259 | wildcards
260 | www
261 | xml
262 | XML
263 | xrange
264 | xyzzy
265 | 


--------------------------------------------------------------------------------
/lab/goals.py:
--------------------------------------------------------------------------------
 1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
 3 | 
 4 | """\
 5 | Check coverage goals.
 6 | 
 7 | Use `coverage json` to get a coverage.json file, then run this tool
 8 | to check goals for subsets of files.
 9 | 
10 | Patterns can use '**/foo*.py' to find files anywhere in the project,
11 | and '!**/something.py' to exclude files matching a pattern.
12 | 
13 | --file will check each file individually for the required coverage.
14 | --group checks the entire group collectively.
15 | 
16 | """
17 | 
18 | import argparse
19 | import json
20 | import sys
21 | 
22 | from wcmatch import fnmatch as wcfnmatch    # python -m pip install wcmatch
23 | 
24 | from coverage.results import Numbers        # Note: an internal class!
25 | 
26 | 
27 | def select_files(files, pat):
28 |     flags = wcfnmatch.NEGATE | wcfnmatch.NEGATEALL
29 |     selected = [f for f in files if wcfnmatch.fnmatch(f, pat, flags=flags)]
30 |     return selected
31 | 
32 | def total_for_files(data, files):
33 |     total = Numbers(precision=3)
34 |     for f in files:
35 |         sel_summ = data["files"][f]["summary"]
36 |         total += Numbers(
37 |             n_statements=sel_summ["num_statements"],
38 |             n_excluded=sel_summ["excluded_lines"],
39 |             n_missing=sel_summ["missing_lines"],
40 |             n_branches=sel_summ.get("num_branches", 0),
41 |             n_partial_branches=sel_summ.get("num_partial_branches", 0),
42 |             n_missing_branches=sel_summ.get("missing_branches", 0),
43 |         )
44 | 
45 |     return total
46 | 
47 | def main(argv):
48 |     parser = argparse.ArgumentParser(description=__doc__)
49 |     parser.add_argument("--file", "-f", action="store_true", help="Check each file individually")
50 |     parser.add_argument("--group", "-g", action="store_true", help="Check a group of files")
51 |     parser.add_argument("--verbose", "-v", action="store_true", help="Be chatty about what's happening")
52 |     parser.add_argument("goal", type=float, help="Coverage goal")
53 |     parser.add_argument("pattern", type=str, nargs="+", help="Patterns to check")
54 |     args = parser.parse_args(argv)
55 | 
56 |     print("** Note: this is a proof-of-concept. Support is not promised. **")
57 |     print("Read more: https://nedbatchelder.com/blog/202111/coverage_goals.html")
58 |     print("Feedback is appreciated: https://github.com/nedbat/coveragepy/issues/691")
59 | 
60 |     if args.file and args.group:
61 |         print("Can't use --file and --group together")
62 |         return 1
63 |     if not (args.file or args.group):
64 |         print("Need either --file or --group")
65 |         return 1
66 | 
67 |     with open("coverage.json") as j:
68 |         data = json.load(j)
69 |     all_files = list(data["files"].keys())
70 |     selected = select_files(all_files, args.pattern)
71 | 
72 |     ok = True
73 |     if args.group:
74 |         total = total_for_files(data, selected)
75 |         pat_nice = ",".join(args.pattern)
76 |         result = f"Coverage for {pat_nice} is {total.pc_covered_str}"
77 |         if total.pc_covered < args.goal:
78 |             print(f"{result}, below {args.goal}")
79 |             ok = False
80 |         elif args.verbose:
81 |             print(result)
82 |     else:
83 |         for fname in selected:
84 |             total = total_for_files(data, [fname])
85 |             result = f"Coverage for {fname} is {total.pc_covered_str}"
86 |             if total.pc_covered < args.goal:
87 |                 print(f"{result}, below {args.goal}")
88 |                 ok = False
89 |             elif args.verbose:
90 |                 print(result)
91 | 
92 |     return 0 if ok else 2
93 | 
94 | if __name__ == "__main__":
95 |     sys.exit(main(sys.argv[1:]))
96 | 


--------------------------------------------------------------------------------
/lab/notes/pypy-738-decorated-functions.txt:
--------------------------------------------------------------------------------
 1 | Comparing versions:
 2 | 
 3 | export PY38=/usr/local/pyenv/pyenv/versions/3.8.12/bin/python3.8
 4 | export PY39=/usr/local/pyenv/pyenv/versions/3.9.10/bin/python3.9
 5 | export PP38old=/usr/local/pypy/pypy3.8-v7.3.7-osx64/bin/pypy3
 6 | export PP38=/usr/local/pypy/pypy3.8-v7.3.8rc1-osx64/bin/pypy3
 7 | export PP39=/usr/local/pypy/pypy3.9-v7.3.8rc1-osx64/bin/pypy3
 8 | 
 9 | $ for py in $PY38 $PY39 $PP38old $PP38 $PP39; do $py -m coverage run --debug=pybehave igor.py; done 2>&1 | grep trace
10 |             trace_decorated_def: True
11 |      trace_decorator_line_again: False
12 |             trace_decorated_def: True
13 |      trace_decorator_line_again: False
14 |             trace_decorated_def: False
15 |      trace_decorator_line_again: False
16 |             trace_decorated_def: False
17 |      trace_decorator_line_again: False
18 |             trace_decorated_def: False
19 |      trace_decorator_line_again: False
20 | 
21 | # t466a_ast.py:
22 |     import ast
23 |     import sys
24 | 
25 |     def find_function(node, name):
26 |         if node.__class__.__name__ == "FunctionDef" and node.name == name:
27 |             return node
28 |         for node in getattr(node, "body", ()):
29 |             fnode = find_function(node, name)
30 |             if fnode is not None:
31 |                 return fnode
32 | 
33 |     root_node = ast.parse(open(__file__).read())
34 |     func_node = find_function(root_node, "parse")
35 | 
36 |     print(func_node.name, func_node.lineno, func_node.end_lineno, tuple(sys.version_info), tuple(getattr(sys, "pypy_version_info", ())))
37 | 
38 |     class Parser(object):
39 | 
40 |         @classmethod
41 |         def parse(cls):
42 |             formats = [ 5 ]
43 | 
44 | 
45 |             return None
46 | 
47 |     Parser.parse()
48 | 
49 | 
50 | $ for py in $PY38 $PY39 $PP38old $PP38 $PP39; do $py t466a_ast.py; done
51 | parse 20 24 (3, 8, 12, 'final', 0) ()
52 | parse 20 24 (3, 9, 10, 'final', 0) ()
53 | parse 19 -1 (3, 8, 12, 'final', 0) (7, 3, 7, 'final', 0)
54 | parse 19 -1 (3, 8, 12, 'final', 0) (7, 3, 8, 'final', 0)
55 | parse 20 24 (3, 9, 10, 'final', 0) (7, 3, 8, 'final', 0)
56 | 
57 | 
58 | PyPy <=3.8 includes the decorator line in the FunctionDef node
59 | PyPy >=3.9 does not include the decorator line in the node
60 | 
61 | PyPy traces the decorator line, but not the def:
62 | 
63 | $ $PP38 -m trace --trace t466a_plain.py
64 |  --- modulename: t466a_plain, funcname: 
65 | t466a_plain.py(1): class Parser(object):
66 |  --- modulename: t466a_plain, funcname: Parser
67 | t466a_plain.py(1): class Parser(object):
68 | t466a_plain.py(3):     @classmethod
69 | t466a_plain.py(10): Parser.parse()
70 |  --- modulename: t466a_plain, funcname: parse
71 | t466a_plain.py(5):         formats = [ 5 ]
72 | t466a_plain.py(8):         return None
73 | 
74 | $ $PP39 -m trace --trace t466a_plain.py
75 |  --- modulename: t466a_plain, funcname: 
76 | t466a_plain.py(1): class Parser(object):
77 |  --- modulename: t466a_plain, funcname: Parser
78 | t466a_plain.py(1): class Parser(object):
79 | t466a_plain.py(3):     @classmethod
80 | t466a_plain.py(10): Parser.parse()
81 |  --- modulename: t466a_plain, funcname: parse
82 | t466a_plain.py(5):         formats = [ 5 ]
83 | t466a_plain.py(8):         return None
84 | 
85 | CPython traces the decorator and the def:
86 | 
87 | $ $PY39 -m trace --trace t466a_plain.py
88 |  --- modulename: t466a_plain, funcname: 
89 | t466a_plain.py(1): class Parser(object):
90 |  --- modulename: t466a_plain, funcname: Parser
91 | t466a_plain.py(1): class Parser(object):
92 | t466a_plain.py(3):     @classmethod
93 | t466a_plain.py(4):     def parse(cls):
94 | t466a_plain.py(10): Parser.parse()
95 |  --- modulename: t466a_plain, funcname: parse
96 | t466a_plain.py(5):         formats = [ 5 ]
97 | t466a_plain.py(8):         return None
98 | 


--------------------------------------------------------------------------------
/requirements/pytest.pip:
--------------------------------------------------------------------------------
 1 | #
 2 | # This file is autogenerated by pip-compile with Python 3.7
 3 | # by the following command:
 4 | #
 5 | #    make upgrade
 6 | #
 7 | attrs==22.2.0 \
 8 |     --hash=sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836 \
 9 |     --hash=sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99
10 |     # via
11 |     #   hypothesis
12 |     #   pytest
13 | colorama==0.4.6 \
14 |     --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
15 |     --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
16 |     # via -r requirements/pytest.in
17 | exceptiongroup==1.1.0 \
18 |     --hash=sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e \
19 |     --hash=sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23
20 |     # via
21 |     #   hypothesis
22 |     #   pytest
23 | execnet==1.9.0 \
24 |     --hash=sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5 \
25 |     --hash=sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142
26 |     # via pytest-xdist
27 | flaky==3.7.0 \
28 |     --hash=sha256:3ad100780721a1911f57a165809b7ea265a7863305acb66708220820caf8aa0d \
29 |     --hash=sha256:d6eda73cab5ae7364504b7c44670f70abed9e75f77dd116352f662817592ec9c
30 |     # via -r requirements/pytest.in
31 | hypothesis==6.68.1 \
32 |     --hash=sha256:3ff6076920e61d4e6362e93edaf09be3034ea7e39e3a75e731d4d1c525dafd84 \
33 |     --hash=sha256:b37bd77b4b7f404a59ff965e24be8aec4209323866e34ececdf416522c6d0854
34 |     # via -r requirements/pytest.in
35 | importlib-metadata==6.0.0 \
36 |     --hash=sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad \
37 |     --hash=sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d
38 |     # via
39 |     #   pluggy
40 |     #   pytest
41 | iniconfig==2.0.0 \
42 |     --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \
43 |     --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374
44 |     # via pytest
45 | packaging==23.0 \
46 |     --hash=sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2 \
47 |     --hash=sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97
48 |     # via pytest
49 | pluggy==1.0.0 \
50 |     --hash=sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159 \
51 |     --hash=sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3
52 |     # via pytest
53 | pytest==7.2.1 \
54 |     --hash=sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5 \
55 |     --hash=sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42
56 |     # via
57 |     #   -r requirements/pytest.in
58 |     #   pytest-xdist
59 | pytest-xdist==3.2.0 \
60 |     --hash=sha256:336098e3bbd8193276867cc87db8b22903c3927665dff9d1ac8684c02f597b68 \
61 |     --hash=sha256:fa10f95a2564cd91652f2d132725183c3b590d9fdcdec09d3677386ecf4c1ce9
62 |     # via -r requirements/pytest.in
63 | sortedcontainers==2.4.0 \
64 |     --hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \
65 |     --hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0
66 |     # via hypothesis
67 | tomli==2.0.1 \
68 |     --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
69 |     --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
70 |     # via pytest
71 | typing-extensions==4.4.0 \
72 |     --hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \
73 |     --hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e
74 |     # via importlib-metadata
75 | zipp==3.13.0 \
76 |     --hash=sha256:23f70e964bc11a34cef175bc90ba2914e1e4545ea1e3e2f67c079671883f9cb6 \
77 |     --hash=sha256:e8b2a36ea17df80ffe9e2c4fda3f693c3dad6df1697d3cd3af232db680950b0b
78 |     # via importlib-metadata
79 | 


--------------------------------------------------------------------------------
/requirements/tox.pip:
--------------------------------------------------------------------------------
 1 | #
 2 | # This file is autogenerated by pip-compile with Python 3.7
 3 | # by the following command:
 4 | #
 5 | #    make upgrade
 6 | #
 7 | cachetools==5.3.0 \
 8 |     --hash=sha256:13dfddc7b8df938c21a940dfa6557ce6e94a2f1cdfa58eb90c805721d58f2c14 \
 9 |     --hash=sha256:429e1a1e845c008ea6c85aa35d4b98b65d6a9763eeef3e37e92728a12d1de9d4
10 |     # via tox
11 | chardet==5.1.0 \
12 |     --hash=sha256:0d62712b956bc154f85fb0a266e2a3c5913c2967e00348701b32411d6def31e5 \
13 |     --hash=sha256:362777fb014af596ad31334fde1e8c327dfdb076e1960d1694662d46a6917ab9
14 |     # via tox
15 | colorama==0.4.6 \
16 |     --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
17 |     --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
18 |     # via
19 |     #   -r requirements/tox.in
20 |     #   tox
21 | distlib==0.3.6 \
22 |     --hash=sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46 \
23 |     --hash=sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e
24 |     # via virtualenv
25 | filelock==3.9.0 \
26 |     --hash=sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de \
27 |     --hash=sha256:f58d535af89bb9ad5cd4df046f741f8553a418c01a7856bf0d173bbc9f6bd16d
28 |     # via
29 |     #   tox
30 |     #   virtualenv
31 | importlib-metadata==6.0.0 \
32 |     --hash=sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad \
33 |     --hash=sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d
34 |     # via
35 |     #   pluggy
36 |     #   tox
37 |     #   virtualenv
38 | packaging==23.0 \
39 |     --hash=sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2 \
40 |     --hash=sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97
41 |     # via
42 |     #   pyproject-api
43 |     #   tox
44 | platformdirs==3.0.0 \
45 |     --hash=sha256:8a1228abb1ef82d788f74139988b137e78692984ec7b08eaa6c65f1723af28f9 \
46 |     --hash=sha256:b1d5eb14f221506f50d6604a561f4c5786d9e80355219694a1b244bcd96f4567
47 |     # via
48 |     #   tox
49 |     #   virtualenv
50 | pluggy==1.0.0 \
51 |     --hash=sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159 \
52 |     --hash=sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3
53 |     # via tox
54 | pyproject-api==1.5.0 \
55 |     --hash=sha256:0962df21f3e633b8ddb9567c011e6c1b3dcdfc31b7860c0ede7e24c5a1200fbe \
56 |     --hash=sha256:4c111277dfb96bcd562c6245428f27250b794bfe3e210b8714c4f893952f2c17
57 |     # via tox
58 | tomli==2.0.1 \
59 |     --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
60 |     --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
61 |     # via
62 |     #   pyproject-api
63 |     #   tox
64 | tox==4.4.5 \
65 |     --hash=sha256:1081864f1a1393ffa11ebe9beaa280349020579310d217a594a4e7b6124c5425 \
66 |     --hash=sha256:f9bc83c5da8666baa2a4d4e884bbbda124fe646e4b1c0e412949cecc2b6e8f90
67 |     # via
68 |     #   -r requirements/tox.in
69 |     #   tox-gh
70 | tox-gh==1.0.0 \
71 |     --hash=sha256:9cfbaa927946887d53bc19ae86621f4e5dc8516f3771ba4e74daeb1a1775efcd \
72 |     --hash=sha256:bda94ac15dbb62ef1e517672c05f8039faad5afaf9d1b4c9fa32d07f18027571
73 |     # via -r requirements/tox.in
74 | typing-extensions==4.4.0 \
75 |     --hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \
76 |     --hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e
77 |     # via
78 |     #   importlib-metadata
79 |     #   platformdirs
80 |     #   tox
81 | virtualenv==20.19.0 \
82 |     --hash=sha256:37a640ba82ed40b226599c522d411e4be5edb339a0c0de030c0dc7b646d61590 \
83 |     --hash=sha256:54eb59e7352b573aa04d53f80fc9736ed0ad5143af445a1e539aada6eb947dd1
84 |     # via tox
85 | zipp==3.13.0 \
86 |     --hash=sha256:23f70e964bc11a34cef175bc90ba2914e1e4545ea1e3e2f67c079671883f9cb6 \
87 |     --hash=sha256:e8b2a36ea17df80ffe9e2c4fda3f693c3dad6df1697d3cd3af232db680950b0b
88 |     # via importlib-metadata
89 | 


--------------------------------------------------------------------------------
/tests/modules/process_test/try_execfile.py:
--------------------------------------------------------------------------------
  1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
  2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
  3 | 
  4 | """Test file for run_python_file.
  5 | 
  6 | This file is executed two ways::
  7 | 
  8 |     $ coverage run try_execfile.py
  9 | 
 10 | and::
 11 | 
 12 |     $ python try_execfile.py
 13 | 
 14 | The output is compared to see that the program execution context is the same
 15 | under coverage and under Python.
 16 | 
 17 | It is not crucial that the execution be identical, there are some differences
 18 | that are OK.  This program canonicalizes the output to gloss over those
 19 | differences and get a clean diff.
 20 | 
 21 | """
 22 | 
 23 | from __future__ import annotations
 24 | 
 25 | import itertools
 26 | import json
 27 | import os
 28 | import sys
 29 | 
 30 | from typing import Any, List
 31 | 
 32 | # sys.path varies by execution environments.  Coverage.py uses setuptools to
 33 | # make console scripts, which means pkg_resources is imported.  pkg_resources
 34 | # removes duplicate entries from sys.path.  So we do that too, since the extra
 35 | # entries don't affect the running of the program.
 36 | 
 37 | def same_file(p1: str, p2: str) -> bool:
 38 |     """Determine if `p1` and `p2` refer to the same existing file."""
 39 |     if not p1:
 40 |         return not p2
 41 |     if not os.path.exists(p1):
 42 |         return False
 43 |     if not os.path.exists(p2):
 44 |         return False
 45 |     if hasattr(os.path, "samefile"):
 46 |         return os.path.samefile(p1, p2)
 47 |     else:
 48 |         norm1 = os.path.normcase(os.path.normpath(p1))
 49 |         norm2 = os.path.normcase(os.path.normpath(p2))
 50 |         return norm1 == norm2
 51 | 
 52 | def without_same_files(filenames: List[str]) -> List[str]:
 53 |     """Return the list `filenames` with duplicates (by same_file) removed."""
 54 |     reduced: List[str] = []
 55 |     for filename in filenames:
 56 |         if not any(same_file(filename, other) for other in reduced):
 57 |             reduced.append(filename)
 58 |     return reduced
 59 | 
 60 | cleaned_sys_path = [os.path.normcase(p) for p in without_same_files(sys.path)]
 61 | 
 62 | DATA = "xyzzy"
 63 | 
 64 | import __main__
 65 | 
 66 | def my_function(a: Any) -> str:
 67 |     """A function to force execution of module-level values."""
 68 |     return f"my_fn({a!r})"
 69 | 
 70 | FN_VAL = my_function("fooey")
 71 | 
 72 | loader = globals().get('__loader__')
 73 | spec = globals().get('__spec__')
 74 | 
 75 | # A more compact ad-hoc grouped-by-first-letter list of builtins.
 76 | CLUMPS = "ABC,DEF,GHI,JKLMN,OPQR,ST,U,VWXYZ_,ab,cd,efg,hij,lmno,pqr,stuvwxyz".split(",")
 77 | 
 78 | def word_group(w: str) -> int:
 79 |     """Figure out which CLUMP the first letter of w is in."""
 80 |     for i, clump in enumerate(CLUMPS):
 81 |         if w[0] in clump:
 82 |             return i
 83 |     return 99
 84 | 
 85 | builtin_dir = [" ".join(s) for _, s in itertools.groupby(dir(__builtins__), key=word_group)]
 86 | 
 87 | globals_to_check = {
 88 |     'os.getcwd': os.getcwd(),
 89 |     '__name__': __name__,
 90 |     '__file__': __file__,
 91 |     '__doc__': __doc__,
 92 |     '__builtins__.has_open': hasattr(__builtins__, 'open'),
 93 |     '__builtins__.dir': builtin_dir,
 94 |     '__loader__ exists': loader is not None,
 95 |     '__package__': __package__,
 96 |     '__spec__ exists': spec is not None,
 97 |     'DATA': DATA,
 98 |     'FN_VAL': FN_VAL,
 99 |     '__main__.DATA': getattr(__main__, "DATA", "nothing"),
100 |     'argv0': sys.argv[0],
101 |     'argv1-n': sys.argv[1:],
102 |     'path': cleaned_sys_path,
103 | }
104 | 
105 | if loader is not None:
106 |     globals_to_check.update({
107 |         '__loader__.fullname': getattr(loader, 'fullname', None) or getattr(loader, 'name', None)
108 |     })
109 | 
110 | if spec is not None:
111 |     globals_to_check.update({
112 |         '__spec__.' + aname: getattr(spec, aname)
113 |         for aname in ['name', 'origin', 'submodule_search_locations', 'parent', 'has_location']
114 |     })
115 | 
116 | print(json.dumps(globals_to_check, indent=4, sort_keys=True))
117 | 


--------------------------------------------------------------------------------
/ci/parse_relnotes.py:
--------------------------------------------------------------------------------
  1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
  2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
  3 | 
  4 | """
  5 | Parse CHANGES.md into a JSON structure.
  6 | 
  7 | Run with two arguments: the .md file to parse, and the JSON file to write:
  8 | 
  9 | 	python parse_relnotes.py CHANGES.md relnotes.json
 10 | 
 11 | Every section that has something that looks like a version number in it will
 12 | be recorded as the release notes for that version.
 13 | 
 14 | """
 15 | 
 16 | import json
 17 | import re
 18 | import sys
 19 | 
 20 | 
 21 | class TextChunkBuffer:
 22 |     """Hold onto text chunks until needed."""
 23 |     def __init__(self):
 24 |         self.buffer = []
 25 | 
 26 |     def append(self, text):
 27 |         """Add `text` to the buffer."""
 28 |         self.buffer.append(text)
 29 | 
 30 |     def clear(self):
 31 |         """Clear the buffer."""
 32 |         self.buffer = []
 33 | 
 34 |     def flush(self):
 35 |         """Produce a ("text", text) tuple if there's anything here."""
 36 |         buffered = "".join(self.buffer).strip()
 37 |         if buffered:
 38 |             yield ("text", buffered)
 39 |         self.clear()
 40 | 
 41 | 
 42 | def parse_md(lines):
 43 |     """Parse markdown lines, producing (type, text) chunks."""
 44 |     buffer = TextChunkBuffer()
 45 | 
 46 |     for line in lines:
 47 |         header_match = re.search(r"^(#+) (.+)$", line)
 48 |         is_header = bool(header_match)
 49 |         if is_header:
 50 |             yield from buffer.flush()
 51 |             hashes, text = header_match.groups()
 52 |             yield (f"h{len(hashes)}", text)
 53 |         else:
 54 |             buffer.append(line)
 55 | 
 56 |     yield from buffer.flush()
 57 | 
 58 | 
 59 | def sections(parsed_data):
 60 |     """Convert a stream of parsed tokens into sections with text and notes.
 61 | 
 62 |     Yields a stream of:
 63 |         ('h-level', 'header text', 'text')
 64 | 
 65 |     """
 66 |     header = None
 67 |     text = []
 68 |     for ttype, ttext in parsed_data:
 69 |         if ttype.startswith('h'):
 70 |             if header:
 71 |                 yield (*header, "\n".join(text))
 72 |             text = []
 73 |             header = (ttype, ttext)
 74 |         elif ttype == "text":
 75 |             text.append(ttext)
 76 |         else:
 77 |             raise RuntimeError(f"Don't know ttype {ttype!r}")
 78 |     yield (*header, "\n".join(text))
 79 | 
 80 | 
 81 | def refind(regex, text):
 82 |     """Find a regex in some text, and return the matched text, or None."""
 83 |     m = re.search(regex, text)
 84 |     if m:
 85 |         return m.group()
 86 |     else:
 87 |         return None
 88 | 
 89 | 
 90 | def fix_ref_links(text, version):
 91 |     """Find links to .rst files, and make them full RTFD links."""
 92 |     def new_link(m):
 93 |         return f"](https://coverage.readthedocs.io/en/{version}/{m[1]}.html{m[2]})"
 94 |     return re.sub(r"\]\((\w+)\.rst(#.*?)\)", new_link, text)
 95 | 
 96 | 
 97 | def relnotes(mdlines):
 98 |     r"""Yield (version, text) pairs from markdown lines.
 99 | 
100 |     Each tuple is a separate version mentioned in the release notes.
101 | 
102 |     A version is any section with \d\.\d in the header text.
103 | 
104 |     """
105 |     for _, htext, text in sections(parse_md(mdlines)):
106 |         version = refind(r"\d+\.\d[^ ]*", htext)
107 |         if version:
108 |             prerelease = any(c in version for c in "abc")
109 |             when = refind(r"\d+-\d+-\d+", htext)
110 |             text = fix_ref_links(text, version)
111 |             yield {
112 |                 "version": version,
113 |                 "text": text,
114 |                 "prerelease": prerelease,
115 |                 "when": when,
116 |             }
117 | 
118 | def parse(md_filename, json_filename):
119 |     """Main function: parse markdown and write JSON."""
120 |     with open(md_filename) as mf:
121 |         markdown = mf.read()
122 |     with open(json_filename, "w") as jf:
123 |         json.dump(list(relnotes(markdown.splitlines(True))), jf, indent=4)
124 | 
125 | if __name__ == "__main__":
126 |     parse(*sys.argv[1:3])
127 | 


--------------------------------------------------------------------------------
/howto.txt:
--------------------------------------------------------------------------------
  1 | * Release checklist
  2 | 
  3 | - Check that the current virtualenv matches the current coverage branch.
  4 | - Version number in coverage/version.py
  5 |         version_info = (4, 0, 2, "alpha", 1)
  6 |         version_info = (4, 0, 2, "beta", 1)
  7 |         version_info = (4, 0, 2, "candidate", 1)
  8 |         version_info = (4, 0, 2, "final", 0)
  9 |     - make sure: _dev = 0
 10 | - Supported Python version numbers. Search for "PYVERSIONS".
 11 | - Update source files with release facts:
 12 |     $ make edit_for_release
 13 | - run `python igor.py cheats` to get useful snippets for next steps.
 14 | - Look over CHANGES.rst
 15 | - Update README.rst
 16 |     - "New in x.y:"
 17 |     - Python versions supported
 18 | - Update docs
 19 |     - Python versions in doc/index.rst
 20 |     - IF PRE-RELEASE:
 21 |         - Version of latest stable release in doc/index.rst
 22 |     - Make sure the docs are cogged:
 23 |         $ make prebuild
 24 |     - Don't forget the man page: doc/python-coverage.1.txt
 25 |     - Check that the docs build correctly:
 26 |         $ tox -e doc
 27 | - commit the release-prep changes
 28 | - Generate new sample_html to get the latest, incl footer version number:
 29 |     - IF PRE-RELEASE:
 30 |         $ make sample_html_beta
 31 |     - IF NOT PRE-RELEASE:
 32 |         $ make sample_html
 33 |         check in the new sample html
 34 | - Done with changes to source files
 35 |     - check them in on a branch
 36 |     - wait for ci to finish
 37 |     - merge to master
 38 |     - git push
 39 | - Start the kits:
 40 |     - Trigger the kit GitHub Action
 41 |         $ make build_kits
 42 | - Build and publish docs:
 43 |     - IF PRE-RELEASE:
 44 |         $ make publishbeta
 45 |     - ELSE:
 46 |         $ make publish
 47 | - Kits:
 48 |     - Wait for kits to finish:
 49 |         - https://github.com/nedbat/coveragepy/actions/workflows/kit.yml
 50 |     - Download and check built kits from GitHub Actions:
 51 |         $ make clean download_kits check_kits
 52 |     - examine the dist directory, and remove anything that looks malformed.
 53 |     - opvars
 54 |     - test the pypi upload:
 55 |         $ make test_upload
 56 |     - upload kits:
 57 |         $ make kit_upload
 58 | - Tag the tree
 59 |     $ make tag
 60 |     - IF NOT PRE-RELEASE:
 61 |         - update git "stable" branch to point to latest release
 62 |             $ make update_stable
 63 | - Update GitHub releases:
 64 |     $ make clean github_releases
 65 | - Visit the fixed issues on GitHub and mention the version it was fixed in.
 66 |     $ make comment_on_fixes
 67 | - unopvars
 68 | - Bump version:
 69 |     $ make bump_version
 70 | - Update readthedocs
 71 |     - @ https://readthedocs.org/projects/coverage/versions/
 72 |         - find the latest tag in the inactive list, edit it, make it active.
 73 |             - readthedocs won't find the tag until a commit is made on master.
 74 |         - keep just the latest version of each x.y release, make the rest active but hidden.
 75 |             - pre-releases should be hidden
 76 |     - IF NOT PRE-RELEASE:
 77 |         - @ https://readthedocs.org/projects/coverage/builds/
 78 |             - wait for the new tag build to finish successfully.
 79 |         - @ https://readthedocs.org/dashboard/coverage/advanced/
 80 |             - change the default version to the new version
 81 | - things to automate:
 82 |     - url to link to latest changes in docs
 83 |     - next version.py line
 84 |     - readthedocs api to do the readthedocs changes
 85 | 
 86 | 
 87 | * Testing
 88 | 
 89 | - Testing of Python code is handled by tox.
 90 |     - Create and activate a virtualenv
 91 |     - pip install -r requirements/dev.pip
 92 |     - $ tox
 93 | 
 94 | - For complete coverage testing:
 95 | 
 96 |     $ make metacov
 97 | 
 98 |     This will run coverage.py under its own measurement.  You can do this in
 99 |     different environments (Linux vs. Windows, for example), then copy the data
100 |     files (.metacov.*) to one machine for combination and reporting.  To
101 |     combine and report:
102 | 
103 |     $ make metahtml
104 | 
105 | - To run the Javascript tests:
106 | 
107 |     open tests/js/index.html in variety of browsers.
108 | 


--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
  1 | # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
  2 | # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
  3 | 
  4 | [tox]
  5 | # When changing this list, be sure to check the [gh] list below.
  6 | # PYVERSIONS
  7 | envlist = py{37,38,39,310,311,312}, pypy3, doc, lint
  8 | skip_missing_interpreters = {env:COVERAGE_SKIP_MISSING_INTERPRETERS:True}
  9 | toxworkdir = {env:TOXWORKDIR:.tox}
 10 | 
 11 | [testenv]
 12 | usedevelop = True
 13 | extras =
 14 |     toml
 15 | 
 16 | # PYVERSIONS
 17 | deps =
 18 |     -r requirements/pip.pip
 19 |     -r requirements/pytest.pip
 20 |     py{37,38,39,310,311}: -r requirements/light-threads.pip
 21 | 
 22 | # Windows can't update the pip version with pip running, so use Python
 23 | # to install things.
 24 | install_command = python -m pip install -U {opts} {packages}
 25 | 
 26 | passenv = *
 27 | setenv =
 28 |     pypy{3,37,38,39}: COVERAGE_NO_CTRACER=no C extension under PyPy
 29 |     # For some tests, we need .pyc files written in the current directory,
 30 |     # so override any local setting.
 31 |     PYTHONPYCACHEPREFIX=
 32 | 
 33 | # $set_env.py: COVERAGE_PIP_ARGS - Extra arguments for `pip install`
 34 | # `--no-build-isolation` will let tox work with no network.
 35 | commands =
 36 |     # Create tests/zipmods.zip
 37 |     python igor.py zip_mods
 38 | 
 39 |     # Build the C extension and test with the CTracer
 40 |     python setup.py --quiet build_ext --inplace
 41 |     python -m pip install {env:COVERAGE_PIP_ARGS} -q -e .
 42 |     python igor.py test_with_tracer c {posargs}
 43 | 
 44 |     # Remove the C extension so that we can test the PyTracer
 45 |     python igor.py remove_extension
 46 | 
 47 |     # Test with the PyTracer
 48 |     python igor.py test_with_tracer py {posargs}
 49 | 
 50 | [testenv:anypy]
 51 | # $set_env.py: COVERAGE_ANYPY - The custom Python for "tox -e anypy"
 52 | # For running against my own builds of CPython, or any other specific Python.
 53 | basepython = {env:COVERAGE_ANYPY}
 54 | 
 55 | [testenv:doc]
 56 | # Build the docs so we know if they are successful.  We build twice: once with
 57 | # -q to get all warnings, and once with -QW to get a success/fail status
 58 | # return.
 59 | deps =
 60 |     -r doc/requirements.pip
 61 | allowlist_externals =
 62 |     make
 63 | commands =
 64 |     # If this command fails, see the comment at the top of doc/cmd.rst
 65 |     python -m cogapp -cP --check --verbosity=1 doc/*.rst
 66 |     #doc8 -q --ignore-path 'doc/_*' doc CHANGES.rst README.rst
 67 |     sphinx-build -b html -aEnqW doc doc/_build/html
 68 |     rst2html.py --strict README.rst doc/_build/trash
 69 |     - sphinx-build -b html -b linkcheck -aEnq doc doc/_build/html
 70 |     - sphinx-build -b html -b linkcheck -aEnQW doc doc/_build/html
 71 | 
 72 | [testenv:lint]
 73 | deps =
 74 |     -r requirements/lint.pip
 75 | 
 76 | setenv =
 77 |     {[testenv]setenv}
 78 |     LINTABLE=coverage tests doc ci igor.py setup.py __main__.py
 79 | 
 80 | commands =
 81 |     python -m tabnanny {env:LINTABLE}
 82 |     # If this command fails, see the comment at the top of doc/cmd.rst
 83 |     python -m cogapp -cP --check --verbosity=1 doc/*.rst
 84 |     python -m cogapp -cP --check --verbosity=1 .github/workflows/*.yml
 85 |     #doc8 -q --ignore-path 'doc/_*' doc CHANGES.rst README.rst
 86 |     python -m pylint --notes= {env:LINTABLE}
 87 |     check-manifest --ignore 'doc/sample_html/*,.treerc'
 88 |     # If 'build -q' becomes a thing (https://github.com/pypa/build/issues/188),
 89 |     # this can be simplified:
 90 |     python igor.py quietly "python -m build"
 91 |     twine check dist/*
 92 | 
 93 | [testenv:mypy]
 94 | basepython = python3.8
 95 | 
 96 | deps =
 97 |     -r requirements/mypy.pip
 98 | 
 99 | setenv =
100 |     {[testenv]setenv}
101 |     TYPEABLE=coverage tests
102 | 
103 | commands =
104 |     # PYVERSIONS
105 |     mypy --python-version=3.8 {env:TYPEABLE}
106 |     mypy --python-version=3.12 {env:TYPEABLE}
107 | 
108 | [gh]
109 | # https://pypi.org/project/tox-gh/
110 | # PYVERSIONS
111 | python =
112 |     3.7 = py37
113 |     3.8 = py38
114 |     3.9 = py39
115 |     3.10 = py310
116 |     3.11 = py311
117 |     3.12 = py312
118 |     pypy-3 = pypy3
119 | 


--------------------------------------------------------------------------------
/tests/gold/html/a/index.html:
--------------------------------------------------------------------------------
  1 | 
  2 | 
  3 | 
  4 |     
  5 |     Coverage report
  6 |     
  7 |     
  8 |     
  9 | 
 10 | 
 11 | 
12 |
13 |

Coverage report: 14 | 67% 15 |

16 | 43 |
44 | 45 |
46 |

47 | coverage.py v6.4a0, 48 | created at 2022-05-20 16:29 -0400 49 |

50 |
51 |
52 |
53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 |
Modulestatementsmissingexcludedcoverage
a.py31067%
Total31067%
82 |

83 | No items found using the specified filter. 84 |

85 |
86 | 101 | 102 | 103 | --------------------------------------------------------------------------------