diff options
Diffstat (limited to 'coverage/misc.py')
-rw-r--r-- | coverage/misc.py | 105 |
1 files changed, 86 insertions, 19 deletions
diff --git a/coverage/misc.py b/coverage/misc.py index b99fbb81..5d330c6d 100644 --- a/coverage/misc.py +++ b/coverage/misc.py @@ -1,32 +1,73 @@ -"""Miscellaneous stuff for Coverage.""" +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt + +"""Miscellaneous stuff for coverage.py.""" import errno import hashlib import inspect +import locale import os +import sys +import types from coverage import env -from coverage.backward import string_class, to_bytes, unicode_class +from coverage.backward import to_bytes, unicode_class + +ISOLATED_MODULES = {} + + +def isolate_module(mod): + """Copy a module so that we are isolated from aggressive mocking. + + If a test suite mocks os.path.exists (for example), and then we need to use + it during the test, everything will get tangled up if we use their mock. + Making a copy of the module when we import it will isolate coverage.py from + those complications. + """ + if mod not in ISOLATED_MODULES: + new_mod = types.ModuleType(mod.__name__) + ISOLATED_MODULES[mod] = new_mod + for name in dir(mod): + value = getattr(mod, name) + if isinstance(value, types.ModuleType): + value = isolate_module(value) + setattr(new_mod, name, value) + return ISOLATED_MODULES[mod] + +os = isolate_module(os) # Use PyContracts for assertion testing on parameters and returns, but only if # we are running our own test suite. if env.TESTING: from contracts import contract # pylint: disable=unused-import - from contracts import new_contract + from contracts import new_contract as raw_new_contract + + def new_contract(*args, **kwargs): + """A proxy for contracts.new_contract that doesn't mind happening twice.""" + try: + return raw_new_contract(*args, **kwargs) + except ValueError: + # During meta-coverage, this module is imported twice, and + # PyContracts doesn't like redefining contracts. It's OK. + pass # Define contract words that PyContract doesn't have. new_contract('bytes', lambda v: isinstance(v, bytes)) if env.PY3: new_contract('unicode', lambda v: isinstance(v, unicode_class)) - -else: +else: # pragma: not covered # We aren't using real PyContracts, so just define a no-op decorator as a # stunt double. def contract(**unused): """Dummy no-op implementation of `contract`.""" return lambda func: func + def new_contract(*args_unused, **kwargs_unused): + """Dummy no-op implementation of `new_contract`.""" + pass + def nice_pair(pair): """Make a nice string representation of a pair of numbers. @@ -76,19 +117,24 @@ def format_lines(statements, lines): def expensive(fn): - """A decorator to cache the result of an expensive operation. + """A decorator to indicate that a method shouldn't be called more than once. - Only applies to methods with no arguments. + Normally, this does nothing. During testing, this raises an exception if + called more than once. """ - attr = "_cache_" + fn.__name__ - - def _wrapped(self): - """Inner fn that checks the cache.""" - if not hasattr(self, attr): - setattr(self, attr, fn(self)) - return getattr(self, attr) - return _wrapped + if env.TESTING: + attr = "_once_" + fn.__name__ + + def _wrapped(self): + """Inner function that checks the cache.""" + if hasattr(self, attr): + raise AssertionError("Shouldn't have called %s more than once" % fn.__name__) + setattr(self, attr, True) + return fn(self) + return _wrapped + else: + return fn # pragma: not covered def bool_or_none(b): @@ -113,6 +159,18 @@ def file_be_gone(path): raise +def output_encoding(outfile=None): + """Determine the encoding to use for output written to `outfile` or stdout.""" + if outfile is None: + outfile = sys.stdout + encoding = ( + getattr(outfile, "encoding", None) or + getattr(sys.__stdout__, "encoding", None) or + locale.getpreferredencoding() + ) + return encoding + + class Hasher(object): """Hashes Python data into md5.""" def __init__(self): @@ -121,8 +179,8 @@ class Hasher(object): def update(self, v): """Add `v` to the hash, recursively if needed.""" self.md5.update(to_bytes(str(type(v)))) - if isinstance(v, string_class): - self.md5.update(to_bytes(v)) + if isinstance(v, unicode_class): + self.md5.update(v.encode('utf8')) elif isinstance(v, bytes): self.md5.update(v) elif v is None: @@ -152,7 +210,6 @@ class Hasher(object): return self.md5.hexdigest() -# TODO: abc? def _needs_to_implement(that, func_name): """Helper to raise NotImplementedError in interface stubs.""" if hasattr(that, "_coverage_plugin_name"): @@ -170,8 +227,18 @@ def _needs_to_implement(that, func_name): ) +class SimpleRepr(object): + """A mixin implementing a simple __repr__.""" + def __repr__(self): + return "<{klass} @{id:x} {attrs}>".format( + klass=self.__class__.__name__, + id=id(self) & 0xFFFFFF, + attrs=" ".join("{}={!r}".format(k, v) for k, v in self.__dict__.items()), + ) + + class CoverageException(Exception): - """An exception specific to Coverage.""" + """An exception specific to coverage.py.""" pass |