Coverage report: 14 | 67% 15 |
16 | 43 | 46 |47 | coverage.py v6.4a0, 48 | created at 2022-05-20 16:29 -0400 49 |
50 | 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 |
46 |
47 | coverage.py v6.4a0,
48 | created at 2022-05-20 16:29 -0400
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | Module
57 | statements
58 | missing
59 | excluded
60 | coverage
61 |
62 |
63 |
64 |
65 | a.py
66 | 3
67 | 1
68 | 0
69 | 67%
70 |
71 |
72 |
73 |
74 | Total
75 | 3
76 | 1
77 | 0
78 | 67%
79 |
80 |
81 |
82 |
83 | No items found using the specified filter.
84 |
85 |
86 |
101 |
102 |
103 |
--------------------------------------------------------------------------------