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