coverage 7.11.3__cp314-cp314-musllinux_1_2_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of coverage might be problematic. Click here for more details.
- coverage/__init__.py +40 -0
- coverage/__main__.py +12 -0
- coverage/annotate.py +114 -0
- coverage/bytecode.py +196 -0
- coverage/cmdline.py +1184 -0
- coverage/collector.py +486 -0
- coverage/config.py +731 -0
- coverage/context.py +74 -0
- coverage/control.py +1481 -0
- coverage/core.py +139 -0
- coverage/data.py +227 -0
- coverage/debug.py +669 -0
- coverage/disposition.py +59 -0
- coverage/env.py +135 -0
- coverage/exceptions.py +85 -0
- coverage/execfile.py +329 -0
- coverage/files.py +553 -0
- coverage/html.py +856 -0
- coverage/htmlfiles/coverage_html.js +733 -0
- coverage/htmlfiles/favicon_32.png +0 -0
- coverage/htmlfiles/index.html +164 -0
- coverage/htmlfiles/keybd_closed.png +0 -0
- coverage/htmlfiles/pyfile.html +149 -0
- coverage/htmlfiles/style.css +377 -0
- coverage/htmlfiles/style.scss +824 -0
- coverage/inorout.py +614 -0
- coverage/jsonreport.py +188 -0
- coverage/lcovreport.py +219 -0
- coverage/misc.py +373 -0
- coverage/multiproc.py +120 -0
- coverage/numbits.py +146 -0
- coverage/parser.py +1213 -0
- coverage/patch.py +166 -0
- coverage/phystokens.py +197 -0
- coverage/plugin.py +617 -0
- coverage/plugin_support.py +299 -0
- coverage/py.typed +1 -0
- coverage/python.py +269 -0
- coverage/pytracer.py +369 -0
- coverage/regions.py +127 -0
- coverage/report.py +298 -0
- coverage/report_core.py +117 -0
- coverage/results.py +471 -0
- coverage/sqldata.py +1153 -0
- coverage/sqlitedb.py +239 -0
- coverage/sysmon.py +482 -0
- coverage/templite.py +306 -0
- coverage/tomlconfig.py +210 -0
- coverage/tracer.cpython-314-x86_64-linux-musl.so +0 -0
- coverage/tracer.pyi +43 -0
- coverage/types.py +206 -0
- coverage/version.py +35 -0
- coverage/xmlreport.py +264 -0
- coverage-7.11.3.dist-info/METADATA +221 -0
- coverage-7.11.3.dist-info/RECORD +59 -0
- coverage-7.11.3.dist-info/WHEEL +5 -0
- coverage-7.11.3.dist-info/entry_points.txt +4 -0
- coverage-7.11.3.dist-info/licenses/LICENSE.txt +177 -0
- coverage-7.11.3.dist-info/top_level.txt +1 -0
coverage/disposition.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
|
2
|
+
# For details: https://github.com/coveragepy/coveragepy/blob/main/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 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: str | None
|
|
22
|
+
trace: bool
|
|
23
|
+
reason: str
|
|
24
|
+
file_tracer: FileTracer | None
|
|
25
|
+
has_dynamic_filename: bool
|
|
26
|
+
|
|
27
|
+
def __repr__(self) -> str:
|
|
28
|
+
return f"<FileDisposition {self.canonical_filename!r}: trace={self.trace}>"
|
|
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
|
+
|
|
36
|
+
def disposition_init(cls: type[TFileDisposition], original_filename: str) -> TFileDisposition:
|
|
37
|
+
"""Construct and initialize a new FileDisposition object."""
|
|
38
|
+
disp = cls()
|
|
39
|
+
disp.original_filename = original_filename
|
|
40
|
+
disp.canonical_filename = original_filename
|
|
41
|
+
disp.source_filename = None
|
|
42
|
+
disp.trace = False
|
|
43
|
+
disp.reason = ""
|
|
44
|
+
disp.file_tracer = None
|
|
45
|
+
disp.has_dynamic_filename = False
|
|
46
|
+
return disp
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def disposition_debug_msg(disp: TFileDisposition) -> str:
|
|
50
|
+
"""Make a nice debug message of what the FileDisposition is doing."""
|
|
51
|
+
if disp.trace:
|
|
52
|
+
msg = f"Tracing {disp.original_filename!r}"
|
|
53
|
+
if disp.original_filename != disp.source_filename:
|
|
54
|
+
msg += f" as {disp.source_filename!r}"
|
|
55
|
+
if disp.file_tracer:
|
|
56
|
+
msg += f": will be traced by {disp.file_tracer!r}"
|
|
57
|
+
else:
|
|
58
|
+
msg = f"Not tracing {disp.original_filename!r}: {disp.reason}"
|
|
59
|
+
return msg
|
coverage/env.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
|
2
|
+
# For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
|
|
3
|
+
|
|
4
|
+
"""Determine facts about the environment."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import platform
|
|
10
|
+
import sys
|
|
11
|
+
from collections.abc import Iterable
|
|
12
|
+
from typing import Any, Final
|
|
13
|
+
|
|
14
|
+
# debug_info() at the bottom wants to show all the globals, but not imports.
|
|
15
|
+
# Grab the global names here to know which names to not show. Nothing defined
|
|
16
|
+
# above this line will be in the output.
|
|
17
|
+
_UNINTERESTING_GLOBALS = list(globals())
|
|
18
|
+
# These names also shouldn't be shown.
|
|
19
|
+
_UNINTERESTING_GLOBALS += ["PYBEHAVIOR", "debug_info"]
|
|
20
|
+
|
|
21
|
+
# Operating systems.
|
|
22
|
+
WINDOWS = sys.platform == "win32"
|
|
23
|
+
LINUX = sys.platform.startswith("linux")
|
|
24
|
+
MACOS = sys.platform == "darwin"
|
|
25
|
+
|
|
26
|
+
# Python implementations.
|
|
27
|
+
CPYTHON = (platform.python_implementation() == "CPython") # fmt: skip
|
|
28
|
+
PYPY = (platform.python_implementation() == "PyPy") # fmt: skip
|
|
29
|
+
|
|
30
|
+
# Python versions. We amend version_info with one more value, a zero if an
|
|
31
|
+
# official version, or 1 if built from source beyond an official version.
|
|
32
|
+
# Only use sys.version_info directly where tools like mypy need it to understand
|
|
33
|
+
# version-specfic code, otherwise use PYVERSION.
|
|
34
|
+
PYVERSION = sys.version_info + (int(platform.python_version()[-1] == "+"),)
|
|
35
|
+
|
|
36
|
+
if PYPY:
|
|
37
|
+
# Minimum now is 7.3.16
|
|
38
|
+
PYPYVERSION = tuple(sys.pypy_version_info) # type: ignore[attr-defined]
|
|
39
|
+
else:
|
|
40
|
+
PYPYVERSION = (0,)
|
|
41
|
+
|
|
42
|
+
# Do we have a GIL?
|
|
43
|
+
GIL = getattr(sys, "_is_gil_enabled", lambda: True)()
|
|
44
|
+
|
|
45
|
+
# Do we ship compiled coveragepy wheels for this version?
|
|
46
|
+
SHIPPING_WHEELS = CPYTHON and PYVERSION[:2] <= (3, 14)
|
|
47
|
+
|
|
48
|
+
# Should we default to sys.monitoring?
|
|
49
|
+
SYSMON_DEFAULT = CPYTHON and PYVERSION >= (3, 14)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# Python behavior.
|
|
53
|
+
class PYBEHAVIOR:
|
|
54
|
+
"""Flags indicating this Python's behavior."""
|
|
55
|
+
|
|
56
|
+
# When leaving a with-block, do we visit the with-line exactly,
|
|
57
|
+
# or the context managers in inner-out order?
|
|
58
|
+
#
|
|
59
|
+
# mwith.py:
|
|
60
|
+
# with (
|
|
61
|
+
# open("/tmp/one", "w") as f2,
|
|
62
|
+
# open("/tmp/two", "w") as f3,
|
|
63
|
+
# open("/tmp/three", "w") as f4,
|
|
64
|
+
# ):
|
|
65
|
+
# print("hello 6")
|
|
66
|
+
#
|
|
67
|
+
# % python3.11 -m trace -t mwith.py | grep mwith
|
|
68
|
+
# --- modulename: mwith, funcname: <module>
|
|
69
|
+
# mwith.py(2): open("/tmp/one", "w") as f2,
|
|
70
|
+
# mwith.py(1): with (
|
|
71
|
+
# mwith.py(2): open("/tmp/one", "w") as f2,
|
|
72
|
+
# mwith.py(3): open("/tmp/two", "w") as f3,
|
|
73
|
+
# mwith.py(1): with (
|
|
74
|
+
# mwith.py(3): open("/tmp/two", "w") as f3,
|
|
75
|
+
# mwith.py(4): open("/tmp/three", "w") as f4,
|
|
76
|
+
# mwith.py(1): with (
|
|
77
|
+
# mwith.py(4): open("/tmp/three", "w") as f4,
|
|
78
|
+
# mwith.py(6): print("hello 6")
|
|
79
|
+
# mwith.py(1): with (
|
|
80
|
+
#
|
|
81
|
+
# % python3.12 -m trace -t mwith.py | grep mwith
|
|
82
|
+
# --- modulename: mwith, funcname: <module>
|
|
83
|
+
# mwith.py(2): open("/tmp/one", "w") as f2,
|
|
84
|
+
# mwith.py(3): open("/tmp/two", "w") as f3,
|
|
85
|
+
# mwith.py(4): open("/tmp/three", "w") as f4,
|
|
86
|
+
# mwith.py(6): print("hello 6")
|
|
87
|
+
# mwith.py(4): open("/tmp/three", "w") as f4,
|
|
88
|
+
# mwith.py(3): open("/tmp/two", "w") as f3,
|
|
89
|
+
# mwith.py(2): open("/tmp/one", "w") as f2,
|
|
90
|
+
|
|
91
|
+
exit_with_through_ctxmgr = (PYVERSION >= (3, 12, 6)) # fmt: skip
|
|
92
|
+
|
|
93
|
+
# f-strings are parsed as code, pep 701
|
|
94
|
+
fstring_syntax = (PYVERSION >= (3, 12)) # fmt: skip
|
|
95
|
+
|
|
96
|
+
# PEP669 Low Impact Monitoring: https://peps.python.org/pep-0669/
|
|
97
|
+
pep669: Final[bool] = bool(getattr(sys, "monitoring", None))
|
|
98
|
+
|
|
99
|
+
# Where does frame.f_lasti point when yielding from a generator?
|
|
100
|
+
# It used to point at the YIELD, in 3.13 it points at the RESUME,
|
|
101
|
+
# then it went back to the YIELD.
|
|
102
|
+
# https://github.com/python/cpython/issues/113728
|
|
103
|
+
lasti_is_yield = (PYVERSION[:2] != (3, 13)) # fmt: skip
|
|
104
|
+
|
|
105
|
+
# PEP649 and PEP749: Deferred annotations
|
|
106
|
+
deferred_annotations = (PYVERSION >= (3, 14)) # fmt: skip
|
|
107
|
+
|
|
108
|
+
# Does sys.monitoring support BRANCH_RIGHT and BRANCH_LEFT? The names
|
|
109
|
+
# were added in early 3.14 alphas, but didn't work entirely correctly until
|
|
110
|
+
# after 3.14.0a5.
|
|
111
|
+
branch_right_left = pep669 and (PYVERSION > (3, 14, 0, "alpha", 5, 0))
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# Coverage.py specifics, about testing scenarios. See tests/testenv.py also.
|
|
115
|
+
|
|
116
|
+
# Are we coverage-measuring ourselves?
|
|
117
|
+
METACOV = os.getenv("COVERAGE_COVERAGE") is not None
|
|
118
|
+
|
|
119
|
+
# Are we running our test suite?
|
|
120
|
+
# Even when running tests, you can use COVERAGE_TESTING=0 to disable the
|
|
121
|
+
# test-specific behavior like AST checking.
|
|
122
|
+
TESTING = os.getenv("COVERAGE_TESTING") == "True"
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def debug_info() -> Iterable[tuple[str, Any]]:
|
|
126
|
+
"""Return a list of (name, value) pairs for printing debug information."""
|
|
127
|
+
info = [
|
|
128
|
+
(name, value)
|
|
129
|
+
for name, value in globals().items()
|
|
130
|
+
if not name.startswith("_") and name not in _UNINTERESTING_GLOBALS
|
|
131
|
+
]
|
|
132
|
+
info += [
|
|
133
|
+
(name, value) for name, value in PYBEHAVIOR.__dict__.items() if not name.startswith("_")
|
|
134
|
+
]
|
|
135
|
+
return sorted(info)
|
coverage/exceptions.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
|
2
|
+
# For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
|
|
3
|
+
|
|
4
|
+
"""Exceptions coverage.py can raise."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CoverageException(Exception):
|
|
12
|
+
"""The base class of all exceptions raised by Coverage.py."""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
*args: Any,
|
|
17
|
+
slug: str | None = None,
|
|
18
|
+
) -> None:
|
|
19
|
+
"""Create an exception.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
slug: A short string identifying the exception, will be used for
|
|
23
|
+
linking to documentation.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
super().__init__(*args)
|
|
27
|
+
self.slug = slug
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ConfigError(CoverageException):
|
|
31
|
+
"""A problem with a config file, or a value in one."""
|
|
32
|
+
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class DataError(CoverageException):
|
|
37
|
+
"""An error in using a data file."""
|
|
38
|
+
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class NoDataError(CoverageException):
|
|
43
|
+
"""We didn't have data to work with."""
|
|
44
|
+
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class NoSource(CoverageException):
|
|
49
|
+
"""We couldn't find the source for a module."""
|
|
50
|
+
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class NoCode(NoSource):
|
|
55
|
+
"""We couldn't find any code at all."""
|
|
56
|
+
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class NotPython(CoverageException):
|
|
61
|
+
"""A source file turned out not to be parsable Python."""
|
|
62
|
+
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class PluginError(CoverageException):
|
|
67
|
+
"""A plugin misbehaved."""
|
|
68
|
+
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class _ExceptionDuringRun(CoverageException):
|
|
73
|
+
"""An exception happened while running customer code.
|
|
74
|
+
|
|
75
|
+
Construct it with three arguments, the values from `sys.exc_info`.
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class CoverageWarning(Warning):
|
|
83
|
+
"""A warning from Coverage.py."""
|
|
84
|
+
|
|
85
|
+
pass
|
coverage/execfile.py
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
|
2
|
+
# For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
|
|
3
|
+
|
|
4
|
+
"""Execute files of Python code."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import importlib.machinery
|
|
9
|
+
import importlib.util
|
|
10
|
+
import inspect
|
|
11
|
+
import marshal
|
|
12
|
+
import os
|
|
13
|
+
import struct
|
|
14
|
+
import sys
|
|
15
|
+
from importlib.machinery import ModuleSpec
|
|
16
|
+
from types import CodeType, ModuleType
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from coverage.exceptions import CoverageException, NoCode, NoSource, _ExceptionDuringRun
|
|
20
|
+
from coverage.files import canonical_filename, python_reported_file
|
|
21
|
+
from coverage.misc import isolate_module
|
|
22
|
+
from coverage.python import get_python_source
|
|
23
|
+
|
|
24
|
+
os = isolate_module(os)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
PYC_MAGIC_NUMBER = importlib.util.MAGIC_NUMBER
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class DummyLoader:
|
|
31
|
+
"""A shim for the pep302 __loader__, emulating pkgutil.ImpLoader.
|
|
32
|
+
|
|
33
|
+
Currently only implements the .fullname attribute
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, fullname: str, *_args: Any) -> None:
|
|
37
|
+
self.fullname = fullname
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def find_module(
|
|
41
|
+
modulename: str,
|
|
42
|
+
) -> tuple[str | None, str, ModuleSpec]:
|
|
43
|
+
"""Find the module named `modulename`.
|
|
44
|
+
|
|
45
|
+
Returns the file path of the module, the name of the enclosing
|
|
46
|
+
package, and the spec.
|
|
47
|
+
"""
|
|
48
|
+
try:
|
|
49
|
+
spec = importlib.util.find_spec(modulename)
|
|
50
|
+
except ImportError as err:
|
|
51
|
+
raise NoSource(str(err)) from err
|
|
52
|
+
if not spec:
|
|
53
|
+
raise NoSource(f"No module named {modulename!r}")
|
|
54
|
+
pathname = spec.origin
|
|
55
|
+
packagename = spec.name
|
|
56
|
+
if spec.submodule_search_locations:
|
|
57
|
+
mod_main = modulename + ".__main__"
|
|
58
|
+
spec = importlib.util.find_spec(mod_main)
|
|
59
|
+
if not spec:
|
|
60
|
+
raise NoSource(
|
|
61
|
+
f"No module named {mod_main}; "
|
|
62
|
+
+ f"{modulename!r} is a package and cannot be directly executed",
|
|
63
|
+
)
|
|
64
|
+
pathname = spec.origin
|
|
65
|
+
packagename = spec.name
|
|
66
|
+
packagename = packagename.rpartition(".")[0]
|
|
67
|
+
return pathname, packagename, spec
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class PyRunner:
|
|
71
|
+
"""Multi-stage execution of Python code.
|
|
72
|
+
|
|
73
|
+
This is meant to emulate real Python execution as closely as possible.
|
|
74
|
+
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def __init__(self, args: list[str], as_module: bool = False) -> None:
|
|
78
|
+
self.args = args
|
|
79
|
+
self.as_module = as_module
|
|
80
|
+
|
|
81
|
+
self.arg0 = args[0]
|
|
82
|
+
self.package: str | None = None
|
|
83
|
+
self.modulename: str | None = None
|
|
84
|
+
self.pathname: str | None = None
|
|
85
|
+
self.loader: DummyLoader | None = None
|
|
86
|
+
self.spec: ModuleSpec | None = None
|
|
87
|
+
|
|
88
|
+
def prepare(self) -> None:
|
|
89
|
+
"""Set sys.path properly.
|
|
90
|
+
|
|
91
|
+
This needs to happen before any importing, and without importing anything.
|
|
92
|
+
"""
|
|
93
|
+
path0: str | None
|
|
94
|
+
if getattr(sys.flags, "safe_path", False):
|
|
95
|
+
# See https://docs.python.org/3/using/cmdline.html#cmdoption-P
|
|
96
|
+
path0 = None
|
|
97
|
+
elif self.as_module:
|
|
98
|
+
path0 = os.getcwd()
|
|
99
|
+
elif os.path.isdir(self.arg0):
|
|
100
|
+
# Running a directory means running the __main__.py file in that
|
|
101
|
+
# directory.
|
|
102
|
+
path0 = self.arg0
|
|
103
|
+
else:
|
|
104
|
+
path0 = os.path.abspath(os.path.dirname(self.arg0))
|
|
105
|
+
|
|
106
|
+
if os.path.isdir(sys.path[0]):
|
|
107
|
+
# sys.path fakery. If we are being run as a command, then sys.path[0]
|
|
108
|
+
# is the directory of the "coverage" script. If this is so, replace
|
|
109
|
+
# sys.path[0] with the directory of the file we're running, or the
|
|
110
|
+
# current directory when running modules. If it isn't so, then we
|
|
111
|
+
# don't know what's going on, and just leave it alone.
|
|
112
|
+
top_file = inspect.stack()[-1][0].f_code.co_filename
|
|
113
|
+
sys_path_0_abs = os.path.abspath(sys.path[0])
|
|
114
|
+
top_file_dir_abs = os.path.abspath(os.path.dirname(top_file))
|
|
115
|
+
sys_path_0_abs = canonical_filename(sys_path_0_abs)
|
|
116
|
+
top_file_dir_abs = canonical_filename(top_file_dir_abs)
|
|
117
|
+
if sys_path_0_abs != top_file_dir_abs:
|
|
118
|
+
path0 = None
|
|
119
|
+
|
|
120
|
+
else:
|
|
121
|
+
# sys.path[0] is a file. Is the next entry the directory containing
|
|
122
|
+
# that file?
|
|
123
|
+
if sys.path[1] == os.path.dirname(sys.path[0]):
|
|
124
|
+
# Can it be right to always remove that?
|
|
125
|
+
del sys.path[1]
|
|
126
|
+
|
|
127
|
+
if path0 is not None:
|
|
128
|
+
sys.path[0] = python_reported_file(path0)
|
|
129
|
+
|
|
130
|
+
def _prepare2(self) -> None:
|
|
131
|
+
"""Do more preparation to run Python code.
|
|
132
|
+
|
|
133
|
+
Includes finding the module to run and adjusting sys.argv[0].
|
|
134
|
+
This method is allowed to import code.
|
|
135
|
+
|
|
136
|
+
"""
|
|
137
|
+
if self.as_module:
|
|
138
|
+
self.modulename = self.arg0
|
|
139
|
+
pathname, self.package, self.spec = find_module(self.modulename)
|
|
140
|
+
if self.spec is not None:
|
|
141
|
+
self.modulename = self.spec.name
|
|
142
|
+
self.loader = DummyLoader(self.modulename)
|
|
143
|
+
assert pathname is not None
|
|
144
|
+
self.pathname = os.path.abspath(pathname)
|
|
145
|
+
self.args[0] = self.arg0 = self.pathname
|
|
146
|
+
elif os.path.isdir(self.arg0):
|
|
147
|
+
# Running a directory means running the __main__.py file in that
|
|
148
|
+
# directory.
|
|
149
|
+
for ext in [".py", ".pyc", ".pyo"]:
|
|
150
|
+
try_filename = os.path.join(self.arg0, f"__main__{ext}")
|
|
151
|
+
# 3.8.10 changed how files are reported when running a
|
|
152
|
+
# directory.
|
|
153
|
+
try_filename = os.path.abspath(try_filename)
|
|
154
|
+
if os.path.exists(try_filename):
|
|
155
|
+
self.arg0 = try_filename
|
|
156
|
+
break
|
|
157
|
+
else:
|
|
158
|
+
raise NoSource(f"Can't find '__main__' module in '{self.arg0}'")
|
|
159
|
+
|
|
160
|
+
# Make a spec. I don't know if this is the right way to do it.
|
|
161
|
+
try_filename = python_reported_file(try_filename)
|
|
162
|
+
self.spec = importlib.machinery.ModuleSpec("__main__", None, origin=try_filename)
|
|
163
|
+
self.spec.has_location = True
|
|
164
|
+
self.package = ""
|
|
165
|
+
self.loader = DummyLoader("__main__")
|
|
166
|
+
else:
|
|
167
|
+
self.loader = DummyLoader("__main__")
|
|
168
|
+
|
|
169
|
+
self.arg0 = python_reported_file(self.arg0)
|
|
170
|
+
|
|
171
|
+
def run(self) -> None:
|
|
172
|
+
"""Run the Python code!"""
|
|
173
|
+
|
|
174
|
+
self._prepare2()
|
|
175
|
+
|
|
176
|
+
# Create a module to serve as __main__
|
|
177
|
+
main_mod = ModuleType("__main__")
|
|
178
|
+
|
|
179
|
+
from_pyc = self.arg0.endswith((".pyc", ".pyo"))
|
|
180
|
+
main_mod.__file__ = self.arg0
|
|
181
|
+
if from_pyc:
|
|
182
|
+
main_mod.__file__ = main_mod.__file__[:-1]
|
|
183
|
+
if self.package is not None:
|
|
184
|
+
main_mod.__package__ = self.package
|
|
185
|
+
main_mod.__loader__ = self.loader # type: ignore[assignment]
|
|
186
|
+
if self.spec is not None:
|
|
187
|
+
main_mod.__spec__ = self.spec
|
|
188
|
+
|
|
189
|
+
main_mod.__builtins__ = sys.modules["builtins"] # type: ignore[attr-defined]
|
|
190
|
+
|
|
191
|
+
sys.modules["__main__"] = main_mod
|
|
192
|
+
|
|
193
|
+
# Set sys.argv properly.
|
|
194
|
+
sys.argv = self.args
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
# Make a code object somehow.
|
|
198
|
+
if from_pyc:
|
|
199
|
+
code = make_code_from_pyc(self.arg0)
|
|
200
|
+
else:
|
|
201
|
+
code = make_code_from_py(self.arg0)
|
|
202
|
+
except CoverageException:
|
|
203
|
+
raise
|
|
204
|
+
except Exception as exc:
|
|
205
|
+
msg = f"Couldn't run '{self.arg0}' as Python code: {exc.__class__.__name__}: {exc}"
|
|
206
|
+
raise CoverageException(msg) from exc
|
|
207
|
+
|
|
208
|
+
# Execute the code object.
|
|
209
|
+
# Return to the original directory in case the test code exits in
|
|
210
|
+
# a non-existent directory.
|
|
211
|
+
cwd = os.getcwd()
|
|
212
|
+
try:
|
|
213
|
+
exec(code, main_mod.__dict__)
|
|
214
|
+
except SystemExit: # pylint: disable=try-except-raise
|
|
215
|
+
# The user called sys.exit(). Just pass it along to the upper
|
|
216
|
+
# layers, where it will be handled.
|
|
217
|
+
raise
|
|
218
|
+
except Exception:
|
|
219
|
+
# Something went wrong while executing the user code.
|
|
220
|
+
# Get the exc_info, and pack them into an exception that we can
|
|
221
|
+
# throw up to the outer loop. We peel one layer off the traceback
|
|
222
|
+
# so that the coverage.py code doesn't appear in the final printed
|
|
223
|
+
# traceback.
|
|
224
|
+
typ, err, tb = sys.exc_info()
|
|
225
|
+
assert typ is not None
|
|
226
|
+
assert err is not None
|
|
227
|
+
assert tb is not None
|
|
228
|
+
|
|
229
|
+
# PyPy3 weirdness. If I don't access __context__, then somehow it
|
|
230
|
+
# is non-None when the exception is reported at the upper layer,
|
|
231
|
+
# and a nested exception is shown to the user. This getattr fixes
|
|
232
|
+
# it somehow? https://bitbucket.org/pypy/pypy/issue/1903
|
|
233
|
+
getattr(err, "__context__", None)
|
|
234
|
+
|
|
235
|
+
# Call the excepthook.
|
|
236
|
+
try:
|
|
237
|
+
assert err.__traceback__ is not None
|
|
238
|
+
err.__traceback__ = err.__traceback__.tb_next
|
|
239
|
+
sys.excepthook(typ, err, tb.tb_next)
|
|
240
|
+
except SystemExit: # pylint: disable=try-except-raise
|
|
241
|
+
raise
|
|
242
|
+
except Exception as exc:
|
|
243
|
+
# Getting the output right in the case of excepthook
|
|
244
|
+
# shenanigans is kind of involved.
|
|
245
|
+
sys.stderr.write("Error in sys.excepthook:\n")
|
|
246
|
+
typ2, err2, tb2 = sys.exc_info()
|
|
247
|
+
assert typ2 is not None
|
|
248
|
+
assert err2 is not None
|
|
249
|
+
assert tb2 is not None
|
|
250
|
+
err2.__suppress_context__ = True
|
|
251
|
+
assert err2.__traceback__ is not None
|
|
252
|
+
err2.__traceback__ = err2.__traceback__.tb_next
|
|
253
|
+
sys.__excepthook__(typ2, err2, tb2.tb_next)
|
|
254
|
+
sys.stderr.write("\nOriginal exception was:\n")
|
|
255
|
+
raise _ExceptionDuringRun(typ, err, tb.tb_next) from exc
|
|
256
|
+
else:
|
|
257
|
+
sys.exit(1)
|
|
258
|
+
finally:
|
|
259
|
+
os.chdir(cwd)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def run_python_module(args: list[str]) -> None:
|
|
263
|
+
"""Run a Python module, as though with ``python -m name args...``.
|
|
264
|
+
|
|
265
|
+
`args` is the argument array to present as sys.argv, including the first
|
|
266
|
+
element naming the module being executed.
|
|
267
|
+
|
|
268
|
+
This is a helper for tests, to encapsulate how to use PyRunner.
|
|
269
|
+
|
|
270
|
+
"""
|
|
271
|
+
runner = PyRunner(args, as_module=True)
|
|
272
|
+
runner.prepare()
|
|
273
|
+
runner.run()
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def run_python_file(args: list[str]) -> None:
|
|
277
|
+
"""Run a Python file as if it were the main program on the command line.
|
|
278
|
+
|
|
279
|
+
`args` is the argument array to present as sys.argv, including the first
|
|
280
|
+
element naming the file being executed. `package` is the name of the
|
|
281
|
+
enclosing package, if any.
|
|
282
|
+
|
|
283
|
+
This is a helper for tests, to encapsulate how to use PyRunner.
|
|
284
|
+
|
|
285
|
+
"""
|
|
286
|
+
runner = PyRunner(args, as_module=False)
|
|
287
|
+
runner.prepare()
|
|
288
|
+
runner.run()
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def make_code_from_py(filename: str) -> CodeType:
|
|
292
|
+
"""Get source from `filename` and make a code object of it."""
|
|
293
|
+
try:
|
|
294
|
+
source = get_python_source(filename)
|
|
295
|
+
except (OSError, NoSource) as exc:
|
|
296
|
+
raise NoSource(f"No file to run: '{filename}'") from exc
|
|
297
|
+
|
|
298
|
+
code = compile(source, filename, mode="exec", dont_inherit=True)
|
|
299
|
+
return code
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def make_code_from_pyc(filename: str) -> CodeType:
|
|
303
|
+
"""Get a code object from a .pyc file."""
|
|
304
|
+
try:
|
|
305
|
+
fpyc = open(filename, "rb")
|
|
306
|
+
except OSError as exc:
|
|
307
|
+
raise NoCode(f"No file to run: '{filename}'") from exc
|
|
308
|
+
|
|
309
|
+
with fpyc:
|
|
310
|
+
# First four bytes are a version-specific magic number. It has to
|
|
311
|
+
# match or we won't run the file.
|
|
312
|
+
magic = fpyc.read(4)
|
|
313
|
+
if magic != PYC_MAGIC_NUMBER:
|
|
314
|
+
raise NoCode(f"Bad magic number in .pyc file: {magic!r} != {PYC_MAGIC_NUMBER!r}")
|
|
315
|
+
|
|
316
|
+
flags = struct.unpack("<L", fpyc.read(4))[0]
|
|
317
|
+
hash_based = flags & 0x01
|
|
318
|
+
if hash_based:
|
|
319
|
+
fpyc.read(8) # Skip the hash.
|
|
320
|
+
else:
|
|
321
|
+
# Skip the junk in the header that we don't need.
|
|
322
|
+
fpyc.read(4) # Skip the moddate.
|
|
323
|
+
fpyc.read(4) # Skip the size.
|
|
324
|
+
|
|
325
|
+
# The rest of the file is the code object we want.
|
|
326
|
+
code = marshal.load(fpyc)
|
|
327
|
+
assert isinstance(code, CodeType)
|
|
328
|
+
|
|
329
|
+
return code
|