8 import copy |
8 import copy |
9 import os |
9 import os |
10 import os.path |
10 import os.path |
11 import re |
11 import re |
12 |
12 |
13 from coverage.exceptions import CoverageException |
13 from coverage.exceptions import ConfigError |
14 from coverage.misc import contract, isolate_module, substitute_variables |
14 from coverage.misc import contract, isolate_module, substitute_variables |
15 |
15 |
16 from coverage.tomlconfig import TomlConfigParser, TomlDecodeError |
16 from coverage.tomlconfig import TomlConfigParser, TomlDecodeError |
17 |
17 |
18 os = isolate_module(os) |
18 os = isolate_module(os) |
57 def options(self, section): |
57 def options(self, section): |
58 for section_prefix in self.section_prefixes: |
58 for section_prefix in self.section_prefixes: |
59 real_section = section_prefix + section |
59 real_section = section_prefix + section |
60 if configparser.RawConfigParser.has_section(self, real_section): |
60 if configparser.RawConfigParser.has_section(self, real_section): |
61 return configparser.RawConfigParser.options(self, real_section) |
61 return configparser.RawConfigParser.options(self, real_section) |
62 raise configparser.NoSectionError(section) |
62 raise ConfigError(f"No section: {section!r}") |
63 |
63 |
64 def get_section(self, section): |
64 def get_section(self, section): |
65 """Get the contents of a section, as a dictionary.""" |
65 """Get the contents of a section, as a dictionary.""" |
66 d = {} |
66 d = {} |
67 for opt in self.options(section): |
67 for opt in self.options(section): |
81 for section_prefix in self.section_prefixes: |
81 for section_prefix in self.section_prefixes: |
82 real_section = section_prefix + section |
82 real_section = section_prefix + section |
83 if configparser.RawConfigParser.has_option(self, real_section, option): |
83 if configparser.RawConfigParser.has_option(self, real_section, option): |
84 break |
84 break |
85 else: |
85 else: |
86 raise configparser.NoOptionError(option, section) |
86 raise ConfigError(f"No option {option!r} in section: {section!r}") |
87 |
87 |
88 v = configparser.RawConfigParser.get(self, real_section, option, *args, **kwargs) |
88 v = configparser.RawConfigParser.get(self, real_section, option, *args, **kwargs) |
89 v = substitute_variables(v, os.environ) |
89 v = substitute_variables(v, os.environ) |
90 return v |
90 return v |
91 |
91 |
121 for value in line_list.splitlines(): |
121 for value in line_list.splitlines(): |
122 value = value.strip() |
122 value = value.strip() |
123 try: |
123 try: |
124 re.compile(value) |
124 re.compile(value) |
125 except re.error as e: |
125 except re.error as e: |
126 raise CoverageException( |
126 raise ConfigError( |
127 f"Invalid [{section}].{option} value {value!r}: {e}" |
127 f"Invalid [{section}].{option} value {value!r}: {e}" |
128 ) from e |
128 ) from e |
129 if value: |
129 if value: |
130 value_list.append(value) |
130 value_list.append(value) |
131 return value_list |
131 return value_list |
231 self.paths = collections.OrderedDict() |
231 self.paths = collections.OrderedDict() |
232 |
232 |
233 # Options for plugins |
233 # Options for plugins |
234 self.plugin_options = {} |
234 self.plugin_options = {} |
235 |
235 |
236 MUST_BE_LIST = [ |
236 MUST_BE_LIST = { |
237 "debug", "concurrency", "plugins", |
237 "debug", "concurrency", "plugins", |
238 "report_omit", "report_include", |
238 "report_omit", "report_include", |
239 "run_omit", "run_include", |
239 "run_omit", "run_include", |
240 ] |
240 } |
241 |
241 |
242 def from_args(self, **kwargs): |
242 def from_args(self, **kwargs): |
243 """Read config values from `kwargs`.""" |
243 """Read config values from `kwargs`.""" |
244 for k, v in kwargs.items(): |
244 for k, v in kwargs.items(): |
245 if v is not None: |
245 if v is not None: |
270 self.attempted_config_files.append(filename) |
270 self.attempted_config_files.append(filename) |
271 |
271 |
272 try: |
272 try: |
273 files_read = cp.read(filename) |
273 files_read = cp.read(filename) |
274 except (configparser.Error, TomlDecodeError) as err: |
274 except (configparser.Error, TomlDecodeError) as err: |
275 raise CoverageException(f"Couldn't read config file {filename}: {err}") from err |
275 raise ConfigError(f"Couldn't read config file {filename}: {err}") from err |
276 if not files_read: |
276 if not files_read: |
277 return False |
277 return False |
278 |
278 |
279 self.config_files_read.extend(map(os.path.abspath, files_read)) |
279 self.config_files_read.extend(map(os.path.abspath, files_read)) |
280 |
280 |
283 for option_spec in self.CONFIG_FILE_OPTIONS: |
283 for option_spec in self.CONFIG_FILE_OPTIONS: |
284 was_set = self._set_attr_from_config_option(cp, *option_spec) |
284 was_set = self._set_attr_from_config_option(cp, *option_spec) |
285 if was_set: |
285 if was_set: |
286 any_set = True |
286 any_set = True |
287 except ValueError as err: |
287 except ValueError as err: |
288 raise CoverageException(f"Couldn't read config file {filename}: {err}") from err |
288 raise ConfigError(f"Couldn't read config file {filename}: {err}") from err |
289 |
289 |
290 # Check that there are no unrecognized options. |
290 # Check that there are no unrecognized options. |
291 all_options = collections.defaultdict(set) |
291 all_options = collections.defaultdict(set) |
292 for option_spec in self.CONFIG_FILE_OPTIONS: |
292 for option_spec in self.CONFIG_FILE_OPTIONS: |
293 section, option = option_spec[1].split(":") |
293 section, option = option_spec[1].split(":") |
331 return used |
331 return used |
332 |
332 |
333 def copy(self): |
333 def copy(self): |
334 """Return a copy of the configuration.""" |
334 """Return a copy of the configuration.""" |
335 return copy.deepcopy(self) |
335 return copy.deepcopy(self) |
|
336 |
|
337 CONCURRENCY_CHOICES = {"thread", "gevent", "greenlet", "eventlet", "multiprocessing"} |
336 |
338 |
337 CONFIG_FILE_OPTIONS = [ |
339 CONFIG_FILE_OPTIONS = [ |
338 # These are *args for _set_attr_from_config_option: |
340 # These are *args for _set_attr_from_config_option: |
339 # (attr, where, type_="") |
341 # (attr, where, type_="") |
340 # |
342 # |
441 if key and plugin_name in self.plugins: |
443 if key and plugin_name in self.plugins: |
442 self.plugin_options.setdefault(plugin_name, {})[key] = value |
444 self.plugin_options.setdefault(plugin_name, {})[key] = value |
443 return |
445 return |
444 |
446 |
445 # If we get here, we didn't find the option. |
447 # If we get here, we didn't find the option. |
446 raise CoverageException(f"No such option: {option_name!r}") |
448 raise ConfigError(f"No such option: {option_name!r}") |
447 |
449 |
448 def get_option(self, option_name): |
450 def get_option(self, option_name): |
449 """Get an option from the configuration. |
451 """Get an option from the configuration. |
450 |
452 |
451 `option_name` is a colon-separated string indicating the section and |
453 `option_name` is a colon-separated string indicating the section and |
469 plugin_name, _, key = option_name.partition(":") |
471 plugin_name, _, key = option_name.partition(":") |
470 if key and plugin_name in self.plugins: |
472 if key and plugin_name in self.plugins: |
471 return self.plugin_options.get(plugin_name, {}).get(key) |
473 return self.plugin_options.get(plugin_name, {}).get(key) |
472 |
474 |
473 # If we get here, we didn't find the option. |
475 # If we get here, we didn't find the option. |
474 raise CoverageException(f"No such option: {option_name!r}") |
476 raise ConfigError(f"No such option: {option_name!r}") |
475 |
477 |
476 def post_process_file(self, path): |
478 def post_process_file(self, path): |
477 """Make final adjustments to a file path to make it usable.""" |
479 """Make final adjustments to a file path to make it usable.""" |
478 return os.path.expanduser(path) |
480 return os.path.expanduser(path) |
479 |
481 |
544 for fname, our_file, specified_file in files_to_try: |
546 for fname, our_file, specified_file in files_to_try: |
545 config_read = config.from_file(fname, warn, our_file=our_file) |
547 config_read = config.from_file(fname, warn, our_file=our_file) |
546 if config_read: |
548 if config_read: |
547 break |
549 break |
548 if specified_file: |
550 if specified_file: |
549 raise CoverageException(f"Couldn't read {fname!r} as a config file") |
551 raise ConfigError(f"Couldn't read {fname!r} as a config file") |
550 |
552 |
551 # $set_env.py: COVERAGE_DEBUG - Options for --debug. |
553 # $set_env.py: COVERAGE_DEBUG - Options for --debug. |
552 # 3) from environment variables: |
554 # 3) from environment variables: |
553 env_data_file = os.environ.get('COVERAGE_FILE') |
555 env_data_file = os.environ.get('COVERAGE_FILE') |
554 if env_data_file: |
556 if env_data_file: |