misc.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  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. """Miscellaneous stuff for coverage.py."""
  4. import errno
  5. import hashlib
  6. import inspect
  7. import locale
  8. import os
  9. import sys
  10. import types
  11. from coverage import env
  12. from coverage.backward import string_class, to_bytes, unicode_class
  13. ISOLATED_MODULES = {}
  14. def isolate_module(mod):
  15. """Copy a module so that we are isolated from aggressive mocking.
  16. If a test suite mocks os.path.exists (for example), and then we need to use
  17. it during the test, everything will get tangled up if we use their mock.
  18. Making a copy of the module when we import it will isolate coverage.py from
  19. those complications.
  20. """
  21. if mod not in ISOLATED_MODULES:
  22. new_mod = types.ModuleType(mod.__name__)
  23. ISOLATED_MODULES[mod] = new_mod
  24. for name in dir(mod):
  25. value = getattr(mod, name)
  26. if isinstance(value, types.ModuleType):
  27. value = isolate_module(value)
  28. setattr(new_mod, name, value)
  29. return ISOLATED_MODULES[mod]
  30. os = isolate_module(os)
  31. # Use PyContracts for assertion testing on parameters and returns, but only if
  32. # we are running our own test suite.
  33. if env.TESTING:
  34. from contracts import contract # pylint: disable=unused-import
  35. from contracts import new_contract as raw_new_contract
  36. def new_contract(*args, **kwargs):
  37. """A proxy for contracts.new_contract that doesn't mind happening twice."""
  38. try:
  39. return raw_new_contract(*args, **kwargs)
  40. except ValueError:
  41. # During meta-coverage, this module is imported twice, and
  42. # PyContracts doesn't like redefining contracts. It's OK.
  43. pass
  44. # Define contract words that PyContract doesn't have.
  45. new_contract('bytes', lambda v: isinstance(v, bytes))
  46. if env.PY3:
  47. new_contract('unicode', lambda v: isinstance(v, unicode_class))
  48. else: # pragma: not covered
  49. # We aren't using real PyContracts, so just define a no-op decorator as a
  50. # stunt double.
  51. def contract(**unused):
  52. """Dummy no-op implementation of `contract`."""
  53. return lambda func: func
  54. def new_contract(*args_unused, **kwargs_unused):
  55. """Dummy no-op implementation of `new_contract`."""
  56. pass
  57. def nice_pair(pair):
  58. """Make a nice string representation of a pair of numbers.
  59. If the numbers are equal, just return the number, otherwise return the pair
  60. with a dash between them, indicating the range.
  61. """
  62. start, end = pair
  63. if start == end:
  64. return "%d" % start
  65. else:
  66. return "%d-%d" % (start, end)
  67. def format_lines(statements, lines):
  68. """Nicely format a list of line numbers.
  69. Format a list of line numbers for printing by coalescing groups of lines as
  70. long as the lines represent consecutive statements. This will coalesce
  71. even if there are gaps between statements.
  72. For example, if `statements` is [1,2,3,4,5,10,11,12,13,14] and
  73. `lines` is [1,2,5,10,11,13,14] then the result will be "1-2, 5-11, 13-14".
  74. """
  75. pairs = []
  76. i = 0
  77. j = 0
  78. start = None
  79. statements = sorted(statements)
  80. lines = sorted(lines)
  81. while i < len(statements) and j < len(lines):
  82. if statements[i] == lines[j]:
  83. if start is None:
  84. start = lines[j]
  85. end = lines[j]
  86. j += 1
  87. elif start:
  88. pairs.append((start, end))
  89. start = None
  90. i += 1
  91. if start:
  92. pairs.append((start, end))
  93. ret = ', '.join(map(nice_pair, pairs))
  94. return ret
  95. def expensive(fn):
  96. """A decorator to indicate that a method shouldn't be called more than once.
  97. Normally, this does nothing. During testing, this raises an exception if
  98. called more than once.
  99. """
  100. if env.TESTING:
  101. attr = "_once_" + fn.__name__
  102. def _wrapped(self):
  103. """Inner function that checks the cache."""
  104. if hasattr(self, attr):
  105. raise Exception("Shouldn't have called %s more than once" % fn.__name__)
  106. setattr(self, attr, True)
  107. return fn(self)
  108. return _wrapped
  109. else:
  110. return fn
  111. def bool_or_none(b):
  112. """Return bool(b), but preserve None."""
  113. if b is None:
  114. return None
  115. else:
  116. return bool(b)
  117. def join_regex(regexes):
  118. """Combine a list of regexes into one that matches any of them."""
  119. return "|".join("(?:%s)" % r for r in regexes)
  120. def file_be_gone(path):
  121. """Remove a file, and don't get annoyed if it doesn't exist."""
  122. try:
  123. os.remove(path)
  124. except OSError as e:
  125. if e.errno != errno.ENOENT:
  126. raise
  127. def output_encoding(outfile=None):
  128. """Determine the encoding to use for output written to `outfile` or stdout."""
  129. if outfile is None:
  130. outfile = sys.stdout
  131. encoding = (
  132. getattr(outfile, "encoding", None) or
  133. getattr(sys.__stdout__, "encoding", None) or
  134. locale.getpreferredencoding()
  135. )
  136. return encoding
  137. class Hasher(object):
  138. """Hashes Python data into md5."""
  139. def __init__(self):
  140. self.md5 = hashlib.md5()
  141. def update(self, v):
  142. """Add `v` to the hash, recursively if needed."""
  143. self.md5.update(to_bytes(str(type(v))))
  144. if isinstance(v, string_class):
  145. self.md5.update(to_bytes(v))
  146. elif isinstance(v, bytes):
  147. self.md5.update(v)
  148. elif v is None:
  149. pass
  150. elif isinstance(v, (int, float)):
  151. self.md5.update(to_bytes(str(v)))
  152. elif isinstance(v, (tuple, list)):
  153. for e in v:
  154. self.update(e)
  155. elif isinstance(v, dict):
  156. keys = v.keys()
  157. for k in sorted(keys):
  158. self.update(k)
  159. self.update(v[k])
  160. else:
  161. for k in dir(v):
  162. if k.startswith('__'):
  163. continue
  164. a = getattr(v, k)
  165. if inspect.isroutine(a):
  166. continue
  167. self.update(k)
  168. self.update(a)
  169. def hexdigest(self):
  170. """Retrieve the hex digest of the hash."""
  171. return self.md5.hexdigest()
  172. def _needs_to_implement(that, func_name):
  173. """Helper to raise NotImplementedError in interface stubs."""
  174. if hasattr(that, "_coverage_plugin_name"):
  175. thing = "Plugin"
  176. name = that._coverage_plugin_name
  177. else:
  178. thing = "Class"
  179. klass = that.__class__
  180. name = "{klass.__module__}.{klass.__name__}".format(klass=klass)
  181. raise NotImplementedError(
  182. "{thing} {name!r} needs to implement {func_name}()".format(
  183. thing=thing, name=name, func_name=func_name
  184. )
  185. )
  186. class SimpleRepr(object):
  187. """A mixin implementing a simple __repr__."""
  188. def __repr__(self):
  189. return "<{klass} @{id:x} {attrs}>".format(
  190. klass=self.__class__.__name__,
  191. id=id(self) & 0xFFFFFF,
  192. attrs=" ".join("{}={!r}".format(k, v) for k, v in self.__dict__.items()),
  193. )
  194. class CoverageException(Exception):
  195. """An exception specific to coverage.py."""
  196. pass
  197. class NoSource(CoverageException):
  198. """We couldn't find the source for a module."""
  199. pass
  200. class NoCode(NoSource):
  201. """We couldn't find any code at all."""
  202. pass
  203. class NotPython(CoverageException):
  204. """A source file turned out not to be parsable Python."""
  205. pass
  206. class ExceptionDuringRun(CoverageException):
  207. """An exception happened while running customer code.
  208. Construct it with three arguments, the values from `sys.exc_info`.
  209. """
  210. pass