summaryrefslogtreecommitdiff
path: root/coverage/debug.py
diff options
context:
space:
mode:
Diffstat (limited to 'coverage/debug.py')
-rw-r--r--coverage/debug.py70
1 files changed, 48 insertions, 22 deletions
diff --git a/coverage/debug.py b/coverage/debug.py
index 29dd1e7f..d1f27cd8 100644
--- a/coverage/debug.py
+++ b/coverage/debug.py
@@ -39,7 +39,12 @@ class DebugControl:
show_repr_attr = False # For AutoReprMixin
- def __init__(self, options: Iterable[str], output: Optional[IO[str]]) -> None:
+ def __init__(
+ self,
+ options: Iterable[str],
+ output: Optional[IO[str]],
+ file_name: Optional[str] = None,
+ ) -> None:
"""Configure the options and output file for debugging."""
self.options = list(options) + FORCED_DEBUG
self.suppress_callers = False
@@ -49,6 +54,7 @@ class DebugControl:
filters.append(add_pid_and_tid)
self.output = DebugOutputFile.get_one(
output,
+ file_name=file_name,
show_process=self.should('process'),
filters=filters,
)
@@ -306,13 +312,11 @@ class DebugOutputFile: # pragma: debugging
if hasattr(os, 'getppid'):
self.write(f"New process: pid: {os.getpid()!r}, parent pid: {os.getppid()!r}\n")
- SYS_MOD_NAME = '$coverage.debug.DebugOutputFile.the_one'
- SINGLETON_ATTR = 'the_one_and_is_interim'
-
@classmethod
def get_one(
cls,
fileobj: Optional[IO[str]] = None,
+ file_name: Optional[str] = None,
show_process: bool = True,
filters: Iterable[Callable[[str], str]] = (),
interim: bool = False,
@@ -321,9 +325,9 @@ class DebugOutputFile: # pragma: debugging
If `fileobj` is provided, then a new DebugOutputFile is made with it.
- If `fileobj` isn't provided, then a file is chosen
- (COVERAGE_DEBUG_FILE, or stderr), and a process-wide singleton
- DebugOutputFile is made.
+ If `fileobj` isn't provided, then a file is chosen (`file_name` if
+ provided, or COVERAGE_DEBUG_FILE, or stderr), and a process-wide
+ singleton DebugOutputFile is made.
`show_process` controls whether the debug file adds process-level
information, and filters is a list of other message filters to apply.
@@ -338,27 +342,49 @@ class DebugOutputFile: # pragma: debugging
# Make DebugOutputFile around the fileobj passed.
return cls(fileobj, show_process, filters)
- # Because of the way igor.py deletes and re-imports modules,
- # this class can be defined more than once. But we really want
- # a process-wide singleton. So stash it in sys.modules instead of
- # on a class attribute. Yes, this is aggressively gross.
- singleton_module = sys.modules.get(cls.SYS_MOD_NAME)
- the_one, is_interim = getattr(singleton_module, cls.SINGLETON_ATTR, (None, True))
+ the_one, is_interim = cls._get_singleton_data()
if the_one is None or is_interim:
- if fileobj is None:
- debug_file_name = os.environ.get("COVERAGE_DEBUG_FILE", FORCED_DEBUG_FILE)
- if debug_file_name in ("stdout", "stderr"):
- fileobj = getattr(sys, debug_file_name)
- elif debug_file_name:
- fileobj = open(debug_file_name, "a")
+ if file_name is not None:
+ fileobj = open(file_name, "a", encoding="utf-8")
+ else:
+ file_name = os.environ.get("COVERAGE_DEBUG_FILE", FORCED_DEBUG_FILE)
+ if file_name in ("stdout", "stderr"):
+ fileobj = getattr(sys, file_name)
+ elif file_name:
+ fileobj = open(file_name, "a", encoding="utf-8")
else:
fileobj = sys.stderr
the_one = cls(fileobj, show_process, filters)
- singleton_module = types.ModuleType(cls.SYS_MOD_NAME)
- setattr(singleton_module, cls.SINGLETON_ATTR, (the_one, interim))
- sys.modules[cls.SYS_MOD_NAME] = singleton_module
+ cls._set_singleton_data(the_one, interim)
return the_one
+ # Because of the way igor.py deletes and re-imports modules,
+ # this class can be defined more than once. But we really want
+ # a process-wide singleton. So stash it in sys.modules instead of
+ # on a class attribute. Yes, this is aggressively gross.
+
+ SYS_MOD_NAME = '$coverage.debug.DebugOutputFile.the_one'
+ SINGLETON_ATTR = 'the_one_and_is_interim'
+
+ @classmethod
+ def _set_singleton_data(cls, the_one: DebugOutputFile, interim: bool) -> None:
+ """Set the one DebugOutputFile to rule them all."""
+ singleton_module = types.ModuleType(cls.SYS_MOD_NAME)
+ setattr(singleton_module, cls.SINGLETON_ATTR, (the_one, interim))
+ sys.modules[cls.SYS_MOD_NAME] = singleton_module
+
+ @classmethod
+ def _get_singleton_data(cls) -> Tuple[Optional[DebugOutputFile], bool]:
+ """Get the one DebugOutputFile."""
+ singleton_module = sys.modules.get(cls.SYS_MOD_NAME)
+ return getattr(singleton_module, cls.SINGLETON_ATTR, (None, True))
+
+ @classmethod
+ def _del_singleton_data(cls) -> None:
+ """Delete the one DebugOutputFile, just for tests to use."""
+ if cls.SYS_MOD_NAME in sys.modules:
+ del sys.modules[cls.SYS_MOD_NAME]
+
def write(self, text: str) -> None:
"""Just like file.write, but filter through all our filters."""
assert self.outfile is not None