Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / pycoverage / coverage / cmdline.py
blobea112a8b8f2d425be141bdde38a9603d9c685b34
1 """Command-line support for Coverage."""
3 import optparse, os, sys, time, traceback
5 from coverage.backward import sorted # pylint: disable=W0622
6 from coverage.execfile import run_python_file, run_python_module
7 from coverage.misc import CoverageException, ExceptionDuringRun, NoSource
8 from coverage.debug import info_formatter
11 class Opts(object):
12 """A namespace class for individual options we'll build parsers from."""
14 append = optparse.make_option(
15 '-a', '--append', action='store_false', dest="erase_first",
16 help="Append coverage data to .coverage, otherwise it is started "
17 "clean with each run."
19 branch = optparse.make_option(
20 '', '--branch', action='store_true',
21 help="Measure branch coverage in addition to statement coverage."
23 debug = optparse.make_option(
24 '', '--debug', action='store', metavar="OPTS",
25 help="Debug options, separated by commas"
27 directory = optparse.make_option(
28 '-d', '--directory', action='store', metavar="DIR",
29 help="Write the output files to DIR."
31 fail_under = optparse.make_option(
32 '', '--fail-under', action='store', metavar="MIN", type="int",
33 help="Exit with a status of 2 if the total coverage is less than MIN."
35 help = optparse.make_option(
36 '-h', '--help', action='store_true',
37 help="Get help on this command."
39 ignore_errors = optparse.make_option(
40 '-i', '--ignore-errors', action='store_true',
41 help="Ignore errors while reading source files."
43 include = optparse.make_option(
44 '', '--include', action='store',
45 metavar="PAT1,PAT2,...",
46 help="Include files only when their filename path matches one of "
47 "these patterns. Usually needs quoting on the command line."
49 pylib = optparse.make_option(
50 '-L', '--pylib', action='store_true',
51 help="Measure coverage even inside the Python installed library, "
52 "which isn't done by default."
54 show_missing = optparse.make_option(
55 '-m', '--show-missing', action='store_true',
56 help="Show line numbers of statements in each module that weren't "
57 "executed."
59 old_omit = optparse.make_option(
60 '-o', '--omit', action='store',
61 metavar="PAT1,PAT2,...",
62 help="Omit files when their filename matches one of these patterns. "
63 "Usually needs quoting on the command line."
65 omit = optparse.make_option(
66 '', '--omit', action='store',
67 metavar="PAT1,PAT2,...",
68 help="Omit files when their filename matches one of these patterns. "
69 "Usually needs quoting on the command line."
71 output_xml = optparse.make_option(
72 '-o', '', action='store', dest="outfile",
73 metavar="OUTFILE",
74 help="Write the XML report to this file. Defaults to 'coverage.xml'"
76 parallel_mode = optparse.make_option(
77 '-p', '--parallel-mode', action='store_true',
78 help="Append the machine name, process id and random number to the "
79 ".coverage data file name to simplify collecting data from "
80 "many processes."
82 module = optparse.make_option(
83 '-m', '--module', action='store_true',
84 help="<pyfile> is an importable Python module, not a script path, "
85 "to be run as 'python -m' would run it."
87 rcfile = optparse.make_option(
88 '', '--rcfile', action='store',
89 help="Specify configuration file. Defaults to '.coveragerc'"
91 source = optparse.make_option(
92 '', '--source', action='store', metavar="SRC1,SRC2,...",
93 help="A list of packages or directories of code to be measured."
95 timid = optparse.make_option(
96 '', '--timid', action='store_true',
97 help="Use a simpler but slower trace method. Try this if you get "
98 "seemingly impossible results!"
100 title = optparse.make_option(
101 '', '--title', action='store', metavar="TITLE",
102 help="A text string to use as the title on the HTML."
104 version = optparse.make_option(
105 '', '--version', action='store_true',
106 help="Display version information and exit."
110 class CoverageOptionParser(optparse.OptionParser, object):
111 """Base OptionParser for coverage.
113 Problems don't exit the program.
114 Defaults are initialized for all options.
118 def __init__(self, *args, **kwargs):
119 super(CoverageOptionParser, self).__init__(
120 add_help_option=False, *args, **kwargs
122 self.set_defaults(
123 actions=[],
124 branch=None,
125 debug=None,
126 directory=None,
127 fail_under=None,
128 help=None,
129 ignore_errors=None,
130 include=None,
131 omit=None,
132 parallel_mode=None,
133 module=None,
134 pylib=None,
135 rcfile=True,
136 show_missing=None,
137 source=None,
138 timid=None,
139 title=None,
140 erase_first=None,
141 version=None,
144 self.disable_interspersed_args()
145 self.help_fn = self.help_noop
147 def help_noop(self, error=None, topic=None, parser=None):
148 """No-op help function."""
149 pass
151 class OptionParserError(Exception):
152 """Used to stop the optparse error handler ending the process."""
153 pass
155 def parse_args(self, args=None, options=None):
156 """Call optparse.parse_args, but return a triple:
158 (ok, options, args)
161 try:
162 options, args = \
163 super(CoverageOptionParser, self).parse_args(args, options)
164 except self.OptionParserError:
165 return False, None, None
166 return True, options, args
168 def error(self, msg):
169 """Override optparse.error so sys.exit doesn't get called."""
170 self.help_fn(msg)
171 raise self.OptionParserError
174 class ClassicOptionParser(CoverageOptionParser):
175 """Command-line parser for coverage.py classic arguments."""
177 def __init__(self):
178 super(ClassicOptionParser, self).__init__()
180 self.add_action('-a', '--annotate', 'annotate')
181 self.add_action('-b', '--html', 'html')
182 self.add_action('-c', '--combine', 'combine')
183 self.add_action('-e', '--erase', 'erase')
184 self.add_action('-r', '--report', 'report')
185 self.add_action('-x', '--execute', 'execute')
187 self.add_options([
188 Opts.directory,
189 Opts.help,
190 Opts.ignore_errors,
191 Opts.pylib,
192 Opts.show_missing,
193 Opts.old_omit,
194 Opts.parallel_mode,
195 Opts.timid,
196 Opts.version,
199 def add_action(self, dash, dashdash, action_code):
200 """Add a specialized option that is the action to execute."""
201 option = self.add_option(dash, dashdash, action='callback',
202 callback=self._append_action
204 option.action_code = action_code
206 def _append_action(self, option, opt_unused, value_unused, parser):
207 """Callback for an option that adds to the `actions` list."""
208 parser.values.actions.append(option.action_code)
211 class CmdOptionParser(CoverageOptionParser):
212 """Parse one of the new-style commands for coverage.py."""
214 def __init__(self, action, options=None, defaults=None, usage=None,
215 cmd=None, description=None
217 """Create an OptionParser for a coverage command.
219 `action` is the slug to put into `options.actions`.
220 `options` is a list of Option's for the command.
221 `defaults` is a dict of default value for options.
222 `usage` is the usage string to display in help.
223 `cmd` is the command name, if different than `action`.
224 `description` is the description of the command, for the help text.
227 if usage:
228 usage = "%prog " + usage
229 super(CmdOptionParser, self).__init__(
230 prog="coverage %s" % (cmd or action),
231 usage=usage,
232 description=description,
234 self.set_defaults(actions=[action], **(defaults or {}))
235 if options:
236 self.add_options(options)
237 self.cmd = cmd or action
239 def __eq__(self, other):
240 # A convenience equality, so that I can put strings in unit test
241 # results, and they will compare equal to objects.
242 return (other == "<CmdOptionParser:%s>" % self.cmd)
244 GLOBAL_ARGS = [
245 Opts.rcfile,
246 Opts.help,
249 CMDS = {
250 'annotate': CmdOptionParser("annotate",
252 Opts.directory,
253 Opts.ignore_errors,
254 Opts.omit,
255 Opts.include,
256 ] + GLOBAL_ARGS,
257 usage = "[options] [modules]",
258 description = "Make annotated copies of the given files, marking "
259 "statements that are executed with > and statements that are "
260 "missed with !."
263 'combine': CmdOptionParser("combine", GLOBAL_ARGS,
264 usage = " ",
265 description = "Combine data from multiple coverage files collected "
266 "with 'run -p'. The combined results are written to a single "
267 "file representing the union of the data."
270 'debug': CmdOptionParser("debug", GLOBAL_ARGS,
271 usage = "<topic>",
272 description = "Display information on the internals of coverage.py, "
273 "for diagnosing problems. "
274 "Topics are 'data' to show a summary of the collected data, "
275 "or 'sys' to show installation information."
278 'erase': CmdOptionParser("erase", GLOBAL_ARGS,
279 usage = " ",
280 description = "Erase previously collected coverage data."
283 'help': CmdOptionParser("help", GLOBAL_ARGS,
284 usage = "[command]",
285 description = "Describe how to use coverage.py"
288 'html': CmdOptionParser("html",
290 Opts.directory,
291 Opts.fail_under,
292 Opts.ignore_errors,
293 Opts.omit,
294 Opts.include,
295 Opts.title,
296 ] + GLOBAL_ARGS,
297 usage = "[options] [modules]",
298 description = "Create an HTML report of the coverage of the files. "
299 "Each file gets its own page, with the source decorated to show "
300 "executed, excluded, and missed lines."
303 'report': CmdOptionParser("report",
305 Opts.fail_under,
306 Opts.ignore_errors,
307 Opts.omit,
308 Opts.include,
309 Opts.show_missing,
310 ] + GLOBAL_ARGS,
311 usage = "[options] [modules]",
312 description = "Report coverage statistics on modules."
315 'run': CmdOptionParser("execute",
317 Opts.append,
318 Opts.branch,
319 Opts.debug,
320 Opts.pylib,
321 Opts.parallel_mode,
322 Opts.module,
323 Opts.timid,
324 Opts.source,
325 Opts.omit,
326 Opts.include,
327 ] + GLOBAL_ARGS,
328 defaults = {'erase_first': True},
329 cmd = "run",
330 usage = "[options] <pyfile> [program options]",
331 description = "Run a Python program, measuring code execution."
334 'xml': CmdOptionParser("xml",
336 Opts.fail_under,
337 Opts.ignore_errors,
338 Opts.omit,
339 Opts.include,
340 Opts.output_xml,
341 ] + GLOBAL_ARGS,
342 cmd = "xml",
343 usage = "[options] [modules]",
344 description = "Generate an XML report of coverage results."
349 OK, ERR, FAIL_UNDER = 0, 1, 2
352 class CoverageScript(object):
353 """The command-line interface to Coverage."""
355 def __init__(self, _covpkg=None, _run_python_file=None,
356 _run_python_module=None, _help_fn=None):
357 # _covpkg is for dependency injection, so we can test this code.
358 if _covpkg:
359 self.covpkg = _covpkg
360 else:
361 import coverage
362 self.covpkg = coverage
364 # For dependency injection:
365 self.run_python_file = _run_python_file or run_python_file
366 self.run_python_module = _run_python_module or run_python_module
367 self.help_fn = _help_fn or self.help
368 self.classic = False
370 self.coverage = None
372 def command_line(self, argv):
373 """The bulk of the command line interface to Coverage.
375 `argv` is the argument list to process.
377 Returns 0 if all is well, 1 if something went wrong.
380 # Collect the command-line options.
381 if not argv:
382 self.help_fn(topic='minimum_help')
383 return OK
385 # The command syntax we parse depends on the first argument. Classic
386 # syntax always starts with an option.
387 self.classic = argv[0].startswith('-')
388 if self.classic:
389 parser = ClassicOptionParser()
390 else:
391 parser = CMDS.get(argv[0])
392 if not parser:
393 self.help_fn("Unknown command: '%s'" % argv[0])
394 return ERR
395 argv = argv[1:]
397 parser.help_fn = self.help_fn
398 ok, options, args = parser.parse_args(argv)
399 if not ok:
400 return ERR
402 # Handle help and version.
403 if self.do_help(options, args, parser):
404 return OK
406 # Check for conflicts and problems in the options.
407 if not self.args_ok(options, args):
408 return ERR
410 # Listify the list options.
411 source = unshell_list(options.source)
412 omit = unshell_list(options.omit)
413 include = unshell_list(options.include)
414 debug = unshell_list(options.debug)
416 # Do something.
417 self.coverage = self.covpkg.coverage(
418 data_suffix = options.parallel_mode,
419 cover_pylib = options.pylib,
420 timid = options.timid,
421 branch = options.branch,
422 config_file = options.rcfile,
423 source = source,
424 omit = omit,
425 include = include,
426 debug = debug,
429 if 'debug' in options.actions:
430 return self.do_debug(args)
432 if 'erase' in options.actions or options.erase_first:
433 self.coverage.erase()
434 else:
435 self.coverage.load()
437 if 'execute' in options.actions:
438 self.do_execute(options, args)
440 if 'combine' in options.actions:
441 self.coverage.combine()
442 self.coverage.save()
444 # Remaining actions are reporting, with some common options.
445 report_args = dict(
446 morfs = args,
447 ignore_errors = options.ignore_errors,
448 omit = omit,
449 include = include,
452 if 'report' in options.actions:
453 total = self.coverage.report(
454 show_missing=options.show_missing, **report_args)
455 if 'annotate' in options.actions:
456 self.coverage.annotate(
457 directory=options.directory, **report_args)
458 if 'html' in options.actions:
459 total = self.coverage.html_report(
460 directory=options.directory, title=options.title,
461 **report_args)
462 if 'xml' in options.actions:
463 outfile = options.outfile
464 total = self.coverage.xml_report(outfile=outfile, **report_args)
466 if options.fail_under is not None:
467 if total >= options.fail_under:
468 return OK
469 else:
470 return FAIL_UNDER
471 else:
472 return OK
474 def help(self, error=None, topic=None, parser=None):
475 """Display an error message, or the named topic."""
476 assert error or topic or parser
477 if error:
478 print(error)
479 print("Use 'coverage help' for help.")
480 elif parser:
481 print(parser.format_help().strip())
482 else:
483 help_msg = HELP_TOPICS.get(topic, '').strip()
484 if help_msg:
485 print(help_msg % self.covpkg.__dict__)
486 else:
487 print("Don't know topic %r" % topic)
489 def do_help(self, options, args, parser):
490 """Deal with help requests.
492 Return True if it handled the request, False if not.
495 # Handle help.
496 if options.help:
497 if self.classic:
498 self.help_fn(topic='help')
499 else:
500 self.help_fn(parser=parser)
501 return True
503 if "help" in options.actions:
504 if args:
505 for a in args:
506 parser = CMDS.get(a)
507 if parser:
508 self.help_fn(parser=parser)
509 else:
510 self.help_fn(topic=a)
511 else:
512 self.help_fn(topic='help')
513 return True
515 # Handle version.
516 if options.version:
517 self.help_fn(topic='version')
518 return True
520 return False
522 def args_ok(self, options, args):
523 """Check for conflicts and problems in the options.
525 Returns True if everything is ok, or False if not.
528 for i in ['erase', 'execute']:
529 for j in ['annotate', 'html', 'report', 'combine']:
530 if (i in options.actions) and (j in options.actions):
531 self.help_fn("You can't specify the '%s' and '%s' "
532 "options at the same time." % (i, j))
533 return False
535 if not options.actions:
536 self.help_fn(
537 "You must specify at least one of -e, -x, -c, -r, -a, or -b."
539 return False
540 args_allowed = (
541 'execute' in options.actions or
542 'annotate' in options.actions or
543 'html' in options.actions or
544 'debug' in options.actions or
545 'report' in options.actions or
546 'xml' in options.actions
548 if not args_allowed and args:
549 self.help_fn("Unexpected arguments: %s" % " ".join(args))
550 return False
552 if 'execute' in options.actions and not args:
553 self.help_fn("Nothing to do.")
554 return False
556 return True
558 def do_execute(self, options, args):
559 """Implementation of 'coverage run'."""
561 # Set the first path element properly.
562 old_path0 = sys.path[0]
564 # Run the script.
565 self.coverage.start()
566 code_ran = True
567 try:
568 try:
569 if options.module:
570 sys.path[0] = ''
571 self.run_python_module(args[0], args)
572 else:
573 filename = args[0]
574 sys.path[0] = os.path.abspath(os.path.dirname(filename))
575 self.run_python_file(filename, args)
576 except NoSource:
577 code_ran = False
578 raise
579 finally:
580 self.coverage.stop()
581 if code_ran:
582 self.coverage.save()
584 # Restore the old path
585 sys.path[0] = old_path0
587 def do_debug(self, args):
588 """Implementation of 'coverage debug'."""
590 if not args:
591 self.help_fn("What information would you like: data, sys?")
592 return ERR
593 for info in args:
594 if info == 'sys':
595 print("-- sys ----------------------------------------")
596 for line in info_formatter(self.coverage.sysinfo()):
597 print(" %s" % line)
598 elif info == 'data':
599 print("-- data ---------------------------------------")
600 self.coverage.load()
601 print("path: %s" % self.coverage.data.filename)
602 print("has_arcs: %r" % self.coverage.data.has_arcs())
603 summary = self.coverage.data.summary(fullpath=True)
604 if summary:
605 filenames = sorted(summary.keys())
606 print("\n%d files:" % len(filenames))
607 for f in filenames:
608 print("%s: %d lines" % (f, summary[f]))
609 else:
610 print("No data collected")
611 else:
612 self.help_fn("Don't know what you mean by %r" % info)
613 return ERR
614 return OK
617 def unshell_list(s):
618 """Turn a command-line argument into a list."""
619 if not s:
620 return None
621 if sys.platform == 'win32':
622 # When running coverage as coverage.exe, some of the behavior
623 # of the shell is emulated: wildcards are expanded into a list of
624 # filenames. So you have to single-quote patterns on the command
625 # line, but (not) helpfully, the single quotes are included in the
626 # argument, so we have to strip them off here.
627 s = s.strip("'")
628 return s.split(',')
631 HELP_TOPICS = {
632 # -------------------------
633 'classic':
634 r"""Coverage.py version %(__version__)s
635 Measure, collect, and report on code coverage in Python programs.
637 Usage:
639 coverage -x [-p] [-L] [--timid] MODULE.py [ARG1 ARG2 ...]
640 Execute the module, passing the given command-line arguments, collecting
641 coverage data. With the -p option, include the machine name and process
642 id in the .coverage file name. With -L, measure coverage even inside the
643 Python installed library, which isn't done by default. With --timid, use a
644 simpler but slower trace method.
646 coverage -e
647 Erase collected coverage data.
649 coverage -c
650 Combine data from multiple coverage files (as created by -p option above)
651 and store it into a single file representing the union of the coverage.
653 coverage -r [-m] [-i] [-o DIR,...] [FILE1 FILE2 ...]
654 Report on the statement coverage for the given files. With the -m
655 option, show line numbers of the statements that weren't executed.
657 coverage -b -d DIR [-i] [-o DIR,...] [FILE1 FILE2 ...]
658 Create an HTML report of the coverage of the given files. Each file gets
659 its own page, with the file listing decorated to show executed, excluded,
660 and missed lines.
662 coverage -a [-d DIR] [-i] [-o DIR,...] [FILE1 FILE2 ...]
663 Make annotated copies of the given files, marking statements that
664 are executed with > and statements that are missed with !.
666 -d DIR
667 Write output files for -b or -a to this directory.
669 -i Ignore errors while reporting or annotating.
671 -o DIR,...
672 Omit reporting or annotating files when their filename path starts with
673 a directory listed in the omit list.
674 e.g. coverage -i -r -o c:\python25,lib\enthought\traits
676 Coverage data is saved in the file .coverage by default. Set the
677 COVERAGE_FILE environment variable to save it somewhere else.
678 """,
679 # -------------------------
680 'help': """\
681 Coverage.py, version %(__version__)s
682 Measure, collect, and report on code coverage in Python programs.
684 usage: coverage <command> [options] [args]
686 Commands:
687 annotate Annotate source files with execution information.
688 combine Combine a number of data files.
689 erase Erase previously collected coverage data.
690 help Get help on using coverage.py.
691 html Create an HTML report.
692 report Report coverage stats on modules.
693 run Run a Python program and measure code execution.
694 xml Create an XML report of coverage results.
696 Use "coverage help <command>" for detailed help on any command.
697 Use "coverage help classic" for help on older command syntax.
698 For more information, see %(__url__)s
699 """,
700 # -------------------------
701 'minimum_help': """\
702 Code coverage for Python. Use 'coverage help' for help.
703 """,
704 # -------------------------
705 'version': """\
706 Coverage.py, version %(__version__)s. %(__url__)s
707 """,
711 def main(argv=None):
712 """The main entry point to Coverage.
714 This is installed as the script entry point.
717 if argv is None:
718 argv = sys.argv[1:]
719 try:
720 start = time.clock()
721 status = CoverageScript().command_line(argv)
722 end = time.clock()
723 if 0:
724 print("time: %.3fs" % (end - start))
725 except ExceptionDuringRun:
726 # An exception was caught while running the product code. The
727 # sys.exc_info() return tuple is packed into an ExceptionDuringRun
728 # exception.
729 _, err, _ = sys.exc_info()
730 traceback.print_exception(*err.args)
731 status = ERR
732 except CoverageException:
733 # A controlled error inside coverage.py: print the message to the user.
734 _, err, _ = sys.exc_info()
735 print(err)
736 status = ERR
737 except SystemExit:
738 # The user called `sys.exit()`. Exit with their argument, if any.
739 _, err, _ = sys.exc_info()
740 if err.args:
741 status = err.args[0]
742 else:
743 status = None
744 return status