DebugClients/Python/coverage/cmdline.py

changeset 6219
d6c795b5ce33
parent 5178
878ce843ca9f
child 6649
f1b3a73831c9
equal deleted inserted replaced
6218:bedab77d0fa3 6219:d6c795b5ce33
1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2 # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt 2 # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
3 3
4 """Command-line support for coverage.py.""" 4 """Command-line support for coverage.py."""
5
6 from __future__ import print_function
5 7
6 import glob 8 import glob
7 import optparse 9 import optparse
8 import os.path 10 import os.path
9 import sys 11 import sys
10 import textwrap 12 import textwrap
11 import traceback 13 import traceback
12 14
13 from coverage import env 15 from coverage import env
14 from coverage.collector import CTracer 16 from coverage.collector import CTracer
17 from coverage.debug import info_formatter, info_header
15 from coverage.execfile import run_python_file, run_python_module 18 from coverage.execfile import run_python_file, run_python_module
16 from coverage.misc import CoverageException, ExceptionDuringRun, NoSource 19 from coverage.misc import BaseCoverageException, ExceptionDuringRun, NoSource
17 from coverage.debug import info_formatter, info_header 20 from coverage.results import should_fail_under
18 21
19 22
20 class Opts(object): 23 class Opts(object):
21 """A namespace class for individual options we'll build parsers from.""" 24 """A namespace class for individual options we'll build parsers from."""
22 25
23 append = optparse.make_option( 26 append = optparse.make_option(
24 '-a', '--append', action='store_true', 27 '-a', '--append', action='store_true',
25 help="Append coverage data to .coverage, otherwise it is started clean with each run.", 28 help="Append coverage data to .coverage, otherwise it starts clean each time.",
26 ) 29 )
27 branch = optparse.make_option( 30 branch = optparse.make_option(
28 '', '--branch', action='store_true', 31 '', '--branch', action='store_true',
29 help="Measure branch coverage in addition to statement coverage.", 32 help="Measure branch coverage in addition to statement coverage.",
30 ) 33 )
46 directory = optparse.make_option( 49 directory = optparse.make_option(
47 '-d', '--directory', action='store', metavar="DIR", 50 '-d', '--directory', action='store', metavar="DIR",
48 help="Write the output files to DIR.", 51 help="Write the output files to DIR.",
49 ) 52 )
50 fail_under = optparse.make_option( 53 fail_under = optparse.make_option(
51 '', '--fail-under', action='store', metavar="MIN", type="int", 54 '', '--fail-under', action='store', metavar="MIN", type="float",
52 help="Exit with a status of 2 if the total coverage is less than MIN.", 55 help="Exit with a status of 2 if the total coverage is less than MIN.",
53 ) 56 )
54 help = optparse.make_option( 57 help = optparse.make_option(
55 '-h', '--help', action='store_true', 58 '-h', '--help', action='store_true',
56 help="Get help on this command.", 59 help="Get help on this command.",
214 217
215 218
216 class CmdOptionParser(CoverageOptionParser): 219 class CmdOptionParser(CoverageOptionParser):
217 """Parse one of the new-style commands for coverage.py.""" 220 """Parse one of the new-style commands for coverage.py."""
218 221
219 def __init__(self, action, options=None, defaults=None, usage=None, description=None): 222 def __init__(self, action, options, defaults=None, usage=None, description=None):
220 """Create an OptionParser for a coverage.py command. 223 """Create an OptionParser for a coverage.py command.
221 224
222 `action` is the slug to put into `options.action`. 225 `action` is the slug to put into `options.action`.
223 `options` is a list of Option's for the command. 226 `options` is a list of Option's for the command.
224 `defaults` is a dict of default value for options. 227 `defaults` is a dict of default value for options.
231 super(CmdOptionParser, self).__init__( 234 super(CmdOptionParser, self).__init__(
232 usage=usage, 235 usage=usage,
233 description=description, 236 description=description,
234 ) 237 )
235 self.set_defaults(action=action, **(defaults or {})) 238 self.set_defaults(action=action, **(defaults or {}))
236 if options: 239 self.add_options(options)
237 self.add_options(options)
238 self.cmd = action 240 self.cmd = action
239 241
240 def __eq__(self, other): 242 def __eq__(self, other):
241 # A convenience equality, so that I can put strings in unit test 243 # A convenience equality, so that I can put strings in unit test
242 # results, and they will compare equal to objects. 244 # results, and they will compare equal to objects.
243 return (other == "<CmdOptionParser:%s>" % self.cmd) 245 return (other == "<CmdOptionParser:%s>" % self.cmd)
244 246
247 __hash__ = None # This object doesn't need to be hashed.
248
245 def get_prog_name(self): 249 def get_prog_name(self):
246 """Override of an undocumented function in optparse.OptionParser.""" 250 """Override of an undocumented function in optparse.OptionParser."""
247 program_name = super(CmdOptionParser, self).get_prog_name() 251 program_name = super(CmdOptionParser, self).get_prog_name()
248 252
249 # Include the sub-command for this parser as part of the command. 253 # Include the sub-command for this parser as part of the command.
250 return "%(command)s %(subcommand)s" % {'command': program_name, 'subcommand': self.cmd} 254 return "{command} {subcommand}".format(command=program_name, subcommand=self.cmd)
251 255
252 256
253 GLOBAL_ARGS = [ 257 GLOBAL_ARGS = [
254 Opts.debug, 258 Opts.debug,
255 Opts.help, 259 Opts.help,
272 ), 276 ),
273 ), 277 ),
274 278
275 'combine': CmdOptionParser( 279 'combine': CmdOptionParser(
276 "combine", 280 "combine",
277 GLOBAL_ARGS, 281 [
278 usage="<path1> <path2> ... <pathN>", 282 Opts.append,
283 ] + GLOBAL_ARGS,
284 usage="[options] <path1> <path2> ... <pathN>",
279 description=( 285 description=(
280 "Combine data from multiple coverage files collected " 286 "Combine data from multiple coverage files collected "
281 "with 'run -p'. The combined results are written to a single " 287 "with 'run -p'. The combined results are written to a single "
282 "file representing the union of the data. The positional " 288 "file representing the union of the data. The positional "
283 "arguments are data files or directories containing data files. " 289 "arguments are data files or directories containing data files. "
297 ), 303 ),
298 ), 304 ),
299 305
300 'erase': CmdOptionParser( 306 'erase': CmdOptionParser(
301 "erase", GLOBAL_ARGS, 307 "erase", GLOBAL_ARGS,
302 usage=" ",
303 description="Erase previously collected coverage data.", 308 description="Erase previously collected coverage data.",
304 ), 309 ),
305 310
306 'help': CmdOptionParser( 311 'help': CmdOptionParser(
307 "help", GLOBAL_ARGS, 312 "help", GLOBAL_ARGS,
316 Opts.fail_under, 321 Opts.fail_under,
317 Opts.ignore_errors, 322 Opts.ignore_errors,
318 Opts.include, 323 Opts.include,
319 Opts.omit, 324 Opts.omit,
320 Opts.title, 325 Opts.title,
326 Opts.skip_covered,
321 ] + GLOBAL_ARGS, 327 ] + GLOBAL_ARGS,
322 usage="[options] [modules]", 328 usage="[options] [modules]",
323 description=( 329 description=(
324 "Create an HTML report of the coverage of the files. " 330 "Create an HTML report of the coverage of the files. "
325 "Each file gets its own page, with the source decorated to show " 331 "Each file gets its own page, with the source decorated to show "
396 self.path_exists = _path_exists or os.path.exists 402 self.path_exists = _path_exists or os.path.exists
397 self.global_option = False 403 self.global_option = False
398 404
399 self.coverage = None 405 self.coverage = None
400 406
401 self.program_name = os.path.basename(sys.argv[0]) 407 program_path = sys.argv[0]
408 if program_path.endswith(os.path.sep + '__main__.py'):
409 # The path is the main module of a package; get that path instead.
410 program_path = os.path.dirname(program_path)
411 self.program_name = os.path.basename(program_path)
402 if env.WINDOWS: 412 if env.WINDOWS:
403 # entry_points={'console_scripts':...} on Windows makes files 413 # entry_points={'console_scripts':...} on Windows makes files
404 # called coverage.exe, coverage3.exe, and coverage-3.5.exe. These 414 # called coverage.exe, coverage3.exe, and coverage-3.5.exe. These
405 # invoke coverage-script.py, coverage3-script.py, and 415 # invoke coverage-script.py, coverage3-script.py, and
406 # coverage-3.5-script.py. argv[0] is the .py file, but we want to 416 # coverage-3.5-script.py. argv[0] is the .py file, but we want to
441 451
442 # Handle help and version. 452 # Handle help and version.
443 if self.do_help(options, args, parser): 453 if self.do_help(options, args, parser):
444 return OK 454 return OK
445 455
446 # Check for conflicts and problems in the options.
447 if not self.args_ok(options, args):
448 return ERR
449
450 # We need to be able to import from the current directory, because 456 # We need to be able to import from the current directory, because
451 # plugins may try to, for example, to read Django settings. 457 # plugins may try to, for example, to read Django settings.
452 sys.path[0] = '' 458 sys.path[0] = ''
453 459
454 # Listify the list options. 460 # Listify the list options.
456 omit = unshell_list(options.omit) 462 omit = unshell_list(options.omit)
457 include = unshell_list(options.include) 463 include = unshell_list(options.include)
458 debug = unshell_list(options.debug) 464 debug = unshell_list(options.debug)
459 465
460 # Do something. 466 # Do something.
461 self.coverage = self.covpkg.coverage( 467 self.coverage = self.covpkg.Coverage(
462 data_suffix=options.parallel_mode, 468 data_suffix=options.parallel_mode,
463 cover_pylib=options.pylib, 469 cover_pylib=options.pylib,
464 timid=options.timid, 470 timid=options.timid,
465 branch=options.branch, 471 branch=options.branch,
466 config_file=options.rcfile, 472 config_file=options.rcfile,
480 486
481 elif options.action == "run": 487 elif options.action == "run":
482 return self.do_run(options, args) 488 return self.do_run(options, args)
483 489
484 elif options.action == "combine": 490 elif options.action == "combine":
485 self.coverage.load() 491 if options.append:
492 self.coverage.load()
486 data_dirs = args or None 493 data_dirs = args or None
487 self.coverage.combine(data_dirs) 494 self.coverage.combine(data_dirs, strict=True)
488 self.coverage.save() 495 self.coverage.save()
489 return OK 496 return OK
490 497
491 # Remaining actions are reporting, with some common options. 498 # Remaining actions are reporting, with some common options.
492 report_args = dict( 499 report_args = dict(
507 self.coverage.annotate( 514 self.coverage.annotate(
508 directory=options.directory, **report_args) 515 directory=options.directory, **report_args)
509 elif options.action == "html": 516 elif options.action == "html":
510 total = self.coverage.html_report( 517 total = self.coverage.html_report(
511 directory=options.directory, title=options.title, 518 directory=options.directory, title=options.title,
512 **report_args) 519 skip_covered=options.skip_covered, **report_args)
513 elif options.action == "xml": 520 elif options.action == "xml":
514 outfile = options.outfile 521 outfile = options.outfile
515 total = self.coverage.xml_report(outfile=outfile, **report_args) 522 total = self.coverage.xml_report(outfile=outfile, **report_args)
516 523
517 if total is not None: 524 if total is not None:
518 # Apply the command line fail-under options, and then use the config 525 # Apply the command line fail-under options, and then use the config
519 # value, so we can get fail_under from the config file. 526 # value, so we can get fail_under from the config file.
520 if options.fail_under is not None: 527 if options.fail_under is not None:
521 self.coverage.set_option("report:fail_under", options.fail_under) 528 self.coverage.set_option("report:fail_under", options.fail_under)
522 529
523 if self.coverage.get_option("report:fail_under"): 530 fail_under = self.coverage.get_option("report:fail_under")
524 531 precision = self.coverage.get_option("report:precision")
525 # Total needs to be rounded, but be careful of 0 and 100. 532 if should_fail_under(total, fail_under, precision):
526 if 0 < total < 1: 533 return FAIL_UNDER
527 total = 1
528 elif 99 < total < 100:
529 total = 99
530 else:
531 total = round(total)
532
533 if total >= self.coverage.get_option("report:fail_under"):
534 return OK
535 else:
536 return FAIL_UNDER
537 534
538 return OK 535 return OK
539 536
540 def help(self, error=None, topic=None, parser=None): 537 def help(self, error=None, topic=None, parser=None):
541 """Display an error message, or the named topic.""" 538 """Display an error message, or the named topic."""
542 assert error or topic or parser 539 assert error or topic or parser
543 if error: 540 if error:
544 print(error) 541 print(error, file=sys.stderr)
545 print("Use '%s help' for help." % (self.program_name,)) 542 print("Use '%s help' for help." % (self.program_name,), file=sys.stderr)
546 elif parser: 543 elif parser:
547 print(parser.format_help().strip()) 544 print(parser.format_help().strip())
548 else: 545 else:
549 help_params = dict(self.covpkg.__dict__) 546 help_params = dict(self.covpkg.__dict__)
550 help_params['program_name'] = self.program_name 547 help_params['program_name'] = self.program_name
589 self.help_fn(topic='version') 586 self.help_fn(topic='version')
590 return True 587 return True
591 588
592 return False 589 return False
593 590
594 def args_ok(self, options, args):
595 """Check for conflicts and problems in the options.
596
597 Returns True if everything is OK, or False if not.
598
599 """
600 if options.action == "run" and not args:
601 self.help_fn("Nothing to do.")
602 return False
603
604 return True
605
606 def do_run(self, options, args): 591 def do_run(self, options, args):
607 """Implementation of 'coverage run'.""" 592 """Implementation of 'coverage run'."""
593
594 if not args:
595 self.help_fn("Nothing to do.")
596 return ERR
608 597
609 if options.append and self.coverage.get_option("run:parallel"): 598 if options.append and self.coverage.get_option("run:parallel"):
610 self.help_fn("Can't append to data files in parallel mode.") 599 self.help_fn("Can't append to data files in parallel mode.")
611 return ERR 600 return ERR
601
602 if options.concurrency == "multiprocessing":
603 # Can't set other run-affecting command line options with
604 # multiprocessing.
605 for opt_name in ['branch', 'include', 'omit', 'pylib', 'source', 'timid']:
606 # As it happens, all of these options have no default, meaning
607 # they will be None if they have not been specified.
608 if getattr(options, opt_name) is not None:
609 self.help_fn(
610 "Options affecting multiprocessing must be specified "
611 "in a configuration file."
612 )
613 return ERR
612 614
613 if not self.coverage.get_option("run:parallel"): 615 if not self.coverage.get_option("run:parallel"):
614 if not options.append: 616 if not options.append:
615 self.coverage.erase() 617 self.coverage.erase()
616 618
639 641
640 def do_debug(self, args): 642 def do_debug(self, args):
641 """Implementation of 'coverage debug'.""" 643 """Implementation of 'coverage debug'."""
642 644
643 if not args: 645 if not args:
644 self.help_fn("What information would you like: data, sys?") 646 self.help_fn("What information would you like: config, data, sys?")
645 return ERR 647 return ERR
646 648
647 for info in args: 649 for info in args:
648 if info == 'sys': 650 if info == 'sys':
649 sys_info = self.coverage.sys_info() 651 sys_info = self.coverage.sys_info()
666 if plugin: 668 if plugin:
667 line += " [%s]" % plugin 669 line += " [%s]" % plugin
668 print(line) 670 print(line)
669 else: 671 else:
670 print("No data collected") 672 print("No data collected")
673 elif info == 'config':
674 print(info_header("config"))
675 config_info = self.coverage.config.__dict__.items()
676 for line in info_formatter(config_info):
677 print(" %s" % line)
671 else: 678 else:
672 self.help_fn("Don't know what you mean by %r" % info) 679 self.help_fn("Don't know what you mean by %r" % info)
673 return ERR 680 return ERR
674 681
675 return OK 682 return OK
746 status = CoverageScript().command_line(argv) 753 status = CoverageScript().command_line(argv)
747 except ExceptionDuringRun as err: 754 except ExceptionDuringRun as err:
748 # An exception was caught while running the product code. The 755 # An exception was caught while running the product code. The
749 # sys.exc_info() return tuple is packed into an ExceptionDuringRun 756 # sys.exc_info() return tuple is packed into an ExceptionDuringRun
750 # exception. 757 # exception.
751 traceback.print_exception(*err.args) 758 traceback.print_exception(*err.args) # pylint: disable=no-value-for-parameter
752 status = ERR 759 status = ERR
753 except CoverageException as err: 760 except BaseCoverageException as err:
754 # A controlled error inside coverage.py: print the message to the user. 761 # A controlled error inside coverage.py: print the message to the user.
755 print(err) 762 print(err)
756 status = ERR 763 status = ERR
757 except SystemExit as err: 764 except SystemExit as err:
758 # The user called `sys.exit()`. Exit with their argument, if any. 765 # The user called `sys.exit()`. Exit with their argument, if any.

eric ide

mercurial