Coverage for coverage / plugin_support.py: 53.191%
174 statements
« prev ^ index » next coverage.py v7.12.1a0.dev1, created at 2025-11-30 17:57 +0000
« prev ^ index » next coverage.py v7.12.1a0.dev1, created at 2025-11-30 17:57 +0000
1# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2# For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
4"""Support for plugins."""
6from __future__ import annotations 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
8import os 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
9import os.path 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
10import sys 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
11from collections.abc import Iterable, Iterator 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
12from types import FrameType 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
13from typing import Any, Callable 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
15from coverage.exceptions import PluginError 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
16from coverage.misc import isolate_module 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
17from coverage.plugin import CoveragePlugin, FileReporter, FileTracer 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
18from coverage.types import TArc, TConfigurable, TDebugCtl, TLineNo, TPluginConfig, TSourceTokenLines 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
20os = isolate_module(os) 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
23class Plugins: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
24 """The currently loaded collection of coverage.py plugins."""
26 def __init__(self, debug: TDebugCtl | None = None) -> None: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
27 self.order: list[CoveragePlugin] = [] 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
28 self.names: dict[str, CoveragePlugin] = {} 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
29 self.file_tracers: list[CoveragePlugin] = [] 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
30 self.configurers: list[CoveragePlugin] = [] 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
31 self.context_switchers: list[CoveragePlugin] = [] 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
33 self.current_module: str | None = None 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
34 self.debug = debug 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
36 def load_from_config( 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
37 self,
38 modules: Iterable[str],
39 config: TPluginConfig,
40 ) -> None:
41 """Load plugin modules, and read their settings from configuration."""
43 for module in modules: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
44 self.current_module = module 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
45 __import__(module) 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
46 mod = sys.modules[module] 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
48 coverage_init = getattr(mod, "coverage_init", None) 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
49 if not coverage_init: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
50 raise PluginError( 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
51 f"Plugin module {module!r} didn't define a coverage_init function",
52 )
54 options = config.get_plugin_options(module) 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
55 coverage_init(self, options) 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
57 self.current_module = None 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
59 def load_from_callables( 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
60 self,
61 plugin_inits: Iterable[TCoverageInit],
62 ) -> None:
63 """Load plugins from callables provided."""
64 for fn in plugin_inits: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
65 fn(self) 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
67 def add_file_tracer(self, plugin: CoveragePlugin) -> None: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
68 """Add a file tracer plugin.
70 `plugin` is an instance of a third-party plugin class. It must
71 implement the :meth:`CoveragePlugin.file_tracer` method.
73 """
74 self._add_plugin(plugin, self.file_tracers) 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
76 def add_configurer(self, plugin: CoveragePlugin) -> None: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
77 """Add a configuring plugin.
79 `plugin` is an instance of a third-party plugin class. It must
80 implement the :meth:`CoveragePlugin.configure` method.
82 """
83 self._add_plugin(plugin, self.configurers) 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
85 def add_dynamic_context(self, plugin: CoveragePlugin) -> None: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
86 """Add a dynamic context plugin.
88 `plugin` is an instance of a third-party plugin class. It must
89 implement the :meth:`CoveragePlugin.dynamic_context` method.
91 """
92 self._add_plugin(plugin, self.context_switchers) 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
94 def add_noop(self, plugin: CoveragePlugin) -> None: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
95 """Add a plugin that does nothing.
97 This is only useful for testing the plugin support.
99 """
100 self._add_plugin(plugin, None) 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
102 def _add_plugin( 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
103 self,
104 plugin: CoveragePlugin,
105 specialized: list[CoveragePlugin] | None,
106 ) -> None:
107 """Add a plugin object.
109 `plugin` is a :class:`CoveragePlugin` instance to add. `specialized`
110 is a list to append the plugin to.
112 """
113 plugin_name = f"{self.current_module}.{plugin.__class__.__name__}" 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
114 if self.debug and self.debug.should("plugin"): 114 ↛ 115line 114 didn't jump to line 115 because the condition on line 114 was never true1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
115 self.debug.write(f"Loaded plugin {self.current_module!r}: {plugin!r}")
116 labelled = LabelledDebug(f"plugin {self.current_module!r}", self.debug)
117 plugin = DebugPluginWrapper(plugin, labelled)
119 plugin._coverage_plugin_name = plugin_name 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
120 plugin._coverage_enabled = True 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
121 self.order.append(plugin) 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
122 self.names[plugin_name] = plugin 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
123 if specialized is not None: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
124 specialized.append(plugin) 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
126 def __bool__(self) -> bool: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
127 return bool(self.order) 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
129 def __iter__(self) -> Iterator[CoveragePlugin]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
130 return iter(self.order) 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
132 def get(self, plugin_name: str) -> CoveragePlugin: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
133 """Return a plugin by name."""
134 return self.names[plugin_name] 1abcdefghijklmnopqrstuvwxyzA
137TCoverageInit = Callable[[Plugins], None] 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
140class LabelledDebug: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
141 """A Debug writer, but with labels for prepending to the messages."""
143 def __init__(self, label: str, debug: TDebugCtl, prev_labels: Iterable[str] = ()): 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
144 self.labels = list(prev_labels) + [label]
145 self.debug = debug
147 def add_label(self, label: str) -> LabelledDebug: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
148 """Add a label to the writer, and return a new `LabelledDebug`."""
149 return LabelledDebug(label, self.debug, self.labels)
151 def message_prefix(self) -> str: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
152 """The prefix to use on messages, combining the labels."""
153 prefixes = self.labels + [""]
154 return ":\n".join(" " * i + label for i, label in enumerate(prefixes))
156 def write(self, message: str) -> None: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
157 """Write `message`, but with the labels prepended."""
158 self.debug.write(f"{self.message_prefix()}{message}")
161class DebugPluginWrapper(CoveragePlugin): 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
162 """Wrap a plugin, and use debug to report on what it's doing."""
164 def __init__(self, plugin: CoveragePlugin, debug: LabelledDebug) -> None: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
165 super().__init__()
166 self.plugin = plugin
167 self.debug = debug
169 def file_tracer(self, filename: str) -> FileTracer | None: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
170 tracer = self.plugin.file_tracer(filename)
171 self.debug.write(f"file_tracer({filename!r}) --> {tracer!r}")
172 if tracer:
173 debug = self.debug.add_label(f"file {filename!r}")
174 tracer = DebugFileTracerWrapper(tracer, debug)
175 return tracer
177 def file_reporter(self, filename: str) -> FileReporter | str: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
178 reporter = self.plugin.file_reporter(filename)
179 assert isinstance(reporter, FileReporter)
180 self.debug.write(f"file_reporter({filename!r}) --> {reporter!r}")
181 if reporter:
182 debug = self.debug.add_label(f"file {filename!r}")
183 reporter = DebugFileReporterWrapper(filename, reporter, debug)
184 return reporter
186 def dynamic_context(self, frame: FrameType) -> str | None: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
187 context = self.plugin.dynamic_context(frame)
188 self.debug.write(f"dynamic_context({frame!r}) --> {context!r}")
189 return context
191 def find_executable_files(self, src_dir: str) -> Iterable[str]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
192 executable_files = self.plugin.find_executable_files(src_dir)
193 self.debug.write(f"find_executable_files({src_dir!r}) --> {executable_files!r}")
194 return executable_files
196 def configure(self, config: TConfigurable) -> None: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
197 self.debug.write(f"configure({config!r})")
198 self.plugin.configure(config)
200 def sys_info(self) -> Iterable[tuple[str, Any]]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
201 return self.plugin.sys_info()
204class DebugFileTracerWrapper(FileTracer): 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
205 """A debugging `FileTracer`."""
207 def __init__(self, tracer: FileTracer, debug: LabelledDebug) -> None: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
208 self.tracer = tracer
209 self.debug = debug
211 def _show_frame(self, frame: FrameType) -> str: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
212 """A short string identifying a frame, for debug messages."""
213 filename = os.path.basename(frame.f_code.co_filename)
214 return f"{filename}@{frame.f_lineno}"
216 def source_filename(self) -> str: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
217 sfilename = self.tracer.source_filename()
218 self.debug.write(f"source_filename() --> {sfilename!r}")
219 return sfilename
221 def has_dynamic_source_filename(self) -> bool: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
222 has = self.tracer.has_dynamic_source_filename()
223 self.debug.write(f"has_dynamic_source_filename() --> {has!r}")
224 return has
226 def dynamic_source_filename(self, filename: str, frame: FrameType) -> str | None: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
227 dyn = self.tracer.dynamic_source_filename(filename, frame)
228 self.debug.write(
229 "dynamic_source_filename({!r}, {}) --> {!r}".format(
230 filename,
231 self._show_frame(frame),
232 dyn,
233 )
234 )
235 return dyn
237 def line_number_range(self, frame: FrameType) -> tuple[TLineNo, TLineNo]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
238 pair = self.tracer.line_number_range(frame)
239 self.debug.write(f"line_number_range({self._show_frame(frame)}) --> {pair!r}")
240 return pair
243class DebugFileReporterWrapper(FileReporter): 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
244 """A debugging `FileReporter`."""
246 def __init__(self, filename: str, reporter: FileReporter, debug: LabelledDebug) -> None: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
247 super().__init__(filename)
248 self.reporter = reporter
249 self.debug = debug
251 def relative_filename(self) -> str: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
252 ret = self.reporter.relative_filename()
253 self.debug.write(f"relative_filename() --> {ret!r}")
254 return ret
256 def lines(self) -> set[TLineNo]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
257 ret = self.reporter.lines()
258 self.debug.write(f"lines() --> {ret!r}")
259 return ret
261 def excluded_lines(self) -> set[TLineNo]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
262 ret = self.reporter.excluded_lines()
263 self.debug.write(f"excluded_lines() --> {ret!r}")
264 return ret
266 def translate_lines(self, lines: Iterable[TLineNo]) -> set[TLineNo]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
267 ret = self.reporter.translate_lines(lines)
268 self.debug.write(f"translate_lines({lines!r}) --> {ret!r}")
269 return ret
271 def translate_arcs(self, arcs: Iterable[TArc]) -> set[TArc]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
272 ret = self.reporter.translate_arcs(arcs)
273 self.debug.write(f"translate_arcs({arcs!r}) --> {ret!r}")
274 return ret
276 def no_branch_lines(self) -> set[TLineNo]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
277 ret = self.reporter.no_branch_lines()
278 self.debug.write(f"no_branch_lines() --> {ret!r}")
279 return ret
281 def exit_counts(self) -> dict[TLineNo, int]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
282 ret = self.reporter.exit_counts()
283 self.debug.write(f"exit_counts() --> {ret!r}")
284 return ret
286 def arcs(self) -> set[TArc]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
287 ret = self.reporter.arcs()
288 self.debug.write(f"arcs() --> {ret!r}")
289 return ret
291 def source(self) -> str: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
292 ret = self.reporter.source()
293 self.debug.write(f"source() --> {len(ret)} chars")
294 return ret
296 def source_token_lines(self) -> TSourceTokenLines: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
297 ret = list(self.reporter.source_token_lines())
298 self.debug.write(f"source_token_lines() --> {len(ret)} tokens")
299 return ret