Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ defaults:

env:
PIP_DISABLE_PIP_VERSION_CHECK: 1
COVERAGE_IGOR_VERBOSE: 1
COVERAGE_IGOR_VERBOSE: 2
FORCE_COLOR: 1 # Get colored pytest output

permissions:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/python-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ defaults:

env:
PIP_DISABLE_PIP_VERSION_CHECK: 1
COVERAGE_IGOR_VERBOSE: 1
COVERAGE_IGOR_VERBOSE: 2

permissions:
contents: read
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/testsuite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ defaults:

env:
PIP_DISABLE_PIP_VERSION_CHECK: 1
COVERAGE_IGOR_VERBOSE: 1
COVERAGE_IGOR_VERBOSE: 2
FORCE_COLOR: 1 # Get colored pytest output

permissions:
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ _clean_platform:
@rm -f .DS_Store */.DS_Store */*/.DS_Store */*/*/.DS_Store */*/*/*/.DS_Store

debug_clean: ## Delete various debugging artifacts.
@rm -rf /tmp/dis $$COVERAGE_DEBUG_FILE
@rm -rf /tmp/dis /tmp/foo.out $$COVERAGE_DEBUG_FILE

clean: debug_clean _clean_platform ## Remove artifacts of test execution, installation, etc.
@echo "Cleaning..."
Expand Down
27 changes: 6 additions & 21 deletions coverage/collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from typing import Any, Callable, TypeVar, cast

from coverage import env
from coverage.config import CoverageConfig
from coverage.core import Core
from coverage.data import CoverageData
from coverage.debug import short_stack
Expand Down Expand Up @@ -60,9 +59,6 @@ class Collector:
# the top, and resumed when they become the top again.
_collectors: list[Collector] = []

# The concurrency settings we support here.
LIGHT_THREADS = {"greenlet", "eventlet", "gevent"}

def __init__(
self,
core: Core,
Expand Down Expand Up @@ -112,8 +108,7 @@ def __init__(
self.file_mapper = file_mapper
self.branch = branch
self.warn = warn
self.concurrency = concurrency
assert isinstance(self.concurrency, list), f"Expected a list: {self.concurrency!r}"
assert isinstance(concurrency, list), f"Expected a list: {concurrency!r}"

self.pid = os.getpid()

Expand All @@ -125,37 +120,27 @@ def __init__(

self.concur_id_func = None

# We can handle a few concurrency options here, but only one at a time.
concurrencies = set(self.concurrency)
unknown = concurrencies - CoverageConfig.CONCURRENCY_CHOICES
if unknown:
show = ", ".join(sorted(unknown))
raise ConfigError(f"Unknown concurrency choices: {show}")
light_threads = concurrencies & self.LIGHT_THREADS
if len(light_threads) > 1:
show = ", ".join(sorted(light_threads))
raise ConfigError(f"Conflicting concurrency settings: {show}")
do_threading = False

tried = "nothing" # to satisfy pylint
try:
if "greenlet" in concurrencies:
if "greenlet" in concurrency:
tried = "greenlet"
import greenlet

self.concur_id_func = greenlet.getcurrent
elif "eventlet" in concurrencies:
elif "eventlet" in concurrency:
tried = "eventlet"
import eventlet.greenthread

self.concur_id_func = eventlet.greenthread.getcurrent
elif "gevent" in concurrencies:
elif "gevent" in concurrency:
tried = "gevent"
import gevent

self.concur_id_func = gevent.getcurrent

if "thread" in concurrencies:
if "thread" in concurrency:
do_threading = True
except ImportError as ex:
msg = f"Couldn't trace with concurrency={tried}, the module isn't installed."
Expand All @@ -169,7 +154,7 @@ def __init__(
),
)

if do_threading or not concurrencies:
if do_threading or not concurrency:
# It's important to import threading only if we need it. If
# it's imported early, and the program being measured uses
# gevent, then gevent's monkey-patching won't work properly.
Expand Down
14 changes: 14 additions & 0 deletions coverage/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,9 @@ def copy(self) -> CoverageConfig:
"multiprocessing",
}

# Mutually exclusive concurrency settings.
LIGHT_THREADS = {"greenlet", "eventlet", "gevent"}

CONFIG_FILE_OPTIONS = [
# These are *args for _set_attr_from_config_option:
# (attr, where, type_="")
Expand Down Expand Up @@ -563,6 +566,17 @@ def post_process(self) -> None:
if "subprocess" in self.patch:
self.parallel = True

# We can handle a few concurrency options here, but only one at a time.
concurrencies = set(self.concurrency)
unknown = concurrencies - self.CONCURRENCY_CHOICES
if unknown:
show = ", ".join(sorted(unknown))
raise ConfigError(f"Unknown concurrency choices: {show}")
light_threads = concurrencies & self.LIGHT_THREADS
if len(light_threads) > 1:
show = ", ".join(sorted(light_threads))
raise ConfigError(f"Conflicting concurrency settings: {show}")

def debug_info(self) -> list[tuple[str, Any]]:
"""Make a list of (name, value) pairs for writing debug info."""
return human_sorted_items((k, v) for k, v in self.__dict__.items() if not k.startswith("_"))
Expand Down
2 changes: 1 addition & 1 deletion coverage/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,7 @@ def load(self) -> None:
def _init_for_start(self) -> None:
"""Initialization for start()"""
# Construct the collector.
concurrency: list[str] = self.config.concurrency or []
concurrency: list[str] = self.config.concurrency
if "multiprocessing" in concurrency:
if self.config.config_file is None:
raise ConfigError("multiprocessing requires a configuration file")
Expand Down
68 changes: 37 additions & 31 deletions igor.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,52 +52,50 @@ def do_show_env():
print(f" {env} = {os.environ[env]!r}")


def do_remove_extension(*args):
def remove_extension(core):
"""Remove the compiled C extension, no matter what its name."""

if core == "ctrace":
return

so_patterns = """
tracer.so
tracer.*.so
tracer.pyd
tracer.*.pyd
""".split()

if "--from-install" in args:
# Get the install location using a subprocess to avoid
# locking the file we are about to delete
root = os.path.dirname(
subprocess.check_output(
[
sys.executable,
"-Xutf8",
"-c",
"import coverage; print(coverage.__file__)",
],
encoding="utf-8",
).strip(),
)
roots = [root]
else:
roots = [
"coverage",
"build/*/coverage",
".tox/*/[Ll]ib/*/site-packages/coverage",
".tox/*/[Ll]ib/site-packages/coverage",
]
roots = [
"coverage",
"build/*/coverage",
".tox/*/[Ll]ib/*/site-packages/coverage",
".tox/*/[Ll]ib/site-packages/coverage",
]

# On windows at least, we can't delete a loaded .pyd file. So move them
# out of the way into the tmp/ directory.
os.makedirs("tmp", exist_ok=True)
for root, pattern in itertools.product(roots, so_patterns):
pattern = os.path.join(root, pattern)
if VERBOSITY > 1:
print(f"Searching for {pattern} from {os.getcwd()}")
for filename in glob.glob(pattern):
if os.path.exists(filename):
hidden = f"tmp/{os.path.basename(filename)}"
if VERBOSITY > 1:
print(f"Removing {os.path.abspath(filename)}")
print(f"Moving {filename} to {hidden}")
try:
os.remove(filename)
if os.path.exists(hidden):
os.remove(hidden)
except OSError as exc:
if VERBOSITY > 1:
print(f"Couldn't remove {os.path.abspath(filename)}: {exc}")
print(f"Couldn't remove {hidden}: {exc}")
else:
try:
os.rename(filename, hidden)
except OSError as exc:
if VERBOSITY > 1:
print(f"Couldn't rename: {exc}")


def label_for_core(core):
Expand All @@ -112,14 +110,17 @@ def label_for_core(core):
raise ValueError(f"Bad core: {core!r}")


def should_skip(core):
def should_skip(core, metacov):
"""Is there a reason to skip these tests?

Return empty string to run tests, or a message about why we are skipping
the tests.
"""
skipper = ""

if metacov and core == "sysmon" and ((3, 12) <= sys.version_info < (3, 14)):
skipper = "sysmon can't measure branches in Python 3.12-3.13"

# $set_env.py: COVERAGE_TEST_CORES - List of cores to run: ctrace, pytrace, sysmon
test_cores = os.getenv("COVERAGE_TEST_CORES")
if test_cores:
Expand All @@ -140,7 +141,8 @@ def should_skip(core):
skipper = f"No C core for {platform.python_implementation()}"

if skipper:
return f"Skipping tests {label_for_core(core)}: {skipper}"
what = "metacov" if metacov else "tests"
return f"Skipping {what} {label_for_core(core)}: {skipper}"
else:
return ""

Expand All @@ -157,10 +159,10 @@ def make_env_id(core):

def run_tests(core, *runner_args):
"""The actual running of tests."""
remove_extension(core)
if "COVERAGE_TESTING" not in os.environ:
os.environ["COVERAGE_TESTING"] = "True"
print_banner(label_for_core(core))

return pytest.main(list(runner_args))


Expand Down Expand Up @@ -209,6 +211,8 @@ def run_tests_with_coverage(core, *runner_args):
if getattr(mod, "__file__", "??").startswith(covdir):
covmods[name] = mod
del sys.modules[name]
remove_extension(core)

import coverage # pylint: disable=reimported

sys.modules.update(covmods)
Expand Down Expand Up @@ -246,15 +250,17 @@ def do_combine_html():

def do_test_with_core(core, *runner_args):
"""Run tests with a particular core."""
metacov = os.getenv("COVERAGE_COVERAGE", "no") == "yes"

# If we should skip these tests, skip them.
skip_msg = should_skip(core)
skip_msg = should_skip(core, metacov)
if skip_msg:
if VERBOSITY > 0:
print(skip_msg)
return None

os.environ["COVERAGE_CORE"] = core
if os.getenv("COVERAGE_COVERAGE", "no") == "yes":
if metacov:
return run_tests_with_coverage(core, *runner_args)
else:
return run_tests(core, *runner_args)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -1372,7 +1372,7 @@ def test_version(self) -> None:
self.command_line("--version")
out = self.stdout()
assert "ersion " in out
if testenv.C_TRACER or testenv.SYS_MON:
if testenv.C_TRACER:
assert "with C extension" in out
else:
assert "without C extension" in out
Expand Down
12 changes: 12 additions & 0 deletions tests/test_concurrency.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,10 +251,16 @@ def try_some_code(
lines = line_count(code)
assert line_counts(data)["try_it.py"] == lines

@pytest.mark.skipif(
not testenv.CAN_MEASURE_THREADS, reason="Can't measure threads with this core."
)
def test_threads(self) -> None:
code = (THREAD + SUM_RANGE_Q + PRINT_SUM_RANGE).format(QLIMIT=self.QLIMIT)
self.try_some_code(code, "thread", threading)

@pytest.mark.skipif(
not testenv.CAN_MEASURE_THREADS, reason="Can't measure threads with this core."
)
def test_threads_simple_code(self) -> None:
code = SIMPLE.format(QLIMIT=self.QLIMIT)
self.try_some_code(code, "thread", threading)
Expand Down Expand Up @@ -318,6 +324,9 @@ def do():
self.try_some_code(BUG_330, "eventlet", eventlet, "0\n")

# Sometimes a test fails due to inherent randomness. Try more times.
@pytest.mark.skipif(
not testenv.CAN_MEASURE_THREADS, reason="Can't measure threads with this core."
)
@pytest.mark.flaky(max_runs=3)
def test_threads_with_gevent(self) -> None:
self.make_file(
Expand Down Expand Up @@ -554,6 +563,9 @@ def test_multiprocessing_and_gevent(self, start_method: str) -> None:
start_method=start_method,
)

@pytest.mark.skipif(
not testenv.CAN_MEASURE_BRANCHES, reason="Can't measure branches with this core"
)
def test_multiprocessing_with_branching(self, start_method: str) -> None:
nprocs = 3
upto = 30
Expand Down
4 changes: 2 additions & 2 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def test_toml_config_file(self) -> None:
[tool.somethingelse]
authors = ["Joe D'Ávila <[email protected]>"]
[tool.coverage.run]
concurrency = ["a", "b"]
concurrency = ["thread", "eventlet"]
timid = true
data_file = ".hello_kitty.data"
plugins = ["plugins.a_plugin"]
Expand All @@ -99,7 +99,7 @@ def test_toml_config_file(self) -> None:
cov = coverage.Coverage()
assert cov.config.timid
assert not cov.config.branch
assert cov.config.concurrency == ["a", "b"]
assert cov.config.concurrency == ["thread", "eventlet"]
assert cov.config.data_file == ".hello_kitty.data"
assert cov.config.plugins == ["plugins.a_plugin"]
assert cov.config.precision == 3
Expand Down
2 changes: 1 addition & 1 deletion tests/test_debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ def test_debug_sys(self) -> None:
def test_debug_sys_ctracer(self) -> None:
out_text = self.f1_debug_output(["sys"])
tracer_line = re_line(r"CTracer:", out_text).strip()
if testenv.C_TRACER or testenv.SYS_MON:
if testenv.C_TRACER:
assert tracer_line.startswith("CTracer: available from ")
else:
assert tracer_line == "CTracer: unavailable"
Expand Down
3 changes: 3 additions & 0 deletions tests/test_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -1516,6 +1516,9 @@ def test_subprocess_in_directories(self) -> None:
assert line_counts(data)["main.py"] == 6
assert line_counts(data)["subproc.py"] == 2

@pytest.mark.skipif(
not testenv.CAN_MEASURE_BRANCHES, reason="Can't measure branches with this core"
)
def test_subprocess_gets_nonfile_config(self) -> None:
# https://github.com/nedbat/coveragepy/issues/2021
self.make_file(
Expand Down
Loading
Loading