| # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 |
| # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt |
| |
| """Management of core choices.""" |
| |
| from __future__ import annotations |
| |
| import os |
| import sys |
| from typing import Any |
| |
| from coverage import env |
| from coverage.config import CoverageConfig |
| from coverage.disposition import FileDisposition |
| from coverage.exceptions import ConfigError |
| from coverage.misc import isolate_module |
| from coverage.pytracer import PyTracer |
| from coverage.sysmon import SysMonitor |
| from coverage.types import TDebugCtl, TFileDisposition, Tracer, TWarnFn |
| |
| os = isolate_module(os) |
| |
| IMPORT_ERROR: str = "" |
| |
| try: |
| # Use the C extension code when we can, for speed. |
| import coverage.tracer |
| |
| CTRACER_FILE: str | None = getattr(coverage.tracer, "__file__", "unknown") |
| except ImportError as imp_err: |
| # Couldn't import the C extension, maybe it isn't built. |
| # We still need to check the environment variable directly here, |
| # as this code runs before configuration is loaded. |
| if os.getenv("COVERAGE_CORE") == "ctrace": # pragma: part covered |
| # During testing, we use the COVERAGE_CORE environment variable |
| # to indicate that we've fiddled with the environment to test this |
| # fallback code. If we thought we had a C tracer, but couldn't import |
| # it, then exit quickly and clearly instead of dribbling confusing |
| # errors. I'm using sys.exit here instead of an exception because an |
| # exception here causes all sorts of other noise in unittest. |
| sys.stderr.write("*** COVERAGE_CORE is 'ctrace' but can't import CTracer!\n") |
| sys.exit(1) |
| IMPORT_ERROR = str(imp_err) |
| CTRACER_FILE = None |
| |
| |
| class Core: |
| """Information about the central technology enabling execution measurement.""" |
| |
| tracer_class: type[Tracer] |
| tracer_kwargs: dict[str, Any] |
| file_disposition_class: type[TFileDisposition] |
| supports_plugins: bool |
| packed_arcs: bool |
| systrace: bool |
| |
| def __init__( |
| self, |
| *, |
| warn: TWarnFn, |
| debug: TDebugCtl | None, |
| config: CoverageConfig, |
| dynamic_contexts: bool, |
| metacov: bool, |
| ) -> None: |
| def _debug(msg: str) -> None: |
| if debug: |
| debug.write(msg) |
| |
| _debug("in core.py") |
| |
| # Check the conditions that preclude us from using sys.monitoring. |
| reason_no_sysmon = "" |
| if not env.PYBEHAVIOR.pep669: |
| reason_no_sysmon = "isn't available in this version" |
| elif config.branch and not env.PYBEHAVIOR.branch_right_left: |
| reason_no_sysmon = "can't measure branches in this version" |
| elif dynamic_contexts: |
| reason_no_sysmon = "doesn't yet support dynamic contexts" |
| elif any((bad := c) in config.concurrency for c in ["greenlet", "eventlet", "gevent"]): |
| reason_no_sysmon = f"doesn't support concurrency={bad}" |
| |
| core_name: str | None = None |
| if config.timid: |
| core_name = "pytrace" |
| _debug("core.py: Using pytrace because timid=True") |
| elif core_name is None: |
| # This could still leave core_name as None. |
| core_name = config.core |
| _debug(f"core.py: core from config is {core_name!r}") |
| |
| if core_name == "sysmon" and reason_no_sysmon: |
| _debug(f"core.py: raising ConfigError because sysmon not usable: {reason_no_sysmon}") |
| raise ConfigError( |
| f"Can't use core=sysmon: sys.monitoring {reason_no_sysmon}", skip_tests=True |
| ) |
| |
| if core_name is None: |
| if env.SYSMON_DEFAULT and not reason_no_sysmon: |
| core_name = "sysmon" |
| _debug("core.py: Using sysmon because SYSMON_DEFAULT is set") |
| else: |
| core_name = "ctrace" |
| _debug("core.py: Defaulting to ctrace core") |
| |
| if core_name == "ctrace": |
| if not CTRACER_FILE: |
| if IMPORT_ERROR and env.SHIPPING_WHEELS: |
| warn(f"Couldn't import C tracer: {IMPORT_ERROR}", slug="no-ctracer", once=True) |
| core_name = "pytrace" |
| _debug("core.py: Falling back to pytrace because C tracer not available") |
| |
| _debug(f"core.py: Using core={core_name}") |
| |
| self.tracer_kwargs = {} |
| |
| if core_name == "sysmon": |
| self.tracer_class = SysMonitor |
| self.tracer_kwargs["tool_id"] = 3 if metacov else 1 |
| self.file_disposition_class = FileDisposition |
| self.supports_plugins = False |
| self.packed_arcs = False |
| self.systrace = False |
| elif core_name == "ctrace": |
| self.tracer_class = coverage.tracer.CTracer |
| self.file_disposition_class = coverage.tracer.CFileDisposition |
| self.supports_plugins = True |
| self.packed_arcs = True |
| self.systrace = True |
| elif core_name == "pytrace": |
| self.tracer_class = PyTracer |
| self.file_disposition_class = FileDisposition |
| self.supports_plugins = False |
| self.packed_arcs = False |
| self.systrace = True |
| else: |
| raise ConfigError(f"Unknown core value: {core_name!r}") |