Skip to content

Commit 1db98e6

Browse files
committed
wip: separate environments for outer and inner coverages during metacov
1 parent 3418eb9 commit 1db98e6

13 files changed

+61
-38
lines changed

coverage/__init__.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,18 @@
1111

1212
from __future__ import annotations
1313

14+
import sys
15+
1416
# mypy's convention is that "import as" names are public from the module.
1517
# We import names as themselves to indicate that. Pylint sees it as pointless,
1618
# so disable its warning.
1719
# pylint: disable=useless-import-alias
1820

21+
from coverage import env
1922
from coverage.version import (
2023
__version__ as __version__,
2124
version_info as version_info,
2225
)
23-
2426
from coverage.control import (
2527
Coverage as Coverage,
2628
process_startup as process_startup,
@@ -36,3 +38,11 @@
3638

3739
# Backward compatibility.
3840
coverage = Coverage
41+
42+
# When using coverage to measure itself, we want the outer coverage to be
43+
# configured separately from the inner coverage running in the tests.
44+
# To do that, we look at different environment variables. We check sys.argv
45+
# here to get set the environment variable prefix as early as possible, before
46+
# any code looks at environment variables.
47+
if "--meta" in sys.argv:
48+
env.PREFIX = "METACOV_"

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -1347,7 +1347,7 @@ def plugin_info(plugins: list[Any]) -> list[str]:
13471347

13481348
# Mega debugging...
13491349
# $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
1350+
if int(env.getenv("COVERAGE_DEBUG_CALLS", "0")): # pragma: debugging
13511351
from coverage.debug import decorate_methods, show_calls
13521352

13531353
Coverage = decorate_methods( # type: ignore[misc]
@@ -1379,7 +1379,7 @@ def process_startup() -> Coverage | None:
13791379
not started by this call.
13801380
13811381
"""
1382-
cps = os.getenv("COVERAGE_PROCESS_START")
1382+
cps = env.getenv("COVERAGE_PROCESS_START")
13831383
if not cps:
13841384
# No request for coverage, nothing to do.
13851385
return None

coverage/core.py

+2-2
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
@@ -64,7 +64,7 @@ def __init__(self,
6464
if timid:
6565
core_name = "pytrace"
6666
else:
67-
core_name = os.getenv("COVERAGE_CORE")
67+
core_name = env.getenv("COVERAGE_CORE")
6868

6969
if core_name == "sysmon" and not env.PYBEHAVIOR.pep669:
7070
warn("sys.monitoring isn't available, using default core", slug="no-sysmon")

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

+20-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,32 @@ class PYBEHAVIOR:
156156
# PEP649 and PEP749: Deferred annotations
157157
deferred_annotations = (PYVERSION >= (3, 14))
158158

159+
# Environment variable access.
160+
PREFIX = "COVERAGE_"
161+
162+
163+
@overload
164+
def getenv(name: str) -> str | None:
165+
...
166+
167+
@overload
168+
def getenv(name: str, default: str) -> str:
169+
...
170+
171+
def getenv(name: str, default: str = "") -> str | None:
172+
"""Like os.getenv, but understands about switching prefixes."""
173+
return os.getenv(name.replace("COVERAGE_", PREFIX, 1), 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+
METACOV = getenv("COVERAGE_COVERAGE") is not None
164180

165181
# Are we running our test suite?
166182
# Even when running tests, you can use COVERAGE_TESTING=0 to disable the
167183
# test-specific behavior like AST checking.
168-
TESTING = os.getenv("COVERAGE_TESTING") == "True"
184+
TESTING = getenv("COVERAGE_TESTING") == "True"
169185

170186

171187
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/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

+1-1
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def run_tests_with_coverage(core, *runner_args):
177177
context = os.getenv("COVERAGE_CONTEXT")
178178
if context:
179179
if context[0] == "$":
180-
context = os.environ[context[1:]]
180+
context = os.getenv(context[1:])
181181
os.environ["COVERAGE_CONTEXT"] = context + "." + core
182182

183183
# Create the .pth file that will let us measure coverage in subprocesses.

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]]:

tests/helpers.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def run_command(cmd: str) -> tuple[int, str]:
6969

7070

7171
# $set_env.py: COVERAGE_DIS - Disassemble test code to /tmp/dis
72-
SHOW_DIS = bool(int(os.getenv("COVERAGE_DIS", "0")))
72+
SHOW_DIS = bool(int(env.getenv("COVERAGE_DIS", "0")))
7373

7474
def make_file(
7575
filename: str,

tests/testenv.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@
55

66
from __future__ import annotations
77

8-
import os
8+
from coverage import env
99

1010
# Are we testing the C-implemented trace function?
11-
C_TRACER = os.getenv("COVERAGE_CORE", "ctrace") == "ctrace"
11+
C_TRACER = env.getenv("COVERAGE_CORE", "ctrace") == "ctrace"
1212

1313
# Are we testing the Python-implemented trace function?
14-
PY_TRACER = os.getenv("COVERAGE_CORE", "ctrace") == "pytrace"
14+
PY_TRACER = env.getenv("COVERAGE_CORE", "ctrace") == "pytrace"
1515

1616
# Are we testing the sys.monitoring implementation?
17-
SYS_MON = os.getenv("COVERAGE_CORE", "ctrace") == "sysmon"
17+
SYS_MON = env.getenv("COVERAGE_CORE", "ctrace") == "sysmon"
1818

1919
# Are we using a settrace function as a core?
2020
SETTRACE_CORE = C_TRACER or PY_TRACER

0 commit comments

Comments
 (0)