eric7/DebugClients/Python/coverage/control.py

branch
eric7
changeset 8775
0802ae193343
parent 8527
2bd1325d727e
child 8929
fcca2fa618bf
--- a/eric7/DebugClients/Python/coverage/control.py	Fri Nov 19 19:28:47 2021 +0100
+++ b/eric7/DebugClients/Python/coverage/control.py	Sat Nov 20 16:47:38 2021 +0100
@@ -11,27 +11,28 @@
 import platform
 import sys
 import time
+import warnings
 
 from coverage import env
 from coverage.annotate import AnnotateReporter
-from coverage.backward import string_class, iitems
 from coverage.collector import Collector, CTracer
 from coverage.config import read_coverage_config
 from coverage.context import should_start_context_test_function, combine_context_switchers
 from coverage.data import CoverageData, combine_parallel_data
 from coverage.debug import DebugControl, short_stack, write_formatted_info
 from coverage.disposition import disposition_debug_msg
+from coverage.exceptions import CoverageException, CoverageWarning
 from coverage.files import PathAliases, abs_file, relative_filename, set_relative_directory
 from coverage.html import HtmlReporter
 from coverage.inorout import InOrOut
 from coverage.jsonreport import JsonReporter
-from coverage.misc import CoverageException, bool_or_none, join_regex
+from coverage.misc import bool_or_none, join_regex, human_sorted, human_sorted_items
 from coverage.misc import DefaultValue, ensure_dir_for_file, isolate_module
 from coverage.plugin import FileReporter
 from coverage.plugin_support import Plugins
 from coverage.python import PythonFileReporter
 from coverage.report import render_report
-from coverage.results import Analysis, Numbers
+from coverage.results import Analysis
 from coverage.summary import SummaryReporter
 from coverage.xmlreport import XmlReporter
 
@@ -61,7 +62,7 @@
 
 _DEFAULT_DATAFILE = DefaultValue("MISSING")
 
-class Coverage(object):
+class Coverage:
     """Programmatic access to coverage.py.
 
     To use::
@@ -102,6 +103,7 @@
         auto_data=False, timid=None, branch=None, config_file=True,
         source=None, source_pkgs=None, omit=None, include=None, debug=None,
         concurrency=None, check_preimported=False, context=None,
+        messages=False,
     ):  # pylint: disable=too-many-arguments
         """
         Many of these arguments duplicate and override values that can be
@@ -172,6 +174,9 @@
         `context` is a string to use as the :ref:`static context
         <static_contexts>` label for collected data.
 
+        If `messages` is true, some messages will be printed to stdout
+        indicating what is happening.
+
         .. versionadded:: 4.0
             The `concurrency` parameter.
 
@@ -184,6 +189,9 @@
         .. versionadded:: 5.3
             The `source_pkgs` parameter.
 
+        .. versionadded:: 6.0
+            The `messages` parameter.
+
         """
         # data_file=None means no disk file at all. data_file missing means
         # use the value from the config file.
@@ -191,15 +199,7 @@
         if data_file is _DEFAULT_DATAFILE:
             data_file = None
 
-        # Build our configuration from a number of sources.
-        self.config = read_coverage_config(
-            config_file=config_file,
-            data_file=data_file, cover_pylib=cover_pylib, timid=timid,
-            branch=branch, parallel=bool_or_none(data_suffix),
-            source=source, source_pkgs=source_pkgs, run_omit=omit, run_include=include, debug=debug,
-            report_omit=omit, report_include=include,
-            concurrency=concurrency, context=context,
-            )
+        self.config = None
 
         # This is injectable by tests.
         self._debug_file = None
@@ -212,6 +212,7 @@
         self._warn_unimported_source = True
         self._warn_preimported_source = check_preimported
         self._no_warn_slugs = None
+        self._messages = messages
 
         # A record of all the warnings that have been issued.
         self._warnings = []
@@ -234,6 +235,16 @@
         # Should we write the debug output?
         self._should_write_debug = True
 
+        # Build our configuration from a number of sources.
+        self.config = read_coverage_config(
+            config_file=config_file, warn=self._warn,
+            data_file=data_file, cover_pylib=cover_pylib, timid=timid,
+            branch=branch, parallel=bool_or_none(data_suffix),
+            source=source, source_pkgs=source_pkgs, run_omit=omit, run_include=include, debug=debug,
+            report_omit=omit, report_include=include,
+            concurrency=concurrency, context=context,
+            )
+
         # If we have sub-process measurement happening automatically, then we
         # want any explicit creation of a Coverage object to mean, this process
         # is already coverage-aware, so don't auto-measure it.  By now, the
@@ -291,14 +302,14 @@
         # '[run] _crash' will raise an exception if the value is close by in
         # the call stack, for testing error handling.
         if self.config._crash and self.config._crash in short_stack(limit=4):
-            raise Exception("Crashing because called by {}".format(self.config._crash))
+            raise Exception(f"Crashing because called by {self.config._crash}")
 
     def _write_startup_debug(self):
         """Write out debug info at startup if needed."""
         wrote_any = False
         with self._debug.without_callers():
             if self._debug.should('config'):
-                config_info = sorted(self.config.__dict__.items())
+                config_info = human_sorted_items(self.config.__dict__.items())
                 config_info = [(k, v) for k, v in config_info if not k.startswith('_')]
                 write_formatted_info(self._debug, "config", config_info)
                 wrote_any = True
@@ -334,9 +345,9 @@
         reason = self._inorout.check_include_omit_etc(filename, frame)
         if self._debug.should('trace'):
             if not reason:
-                msg = "Including %r" % (filename,)
+                msg = f"Including {filename!r}"
             else:
-                msg = "Not including %r: %s" % (filename, reason)
+                msg = f"Not including {filename!r}: {reason}"
             self._debug.write(msg)
 
         return not reason
@@ -351,22 +362,29 @@
 
         """
         if self._no_warn_slugs is None:
-            self._no_warn_slugs = list(self.config.disable_warnings)
+            if self.config is not None:
+                self._no_warn_slugs = list(self.config.disable_warnings)
 
-        if slug in self._no_warn_slugs:
-            # Don't issue the warning
-            return
+        if self._no_warn_slugs is not None:
+            if slug in self._no_warn_slugs:
+                # Don't issue the warning
+                return
 
         self._warnings.append(msg)
         if slug:
-            msg = "%s (%s)" % (msg, slug)
-        if self._debug.should('pid'):
-            msg = "[%d] %s" % (os.getpid(), msg)
-        sys.stderr.write("Coverage.py warning: %s\n" % msg)
+            msg = f"{msg} ({slug})"
+        if self._debug is not None and self._debug.should('pid'):
+            msg = f"[{os.getpid()}] {msg}"
+        warnings.warn(msg, category=CoverageWarning, stacklevel=2)
 
         if once:
             self._no_warn_slugs.append(slug)
 
+    def _message(self, msg):
+        """Write a message to the user, if configured to do so."""
+        if self._messages:
+            print(msg)
+
     def get_option(self, option_name):
         """Get an option from the configuration.
 
@@ -442,9 +460,7 @@
         elif dycon == "test_function":
             context_switchers = [should_start_context_test_function]
         else:
-            raise CoverageException(
-                "Don't understand dynamic_context setting: {!r}".format(dycon)
-            )
+            raise CoverageException(f"Don't understand dynamic_context setting: {dycon!r}")
 
         context_switchers.extend(
             plugin.dynamic_context for plugin in self._plugins.context_switchers
@@ -465,7 +481,7 @@
 
         suffix = self._data_suffix_specified
         if suffix or self.config.parallel:
-            if not isinstance(suffix, string_class):
+            if not isinstance(suffix, str):
                 # if data_suffix=True, use .machinename.pid.random
                 suffix = True
         else:
@@ -478,7 +494,7 @@
         # Early warning if we aren't going to be able to support plugins.
         if self._plugins.file_tracers and not self._collector.supports_plugins:
             self._warn(
-                "Plugin file tracers (%s) aren't supported with %s" % (
+                "Plugin file tracers ({}) aren't supported with {}".format(
                     ", ".join(
                         plugin._coverage_plugin_name
                             for plugin in self._plugins.file_tracers
@@ -562,7 +578,7 @@
     def _atexit(self):
         """Clean up on process shutdown."""
         if self._debug.should("process"):
-            self._debug.write("atexit: pid: {}, instance: {!r}".format(os.getpid(), self))
+            self._debug.write(f"atexit: pid: {os.getpid()}, instance: {self!r}")
         if self._started:
             self.stop()
         if self._auto_save:
@@ -598,9 +614,7 @@
 
         """
         if not self._started:                           # pragma: part started
-            raise CoverageException(
-                "Cannot switch context, coverage is not started"
-                )
+            raise CoverageException("Cannot switch context, coverage is not started")
 
         if self._collector.should_start_context:
             self._warn("Conflicting dynamic contexts", slug="dynamic-conflict", once=True)
@@ -692,7 +706,7 @@
 
         aliases = None
         if self.config.paths:
-            aliases = PathAliases()
+            aliases = PathAliases(relative=self.config.relative_files)
             for paths in self.config.paths.values():
                 result = paths[0]
                 for pattern in paths[1:]:
@@ -704,6 +718,7 @@
             data_paths=data_paths,
             strict=strict,
             keep=keep,
+            message=self._message,
         )
 
     def get_data(self):
@@ -798,21 +813,20 @@
         """
         # All reporting comes through here, so do reporting initialization.
         self._init()
-        Numbers.set_precision(self.config.precision)
         self._post_init()
 
         data = self.get_data()
         if not isinstance(it, FileReporter):
             it = self._get_file_reporter(it)
 
-        return Analysis(data, it, self._file_mapper)
+        return Analysis(data, self.config.precision, it, self._file_mapper)
 
     def _get_file_reporter(self, morf):
         """Get a FileReporter for a module or file name."""
         plugin = None
         file_reporter = "python"
 
-        if isinstance(morf, string_class):
+        if isinstance(morf, str):
             mapped_morf = self._file_mapper(morf)
             plugin_name = self._data.file_tracer(mapped_morf)
             if plugin_name:
@@ -822,7 +836,7 @@
                     file_reporter = plugin.file_reporter(mapped_morf)
                     if file_reporter is None:
                         raise CoverageException(
-                            "Plugin %r did not provide a file reporter for %r." % (
+                            "Plugin {!r} did not provide a file reporter for {!r}.".format(
                                 plugin._coverage_plugin_name, morf
                             )
                         )
@@ -918,6 +932,11 @@
     ):
         """Annotate a list of modules.
 
+        .. note::
+           This method has been obsoleted by more modern reporting tools,
+           including the :meth:`html_report` method.  It will be removed in a
+           future version.
+
         Each module in `morfs` is annotated.  The source is written to a new
         file, named with a ",cover" suffix, with each line prefixed with a
         marker to indicate the coverage of the line.  Covered lines have ">",
@@ -926,6 +945,9 @@
         See :meth:`report` for other arguments.
 
         """
+        print("The annotate command will be removed in a future version.")
+        print("Get in touch if you still use it: ned@nedbatchelder.com")
+
         with override_config(self,
             ignore_errors=ignore_errors, report_omit=omit,
             report_include=include, report_contexts=contexts,
@@ -969,7 +991,8 @@
             html_skip_empty=skip_empty, precision=precision,
         ):
             reporter = HtmlReporter(self)
-            return reporter.report(morfs)
+            ret = reporter.report(morfs)
+            return ret
 
     def xml_report(
         self, morfs=None, outfile=None, ignore_errors=None,
@@ -991,7 +1014,7 @@
             ignore_errors=ignore_errors, report_omit=omit, report_include=include,
             xml_output=outfile, report_contexts=contexts, skip_empty=skip_empty,
         ):
-            return render_report(self.config.xml_output, XmlReporter(self), morfs)
+            return render_report(self.config.xml_output, XmlReporter(self), morfs, self._message)
 
     def json_report(
         self, morfs=None, outfile=None, ignore_errors=None,
@@ -1015,7 +1038,7 @@
             json_output=outfile, report_contexts=contexts, json_pretty_print=pretty_print,
             json_show_contexts=show_contexts
         ):
-            return render_report(self.config.json_output, JsonReporter(self), morfs)
+            return render_report(self.config.json_output, JsonReporter(self), morfs, self._message)
 
     def sys_info(self):
         """Return a list of (key, value) pairs showing internal information."""
@@ -1036,8 +1059,8 @@
             return entries
 
         info = [
-            ('version', covmod.__version__),
-            ('coverage', covmod.__file__),
+            ('coverage_version', covmod.__version__),
+            ('coverage_module', covmod.__file__),
             ('tracer', self._collector.tracer_name() if self._collector else "-none-"),
             ('CTracer', 'available' if CTracer else "unavailable"),
             ('plugins.file_tracers', plugin_info(self._plugins.file_tracers)),
@@ -1061,10 +1084,13 @@
             ('pid', os.getpid()),
             ('cwd', os.getcwd()),
             ('path', sys.path),
-            ('environment', sorted(
-                ("%s = %s" % (k, v))
-                for k, v in iitems(os.environ)
-                if any(slug in k for slug in ("COV", "PY"))
+            ('environment', human_sorted(
+                f"{k} = {v}"
+                for k, v in os.environ.items()
+                if (
+                    any(slug in k for slug in ("COV", "PY")) or
+                    (k in ("HOME", "TEMP", "TMP"))
+                )
             )),
             ('command_line', " ".join(getattr(sys, 'argv', ['-none-']))),
             ]

eric ide

mercurial