DebugClients/Python/coverage/config.py

changeset 6219
d6c795b5ce33
parent 5178
878ce843ca9f
--- a/DebugClients/Python/coverage/config.py	Sat Apr 07 13:17:06 2018 +0200
+++ b/DebugClients/Python/coverage/config.py	Sat Apr 07 13:35:10 2018 +0200
@@ -9,7 +9,7 @@
 import sys
 
 from coverage.backward import configparser, iitems, string_class
-from coverage.misc import CoverageException, isolate_module
+from coverage.misc import contract, CoverageException, isolate_module
 
 os = isolate_module(os)
 
@@ -17,28 +17,48 @@
 class HandyConfigParser(configparser.RawConfigParser):
     """Our specialization of ConfigParser."""
 
-    def __init__(self, section_prefix):
+    def __init__(self, our_file):
+        """Create the HandyConfigParser.
+
+        `our_file` is True if this config file is specifically for coverage,
+        False if we are examining another config file (tox.ini, setup.cfg)
+        for possible settings.
+        """
+
         configparser.RawConfigParser.__init__(self)
-        self.section_prefix = section_prefix
+        self.section_prefixes = ["coverage:"]
+        if our_file:
+            self.section_prefixes.append("")
 
-    def read(self, filename):
+    def read(self, filenames):
         """Read a file name as UTF-8 configuration data."""
         kwargs = {}
         if sys.version_info >= (3, 2):
             kwargs['encoding'] = "utf-8"
-        return configparser.RawConfigParser.read(self, filename, **kwargs)
+        return configparser.RawConfigParser.read(self, filenames, **kwargs)
 
     def has_option(self, section, option):
-        section = self.section_prefix + section
-        return configparser.RawConfigParser.has_option(self, section, option)
+        for section_prefix in self.section_prefixes:
+            real_section = section_prefix + section
+            has = configparser.RawConfigParser.has_option(self, real_section, option)
+            if has:
+                return has
+        return False
 
     def has_section(self, section):
-        section = self.section_prefix + section
-        return configparser.RawConfigParser.has_section(self, section)
+        for section_prefix in self.section_prefixes:
+            real_section = section_prefix + section
+            has = configparser.RawConfigParser.has_section(self, real_section)
+            if has:
+                return real_section
+        return False
 
     def options(self, section):
-        section = self.section_prefix + section
-        return configparser.RawConfigParser.options(self, section)
+        for section_prefix in self.section_prefixes:
+            real_section = section_prefix + section
+            if configparser.RawConfigParser.has_section(self, real_section):
+                return configparser.RawConfigParser.options(self, real_section)
+        raise configparser.NoSectionError
 
     def get_section(self, section):
         """Get the contents of a section, as a dictionary."""
@@ -47,7 +67,7 @@
             d[opt] = self.get(section, opt)
         return d
 
-    def get(self, section, *args, **kwargs):
+    def get(self, section, option, *args, **kwargs):        # pylint: disable=arguments-differ
         """Get a value, replacing environment variables also.
 
         The arguments are the same as `RawConfigParser.get`, but in the found
@@ -57,8 +77,14 @@
         Returns the finished value.
 
         """
-        section = self.section_prefix + section
-        v = configparser.RawConfigParser.get(self, section, *args, **kwargs)
+        for section_prefix in self.section_prefixes:
+            real_section = section_prefix + section
+            if configparser.RawConfigParser.has_option(self, real_section, option):
+                break
+        else:
+            raise configparser.NoOptionError
+
+        v = configparser.RawConfigParser.get(self, real_section, option, *args, **kwargs)
         def dollar_replace(m):
             """Called for each $replacement."""
             # Only one of the groups will have matched, just get its text.
@@ -122,12 +148,12 @@
 
 # The default line exclusion regexes.
 DEFAULT_EXCLUDE = [
-    r'(?i)#\s*pragma[:\s]?\s*no\s*cover',
+    r'#\s*(pragma|PRAGMA)[:\s]?\s*(no|NO)\s*(cover|COVER)',
 ]
 
 # The default partial branch regexes, to be modified by the user.
 DEFAULT_PARTIAL = [
-    r'(?i)#\s*pragma[:\s]?\s*no\s*branch',
+    r'#\s*(pragma|PRAGMA)[:\s]?\s*(no|NO)\s*(branch|BRANCH)',
 ]
 
 # The default partial branch regexes, based on Python semantics.
@@ -152,24 +178,31 @@
         self.attempted_config_files = []
         self.config_files = []
 
+        # Defaults for [run] and [report]
+        self._include = None
+        self._omit = None
+
         # Defaults for [run]
         self.branch = False
         self.concurrency = None
         self.cover_pylib = False
         self.data_file = ".coverage"
         self.debug = []
+        self.disable_warnings = []
         self.note = None
         self.parallel = False
         self.plugins = []
         self.source = None
+        self.run_include = None
+        self.run_omit = None
         self.timid = False
 
         # Defaults for [report]
         self.exclude_list = DEFAULT_EXCLUDE[:]
-        self.fail_under = 0
+        self.fail_under = 0.0
         self.ignore_errors = False
-        self.include = None
-        self.omit = None
+        self.report_include = None
+        self.report_omit = None
         self.partial_always_list = DEFAULT_PARTIAL_ALWAYS[:]
         self.partial_list = DEFAULT_PARTIAL[:]
         self.precision = 0
@@ -191,7 +224,11 @@
         # Options for plugins
         self.plugin_options = {}
 
-    MUST_BE_LIST = ["omit", "include", "debug", "plugins"]
+    MUST_BE_LIST = [
+        "debug", "concurrency", "plugins",
+        "report_omit", "report_include",
+        "run_omit", "run_include",
+    ]
 
     def from_args(self, **kwargs):
         """Read config values from `kwargs`."""
@@ -201,17 +238,23 @@
                     v = [v]
                 setattr(self, k, v)
 
-    def from_file(self, filename, section_prefix=""):
+    @contract(filename=str)
+    def from_file(self, filename, our_file):
         """Read configuration from a .rc file.
 
         `filename` is a file name to read.
 
-        Returns True or False, whether the file could be read.
+        `our_file` is True if this config file is specifically for coverage,
+        False if we are examining another config file (tox.ini, setup.cfg)
+        for possible settings.
+
+        Returns True or False, whether the file could be read, and it had some
+        coverage.py settings in it.
 
         """
         self.attempted_config_files.append(filename)
 
-        cp = HandyConfigParser(section_prefix)
+        cp = HandyConfigParser(our_file)
         try:
             files_read = cp.read(filename)
         except configparser.Error as err:
@@ -221,9 +264,12 @@
 
         self.config_files.extend(files_read)
 
+        any_set = False
         try:
             for option_spec in self.CONFIG_FILE_OPTIONS:
-                self._set_attr_from_config_option(cp, *option_spec)
+                was_set = self._set_attr_from_config_option(cp, *option_spec)
+                if was_set:
+                    any_set = True
         except ValueError as err:
             raise CoverageException("Couldn't read config file %s: %s" % (filename, err))
 
@@ -234,13 +280,12 @@
             all_options[section].add(option)
 
         for section, options in iitems(all_options):
-            if cp.has_section(section):
+            real_section = cp.has_section(section)
+            if real_section:
                 for unknown in set(cp.options(section)) - options:
-                    if section_prefix:
-                        section = section_prefix + section
                     raise CoverageException(
                         "Unrecognized option '[%s] %s=' in config file %s" % (
-                            section, unknown, filename
+                            real_section, unknown, filename
                         )
                     )
 
@@ -248,13 +293,21 @@
         if cp.has_section('paths'):
             for option in cp.options('paths'):
                 self.paths[option] = cp.getlist('paths', option)
+                any_set = True
 
         # plugins can have options
         for plugin in self.plugins:
             if cp.has_section(plugin):
                 self.plugin_options[plugin] = cp.get_section(plugin)
+                any_set = True
 
-        return True
+        # Was this file used as a config file? If it's specifically our file,
+        # then it was used.  If we're piggybacking on someone else's file,
+        # then it was only used if we found some settings in it.
+        if our_file:
+            return True
+        else:
+            return any_set
 
     CONFIG_FILE_OPTIONS = [
         # These are *args for _set_attr_from_config_option:
@@ -267,29 +320,31 @@
 
         # [run]
         ('branch', 'run:branch', 'boolean'),
-        ('concurrency', 'run:concurrency'),
+        ('concurrency', 'run:concurrency', 'list'),
         ('cover_pylib', 'run:cover_pylib', 'boolean'),
         ('data_file', 'run:data_file'),
         ('debug', 'run:debug', 'list'),
-        ('include', 'run:include', 'list'),
+        ('disable_warnings', 'run:disable_warnings', 'list'),
         ('note', 'run:note'),
-        ('omit', 'run:omit', 'list'),
         ('parallel', 'run:parallel', 'boolean'),
         ('plugins', 'run:plugins', 'list'),
+        ('run_include', 'run:include', 'list'),
+        ('run_omit', 'run:omit', 'list'),
         ('source', 'run:source', 'list'),
         ('timid', 'run:timid', 'boolean'),
 
         # [report]
         ('exclude_list', 'report:exclude_lines', 'regexlist'),
-        ('fail_under', 'report:fail_under', 'int'),
+        ('fail_under', 'report:fail_under', 'float'),
         ('ignore_errors', 'report:ignore_errors', 'boolean'),
-        ('include', 'report:include', 'list'),
-        ('omit', 'report:omit', 'list'),
         ('partial_always_list', 'report:partial_branches_always', 'regexlist'),
         ('partial_list', 'report:partial_branches', 'regexlist'),
         ('precision', 'report:precision', 'int'),
+        ('report_include', 'report:include', 'list'),
+        ('report_omit', 'report:omit', 'list'),
         ('show_missing', 'report:show_missing', 'boolean'),
         ('skip_covered', 'report:skip_covered', 'boolean'),
+        ('sort', 'report:sort'),
 
         # [html]
         ('extra_css', 'html:extra_css'),
@@ -302,11 +357,17 @@
     ]
 
     def _set_attr_from_config_option(self, cp, attr, where, type_=''):
-        """Set an attribute on self if it exists in the ConfigParser."""
+        """Set an attribute on self if it exists in the ConfigParser.
+
+        Returns True if the attribute was set.
+
+        """
         section, option = where.split(":")
         if cp.has_option(section, option):
             method = getattr(cp, 'get' + type_)
             setattr(self, attr, method(section, option))
+            return True
+        return False
 
     def get_plugin_options(self, plugin):
         """Get a dictionary of options for the plugin named `plugin`."""
@@ -349,7 +410,6 @@
         Returns the value of the option.
 
         """
-
         # Check all the hard-coded options.
         for option_spec in self.CONFIG_FILE_OPTIONS:
             attr, where = option_spec[:2]
@@ -363,3 +423,67 @@
 
         # If we get here, we didn't find the option.
         raise CoverageException("No such option: %r" % option_name)
+
+
+def read_coverage_config(config_file, **kwargs):
+    """Read the coverage.py configuration.
+
+    Arguments:
+        config_file: a boolean or string, see the `Coverage` class for the
+            tricky details.
+        all others: keyword arguments from the `Coverage` class, used for
+            setting values in the configuration.
+
+    Returns:
+        config_file, config:
+            config_file is the value to use for config_file in other
+            invocations of coverage.
+
+            config is a CoverageConfig object read from the appropriate
+            configuration file.
+
+    """
+    # Build the configuration from a number of sources:
+    # 1) defaults:
+    config = CoverageConfig()
+
+    # 2) from a file:
+    if config_file:
+        # Some API users were specifying ".coveragerc" to mean the same as
+        # True, so make it so.
+        if config_file == ".coveragerc":
+            config_file = True
+        specified_file = (config_file is not True)
+        if not specified_file:
+            config_file = ".coveragerc"
+
+        for fname, our_file in [(config_file, True),
+                                ("setup.cfg", False),
+                                ("tox.ini", False)]:
+            config_read = config.from_file(fname, our_file=our_file)
+            is_config_file = fname == config_file
+
+            if not config_read and is_config_file and specified_file:
+                raise CoverageException("Couldn't read '%s' as a config file" % fname)
+
+            if config_read:
+                break
+
+    # 3) from environment variables:
+    env_data_file = os.environ.get('COVERAGE_FILE')
+    if env_data_file:
+        config.data_file = env_data_file
+    debugs = os.environ.get('COVERAGE_DEBUG')
+    if debugs:
+        config.debug.extend(d.strip() for d in debugs.split(","))
+
+    # 4) from constructor arguments:
+    config.from_args(**kwargs)
+
+    # Once all the config has been collected, there's a little post-processing
+    # to do.
+    config.data_file = os.path.expanduser(config.data_file)
+    config.html_dir = os.path.expanduser(config.html_dir)
+    config.xml_output = os.path.expanduser(config.xml_output)
+
+    return config_file, config

eric ide

mercurial