src/eric7/DebugClients/Python/coverage/plugin_support.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8929
fcca2fa618bf
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2 # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
3
4 """Support for plugins."""
5
6 import os
7 import os.path
8 import sys
9
10 from coverage.exceptions import PluginError
11 from coverage.misc import isolate_module
12 from coverage.plugin import CoveragePlugin, FileTracer, FileReporter
13
14 os = isolate_module(os)
15
16
17 class Plugins:
18 """The currently loaded collection of coverage.py plugins."""
19
20 def __init__(self):
21 self.order = []
22 self.names = {}
23 self.file_tracers = []
24 self.configurers = []
25 self.context_switchers = []
26
27 self.current_module = None
28 self.debug = None
29
30 @classmethod
31 def load_plugins(cls, modules, config, debug=None):
32 """Load plugins from `modules`.
33
34 Returns a Plugins object with the loaded and configured plugins.
35
36 """
37 plugins = cls()
38 plugins.debug = debug
39
40 for module in modules:
41 plugins.current_module = module
42 __import__(module)
43 mod = sys.modules[module]
44
45 coverage_init = getattr(mod, "coverage_init", None)
46 if not coverage_init:
47 raise PluginError(
48 f"Plugin module {module!r} didn't define a coverage_init function"
49 )
50
51 options = config.get_plugin_options(module)
52 coverage_init(plugins, options)
53
54 plugins.current_module = None
55 return plugins
56
57 def add_file_tracer(self, plugin):
58 """Add a file tracer plugin.
59
60 `plugin` is an instance of a third-party plugin class. It must
61 implement the :meth:`CoveragePlugin.file_tracer` method.
62
63 """
64 self._add_plugin(plugin, self.file_tracers)
65
66 def add_configurer(self, plugin):
67 """Add a configuring plugin.
68
69 `plugin` is an instance of a third-party plugin class. It must
70 implement the :meth:`CoveragePlugin.configure` method.
71
72 """
73 self._add_plugin(plugin, self.configurers)
74
75 def add_dynamic_context(self, plugin):
76 """Add a dynamic context plugin.
77
78 `plugin` is an instance of a third-party plugin class. It must
79 implement the :meth:`CoveragePlugin.dynamic_context` method.
80
81 """
82 self._add_plugin(plugin, self.context_switchers)
83
84 def add_noop(self, plugin):
85 """Add a plugin that does nothing.
86
87 This is only useful for testing the plugin support.
88
89 """
90 self._add_plugin(plugin, None)
91
92 def _add_plugin(self, plugin, specialized):
93 """Add a plugin object.
94
95 `plugin` is a :class:`CoveragePlugin` instance to add. `specialized`
96 is a list to append the plugin to.
97
98 """
99 plugin_name = f"{self.current_module}.{plugin.__class__.__name__}"
100 if self.debug and self.debug.should('plugin'):
101 self.debug.write(f"Loaded plugin {self.current_module!r}: {plugin!r}")
102 labelled = LabelledDebug(f"plugin {self.current_module!r}", self.debug)
103 plugin = DebugPluginWrapper(plugin, labelled)
104
105 # pylint: disable=attribute-defined-outside-init
106 plugin._coverage_plugin_name = plugin_name
107 plugin._coverage_enabled = True
108 self.order.append(plugin)
109 self.names[plugin_name] = plugin
110 if specialized is not None:
111 specialized.append(plugin)
112
113 def __bool__(self):
114 return bool(self.order)
115
116 def __iter__(self):
117 return iter(self.order)
118
119 def get(self, plugin_name):
120 """Return a plugin by name."""
121 return self.names[plugin_name]
122
123
124 class LabelledDebug:
125 """A Debug writer, but with labels for prepending to the messages."""
126
127 def __init__(self, label, debug, prev_labels=()):
128 self.labels = list(prev_labels) + [label]
129 self.debug = debug
130
131 def add_label(self, label):
132 """Add a label to the writer, and return a new `LabelledDebug`."""
133 return LabelledDebug(label, self.debug, self.labels)
134
135 def message_prefix(self):
136 """The prefix to use on messages, combining the labels."""
137 prefixes = self.labels + ['']
138 return ":\n".join(" "*i+label for i, label in enumerate(prefixes))
139
140 def write(self, message):
141 """Write `message`, but with the labels prepended."""
142 self.debug.write(f"{self.message_prefix()}{message}")
143
144
145 class DebugPluginWrapper(CoveragePlugin):
146 """Wrap a plugin, and use debug to report on what it's doing."""
147
148 def __init__(self, plugin, debug):
149 super().__init__()
150 self.plugin = plugin
151 self.debug = debug
152
153 def file_tracer(self, filename):
154 tracer = self.plugin.file_tracer(filename)
155 self.debug.write(f"file_tracer({filename!r}) --> {tracer!r}")
156 if tracer:
157 debug = self.debug.add_label(f"file {filename!r}")
158 tracer = DebugFileTracerWrapper(tracer, debug)
159 return tracer
160
161 def file_reporter(self, filename):
162 reporter = self.plugin.file_reporter(filename)
163 self.debug.write(f"file_reporter({filename!r}) --> {reporter!r}")
164 if reporter:
165 debug = self.debug.add_label(f"file {filename!r}")
166 reporter = DebugFileReporterWrapper(filename, reporter, debug)
167 return reporter
168
169 def dynamic_context(self, frame):
170 context = self.plugin.dynamic_context(frame)
171 self.debug.write(f"dynamic_context({frame!r}) --> {context!r}")
172 return context
173
174 def find_executable_files(self, src_dir):
175 executable_files = self.plugin.find_executable_files(src_dir)
176 self.debug.write(f"find_executable_files({src_dir!r}) --> {executable_files!r}")
177 return executable_files
178
179 def configure(self, config):
180 self.debug.write(f"configure({config!r})")
181 self.plugin.configure(config)
182
183 def sys_info(self):
184 return self.plugin.sys_info()
185
186
187 class DebugFileTracerWrapper(FileTracer):
188 """A debugging `FileTracer`."""
189
190 def __init__(self, tracer, debug):
191 self.tracer = tracer
192 self.debug = debug
193
194 def _show_frame(self, frame):
195 """A short string identifying a frame, for debug messages."""
196 return "%s@%d" % (
197 os.path.basename(frame.f_code.co_filename),
198 frame.f_lineno,
199 )
200
201 def source_filename(self):
202 sfilename = self.tracer.source_filename()
203 self.debug.write(f"source_filename() --> {sfilename!r}")
204 return sfilename
205
206 def has_dynamic_source_filename(self):
207 has = self.tracer.has_dynamic_source_filename()
208 self.debug.write(f"has_dynamic_source_filename() --> {has!r}")
209 return has
210
211 def dynamic_source_filename(self, filename, frame):
212 dyn = self.tracer.dynamic_source_filename(filename, frame)
213 self.debug.write("dynamic_source_filename({!r}, {}) --> {!r}".format(
214 filename, self._show_frame(frame), dyn,
215 ))
216 return dyn
217
218 def line_number_range(self, frame):
219 pair = self.tracer.line_number_range(frame)
220 self.debug.write(f"line_number_range({self._show_frame(frame)}) --> {pair!r}")
221 return pair
222
223
224 class DebugFileReporterWrapper(FileReporter):
225 """A debugging `FileReporter`."""
226
227 def __init__(self, filename, reporter, debug):
228 super().__init__(filename)
229 self.reporter = reporter
230 self.debug = debug
231
232 def relative_filename(self):
233 ret = self.reporter.relative_filename()
234 self.debug.write(f"relative_filename() --> {ret!r}")
235 return ret
236
237 def lines(self):
238 ret = self.reporter.lines()
239 self.debug.write(f"lines() --> {ret!r}")
240 return ret
241
242 def excluded_lines(self):
243 ret = self.reporter.excluded_lines()
244 self.debug.write(f"excluded_lines() --> {ret!r}")
245 return ret
246
247 def translate_lines(self, lines):
248 ret = self.reporter.translate_lines(lines)
249 self.debug.write(f"translate_lines({lines!r}) --> {ret!r}")
250 return ret
251
252 def translate_arcs(self, arcs):
253 ret = self.reporter.translate_arcs(arcs)
254 self.debug.write(f"translate_arcs({arcs!r}) --> {ret!r}")
255 return ret
256
257 def no_branch_lines(self):
258 ret = self.reporter.no_branch_lines()
259 self.debug.write(f"no_branch_lines() --> {ret!r}")
260 return ret
261
262 def exit_counts(self):
263 ret = self.reporter.exit_counts()
264 self.debug.write(f"exit_counts() --> {ret!r}")
265 return ret
266
267 def arcs(self):
268 ret = self.reporter.arcs()
269 self.debug.write(f"arcs() --> {ret!r}")
270 return ret
271
272 def source(self):
273 ret = self.reporter.source()
274 self.debug.write("source() --> %d chars" % (len(ret),))
275 return ret
276
277 def source_token_lines(self):
278 ret = list(self.reporter.source_token_lines())
279 self.debug.write("source_token_lines() --> %d tokens" % (len(ret),))
280 return ret

eric ide

mercurial