1# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 2# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt 3 4"""Plugin interfaces for coverage.py""" 5 6from coverage import files 7from coverage.misc import contract, _needs_to_implement 8 9 10class CoveragePlugin(object): 11 """Base class for coverage.py plugins. 12 13 To write a coverage.py plugin, create a module with a subclass of 14 :class:`CoveragePlugin`. You will override methods in your class to 15 participate in various aspects of coverage.py's processing. 16 17 Currently the only plugin type is a file tracer, for implementing 18 measurement support for non-Python files. File tracer plugins implement 19 the :meth:`file_tracer` method to claim files and the :meth:`file_reporter` 20 method to report on those files. 21 22 Any plugin can optionally implement :meth:`sys_info` to provide debugging 23 information about their operation. 24 25 Coverage.py will store its own information on your plugin object, using 26 attributes whose names start with ``_coverage_``. Don't be startled. 27 28 To register your plugin, define a function called `coverage_init` in your 29 module:: 30 31 def coverage_init(reg, options): 32 reg.add_file_tracer(MyPlugin()) 33 34 You use the `reg` parameter passed to your `coverage_init` function to 35 register your plugin object. It has one method, `add_file_tracer`, which 36 takes a newly created instance of your plugin. 37 38 If your plugin takes options, the `options` parameter is a dictionary of 39 your plugin's options from the coverage.py configuration file. Use them 40 however you want to configure your object before registering it. 41 42 """ 43 44 def file_tracer(self, filename): # pylint: disable=unused-argument 45 """Get a :class:`FileTracer` object for a file. 46 47 Every Python source file is offered to the plugin to give it a chance 48 to take responsibility for tracing the file. If your plugin can handle 49 the file, then return a :class:`FileTracer` object. Otherwise return 50 None. 51 52 There is no way to register your plugin for particular files. Instead, 53 this method is invoked for all files, and the plugin decides whether it 54 can trace the file or not. Be prepared for `filename` to refer to all 55 kinds of files that have nothing to do with your plugin. 56 57 The file name will be a Python file being executed. There are two 58 broad categories of behavior for a plugin, depending on the kind of 59 files your plugin supports: 60 61 * Static file names: each of your original source files has been 62 converted into a distinct Python file. Your plugin is invoked with 63 the Python file name, and it maps it back to its original source 64 file. 65 66 * Dynamic file names: all of your source files are executed by the same 67 Python file. In this case, your plugin implements 68 :meth:`FileTracer.dynamic_source_filename` to provide the actual 69 source file for each execution frame. 70 71 `filename` is a string, the path to the file being considered. This is 72 the absolute real path to the file. If you are comparing to other 73 paths, be sure to take this into account. 74 75 Returns a :class:`FileTracer` object to use to trace `filename`, or 76 None if this plugin cannot trace this file. 77 78 """ 79 return None 80 81 def file_reporter(self, filename): # pylint: disable=unused-argument 82 """Get the :class:`FileReporter` class to use for a file. 83 84 This will only be invoked if `filename` returns non-None from 85 :meth:`file_tracer`. It's an error to return None from this method. 86 87 Returns a :class:`FileReporter` object to use to report on `filename`. 88 89 """ 90 _needs_to_implement(self, "file_reporter") 91 92 def sys_info(self): 93 """Get a list of information useful for debugging. 94 95 This method will be invoked for ``--debug=sys``. Your 96 plugin can return any information it wants to be displayed. 97 98 Returns a list of pairs: `[(name, value), ...]`. 99 100 """ 101 return [] 102 103 104class FileTracer(object): 105 """Support needed for files during the execution phase. 106 107 You may construct this object from :meth:`CoveragePlugin.file_tracer` any 108 way you like. A natural choice would be to pass the file name given to 109 `file_tracer`. 110 111 `FileTracer` objects should only be created in the 112 :meth:`CoveragePlugin.file_tracer` method. 113 114 See :ref:`howitworks` for details of the different coverage.py phases. 115 116 """ 117 118 def source_filename(self): 119 """The source file name for this file. 120 121 This may be any file name you like. A key responsibility of a plugin 122 is to own the mapping from Python execution back to whatever source 123 file name was originally the source of the code. 124 125 See :meth:`CoveragePlugin.file_tracer` for details about static and 126 dynamic file names. 127 128 Returns the file name to credit with this execution. 129 130 """ 131 _needs_to_implement(self, "source_filename") 132 133 def has_dynamic_source_filename(self): 134 """Does this FileTracer have dynamic source file names? 135 136 FileTracers can provide dynamically determined file names by 137 implementing :meth:`dynamic_source_filename`. Invoking that function 138 is expensive. To determine whether to invoke it, coverage.py uses the 139 result of this function to know if it needs to bother invoking 140 :meth:`dynamic_source_filename`. 141 142 See :meth:`CoveragePlugin.file_tracer` for details about static and 143 dynamic file names. 144 145 Returns True if :meth:`dynamic_source_filename` should be called to get 146 dynamic source file names. 147 148 """ 149 return False 150 151 def dynamic_source_filename(self, filename, frame): # pylint: disable=unused-argument 152 """Get a dynamically computed source file name. 153 154 Some plugins need to compute the source file name dynamically for each 155 frame. 156 157 This function will not be invoked if 158 :meth:`has_dynamic_source_filename` returns False. 159 160 Returns the source file name for this frame, or None if this frame 161 shouldn't be measured. 162 163 """ 164 return None 165 166 def line_number_range(self, frame): 167 """Get the range of source line numbers for a given a call frame. 168 169 The call frame is examined, and the source line number in the original 170 file is returned. The return value is a pair of numbers, the starting 171 line number and the ending line number, both inclusive. For example, 172 returning (5, 7) means that lines 5, 6, and 7 should be considered 173 executed. 174 175 This function might decide that the frame doesn't indicate any lines 176 from the source file were executed. Return (-1, -1) in this case to 177 tell coverage.py that no lines should be recorded for this frame. 178 179 """ 180 lineno = frame.f_lineno 181 return lineno, lineno 182 183 184class FileReporter(object): 185 """Support needed for files during the analysis and reporting phases. 186 187 See :ref:`howitworks` for details of the different coverage.py phases. 188 189 `FileReporter` objects should only be created in the 190 :meth:`CoveragePlugin.file_reporter` method. 191 192 There are many methods here, but only :meth:`lines` is required, to provide 193 the set of executable lines in the file. 194 195 """ 196 197 def __init__(self, filename): 198 """Simple initialization of a `FileReporter`. 199 200 The `filename` argument is the path to the file being reported. This 201 will be available as the `.filename` attribute on the object. Other 202 method implementations on this base class rely on this attribute. 203 204 """ 205 self.filename = filename 206 207 def __repr__(self): 208 return "<{0.__class__.__name__} filename={0.filename!r}>".format(self) 209 210 def relative_filename(self): 211 """Get the relative file name for this file. 212 213 This file path will be displayed in reports. The default 214 implementation will supply the actual project-relative file path. You 215 only need to supply this method if you have an unusual syntax for file 216 paths. 217 218 """ 219 return files.relative_filename(self.filename) 220 221 @contract(returns='unicode') 222 def source(self): 223 """Get the source for the file. 224 225 Returns a Unicode string. 226 227 The base implementation simply reads the `self.filename` file and 228 decodes it as UTF8. Override this method if your file isn't readable 229 as a text file, or if you need other encoding support. 230 231 """ 232 with open(self.filename, "rb") as f: 233 return f.read().decode("utf8") 234 235 def lines(self): 236 """Get the executable lines in this file. 237 238 Your plugin must determine which lines in the file were possibly 239 executable. This method returns a set of those line numbers. 240 241 Returns a set of line numbers. 242 243 """ 244 _needs_to_implement(self, "lines") 245 246 def excluded_lines(self): 247 """Get the excluded executable lines in this file. 248 249 Your plugin can use any method it likes to allow the user to exclude 250 executable lines from consideration. 251 252 Returns a set of line numbers. 253 254 The base implementation returns the empty set. 255 256 """ 257 return set() 258 259 def translate_lines(self, lines): 260 """Translate recorded lines into reported lines. 261 262 Some file formats will want to report lines slightly differently than 263 they are recorded. For example, Python records the last line of a 264 multi-line statement, but reports are nicer if they mention the first 265 line. 266 267 Your plugin can optionally define this method to perform these kinds of 268 adjustment. 269 270 `lines` is a sequence of integers, the recorded line numbers. 271 272 Returns a set of integers, the adjusted line numbers. 273 274 The base implementation returns the numbers unchanged. 275 276 """ 277 return set(lines) 278 279 def arcs(self): 280 """Get the executable arcs in this file. 281 282 To support branch coverage, your plugin needs to be able to indicate 283 possible execution paths, as a set of line number pairs. Each pair is 284 a `(prev, next)` pair indicating that execution can transition from the 285 `prev` line number to the `next` line number. 286 287 Returns a set of pairs of line numbers. The default implementation 288 returns an empty set. 289 290 """ 291 return set() 292 293 def no_branch_lines(self): 294 """Get the lines excused from branch coverage in this file. 295 296 Your plugin can use any method it likes to allow the user to exclude 297 lines from consideration of branch coverage. 298 299 Returns a set of line numbers. 300 301 The base implementation returns the empty set. 302 303 """ 304 return set() 305 306 def translate_arcs(self, arcs): 307 """Translate recorded arcs into reported arcs. 308 309 Similar to :meth:`translate_lines`, but for arcs. `arcs` is a set of 310 line number pairs. 311 312 Returns a set of line number pairs. 313 314 The default implementation returns `arcs` unchanged. 315 316 """ 317 return arcs 318 319 def exit_counts(self): 320 """Get a count of exits from that each line. 321 322 To determine which lines are branches, coverage.py looks for lines that 323 have more than one exit. This function creates a dict mapping each 324 executable line number to a count of how many exits it has. 325 326 To be honest, this feels wrong, and should be refactored. Let me know 327 if you attempt to implement this... 328 329 """ 330 return {} 331 332 def source_token_lines(self): 333 """Generate a series of tokenized lines, one for each line in `source`. 334 335 These tokens are used for syntax-colored reports. 336 337 Each line is a list of pairs, each pair is a token:: 338 339 [('key', 'def'), ('ws', ' '), ('nam', 'hello'), ('op', '('), ... ] 340 341 Each pair has a token class, and the token text. The token classes 342 are: 343 344 * ``'com'``: a comment 345 * ``'key'``: a keyword 346 * ``'nam'``: a name, or identifier 347 * ``'num'``: a number 348 * ``'op'``: an operator 349 * ``'str'``: a string literal 350 * ``'txt'``: some other kind of text 351 352 If you concatenate all the token texts, and then join them with 353 newlines, you should have your original source back. 354 355 The default implementation simply returns each line tagged as 356 ``'txt'``. 357 358 """ 359 for line in self.source().splitlines(): 360 yield [('txt', line)] 361 362 # Annoying comparison operators. Py3k wants __lt__ etc, and Py2k needs all 363 # of them defined. 364 365 def __eq__(self, other): 366 return isinstance(other, FileReporter) and self.filename == other.filename 367 368 def __ne__(self, other): 369 return not (self == other) 370 371 def __lt__(self, other): 372 return self.filename < other.filename 373 374 def __le__(self, other): 375 return self.filename <= other.filename 376 377 def __gt__(self, other): 378 return self.filename > other.filename 379 380 def __ge__(self, other): 381 return self.filename >= other.filename 382