# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 # For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt """Determine facts about the environment.""" from __future__ import annotations import os import platform import sys from collections.abc import Iterable from typing import Any, Final # debug_info() at the bottom wants to show all the globals, but not imports. # Grab the global names here to know which names to not show. Nothing defined # above this line will be in the output. _UNINTERESTING_GLOBALS = list(globals()) # These names also shouldn't be shown. _UNINTERESTING_GLOBALS += ["PYBEHAVIOR", "debug_info"] # Operating systems. WINDOWS = sys.platform == "win32" LINUX = sys.platform.startswith("linux") MACOS = sys.platform == "darwin" # Python implementations. CPYTHON = (platform.python_implementation() == "CPython") # fmt: skip PYPY = (platform.python_implementation() == "PyPy") # fmt: skip # Python versions. We amend version_info with one more value, a zero if an # official version, or 1 if built from source beyond an official version. # Only use sys.version_info directly where tools like mypy need it to understand # version-specfic code, otherwise use PYVERSION. PYVERSION = sys.version_info + (int(platform.python_version()[-1] == "+"),) if PYPY: # Minimum now is 7.3.16 PYPYVERSION = tuple(sys.pypy_version_info) # type: ignore[attr-defined] else: PYPYVERSION = (0,) # Do we have a GIL? GIL = getattr(sys, "_is_gil_enabled", lambda: True)() # Do we ship compiled coveragepy wheels for this version? SHIPPING_WHEELS = CPYTHON and PYVERSION[:2] <= (3, 14) # Should we default to sys.monitoring? SYSMON_DEFAULT = CPYTHON and PYVERSION >= (3, 14) # Python behavior. class PYBEHAVIOR: """Flags indicating this Python's behavior.""" # When leaving a with-block, do we visit the with-line exactly, # or the context managers in inner-out order? # # mwith.py: # with ( # open("/tmp/one", "w") as f2, # open("/tmp/two", "w") as f3, # open("/tmp/three", "w") as f4, # ): # print("hello 6") # # % python3.11 -m trace -t mwith.py | grep mwith # --- modulename: mwith, funcname: # mwith.py(2): open("/tmp/one", "w") as f2, # mwith.py(1): with ( # mwith.py(2): open("/tmp/one", "w") as f2, # mwith.py(3): open("/tmp/two", "w") as f3, # mwith.py(1): with ( # mwith.py(3): open("/tmp/two", "w") as f3, # mwith.py(4): open("/tmp/three", "w") as f4, # mwith.py(1): with ( # mwith.py(4): open("/tmp/three", "w") as f4, # mwith.py(6): print("hello 6") # mwith.py(1): with ( # # % python3.12 -m trace -t mwith.py | grep mwith # --- modulename: mwith, funcname: # mwith.py(2): open("/tmp/one", "w") as f2, # mwith.py(3): open("/tmp/two", "w") as f3, # mwith.py(4): open("/tmp/three", "w") as f4, # mwith.py(6): print("hello 6") # mwith.py(4): open("/tmp/three", "w") as f4, # mwith.py(3): open("/tmp/two", "w") as f3, # mwith.py(2): open("/tmp/one", "w") as f2, exit_with_through_ctxmgr = (PYVERSION >= (3, 12, 6)) # fmt: skip # f-strings are parsed as code, pep 701 fstring_syntax = (PYVERSION >= (3, 12)) # fmt: skip # PEP669 Low Impact Monitoring: https://peps.python.org/pep-0669/ pep669: Final[bool] = bool(getattr(sys, "monitoring", None)) # Where does frame.f_lasti point when yielding from a generator? # It used to point at the YIELD, in 3.13 it points at the RESUME, # then it went back to the YIELD. # https://github.com/python/cpython/issues/113728 lasti_is_yield = (PYVERSION[:2] != (3, 13)) # fmt: skip # PEP649 and PEP749: Deferred annotations deferred_annotations = (PYVERSION >= (3, 14)) # fmt: skip # Does sys.monitoring support BRANCH_RIGHT and BRANCH_LEFT? The names # were added in early 3.14 alphas, but didn't work entirely correctly until # after 3.14.0a5. branch_right_left = pep669 and (PYVERSION > (3, 14, 0, "alpha", 5, 0)) # Coverage.py specifics, about testing scenarios. See tests/testenv.py also. # Are we coverage-measuring ourselves? METACOV = os.getenv("COVERAGE_COVERAGE") is not None # Are we running our test suite? # Even when running tests, you can use COVERAGE_TESTING=0 to disable the # test-specific behavior like AST checking. TESTING = os.getenv("COVERAGE_TESTING") == "True" def debug_info() -> Iterable[tuple[str, Any]]: """Return a list of (name, value) pairs for printing debug information.""" info = [ (name, value) for name, value in globals().items() if not name.startswith("_") and name not in _UNINTERESTING_GLOBALS ] info += [ (name, value) for name, value in PYBEHAVIOR.__dict__.items() if not name.startswith("_") ] return sorted(info)