eric6/DebugClients/Python/coverage/misc.py

changeset 7427
362cd1b6f81a
parent 6942
2602857055c5
equal deleted inserted replaced
7426:dc171b1d8261 7427:362cd1b6f81a
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):
224 a = getattr(v, k) 228 a = getattr(v, k)
225 if inspect.isroutine(a): 229 if inspect.isroutine(a):
226 continue 230 continue
227 self.update(k) 231 self.update(k)
228 self.update(a) 232 self.update(a)
233 self.md5.update(b'.')
229 234
230 def hexdigest(self): 235 def hexdigest(self):
231 """Retrieve the hex digest of the hash.""" 236 """Retrieve the hex digest of the hash."""
232 return self.md5.hexdigest() 237 return self.md5.hexdigest()
233 238
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."""

eric ide

mercurial