DebugClients/Python/coverage/config.py

changeset 6219
d6c795b5ce33
parent 5178
878ce843ca9f
equal deleted inserted replaced
6218:bedab77d0fa3 6219:d6c795b5ce33
7 import os 7 import os
8 import re 8 import re
9 import sys 9 import sys
10 10
11 from coverage.backward import configparser, iitems, string_class 11 from coverage.backward import configparser, iitems, string_class
12 from coverage.misc import CoverageException, isolate_module 12 from coverage.misc import contract, CoverageException, isolate_module
13 13
14 os = isolate_module(os) 14 os = isolate_module(os)
15 15
16 16
17 class HandyConfigParser(configparser.RawConfigParser): 17 class HandyConfigParser(configparser.RawConfigParser):
18 """Our specialization of ConfigParser.""" 18 """Our specialization of ConfigParser."""
19 19
20 def __init__(self, section_prefix): 20 def __init__(self, our_file):
21 """Create the HandyConfigParser.
22
23 `our_file` is True if this config file is specifically for coverage,
24 False if we are examining another config file (tox.ini, setup.cfg)
25 for possible settings.
26 """
27
21 configparser.RawConfigParser.__init__(self) 28 configparser.RawConfigParser.__init__(self)
22 self.section_prefix = section_prefix 29 self.section_prefixes = ["coverage:"]
23 30 if our_file:
24 def read(self, filename): 31 self.section_prefixes.append("")
32
33 def read(self, filenames):
25 """Read a file name as UTF-8 configuration data.""" 34 """Read a file name as UTF-8 configuration data."""
26 kwargs = {} 35 kwargs = {}
27 if sys.version_info >= (3, 2): 36 if sys.version_info >= (3, 2):
28 kwargs['encoding'] = "utf-8" 37 kwargs['encoding'] = "utf-8"
29 return configparser.RawConfigParser.read(self, filename, **kwargs) 38 return configparser.RawConfigParser.read(self, filenames, **kwargs)
30 39
31 def has_option(self, section, option): 40 def has_option(self, section, option):
32 section = self.section_prefix + section 41 for section_prefix in self.section_prefixes:
33 return configparser.RawConfigParser.has_option(self, section, option) 42 real_section = section_prefix + section
43 has = configparser.RawConfigParser.has_option(self, real_section, option)
44 if has:
45 return has
46 return False
34 47
35 def has_section(self, section): 48 def has_section(self, section):
36 section = self.section_prefix + section 49 for section_prefix in self.section_prefixes:
37 return configparser.RawConfigParser.has_section(self, section) 50 real_section = section_prefix + section
51 has = configparser.RawConfigParser.has_section(self, real_section)
52 if has:
53 return real_section
54 return False
38 55
39 def options(self, section): 56 def options(self, section):
40 section = self.section_prefix + section 57 for section_prefix in self.section_prefixes:
41 return configparser.RawConfigParser.options(self, section) 58 real_section = section_prefix + section
59 if configparser.RawConfigParser.has_section(self, real_section):
60 return configparser.RawConfigParser.options(self, real_section)
61 raise configparser.NoSectionError
42 62
43 def get_section(self, section): 63 def get_section(self, section):
44 """Get the contents of a section, as a dictionary.""" 64 """Get the contents of a section, as a dictionary."""
45 d = {} 65 d = {}
46 for opt in self.options(section): 66 for opt in self.options(section):
47 d[opt] = self.get(section, opt) 67 d[opt] = self.get(section, opt)
48 return d 68 return d
49 69
50 def get(self, section, *args, **kwargs): 70 def get(self, section, option, *args, **kwargs): # pylint: disable=arguments-differ
51 """Get a value, replacing environment variables also. 71 """Get a value, replacing environment variables also.
52 72
53 The arguments are the same as `RawConfigParser.get`, but in the found 73 The arguments are the same as `RawConfigParser.get`, but in the found
54 value, ``$WORD`` or ``${WORD}`` are replaced by the value of the 74 value, ``$WORD`` or ``${WORD}`` are replaced by the value of the
55 environment variable ``WORD``. 75 environment variable ``WORD``.
56 76
57 Returns the finished value. 77 Returns the finished value.
58 78
59 """ 79 """
60 section = self.section_prefix + section 80 for section_prefix in self.section_prefixes:
61 v = configparser.RawConfigParser.get(self, section, *args, **kwargs) 81 real_section = section_prefix + section
82 if configparser.RawConfigParser.has_option(self, real_section, option):
83 break
84 else:
85 raise configparser.NoOptionError
86
87 v = configparser.RawConfigParser.get(self, real_section, option, *args, **kwargs)
62 def dollar_replace(m): 88 def dollar_replace(m):
63 """Called for each $replacement.""" 89 """Called for each $replacement."""
64 # Only one of the groups will have matched, just get its text. 90 # Only one of the groups will have matched, just get its text.
65 word = next(w for w in m.groups() if w is not None) # pragma: part covered 91 word = next(w for w in m.groups() if w is not None) # pragma: part covered
66 if word == "$": 92 if word == "$":
120 return value_list 146 return value_list
121 147
122 148
123 # The default line exclusion regexes. 149 # The default line exclusion regexes.
124 DEFAULT_EXCLUDE = [ 150 DEFAULT_EXCLUDE = [
125 r'(?i)#\s*pragma[:\s]?\s*no\s*cover', 151 r'#\s*(pragma|PRAGMA)[:\s]?\s*(no|NO)\s*(cover|COVER)',
126 ] 152 ]
127 153
128 # The default partial branch regexes, to be modified by the user. 154 # The default partial branch regexes, to be modified by the user.
129 DEFAULT_PARTIAL = [ 155 DEFAULT_PARTIAL = [
130 r'(?i)#\s*pragma[:\s]?\s*no\s*branch', 156 r'#\s*(pragma|PRAGMA)[:\s]?\s*(no|NO)\s*(branch|BRANCH)',
131 ] 157 ]
132 158
133 # The default partial branch regexes, based on Python semantics. 159 # The default partial branch regexes, based on Python semantics.
134 # These are any Python branching constructs that can't actually execute all 160 # These are any Python branching constructs that can't actually execute all
135 # their branches. 161 # their branches.
150 """Initialize the configuration attributes to their defaults.""" 176 """Initialize the configuration attributes to their defaults."""
151 # Metadata about the config. 177 # Metadata about the config.
152 self.attempted_config_files = [] 178 self.attempted_config_files = []
153 self.config_files = [] 179 self.config_files = []
154 180
181 # Defaults for [run] and [report]
182 self._include = None
183 self._omit = None
184
155 # Defaults for [run] 185 # Defaults for [run]
156 self.branch = False 186 self.branch = False
157 self.concurrency = None 187 self.concurrency = None
158 self.cover_pylib = False 188 self.cover_pylib = False
159 self.data_file = ".coverage" 189 self.data_file = ".coverage"
160 self.debug = [] 190 self.debug = []
191 self.disable_warnings = []
161 self.note = None 192 self.note = None
162 self.parallel = False 193 self.parallel = False
163 self.plugins = [] 194 self.plugins = []
164 self.source = None 195 self.source = None
196 self.run_include = None
197 self.run_omit = None
165 self.timid = False 198 self.timid = False
166 199
167 # Defaults for [report] 200 # Defaults for [report]
168 self.exclude_list = DEFAULT_EXCLUDE[:] 201 self.exclude_list = DEFAULT_EXCLUDE[:]
169 self.fail_under = 0 202 self.fail_under = 0.0
170 self.ignore_errors = False 203 self.ignore_errors = False
171 self.include = None 204 self.report_include = None
172 self.omit = None 205 self.report_omit = None
173 self.partial_always_list = DEFAULT_PARTIAL_ALWAYS[:] 206 self.partial_always_list = DEFAULT_PARTIAL_ALWAYS[:]
174 self.partial_list = DEFAULT_PARTIAL[:] 207 self.partial_list = DEFAULT_PARTIAL[:]
175 self.precision = 0 208 self.precision = 0
176 self.show_missing = False 209 self.show_missing = False
177 self.skip_covered = False 210 self.skip_covered = False
189 self.paths = {} 222 self.paths = {}
190 223
191 # Options for plugins 224 # Options for plugins
192 self.plugin_options = {} 225 self.plugin_options = {}
193 226
194 MUST_BE_LIST = ["omit", "include", "debug", "plugins"] 227 MUST_BE_LIST = [
228 "debug", "concurrency", "plugins",
229 "report_omit", "report_include",
230 "run_omit", "run_include",
231 ]
195 232
196 def from_args(self, **kwargs): 233 def from_args(self, **kwargs):
197 """Read config values from `kwargs`.""" 234 """Read config values from `kwargs`."""
198 for k, v in iitems(kwargs): 235 for k, v in iitems(kwargs):
199 if v is not None: 236 if v is not None:
200 if k in self.MUST_BE_LIST and isinstance(v, string_class): 237 if k in self.MUST_BE_LIST and isinstance(v, string_class):
201 v = [v] 238 v = [v]
202 setattr(self, k, v) 239 setattr(self, k, v)
203 240
204 def from_file(self, filename, section_prefix=""): 241 @contract(filename=str)
242 def from_file(self, filename, our_file):
205 """Read configuration from a .rc file. 243 """Read configuration from a .rc file.
206 244
207 `filename` is a file name to read. 245 `filename` is a file name to read.
208 246
209 Returns True or False, whether the file could be read. 247 `our_file` is True if this config file is specifically for coverage,
248 False if we are examining another config file (tox.ini, setup.cfg)
249 for possible settings.
250
251 Returns True or False, whether the file could be read, and it had some
252 coverage.py settings in it.
210 253
211 """ 254 """
212 self.attempted_config_files.append(filename) 255 self.attempted_config_files.append(filename)
213 256
214 cp = HandyConfigParser(section_prefix) 257 cp = HandyConfigParser(our_file)
215 try: 258 try:
216 files_read = cp.read(filename) 259 files_read = cp.read(filename)
217 except configparser.Error as err: 260 except configparser.Error as err:
218 raise CoverageException("Couldn't read config file %s: %s" % (filename, err)) 261 raise CoverageException("Couldn't read config file %s: %s" % (filename, err))
219 if not files_read: 262 if not files_read:
220 return False 263 return False
221 264
222 self.config_files.extend(files_read) 265 self.config_files.extend(files_read)
223 266
267 any_set = False
224 try: 268 try:
225 for option_spec in self.CONFIG_FILE_OPTIONS: 269 for option_spec in self.CONFIG_FILE_OPTIONS:
226 self._set_attr_from_config_option(cp, *option_spec) 270 was_set = self._set_attr_from_config_option(cp, *option_spec)
271 if was_set:
272 any_set = True
227 except ValueError as err: 273 except ValueError as err:
228 raise CoverageException("Couldn't read config file %s: %s" % (filename, err)) 274 raise CoverageException("Couldn't read config file %s: %s" % (filename, err))
229 275
230 # Check that there are no unrecognized options. 276 # Check that there are no unrecognized options.
231 all_options = collections.defaultdict(set) 277 all_options = collections.defaultdict(set)
232 for option_spec in self.CONFIG_FILE_OPTIONS: 278 for option_spec in self.CONFIG_FILE_OPTIONS:
233 section, option = option_spec[1].split(":") 279 section, option = option_spec[1].split(":")
234 all_options[section].add(option) 280 all_options[section].add(option)
235 281
236 for section, options in iitems(all_options): 282 for section, options in iitems(all_options):
237 if cp.has_section(section): 283 real_section = cp.has_section(section)
284 if real_section:
238 for unknown in set(cp.options(section)) - options: 285 for unknown in set(cp.options(section)) - options:
239 if section_prefix:
240 section = section_prefix + section
241 raise CoverageException( 286 raise CoverageException(
242 "Unrecognized option '[%s] %s=' in config file %s" % ( 287 "Unrecognized option '[%s] %s=' in config file %s" % (
243 section, unknown, filename 288 real_section, unknown, filename
244 ) 289 )
245 ) 290 )
246 291
247 # [paths] is special 292 # [paths] is special
248 if cp.has_section('paths'): 293 if cp.has_section('paths'):
249 for option in cp.options('paths'): 294 for option in cp.options('paths'):
250 self.paths[option] = cp.getlist('paths', option) 295 self.paths[option] = cp.getlist('paths', option)
296 any_set = True
251 297
252 # plugins can have options 298 # plugins can have options
253 for plugin in self.plugins: 299 for plugin in self.plugins:
254 if cp.has_section(plugin): 300 if cp.has_section(plugin):
255 self.plugin_options[plugin] = cp.get_section(plugin) 301 self.plugin_options[plugin] = cp.get_section(plugin)
256 302 any_set = True
257 return True 303
304 # Was this file used as a config file? If it's specifically our file,
305 # then it was used. If we're piggybacking on someone else's file,
306 # then it was only used if we found some settings in it.
307 if our_file:
308 return True
309 else:
310 return any_set
258 311
259 CONFIG_FILE_OPTIONS = [ 312 CONFIG_FILE_OPTIONS = [
260 # These are *args for _set_attr_from_config_option: 313 # These are *args for _set_attr_from_config_option:
261 # (attr, where, type_="") 314 # (attr, where, type_="")
262 # 315 #
265 # type_ is the optional type to apply, by using .getTYPE to read the 318 # type_ is the optional type to apply, by using .getTYPE to read the
266 # configuration value from the file. 319 # configuration value from the file.
267 320
268 # [run] 321 # [run]
269 ('branch', 'run:branch', 'boolean'), 322 ('branch', 'run:branch', 'boolean'),
270 ('concurrency', 'run:concurrency'), 323 ('concurrency', 'run:concurrency', 'list'),
271 ('cover_pylib', 'run:cover_pylib', 'boolean'), 324 ('cover_pylib', 'run:cover_pylib', 'boolean'),
272 ('data_file', 'run:data_file'), 325 ('data_file', 'run:data_file'),
273 ('debug', 'run:debug', 'list'), 326 ('debug', 'run:debug', 'list'),
274 ('include', 'run:include', 'list'), 327 ('disable_warnings', 'run:disable_warnings', 'list'),
275 ('note', 'run:note'), 328 ('note', 'run:note'),
276 ('omit', 'run:omit', 'list'),
277 ('parallel', 'run:parallel', 'boolean'), 329 ('parallel', 'run:parallel', 'boolean'),
278 ('plugins', 'run:plugins', 'list'), 330 ('plugins', 'run:plugins', 'list'),
331 ('run_include', 'run:include', 'list'),
332 ('run_omit', 'run:omit', 'list'),
279 ('source', 'run:source', 'list'), 333 ('source', 'run:source', 'list'),
280 ('timid', 'run:timid', 'boolean'), 334 ('timid', 'run:timid', 'boolean'),
281 335
282 # [report] 336 # [report]
283 ('exclude_list', 'report:exclude_lines', 'regexlist'), 337 ('exclude_list', 'report:exclude_lines', 'regexlist'),
284 ('fail_under', 'report:fail_under', 'int'), 338 ('fail_under', 'report:fail_under', 'float'),
285 ('ignore_errors', 'report:ignore_errors', 'boolean'), 339 ('ignore_errors', 'report:ignore_errors', 'boolean'),
286 ('include', 'report:include', 'list'),
287 ('omit', 'report:omit', 'list'),
288 ('partial_always_list', 'report:partial_branches_always', 'regexlist'), 340 ('partial_always_list', 'report:partial_branches_always', 'regexlist'),
289 ('partial_list', 'report:partial_branches', 'regexlist'), 341 ('partial_list', 'report:partial_branches', 'regexlist'),
290 ('precision', 'report:precision', 'int'), 342 ('precision', 'report:precision', 'int'),
343 ('report_include', 'report:include', 'list'),
344 ('report_omit', 'report:omit', 'list'),
291 ('show_missing', 'report:show_missing', 'boolean'), 345 ('show_missing', 'report:show_missing', 'boolean'),
292 ('skip_covered', 'report:skip_covered', 'boolean'), 346 ('skip_covered', 'report:skip_covered', 'boolean'),
347 ('sort', 'report:sort'),
293 348
294 # [html] 349 # [html]
295 ('extra_css', 'html:extra_css'), 350 ('extra_css', 'html:extra_css'),
296 ('html_dir', 'html:directory'), 351 ('html_dir', 'html:directory'),
297 ('html_title', 'html:title'), 352 ('html_title', 'html:title'),
300 ('xml_output', 'xml:output'), 355 ('xml_output', 'xml:output'),
301 ('xml_package_depth', 'xml:package_depth', 'int'), 356 ('xml_package_depth', 'xml:package_depth', 'int'),
302 ] 357 ]
303 358
304 def _set_attr_from_config_option(self, cp, attr, where, type_=''): 359 def _set_attr_from_config_option(self, cp, attr, where, type_=''):
305 """Set an attribute on self if it exists in the ConfigParser.""" 360 """Set an attribute on self if it exists in the ConfigParser.
361
362 Returns True if the attribute was set.
363
364 """
306 section, option = where.split(":") 365 section, option = where.split(":")
307 if cp.has_option(section, option): 366 if cp.has_option(section, option):
308 method = getattr(cp, 'get' + type_) 367 method = getattr(cp, 'get' + type_)
309 setattr(self, attr, method(section, option)) 368 setattr(self, attr, method(section, option))
369 return True
370 return False
310 371
311 def get_plugin_options(self, plugin): 372 def get_plugin_options(self, plugin):
312 """Get a dictionary of options for the plugin named `plugin`.""" 373 """Get a dictionary of options for the plugin named `plugin`."""
313 return self.plugin_options.get(plugin, {}) 374 return self.plugin_options.get(plugin, {})
314 375
347 section of the config file would be indicated with `"run:branch"`. 408 section of the config file would be indicated with `"run:branch"`.
348 409
349 Returns the value of the option. 410 Returns the value of the option.
350 411
351 """ 412 """
352
353 # Check all the hard-coded options. 413 # Check all the hard-coded options.
354 for option_spec in self.CONFIG_FILE_OPTIONS: 414 for option_spec in self.CONFIG_FILE_OPTIONS:
355 attr, where = option_spec[:2] 415 attr, where = option_spec[:2]
356 if where == option_name: 416 if where == option_name:
357 return getattr(self, attr) 417 return getattr(self, attr)
361 if key and plugin_name in self.plugins: 421 if key and plugin_name in self.plugins:
362 return self.plugin_options.get(plugin_name, {}).get(key) 422 return self.plugin_options.get(plugin_name, {}).get(key)
363 423
364 # If we get here, we didn't find the option. 424 # If we get here, we didn't find the option.
365 raise CoverageException("No such option: %r" % option_name) 425 raise CoverageException("No such option: %r" % option_name)
426
427
428 def read_coverage_config(config_file, **kwargs):
429 """Read the coverage.py configuration.
430
431 Arguments:
432 config_file: a boolean or string, see the `Coverage` class for the
433 tricky details.
434 all others: keyword arguments from the `Coverage` class, used for
435 setting values in the configuration.
436
437 Returns:
438 config_file, config:
439 config_file is the value to use for config_file in other
440 invocations of coverage.
441
442 config is a CoverageConfig object read from the appropriate
443 configuration file.
444
445 """
446 # Build the configuration from a number of sources:
447 # 1) defaults:
448 config = CoverageConfig()
449
450 # 2) from a file:
451 if config_file:
452 # Some API users were specifying ".coveragerc" to mean the same as
453 # True, so make it so.
454 if config_file == ".coveragerc":
455 config_file = True
456 specified_file = (config_file is not True)
457 if not specified_file:
458 config_file = ".coveragerc"
459
460 for fname, our_file in [(config_file, True),
461 ("setup.cfg", False),
462 ("tox.ini", False)]:
463 config_read = config.from_file(fname, our_file=our_file)
464 is_config_file = fname == config_file
465
466 if not config_read and is_config_file and specified_file:
467 raise CoverageException("Couldn't read '%s' as a config file" % fname)
468
469 if config_read:
470 break
471
472 # 3) from environment variables:
473 env_data_file = os.environ.get('COVERAGE_FILE')
474 if env_data_file:
475 config.data_file = env_data_file
476 debugs = os.environ.get('COVERAGE_DEBUG')
477 if debugs:
478 config.debug.extend(d.strip() for d in debugs.split(","))
479
480 # 4) from constructor arguments:
481 config.from_args(**kwargs)
482
483 # Once all the config has been collected, there's a little post-processing
484 # to do.
485 config.data_file = os.path.expanduser(config.data_file)
486 config.html_dir = os.path.expanduser(config.html_dir)
487 config.xml_output = os.path.expanduser(config.xml_output)
488
489 return config_file, config

eric ide

mercurial