1 """Miscellaneous stuff for Coverage.""" |
1 """Miscellaneous stuff for Coverage.""" |
|
2 |
|
3 import errno |
|
4 import inspect |
|
5 import os |
|
6 import sys |
|
7 |
|
8 from .backward import md5, sorted # pylint: disable=W0622 |
|
9 from .backward import string_class, to_bytes |
|
10 |
2 |
11 |
3 def nice_pair(pair): |
12 def nice_pair(pair): |
4 """Make a nice string representation of a pair of numbers. |
13 """Make a nice string representation of a pair of numbers. |
5 |
14 |
6 If the numbers are equal, just return the number, otherwise return the pair |
15 If the numbers are equal, just return the number, otherwise return the pair |
41 i += 1 |
52 i += 1 |
42 if start: |
53 if start: |
43 pairs.append((start, end)) |
54 pairs.append((start, end)) |
44 ret = ', '.join(map(nice_pair, pairs)) |
55 ret = ', '.join(map(nice_pair, pairs)) |
45 return ret |
56 return ret |
|
57 |
|
58 |
|
59 def short_stack(): |
|
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]) |
46 |
63 |
47 |
64 |
48 def expensive(fn): |
65 def expensive(fn): |
49 """A decorator to cache the result of an expensive operation. |
66 """A decorator to cache the result of an expensive operation. |
50 |
67 |
58 setattr(self, attr, fn(self)) |
75 setattr(self, attr, fn(self)) |
59 return getattr(self, attr) |
76 return getattr(self, attr) |
60 return _wrapped |
77 return _wrapped |
61 |
78 |
62 |
79 |
|
80 def bool_or_none(b): |
|
81 """Return bool(b), but preserve None.""" |
|
82 if b is None: |
|
83 return None |
|
84 else: |
|
85 return bool(b) |
|
86 |
|
87 |
|
88 def join_regex(regexes): |
|
89 """Combine a list of regexes into one that matches any of them.""" |
|
90 if len(regexes) > 1: |
|
91 return "|".join(["(%s)" % r for r in regexes]) |
|
92 elif regexes: |
|
93 return regexes[0] |
|
94 else: |
|
95 return "" |
|
96 |
|
97 |
|
98 def file_be_gone(path): |
|
99 """Remove a file, and don't get annoyed if it doesn't exist.""" |
|
100 try: |
|
101 os.remove(path) |
|
102 except OSError: |
|
103 _, e, _ = sys.exc_info() |
|
104 if e.errno != errno.ENOENT: |
|
105 raise |
|
106 |
|
107 |
|
108 class Hasher(object): |
|
109 """Hashes Python data into md5.""" |
|
110 def __init__(self): |
|
111 self.md5 = md5() |
|
112 |
|
113 def update(self, v): |
|
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)) |
|
118 elif v is None: |
|
119 pass |
|
120 elif isinstance(v, (int, float)): |
|
121 self.md5.update(to_bytes(str(v))) |
|
122 elif isinstance(v, (tuple, list)): |
|
123 for e in v: |
|
124 self.update(e) |
|
125 elif isinstance(v, dict): |
|
126 keys = v.keys() |
|
127 for k in sorted(keys): |
|
128 self.update(k) |
|
129 self.update(v[k]) |
|
130 else: |
|
131 for k in dir(v): |
|
132 if k.startswith('__'): |
|
133 continue |
|
134 a = getattr(v, k) |
|
135 if inspect.isroutine(a): |
|
136 continue |
|
137 self.update(k) |
|
138 self.update(a) |
|
139 |
|
140 def digest(self): |
|
141 """Retrieve the digest of the hash.""" |
|
142 return self.md5.digest() |
|
143 |
|
144 |
63 class CoverageException(Exception): |
145 class CoverageException(Exception): |
64 """An exception specific to Coverage.""" |
146 """An exception specific to Coverage.""" |
65 pass |
147 pass |
66 |
148 |
67 class NoSource(CoverageException): |
149 class NoSource(CoverageException): |
68 """Used to indicate we couldn't find the source for a module.""" |
150 """We couldn't find the source for a module.""" |
69 pass |
151 pass |
|
152 |
|
153 class NoCode(NoSource): |
|
154 """We couldn't find any code at all.""" |
|
155 pass |
|
156 |
|
157 class NotPython(CoverageException): |
|
158 """A source file turned out not to be parsable Python.""" |
|
159 pass |
|
160 |
|
161 class ExceptionDuringRun(CoverageException): |
|
162 """An exception happened while running customer code. |
|
163 |
|
164 Construct it with three arguments, the values from `sys.exc_info`. |
|
165 |
|
166 """ |
|
167 pass |