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
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 "
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",
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 "
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
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."""
151 class OptionParserError(Exception):
152 """Used to stop the optparse error handler ending the process."""
155 def parse_args(self
, args
=None, options
=None):
156 """Call optparse.parse_args, but return a triple:
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."""
171 raise self
.OptionParserError
174 class ClassicOptionParser(CoverageOptionParser
):
175 """Command-line parser for coverage.py classic arguments."""
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')
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.
228 usage
= "%prog " + usage
229 super(CmdOptionParser
, self
).__init
__(
230 prog
="coverage %s" % (cmd
or action
),
232 description
=description
,
234 self
.set_defaults(actions
=[action
], **(defaults
or {}))
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
)
250 'annotate': CmdOptionParser("annotate",
257 usage
= "[options] [modules]",
258 description
= "Make annotated copies of the given files, marking "
259 "statements that are executed with > and statements that are "
263 'combine': CmdOptionParser("combine", GLOBAL_ARGS
,
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
,
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
,
280 description
= "Erase previously collected coverage data."
283 'help': CmdOptionParser("help", GLOBAL_ARGS
,
285 description
= "Describe how to use coverage.py"
288 'html': CmdOptionParser("html",
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",
311 usage
= "[options] [modules]",
312 description
= "Report coverage statistics on modules."
315 'run': CmdOptionParser("execute",
328 defaults
= {'erase_first': True},
330 usage
= "[options] <pyfile> [program options]",
331 description
= "Run a Python program, measuring code execution."
334 'xml': CmdOptionParser("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.
359 self
.covpkg
= _covpkg
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
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.
382 self
.help_fn(topic
='minimum_help')
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('-')
389 parser
= ClassicOptionParser()
391 parser
= CMDS
.get(argv
[0])
393 self
.help_fn("Unknown command: '%s'" % argv
[0])
397 parser
.help_fn
= self
.help_fn
398 ok
, options
, args
= parser
.parse_args(argv
)
402 # Handle help and version.
403 if self
.do_help(options
, args
, parser
):
406 # Check for conflicts and problems in the options.
407 if not self
.args_ok(options
, args
):
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
)
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
,
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()
437 if 'execute' in options
.actions
:
438 self
.do_execute(options
, args
)
440 if 'combine' in options
.actions
:
441 self
.coverage
.combine()
444 # Remaining actions are reporting, with some common options.
447 ignore_errors
= options
.ignore_errors
,
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
,
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
:
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
479 print("Use 'coverage help' for help.")
481 print(parser
.format_help().strip())
483 help_msg
= HELP_TOPICS
.get(topic
, '').strip()
485 print(help_msg
% self
.covpkg
.__dict
__)
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.
498 self
.help_fn(topic
='help')
500 self
.help_fn(parser
=parser
)
503 if "help" in options
.actions
:
508 self
.help_fn(parser
=parser
)
510 self
.help_fn(topic
=a
)
512 self
.help_fn(topic
='help')
517 self
.help_fn(topic
='version')
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
))
535 if not options
.actions
:
537 "You must specify at least one of -e, -x, -c, -r, -a, or -b."
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
))
552 if 'execute' in options
.actions
and not args
:
553 self
.help_fn("Nothing to do.")
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]
565 self
.coverage
.start()
571 self
.run_python_module(args
[0], args
)
574 sys
.path
[0] = os
.path
.abspath(os
.path
.dirname(filename
))
575 self
.run_python_file(filename
, args
)
584 # Restore the old path
585 sys
.path
[0] = old_path0
587 def do_debug(self
, args
):
588 """Implementation of 'coverage debug'."""
591 self
.help_fn("What information would you like: data, sys?")
595 print("-- sys ----------------------------------------")
596 for line
in info_formatter(self
.coverage
.sysinfo()):
599 print("-- data ---------------------------------------")
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)
605 filenames
= sorted(summary
.keys())
606 print("\n%d files:" % len(filenames
))
608 print("%s: %d lines" % (f
, summary
[f
]))
610 print("No data collected")
612 self
.help_fn("Don't know what you mean by %r" % info
)
618 """Turn a command-line argument into a list."""
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.
632 # -------------------------
634 r
"""Coverage.py version %(__version__)s
635 Measure, collect, and report on code coverage in Python programs.
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.
647 Erase collected coverage data.
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,
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 !.
667 Write output files for -b or -a to this directory.
669 -i Ignore errors while reporting or annotating.
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.
679 # -------------------------
681 Coverage.py, version %(__version__)s
682 Measure, collect, and report on code coverage in Python programs.
684 usage: coverage <command> [options] [args]
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
700 # -------------------------
702 Code coverage for Python. Use 'coverage help' for help.
704 # -------------------------
706 Coverage.py, version %(__version__)s. %(__url__)s
712 """The main entry point to Coverage.
714 This is installed as the script entry point.
721 status
= CoverageScript().command_line(argv
)
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
729 _
, err
, _
= sys
.exc_info()
730 traceback
.print_exception(*err
.args
)
732 except CoverageException
:
733 # A controlled error inside coverage.py: print the message to the user.
734 _
, err
, _
= sys
.exc_info()
738 # The user called `sys.exit()`. Exit with their argument, if any.
739 _
, err
, _
= sys
.exc_info()