summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2023-02-13 18:29:25 -0500
committerNed Batchelder <ned@nedbatchelder.com>2023-02-14 05:32:26 -0500
commit026d924e6d9449c632b1fec0c1f82f4f867e9724 (patch)
tree0a4c114ab7fceed7e2b67078105140541ea0457d
parentd06ace282bf26c5c7f3becb21db0cdd028ec0d9a (diff)
downloadpython-coveragepy-git-026d924e6d9449c632b1fec0c1f82f4f867e9724.tar.gz
refactor: no placebos, use true Optional
For objects that truly might not exist, use Optional. Some objects will always exist eventually, and for those we have some null implementation standins to use without making new placebo classes.
-rw-r--r--coverage/control.py87
-rw-r--r--coverage/sqldata.py2
-rw-r--r--tests/test_oddball.py1
3 files changed, 40 insertions, 50 deletions
diff --git a/coverage/control.py b/coverage/control.py
index d452b8d8..290da655 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -249,10 +249,10 @@ class Coverage(TConfigurable):
# Other instance attributes, set with placebos or placeholders.
# More useful objects will be created later.
self._debug: DebugControl = NoDebugging()
- self._inorout: InOrOut = _InOrOutPlacebo()
+ self._inorout: Optional[InOrOut] = None
self._plugins: Plugins = Plugins()
- self._data: CoverageData = _CoverageDataPlacebo()
- self._collector: Collector = _CollectorPlacebo()
+ self._data: Optional[CoverageData] = None
+ self._collector: Optional[Collector] = None
self._file_mapper: Callable[[str], str] = abs_file
self._data_suffix = self._run_suffix = None
@@ -378,6 +378,7 @@ class Coverage(TConfigurable):
Calls `_should_trace_internal`, and returns the FileDisposition.
"""
+ assert self._inorout is not None
disp = self._inorout.should_trace(filename, frame)
if self._debug.should('trace'):
self._debug.write(disposition_debug_msg(disp))
@@ -389,6 +390,7 @@ class Coverage(TConfigurable):
Returns a boolean: True if the file should be traced, False if not.
"""
+ assert self._inorout is not None
reason = self._inorout.check_include_omit_etc(filename, frame)
if self._debug.should('trace'):
if not reason:
@@ -484,12 +486,14 @@ class Coverage(TConfigurable):
def load(self) -> None:
"""Load previously-collected coverage data from the data file."""
self._init()
- self._collector.reset()
+ if self._collector is not None:
+ self._collector.reset()
should_skip = self.config.parallel and not os.path.exists(self.config.data_file)
if not should_skip:
self._init_data(suffix=None)
self._post_init()
if not should_skip:
+ assert self._data is not None
self._data.read()
def _init_for_start(self) -> None:
@@ -541,6 +545,7 @@ class Coverage(TConfigurable):
self._init_data(suffix)
+ assert self._data is not None
self._collector.use_data(self._data, self.config.context)
# Early warning if we aren't going to be able to support plugins.
@@ -584,7 +589,7 @@ class Coverage(TConfigurable):
def _init_data(self, suffix: Optional[Union[str, bool]]) -> None:
"""Create a data file if we don't have one yet."""
- if not self._data._real:
+ if self._data is None:
# Create the data file. We do this at construction time so that the
# data file will be written into the directory where the process
# started rather than wherever the process eventually chdir'd to.
@@ -614,6 +619,9 @@ class Coverage(TConfigurable):
self._init_for_start()
self._post_init()
+ assert self._collector is not None
+ assert self._inorout is not None
+
# Issue warnings for possible problems.
self._inorout.warn_conflicting_settings()
@@ -635,6 +643,7 @@ class Coverage(TConfigurable):
if self._instances[-1] is self:
self._instances.pop()
if self._started:
+ assert self._collector is not None
self._collector.stop()
self._started = False
@@ -664,10 +673,12 @@ class Coverage(TConfigurable):
"""
self._init()
self._post_init()
- self._collector.reset()
+ if self._collector is not None:
+ self._collector.reset()
self._init_data(suffix=None)
+ assert self._data is not None
self._data.erase(parallel=self.config.parallel)
- self._data = _CoverageDataPlacebo()
+ self._data = None
self._inited_for_start = False
def switch_context(self, new_context: str) -> None:
@@ -686,6 +697,7 @@ class Coverage(TConfigurable):
if not self._started: # pragma: part started
raise CoverageException("Cannot switch context, coverage is not started")
+ assert self._collector is not None
if self._collector.should_start_context:
self._warn("Conflicting dynamic contexts", slug="dynamic-conflict", once=True)
@@ -791,6 +803,7 @@ class Coverage(TConfigurable):
self._post_init()
self.get_data()
+ assert self._data is not None
combine_parallel_data(
self._data,
aliases=self._make_aliases(),
@@ -814,13 +827,15 @@ class Coverage(TConfigurable):
self._init_data(suffix=None)
self._post_init()
- for plugin in self._plugins:
- if not plugin._coverage_enabled:
- self._collector.plugin_was_disabled(plugin)
+ if self._collector is not None:
+ for plugin in self._plugins:
+ if not plugin._coverage_enabled:
+ self._collector.plugin_was_disabled(plugin)
- if self._collector.flush_data():
- self._post_save_work()
+ if self._collector.flush_data():
+ self._post_save_work()
+ assert self._data is not None
return self._data
def _post_save_work(self) -> None:
@@ -830,6 +845,9 @@ class Coverage(TConfigurable):
Look for un-executed files.
"""
+ assert self._data is not None
+ assert self._inorout is not None
+
# If there are still entries in the source_pkgs_unmatched list,
# then we never encountered those packages.
if self._warn_unimported_source:
@@ -903,6 +921,7 @@ class Coverage(TConfigurable):
def _get_file_reporter(self, morf: TMorf) -> FileReporter:
"""Get a FileReporter for a module or file name."""
+ assert self._data is not None
plugin = None
file_reporter: Union[str, FileReporter] = "python"
@@ -938,6 +957,7 @@ class Coverage(TConfigurable):
measured is used to find the FileReporters.
"""
+ assert self._data is not None
if not morfs:
morfs = self._data.measured_files()
@@ -952,7 +972,8 @@ class Coverage(TConfigurable):
"""Re-map data before reporting, to get implicit 'combine' behavior."""
if self.config.paths:
mapped_data = CoverageData(warn=self._warn, debug=self._debug, no_disk=True)
- mapped_data.update(self._data, aliases=self._make_aliases())
+ if self._data is not None:
+ mapped_data.update(self._data, aliases=self._make_aliases())
self._data = mapped_data
def report(
@@ -1256,7 +1277,7 @@ class Coverage(TConfigurable):
info = [
('coverage_version', covmod.__version__),
('coverage_module', covmod.__file__),
- ('tracer', self._collector.tracer_name()),
+ ('tracer', self._collector.tracer_name() if self._collector is not None else "-none-"),
('CTracer', 'available' if HAS_CTRACER else "unavailable"),
('plugins.file_tracers', plugin_info(self._plugins.file_tracers)),
('plugins.configurers', plugin_info(self._plugins.configurers)),
@@ -1267,7 +1288,7 @@ class Coverage(TConfigurable):
('config_contents',
repr(self.config._config_contents) if self.config._config_contents else '-none-'
),
- ('data_file', self._data.data_filename()),
+ ('data_file', self._data.data_filename() if self._data is not None else "-none-"),
('python', sys.version.replace('\n', '')),
('platform', platform.platform()),
('implementation', platform.python_implementation()),
@@ -1288,44 +1309,14 @@ class Coverage(TConfigurable):
('command_line', " ".join(getattr(sys, 'argv', ['-none-']))),
]
- info.extend(self._inorout.sys_info())
+ if self._inorout is not None:
+ info.extend(self._inorout.sys_info())
+
info.extend(CoverageData.sys_info())
return info
-class _Placebo:
- """Base class for placebos, to prevent calling the real base class __init__."""
- def __init__(self) -> None:
- ...
-
-
-class _CoverageDataPlacebo(_Placebo, CoverageData):
- """Just enough of a CoverageData to be used when we don't have a real one."""
- _real = False
-
- def data_filename(self) -> str:
- return "-none-"
-
-
-class _CollectorPlacebo(_Placebo, Collector):
- """Just enough of a Collector to be used when we don't have a real one."""
- def reset(self) -> None:
- ...
-
- def flush_data(self) -> bool:
- return False
-
- def tracer_name(self) -> str:
- return "-none-"
-
-
-class _InOrOutPlacebo(_Placebo, InOrOut):
- """Just enough of an InOrOut to be used when we don't have a real one."""
- def sys_info(self) -> Iterable[Tuple[str, Any]]:
- return []
-
-
# Mega debugging...
# $set_env.py: COVERAGE_DEBUG_CALLS - Lots and lots of output about calls to Coverage.
if int(os.environ.get("COVERAGE_DEBUG_CALLS", 0)): # pragma: debugging
diff --git a/coverage/sqldata.py b/coverage/sqldata.py
index 15b30ae9..42cf4501 100644
--- a/coverage/sqldata.py
+++ b/coverage/sqldata.py
@@ -212,8 +212,6 @@ class CoverageData(AutoReprMixin):
"""
- _real = True # to distinguish from a placebo in control.py
-
def __init__(
self,
basename: Optional[FilePath] = None,
diff --git a/tests/test_oddball.py b/tests/test_oddball.py
index e4147dc6..2bcb4276 100644
--- a/tests/test_oddball.py
+++ b/tests/test_oddball.py
@@ -124,6 +124,7 @@ class RecursionTest(CoverageTest):
with swallow_warnings("Trace function changed, data is likely wrong: None"):
self.start_import_stop(cov, "recur")
+ assert cov._collector is not None
pytrace = (cov._collector.tracer_name() == "PyTracer")
expected_missing = [3]
if pytrace: # pragma: no metacov