Skip to content

Commit 99d4ef0

Browse files
committed
refactor: separate inner and outer coverage during metacov
1 parent 3418eb9 commit 99d4ef0

19 files changed

+79
-62
lines changed

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ clean: debug_clean _clean_platform ## Remove artifacts of test execution, instal
3838
@rm -f .coverage .coverage.* .metacov*
3939
@rm -f coverage.xml coverage.json
4040
@rm -f .tox/*/lib/*/site-packages/zzz_metacov.pth
41+
@rm -f .tox/*/lib/*/site-packages/subcover.pth
4142
@rm -f */.coverage */*/.coverage */*/*/.coverage */*/*/*/.coverage */*/*/*/*/.coverage */*/*/*/*/*/.coverage
4243
@rm -f tests/covmain.zip tests/zipmods.zip tests/zip1.zip
4344
@rm -rf doc/_build doc/_spell doc/sample_html_beta

coverage/cmdline.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -991,7 +991,7 @@ def main(argv: list[str] | None = None) -> int | None:
991991
# pip install git+https://github.com/emin63/ox_profile.git
992992
#
993993
# $set_env.py: COVERAGE_PROFILE - Set to use ox_profile.
994-
_profile = os.getenv("COVERAGE_PROFILE")
994+
_profile = env.getenv("COVERAGE_PROFILE")
995995
if _profile: # pragma: debugging
996996
from ox_profile.core.launchers import SimpleLauncher # pylint: disable=import-error
997997
original_main = main

coverage/config.py

+6-7
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,10 @@
1212
import os.path
1313
import re
1414

15-
from typing import (
16-
Any, Callable, Final, Mapping, Union,
17-
)
1815
from collections.abc import Iterable
16+
from typing import Any, Callable, Final, Mapping, Union
1917

18+
from coverage import env
2019
from coverage.exceptions import ConfigError
2120
from coverage.misc import isolate_module, human_sorted_items, substitute_variables
2221
from coverage.tomlconfig import TomlConfigParser, TomlDecodeError
@@ -559,7 +558,7 @@ def config_files_to_try(config_file: bool | str) -> list[tuple[str, bool, bool]]
559558
specified_file = (config_file is not True)
560559
if not specified_file:
561560
# No file was specified. Check COVERAGE_RCFILE.
562-
rcfile = os.getenv("COVERAGE_RCFILE")
561+
rcfile = env.getenv("COVERAGE_RCFILE")
563562
if rcfile:
564563
config_file = rcfile
565564
specified_file = True
@@ -612,19 +611,19 @@ def read_coverage_config(
612611
raise ConfigError(f"Couldn't read {fname!r} as a config file")
613612

614613
# 3) from environment variables:
615-
env_data_file = os.getenv("COVERAGE_FILE")
614+
env_data_file = env.getenv("COVERAGE_FILE")
616615
if env_data_file:
617616
config.data_file = env_data_file
618617
# $set_env.py: COVERAGE_DEBUG - Debug options: https://coverage.rtfd.io/cmd.html#debug
619-
debugs = os.getenv("COVERAGE_DEBUG")
618+
debugs = env.getenv("COVERAGE_DEBUG")
620619
if debugs:
621620
config.debug.extend(d.strip() for d in debugs.split(","))
622621

623622
# 4) from constructor arguments:
624623
config.from_args(**kwargs)
625624

626625
# 5) for our benchmark, force settings using a secret environment variable:
627-
force_file = os.getenv("COVERAGE_FORCE_CONFIG")
626+
force_file = env.getenv("COVERAGE_FORCE_CONFIG")
628627
if force_file:
629628
config.from_file(force_file, warn, our_file=True)
630629

coverage/control.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ def __init__( # pylint: disable=too-many-arguments
316316
# is already coverage-aware, so don't auto-measure it. By now, the
317317
# auto-creation of a Coverage object has already happened. But we can
318318
# find it and tell it not to save its data.
319-
if not env.METACOV:
319+
if not env.INNER_METACOV:
320320
_prevent_sub_process_measurement()
321321

322322
def _init(self) -> None:
@@ -549,7 +549,6 @@ def _init_for_start(self) -> None:
549549
self._core = Core(
550550
warn=self._warn,
551551
timid=self.config.timid,
552-
metacov=self._metacov,
553552
)
554553
self._collector = Collector(
555554
core=self._core,
@@ -1347,7 +1346,7 @@ def plugin_info(plugins: list[Any]) -> list[str]:
13471346

13481347
# Mega debugging...
13491348
# $set_env.py: COVERAGE_DEBUG_CALLS - Lots and lots of output about calls to Coverage.
1350-
if int(os.getenv("COVERAGE_DEBUG_CALLS", 0)): # pragma: debugging
1349+
if int(env.getenv("COVERAGE_DEBUG_CALLS", "0")): # pragma: debugging
13511350
from coverage.debug import decorate_methods, show_calls
13521351

13531352
Coverage = decorate_methods( # type: ignore[misc]
@@ -1379,7 +1378,7 @@ def process_startup() -> Coverage | None:
13791378
not started by this call.
13801379
13811380
"""
1382-
cps = os.getenv("COVERAGE_PROCESS_START")
1381+
cps = env.getenv("COVERAGE_PROCESS_START")
13831382
if not cps:
13841383
# No request for coverage, nothing to do.
13851384
return None

coverage/core.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
HAS_CTRACER = True
3131
except ImportError:
3232
# Couldn't import the C extension, maybe it isn't built.
33-
if os.getenv("COVERAGE_CORE") == "ctrace": # pragma: part covered
33+
if env.getenv("COVERAGE_CORE") == "ctrace": # pragma: part covered
3434
# During testing, we use the COVERAGE_CORE environment variable
3535
# to indicate that we've fiddled with the environment to test this
3636
# fallback code. If we thought we had a C tracer, but couldn't import
@@ -55,7 +55,6 @@ class Core:
5555
def __init__(self,
5656
warn: TWarnFn,
5757
timid: bool,
58-
metacov: bool,
5958
) -> None:
6059
# Defaults
6160
self.tracer_kwargs = {}
@@ -64,7 +63,7 @@ def __init__(self,
6463
if timid:
6564
core_name = "pytrace"
6665
else:
67-
core_name = os.getenv("COVERAGE_CORE")
66+
core_name = env.getenv("COVERAGE_CORE")
6867

6968
if core_name == "sysmon" and not env.PYBEHAVIOR.pep669:
7069
warn("sys.monitoring isn't available, using default core", slug="no-sysmon")
@@ -81,7 +80,7 @@ def __init__(self,
8180

8281
if core_name == "sysmon":
8382
self.tracer_class = SysMonitor
84-
self.tracer_kwargs = {"tool_id": 3 if metacov else 1}
83+
self.tracer_kwargs = {"tool_id": 3 if env.OUTER_METACOV else 1}
8584
self.file_disposition_class = FileDisposition
8685
self.supports_plugins = False
8786
self.packed_arcs = False

coverage/debug.py

+8-10
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,10 @@
1919
import types
2020
import _thread
2121

22-
from typing import (
23-
overload,
24-
Any, Callable, Final, IO,
25-
)
22+
from typing import overload, Any, Callable, Final, IO
2623
from collections.abc import Iterable, Iterator, Mapping
2724

25+
from coverage import env
2826
from coverage.misc import human_sorted_items, isolate_module
2927
from coverage.types import AnyCallable, TWritable
3028

@@ -35,7 +33,7 @@
3533
# debugging the configuration mechanisms you usually use to control debugging!
3634
# This is a list of forced debugging options.
3735
FORCED_DEBUG: list[str] = []
38-
FORCED_DEBUG_FILE = None
36+
FORCED_DEBUG_FILE = ""
3937

4038

4139
class DebugControl:
@@ -402,7 +400,7 @@ def __init__(self) -> None:
402400

403401
def filter(self, text: str) -> str:
404402
"""Add a message when the pytest test changes."""
405-
test_name = os.getenv("PYTEST_CURRENT_TEST")
403+
test_name = env.getenv("PYTEST_CURRENT_TEST")
406404
if test_name != self.test_name:
407405
text = f"Pytest context: {test_name}\n" + text
408406
self.test_name = test_name
@@ -452,7 +450,7 @@ def get_one(
452450
fileobj = open(file_name, "a", encoding="utf-8")
453451
else:
454452
# $set_env.py: COVERAGE_DEBUG_FILE - Where to write debug output
455-
file_name = os.getenv("COVERAGE_DEBUG_FILE", FORCED_DEBUG_FILE)
453+
file_name = env.getenv("COVERAGE_DEBUG_FILE", FORCED_DEBUG_FILE)
456454
if file_name in ("stdout", "stderr"):
457455
fileobj = getattr(sys, file_name)
458456
elif file_name:
@@ -587,14 +585,14 @@ def _wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
587585
return _decorator
588586

589587

590-
def relevant_environment_display(env: Mapping[str, str]) -> list[tuple[str, str]]:
588+
def relevant_environment_display(env_map: Mapping[str, str]) -> Iterable[tuple[str, str]]:
591589
"""Filter environment variables for a debug display.
592590
593591
Select variables to display (with COV or PY in the name, or HOME, TEMP, or
594592
TMP), and also cloak sensitive values with asterisks.
595593
596594
Arguments:
597-
env: a dict of environment variable names and values.
595+
env_map: a dict of environment variable names and values.
598596
599597
Returns:
600598
A list of pairs (name, value) to show.
@@ -605,7 +603,7 @@ def relevant_environment_display(env: Mapping[str, str]) -> list[tuple[str, str]
605603
cloak = {"API", "TOKEN", "KEY", "SECRET", "PASS", "SIGNATURE"}
606604

607605
to_show = []
608-
for name, val in env.items():
606+
for name, val in env_map.items():
609607
keep = False
610608
if name in include:
611609
keep = True

coverage/env.py

+24-4
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@
99
import platform
1010
import sys
1111

12-
from typing import Any, Final
12+
from typing import Any, Final, overload
1313
from collections.abc import Iterable
1414

1515
# debug_info() at the bottom wants to show all the globals, but not imports.
1616
# Grab the global names here to know which names to not show. Nothing defined
1717
# above this line will be in the output.
1818
_UNINTERESTING_GLOBALS = list(globals())
1919
# These names also shouldn't be shown.
20-
_UNINTERESTING_GLOBALS += ["PYBEHAVIOR", "debug_info"]
20+
_UNINTERESTING_GLOBALS += ["PYBEHAVIOR", "debug_info", "PREFIX", "getenv"]
2121

2222
# Operating systems.
2323
WINDOWS = sys.platform == "win32"
@@ -156,16 +156,36 @@ class PYBEHAVIOR:
156156
# PEP649 and PEP749: Deferred annotations
157157
deferred_annotations = (PYVERSION >= (3, 14))
158158

159+
# Environment variable access.
160+
PREFIX = "COVERAGE_"
161+
162+
@overload
163+
def getenv(name: str) -> str | None:
164+
...
165+
166+
@overload
167+
def getenv(name: str, default: str) -> str:
168+
...
169+
170+
def getenv(name: str, default: str = "") -> str | None:
171+
"""Like os.getenv, but understands about switching prefixes."""
172+
real_name = name.replace("COVERAGE_", PREFIX, 1)
173+
return os.getenv(real_name, default)
174+
159175

160176
# Coverage.py specifics, about testing scenarios. See tests/testenv.py also.
161177

162178
# Are we coverage-measuring ourselves?
163-
METACOV = os.getenv("COVERAGE_COVERAGE") is not None
179+
OUTER_METACOV = bool(getenv("COVERAGE_METAFILE"))
180+
if OUTER_METACOV:
181+
PREFIX = "METACOV_"
182+
183+
INNER_METACOV = bool(getenv("COVERAGE_INNER_META"))
164184

165185
# Are we running our test suite?
166186
# Even when running tests, you can use COVERAGE_TESTING=0 to disable the
167187
# test-specific behavior like AST checking.
168-
TESTING = os.getenv("COVERAGE_TESTING") == "True"
188+
TESTING = getenv("COVERAGE_TESTING") == "True"
169189

170190

171191
def debug_info() -> Iterable[tuple[str, Any]]:

coverage/parser.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,7 @@ def __init__(
713713

714714
# Turn on AST dumps with an environment variable.
715715
# $set_env.py: COVERAGE_AST_DUMP - Dump the AST nodes when parsing code.
716-
dump_ast = bool(int(os.getenv("COVERAGE_AST_DUMP", "0")))
716+
dump_ast = bool(int(env.getenv("COVERAGE_AST_DUMP", "0")))
717717

718718
if dump_ast: # pragma: debugging
719719
# Dump the AST so that failing tests have helpful output.
@@ -735,7 +735,7 @@ def __init__(
735735
self.with_exits: set[TArc] = set()
736736

737737
# $set_env.py: COVERAGE_TRACK_ARCS - Trace possible arcs added while parsing code.
738-
self.debug = bool(int(os.getenv("COVERAGE_TRACK_ARCS", "0")))
738+
self.debug = bool(int(env.getenv("COVERAGE_TRACK_ARCS", "0")))
739739

740740
def analyze(self) -> None:
741741
"""Examine the AST tree from `self.root_node` to determine possible arcs."""

coverage/pytracer.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ def stop(self) -> None:
348348
# warn if we are measuring ourselves.
349349
suppress_warning = (
350350
(env.PYPY and self.in_atexit and tf is None)
351-
or env.METACOV
351+
or env.INNER_METACOV
352352
)
353353
if self.warn and not suppress_warning:
354354
if tf != self._cached_bound_method_trace: # pylint: disable=comparison-with-callable

coverage/sysmon.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def log(msg: str) -> None:
9090
if tid not in seen_threads:
9191
seen_threads.add(tid)
9292
log(f"New thread {tid} {tslug}:\n{short_stack()}")
93-
# log_seq = int(os.getenv("PANSEQ", "0"))
93+
# log_seq = int(env.getenv("PANSEQ", "0"))
9494
# root = f"/tmp/pan.{log_seq:03d}"
9595
for filename in [
9696
"/tmp/foo.out",

igor.py

+11-10
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import inspect
1515
import itertools
1616
import os
17+
import os.path
1718
import platform
1819
import pprint
1920
import re
@@ -170,14 +171,17 @@ def run_tests(core, *runner_args):
170171

171172
def run_tests_with_coverage(core, *runner_args):
172173
"""Run tests, but with coverage."""
174+
if "coverage" in sys.modules:
175+
raise RuntimeError("Something imported coverage too early! `make clean` to fix.")
176+
173177
# Need to define this early enough that the first import of env.py sees it.
174178
os.environ["COVERAGE_TESTING"] = "True"
175179
os.environ["COVERAGE_PROCESS_START"] = os.path.abspath("metacov.ini")
176180
os.environ["COVERAGE_HOME"] = os.getcwd()
177181
context = os.getenv("COVERAGE_CONTEXT")
178182
if context:
179183
if context[0] == "$":
180-
context = os.environ[context[1:]]
184+
context = os.getenv(context[1:])
181185
os.environ["COVERAGE_CONTEXT"] = context + "." + core
182186

183187
# Create the .pth file that will let us measure coverage in subprocesses.
@@ -197,27 +201,24 @@ def run_tests_with_coverage(core, *runner_args):
197201
cov = coverage.Coverage(config_file="metacov.ini")
198202
cov._warn_unimported_source = False
199203
cov._warn_preimported_source = False
200-
cov._metacov = True
201204
cov.start()
202205

206+
del os.environ["COVERAGE_METAFILE"]
207+
203208
try:
204-
# Re-import coverage to get it coverage tested! I don't understand all
205-
# the mechanics here, but if I don't carry over the imported modules
206-
# (in covmods), then things go haywire (os is None, eventually).
207-
covmods = {}
209+
# Re-import coverage to get it coverage tested!
208210
covdir = os.path.split(coverage.__file__)[0]
209211
# We have to make a list since we'll be deleting in the loop.
210212
modules = list(sys.modules.items())
211213
for name, mod in modules:
212214
if name.startswith("coverage"):
213215
if getattr(mod, "__file__", "??").startswith(covdir):
214-
covmods[name] = mod
215216
del sys.modules[name]
216-
import coverage # pylint: disable=reimported
217217

218-
sys.modules.update(covmods)
218+
os.environ["COVERAGE_INNER_META"] = "1"
219+
import coverage # pylint: disable=reimported
219220

220-
# Run tests, with the arguments from our command line.
221+
# Run tests with the arguments from our command line.
221222
status = run_tests(core, *runner_args)
222223

223224
finally:

metacov.ini

+3-3
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ exclude_lines =
5454

5555
# Lines that we can't run during metacov.
5656
pragma: no metacov
57-
pytest.mark.skipif\(env.METACOV
58-
if not env.METACOV:
57+
pytest.mark.skipif\(env.INNER_METACOV
58+
if not env.INNER_METACOV:
5959

6060
# These lines only happen if tests fail.
6161
raise AssertionError
@@ -84,7 +84,7 @@ partial_branches =
8484
# If we're asserting that any() is true, it didn't finish.
8585
assert any\(
8686
if env.TESTING:
87-
if env.METACOV:
87+
if env.INNER_METACOV:
8888

8989
precision = 3
9090

tests/coveragetest.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from collections.abc import Collection, Iterable, Iterator, Mapping, Sequence
2525

2626
import coverage
27-
from coverage import Coverage
27+
from coverage import Coverage, env
2828
from coverage.cmdline import CoverageScript
2929
from coverage.data import CoverageData
3030
from coverage.misc import import_local_file
@@ -44,7 +44,7 @@
4444
# Install arguments to pass to pip when reinstalling ourselves.
4545
# Defaults to the top of the source tree, but can be overridden if we need
4646
# some help on certain platforms.
47-
COVERAGE_INSTALL_ARGS = os.getenv("COVERAGE_INSTALL_ARGS", nice_file(TESTS_DIR, ".."))
47+
COVERAGE_INSTALL_ARGS = env.getenv("COVERAGE_INSTALL_ARGS", nice_file(TESTS_DIR, ".."))
4848

4949

5050
def arcs_to_branches(arcs: Iterable[TArc]) -> dict[TLineNo, list[TLineNo]]:

0 commit comments

Comments
 (0)