eric7/DebugClients/Python/coverage/misc.py

branch
eric7
changeset 8775
0802ae193343
parent 8527
2bd1325d727e
child 8929
fcca2fa618bf
--- a/eric7/DebugClients/Python/coverage/misc.py	Fri Nov 19 19:28:47 2021 +0100
+++ b/eric7/DebugClients/Python/coverage/misc.py	Sat Nov 20 16:47:38 2021 +0100
@@ -3,8 +3,11 @@
 
 """Miscellaneous stuff for coverage.py."""
 
+import contextlib
 import errno
 import hashlib
+import importlib
+import importlib.util
 import inspect
 import locale
 import os
@@ -16,7 +19,12 @@
 import types
 
 from coverage import env
-from coverage.backward import to_bytes, unicode_class
+from coverage.exceptions import CoverageException
+
+# In 6.0, the exceptions moved from misc.py to exceptions.py.  But a number of
+# other packages were importing the exceptions from misc, so import them here.
+# pylint: disable=unused-wildcard-import
+from coverage.exceptions import *   # pylint: disable=wildcard-import
 
 ISOLATED_MODULES = {}
 
@@ -42,6 +50,49 @@
 os = isolate_module(os)
 
 
+class SysModuleSaver:
+    """Saves the contents of sys.modules, and removes new modules later."""
+    def __init__(self):
+        self.old_modules = set(sys.modules)
+
+    def restore(self):
+        """Remove any modules imported since this object started."""
+        new_modules = set(sys.modules) - self.old_modules
+        for m in new_modules:
+            del sys.modules[m]
+
+
+@contextlib.contextmanager
+def sys_modules_saved():
+    """A context manager to remove any modules imported during a block."""
+    saver = SysModuleSaver()
+    try:
+        yield
+    finally:
+        saver.restore()
+
+
+def import_third_party(modname):
+    """Import a third-party module we need, but might not be installed.
+
+    This also cleans out the module after the import, so that coverage won't
+    appear to have imported it.  This lets the third party use coverage for
+    their own tests.
+
+    Arguments:
+        modname (str): the name of the module to import.
+
+    Returns:
+        The imported module, or None if the module couldn't be imported.
+
+    """
+    with sys_modules_saved():
+        try:
+            return importlib.import_module(modname)
+        except ImportError:
+            return None
+
+
 def dummy_decorator_with_args(*args_unused, **kwargs_unused):
     """Dummy no-op implementation of a decorator with arguments."""
     def _decorator(func):
@@ -49,14 +100,9 @@
     return _decorator
 
 
-# Environment COVERAGE_NO_CONTRACTS=1 can turn off contracts while debugging
-# tests to remove noise from stack traces.
-# $set_env.py: COVERAGE_NO_CONTRACTS - Disable PyContracts to simplify stack traces.
-USE_CONTRACTS = env.TESTING and not bool(int(os.environ.get("COVERAGE_NO_CONTRACTS", 0)))
-
 # Use PyContracts for assertion testing on parameters and returns, but only if
 # we are running our own test suite.
-if USE_CONTRACTS:
+if env.USE_CONTRACTS:
     from contracts import contract              # pylint: disable=unused-import
     from contracts import new_contract as raw_new_contract
 
@@ -71,8 +117,7 @@
 
     # 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))
+    new_contract('unicode', lambda v: isinstance(v, str))
 
     def one_of(argnames):
         """Ensure that only one of the argnames is non-None."""
@@ -121,7 +166,7 @@
 
         def _wrapper(self):
             if hasattr(self, attr):
-                raise AssertionError("Shouldn't have called %s more than once" % fn.__name__)
+                raise AssertionError(f"Shouldn't have called {fn.__name__} more than once")
             setattr(self, attr, True)
             return fn(self)
         return _wrapper
@@ -156,8 +201,8 @@
 
     If `directory` is None or empty, do nothing.
     """
-    if directory and not os.path.isdir(directory):
-        os.makedirs(directory)
+    if directory:
+        os.makedirs(directory, exist_ok=True)
 
 
 def ensure_dir_for_file(path):
@@ -197,22 +242,22 @@
     return suffix
 
 
-class Hasher(object):
-    """Hashes Python data into md5."""
+class Hasher:
+    """Hashes Python data for fingerprinting."""
     def __init__(self):
-        self.md5 = hashlib.md5()
+        self.hash = hashlib.new("sha3_256")
 
     def update(self, v):
         """Add `v` to the hash, recursively if needed."""
-        self.md5.update(to_bytes(str(type(v))))
-        if isinstance(v, unicode_class):
-            self.md5.update(v.encode('utf8'))
+        self.hash.update(str(type(v)).encode("utf-8"))
+        if isinstance(v, str):
+            self.hash.update(v.encode("utf-8"))
         elif isinstance(v, bytes):
-            self.md5.update(v)
+            self.hash.update(v)
         elif v is None:
             pass
         elif isinstance(v, (int, float)):
-            self.md5.update(to_bytes(str(v)))
+            self.hash.update(str(v).encode("utf-8"))
         elif isinstance(v, (tuple, list)):
             for e in v:
                 self.update(e)
@@ -230,11 +275,11 @@
                     continue
                 self.update(k)
                 self.update(a)
-        self.md5.update(b'.')
+        self.hash.update(b'.')
 
     def hexdigest(self):
         """Retrieve the hex digest of the hash."""
-        return self.md5.hexdigest()
+        return self.hash.hexdigest()[:32]
 
 
 def _needs_to_implement(that, func_name):
@@ -245,16 +290,14 @@
     else:
         thing = "Class"
         klass = that.__class__
-        name = "{klass.__module__}.{klass.__name__}".format(klass=klass)
+        name = f"{klass.__module__}.{klass.__name__}"
 
     raise NotImplementedError(
-        "{thing} {name!r} needs to implement {func_name}()".format(
-            thing=thing, name=name, func_name=func_name
-            )
+        f"{thing} {name!r} needs to implement {func_name}()"
         )
 
 
-class DefaultValue(object):
+class DefaultValue:
     """A sentinel object to use for unusual default-value needs.
 
     Construct with a string that will be used as the repr, for display in help
@@ -299,16 +342,18 @@
         )
         """
 
+    dollar_groups = ('dollar', 'word1', 'word2')
+
     def dollar_replace(match):
         """Called for each $replacement."""
         # Only one of the groups will have matched, just get its text.
-        word = next(g for g in match.group('dollar', 'word1', 'word2') if g)
+        word = next(g for g in match.group(*dollar_groups) if g)    # pragma: always breaks
         if word == "$":
             return "$"
         elif word in variables:
             return variables[word]
         elif match.group('strict'):
-            msg = "Variable {} is undefined: {!r}".format(word, text)
+            msg = f"Variable {word} is undefined: {text!r}"
             raise CoverageException(msg)
         else:
             return match.group('defval')
@@ -317,45 +362,56 @@
     return text
 
 
-class BaseCoverageException(Exception):
-    """The base of all Coverage exceptions."""
-    pass
+def format_local_datetime(dt):
+    """Return a string with local timezone representing the date.
+    """
+    return dt.astimezone().strftime('%Y-%m-%d %H:%M %z')
 
 
-class CoverageException(BaseCoverageException):
-    """An exception raised by a coverage.py function."""
-    pass
+def import_local_file(modname, modfile=None):
+    """Import a local file as a module.
 
+    Opens a file in the current directory named `modname`.py, imports it
+    as `modname`, and returns the module object.  `modfile` is the file to
+    import if it isn't in the current directory.
 
-class NoSource(CoverageException):
-    """We couldn't find the source for a module."""
-    pass
+    """
+    if modfile is None:
+        modfile = modname + '.py'
+    spec = importlib.util.spec_from_file_location(modname, modfile)
+    mod = importlib.util.module_from_spec(spec)
+    sys.modules[modname] = mod
+    spec.loader.exec_module(mod)
 
-
-class NoCode(NoSource):
-    """We couldn't find any code at all."""
-    pass
+    return mod
 
 
-class NotPython(CoverageException):
-    """A source file turned out not to be parsable Python."""
-    pass
-
+def human_key(s):
+    """Turn a string into a list of string and number chunks.
+        "z23a" -> ["z", 23, "a"]
+    """
+    def tryint(s):
+        """If `s` is a number, return an int, else `s` unchanged."""
+        try:
+            return int(s)
+        except ValueError:
+            return s
 
-class ExceptionDuringRun(CoverageException):
-    """An exception happened while running customer code.
+    return [tryint(c) for c in re.split(r"(\d+)", s)]
 
-    Construct it with three arguments, the values from `sys.exc_info`.
+def human_sorted(strings):
+    """Sort the given iterable of strings the way that humans expect.
+
+    Numeric components in the strings are sorted as numbers.
+
+    Returns the sorted list.
 
     """
-    pass
-
-
-class StopEverything(BaseCoverageException):
-    """An exception that means everything should stop.
+    return sorted(strings, key=human_key)
 
-    The CoverageTest class converts these to SkipTest, so that when running
-    tests, raising this exception will automatically skip the test.
+def human_sorted_items(items, reverse=False):
+    """Sort the (string, value) items the way humans expect.
 
+    Returns the sorted list of items.
     """
-    pass
+    return sorted(items, key=lambda pair: (human_key(pair[0]), pair[1]), reverse=reverse)

eric ide

mercurial