1 """Miscellaneous stuff for Coverage."""
8 from coverage
.backward
import md5
, sorted # pylint: disable=W0622
9 from coverage
.backward
import string_class
, to_bytes
13 """Make a nice string representation of a pair of numbers.
15 If the numbers are equal, just return the number, otherwise return the pair
16 with a dash between them, indicating the range.
23 return "%d-%d" % (start
, end
)
26 def format_lines(statements
, lines
):
27 """Nicely format a list of line numbers.
29 Format a list of line numbers for printing by coalescing groups of lines as
30 long as the lines represent consecutive statements. This will coalesce
31 even if there are gaps between statements.
33 For example, if `statements` is [1,2,3,4,5,10,11,12,13,14] and
34 `lines` is [1,2,5,10,11,13,14] then the result will be "1-2, 5-11, 13-14".
41 statements
= sorted(statements
)
43 while i
< len(statements
) and j
< len(lines
):
44 if statements
[i
] == lines
[j
]:
50 pairs
.append((start
, end
))
54 pairs
.append((start
, end
))
55 ret
= ', '.join(map(nice_pair
, pairs
))
60 """Return a string summarizing the call stack."""
61 stack
= inspect
.stack()[:0:-1]
62 return "\n".join(["%30s : %s @%d" % (t
[3],t
[1],t
[2]) for t
in stack
])
66 """A decorator to cache the result of an expensive operation.
68 Only applies to methods with no arguments.
71 attr
= "_cache_" + fn
.__name
__
73 """Inner fn that checks the cache."""
74 if not hasattr(self
, attr
):
75 setattr(self
, attr
, fn(self
))
76 return getattr(self
, attr
)
81 """Return bool(b), but preserve None."""
88 def join_regex(regexes
):
89 """Combine a list of regexes into one that matches any of them."""
91 return "|".join(["(%s)" % r
for r
in regexes
])
98 def file_be_gone(path
):
99 """Remove a file, and don't get annoyed if it doesn't exist."""
103 _
, e
, _
= sys
.exc_info()
104 if e
.errno
!= errno
.ENOENT
:
108 class Hasher(object):
109 """Hashes Python data into md5."""
114 """Add `v` to the hash, recursively if needed."""
115 self
.md5
.update(to_bytes(str(type(v
))))
116 if isinstance(v
, string_class
):
117 self
.md5
.update(to_bytes(v
))
120 elif isinstance(v
, (int, float)):
121 self
.md5
.update(to_bytes(str(v
)))
122 elif isinstance(v
, (tuple, list)):
125 elif isinstance(v
, dict):
127 for k
in sorted(keys
):
132 if k
.startswith('__'):
135 if inspect
.isroutine(a
):
141 """Retrieve the digest of the hash."""
142 return self
.md5
.digest()
145 class CoverageException(Exception):
146 """An exception specific to Coverage."""
149 class NoSource(CoverageException
):
150 """We couldn't find the source for a module."""
153 class NoCode(NoSource
):
154 """We couldn't find any code at all."""
157 class NotPython(CoverageException
):
158 """A source file turned out not to be parsable Python."""
161 class ExceptionDuringRun(CoverageException
):
162 """An exception happened while running customer code.
164 Construct it with three arguments, the values from `sys.exc_info`.