1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 |
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 |
2 # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt |
3 |
3 |
4 """Miscellaneous stuff for coverage.py.""" |
4 """Miscellaneous stuff for coverage.py.""" |
5 |
5 |
6 import errno |
6 import errno |
7 import hashlib |
7 import hashlib |
8 import inspect |
8 import inspect |
9 import locale |
9 import locale |
10 import os |
10 import os |
|
11 import os.path |
|
12 import random |
|
13 import re |
|
14 import socket |
11 import sys |
15 import sys |
12 import types |
16 import types |
13 |
17 |
14 from coverage import env |
18 from coverage import env |
15 from coverage.backward import to_bytes, unicode_class |
19 from coverage.backward import to_bytes, unicode_class |
43 def _decorator(func): |
47 def _decorator(func): |
44 return func |
48 return func |
45 return _decorator |
49 return _decorator |
46 |
50 |
47 |
51 |
|
52 # Environment COVERAGE_NO_CONTRACTS=1 can turn off contracts while debugging |
|
53 # tests to remove noise from stack traces. |
|
54 # $set_env.py: COVERAGE_NO_CONTRACTS - Disable PyContracts to simplify stack traces. |
|
55 USE_CONTRACTS = env.TESTING and not bool(int(os.environ.get("COVERAGE_NO_CONTRACTS", 0))) |
|
56 |
48 # Use PyContracts for assertion testing on parameters and returns, but only if |
57 # Use PyContracts for assertion testing on parameters and returns, but only if |
49 # we are running our own test suite. |
58 # we are running our own test suite. |
50 if env.TESTING: |
59 if USE_CONTRACTS: |
51 from contracts import contract # pylint: disable=unused-import |
60 from contracts import contract # pylint: disable=unused-import |
52 from contracts import new_contract as raw_new_contract |
61 from contracts import new_contract as raw_new_contract |
53 |
62 |
54 def new_contract(*args, **kwargs): |
63 def new_contract(*args, **kwargs): |
55 """A proxy for contracts.new_contract that doesn't mind happening twice.""" |
64 """A proxy for contracts.new_contract that doesn't mind happening twice.""" |
67 |
76 |
68 def one_of(argnames): |
77 def one_of(argnames): |
69 """Ensure that only one of the argnames is non-None.""" |
78 """Ensure that only one of the argnames is non-None.""" |
70 def _decorator(func): |
79 def _decorator(func): |
71 argnameset = set(name.strip() for name in argnames.split(",")) |
80 argnameset = set(name.strip() for name in argnames.split(",")) |
72 def _wrapped(*args, **kwargs): |
81 def _wrapper(*args, **kwargs): |
73 vals = [kwargs.get(name) for name in argnameset] |
82 vals = [kwargs.get(name) for name in argnameset] |
74 assert sum(val is not None for val in vals) == 1 |
83 assert sum(val is not None for val in vals) == 1 |
75 return func(*args, **kwargs) |
84 return func(*args, **kwargs) |
76 return _wrapped |
85 return _wrapper |
77 return _decorator |
86 return _decorator |
78 else: # pragma: not testing |
87 else: # pragma: not testing |
79 # We aren't using real PyContracts, so just define our decorators as |
88 # We aren't using real PyContracts, so just define our decorators as |
80 # stunt-double no-ops. |
89 # stunt-double no-ops. |
81 contract = dummy_decorator_with_args |
90 contract = dummy_decorator_with_args |
98 return "%d" % start |
107 return "%d" % start |
99 else: |
108 else: |
100 return "%d-%d" % (start, end) |
109 return "%d-%d" % (start, end) |
101 |
110 |
102 |
111 |
103 def format_lines(statements, lines): |
|
104 """Nicely format a list of line numbers. |
|
105 |
|
106 Format a list of line numbers for printing by coalescing groups of lines as |
|
107 long as the lines represent consecutive statements. This will coalesce |
|
108 even if there are gaps between statements. |
|
109 |
|
110 For example, if `statements` is [1,2,3,4,5,10,11,12,13,14] and |
|
111 `lines` is [1,2,5,10,11,13,14] then the result will be "1-2, 5-11, 13-14". |
|
112 |
|
113 Both `lines` and `statements` can be any iterable. All of the elements of |
|
114 `lines` must be in `statements`, and all of the values must be positive |
|
115 integers. |
|
116 |
|
117 """ |
|
118 statements = sorted(statements) |
|
119 lines = sorted(lines) |
|
120 |
|
121 pairs = [] |
|
122 start = None |
|
123 lidx = 0 |
|
124 for stmt in statements: |
|
125 if lidx >= len(lines): |
|
126 break |
|
127 if stmt == lines[lidx]: |
|
128 lidx += 1 |
|
129 if not start: |
|
130 start = stmt |
|
131 end = stmt |
|
132 elif start: |
|
133 pairs.append((start, end)) |
|
134 start = None |
|
135 if start: |
|
136 pairs.append((start, end)) |
|
137 ret = ', '.join(map(nice_pair, pairs)) |
|
138 return ret |
|
139 |
|
140 |
|
141 def expensive(fn): |
112 def expensive(fn): |
142 """A decorator to indicate that a method shouldn't be called more than once. |
113 """A decorator to indicate that a method shouldn't be called more than once. |
143 |
114 |
144 Normally, this does nothing. During testing, this raises an exception if |
115 Normally, this does nothing. During testing, this raises an exception if |
145 called more than once. |
116 called more than once. |
146 |
117 |
147 """ |
118 """ |
148 if env.TESTING: |
119 if env.TESTING: |
149 attr = "_once_" + fn.__name__ |
120 attr = "_once_" + fn.__name__ |
150 |
121 |
151 def _wrapped(self): |
122 def _wrapper(self): |
152 """Inner function that checks the cache.""" |
|
153 if hasattr(self, attr): |
123 if hasattr(self, attr): |
154 raise AssertionError("Shouldn't have called %s more than once" % fn.__name__) |
124 raise AssertionError("Shouldn't have called %s more than once" % fn.__name__) |
155 setattr(self, attr, True) |
125 setattr(self, attr, True) |
156 return fn(self) |
126 return fn(self) |
157 return _wrapped |
127 return _wrapper |
158 else: |
128 else: |
159 return fn # pragma: not testing |
129 return fn # pragma: not testing |
160 |
130 |
161 |
131 |
162 def bool_or_none(b): |
132 def bool_or_none(b): |
177 try: |
147 try: |
178 os.remove(path) |
148 os.remove(path) |
179 except OSError as e: |
149 except OSError as e: |
180 if e.errno != errno.ENOENT: |
150 if e.errno != errno.ENOENT: |
181 raise |
151 raise |
|
152 |
|
153 |
|
154 def ensure_dir(directory): |
|
155 """Make sure the directory exists. |
|
156 |
|
157 If `directory` is None or empty, do nothing. |
|
158 """ |
|
159 if directory and not os.path.isdir(directory): |
|
160 os.makedirs(directory) |
|
161 |
|
162 |
|
163 def ensure_dir_for_file(path): |
|
164 """Make sure the directory for the path exists.""" |
|
165 ensure_dir(os.path.dirname(path)) |
182 |
166 |
183 |
167 |
184 def output_encoding(outfile=None): |
168 def output_encoding(outfile=None): |
185 """Determine the encoding to use for output written to `outfile` or stdout.""" |
169 """Determine the encoding to use for output written to `outfile` or stdout.""" |
186 if outfile is None: |
170 if outfile is None: |
189 getattr(outfile, "encoding", None) or |
173 getattr(outfile, "encoding", None) or |
190 getattr(sys.__stdout__, "encoding", None) or |
174 getattr(sys.__stdout__, "encoding", None) or |
191 locale.getpreferredencoding() |
175 locale.getpreferredencoding() |
192 ) |
176 ) |
193 return encoding |
177 return encoding |
|
178 |
|
179 |
|
180 def filename_suffix(suffix): |
|
181 """Compute a filename suffix for a data file. |
|
182 |
|
183 If `suffix` is a string or None, simply return it. If `suffix` is True, |
|
184 then build a suffix incorporating the hostname, process id, and a random |
|
185 number. |
|
186 |
|
187 Returns a string or None. |
|
188 |
|
189 """ |
|
190 if suffix is True: |
|
191 # If data_suffix was a simple true value, then make a suffix with |
|
192 # plenty of distinguishing information. We do this here in |
|
193 # `save()` at the last minute so that the pid will be correct even |
|
194 # if the process forks. |
|
195 dice = random.Random(os.urandom(8)).randint(0, 999999) |
|
196 suffix = "%s.%s.%06d" % (socket.gethostname(), os.getpid(), dice) |
|
197 return suffix |
194 |
198 |
195 |
199 |
196 class Hasher(object): |
200 class Hasher(object): |
197 """Hashes Python data into md5.""" |
201 """Hashes Python data into md5.""" |
198 def __init__(self): |
202 def __init__(self): |
247 thing=thing, name=name, func_name=func_name |
252 thing=thing, name=name, func_name=func_name |
248 ) |
253 ) |
249 ) |
254 ) |
250 |
255 |
251 |
256 |
252 class SimpleRepr(object): |
257 class DefaultValue(object): |
253 """A mixin implementing a simple __repr__.""" |
258 """A sentinel object to use for unusual default-value needs. |
|
259 |
|
260 Construct with a string that will be used as the repr, for display in help |
|
261 and Sphinx output. |
|
262 |
|
263 """ |
|
264 def __init__(self, display_as): |
|
265 self.display_as = display_as |
|
266 |
254 def __repr__(self): |
267 def __repr__(self): |
255 return "<{klass} @{id:x} {attrs}>".format( |
268 return self.display_as |
256 klass=self.__class__.__name__, |
269 |
257 id=id(self) & 0xFFFFFF, |
270 |
258 attrs=" ".join("{}={!r}".format(k, v) for k, v in self.__dict__.items()), |
271 def substitute_variables(text, variables): |
259 ) |
272 """Substitute ``${VAR}`` variables in `text` with their values. |
|
273 |
|
274 Variables in the text can take a number of shell-inspired forms:: |
|
275 |
|
276 $VAR |
|
277 ${VAR} |
|
278 ${VAR?} strict: an error if VAR isn't defined. |
|
279 ${VAR-missing} defaulted: "missing" if VAR isn't defined. |
|
280 $$ just a dollar sign. |
|
281 |
|
282 `variables` is a dictionary of variable values. |
|
283 |
|
284 Returns the resulting text with values substituted. |
|
285 |
|
286 """ |
|
287 dollar_pattern = r"""(?x) # Use extended regex syntax |
|
288 \$ # A dollar sign, |
|
289 (?: # then |
|
290 (?P<dollar>\$) | # a dollar sign, or |
|
291 (?P<word1>\w+) | # a plain word, or |
|
292 { # a {-wrapped |
|
293 (?P<word2>\w+) # word, |
|
294 (?: |
|
295 (?P<strict>\?) | # with a strict marker |
|
296 -(?P<defval>[^}]*) # or a default value |
|
297 )? # maybe. |
|
298 } |
|
299 ) |
|
300 """ |
|
301 |
|
302 def dollar_replace(match): |
|
303 """Called for each $replacement.""" |
|
304 # Only one of the groups will have matched, just get its text. |
|
305 word = next(g for g in match.group('dollar', 'word1', 'word2') if g) |
|
306 if word == "$": |
|
307 return "$" |
|
308 elif word in variables: |
|
309 return variables[word] |
|
310 elif match.group('strict'): |
|
311 msg = "Variable {} is undefined: {!r}".format(word, text) |
|
312 raise CoverageException(msg) |
|
313 else: |
|
314 return match.group('defval') |
|
315 |
|
316 text = re.sub(dollar_pattern, dollar_replace, text) |
|
317 return text |
260 |
318 |
261 |
319 |
262 class BaseCoverageException(Exception): |
320 class BaseCoverageException(Exception): |
263 """The base of all Coverage exceptions.""" |
321 """The base of all Coverage exceptions.""" |
264 pass |
322 pass |
265 |
323 |
266 |
324 |
267 class CoverageException(BaseCoverageException): |
325 class CoverageException(BaseCoverageException): |
268 """A run-of-the-mill exception specific to coverage.py.""" |
326 """An exception raised by a coverage.py function.""" |
269 pass |
327 pass |
270 |
328 |
271 |
329 |
272 class NoSource(CoverageException): |
330 class NoSource(CoverageException): |
273 """We couldn't find the source for a module.""" |
331 """We couldn't find the source for a module.""" |