DebugClients/Python/coverage/cmdline.py

changeset 5051
3586ebd9fac8
parent 4491
0d8612e24fef
equal deleted inserted replaced
5047:04e5dfbd3f3d 5051:3586ebd9fac8
5 5
6 import glob 6 import glob
7 import optparse 7 import optparse
8 import os.path 8 import os.path
9 import sys 9 import sys
10 import textwrap
10 import traceback 11 import traceback
11 12
12 from coverage import env 13 from coverage import env
14 from coverage.collector import CTracer
13 from coverage.execfile import run_python_file, run_python_module 15 from coverage.execfile import run_python_file, run_python_module
14 from coverage.misc import CoverageException, ExceptionDuringRun, NoSource 16 from coverage.misc import CoverageException, ExceptionDuringRun, NoSource
15 from coverage.debug import info_formatter, info_header 17 from coverage.debug import info_formatter, info_header
16 18
17 19
18 class Opts(object): 20 class Opts(object):
19 """A namespace class for individual options we'll build parsers from.""" 21 """A namespace class for individual options we'll build parsers from."""
20 22
21 append = optparse.make_option( 23 append = optparse.make_option(
22 '-a', '--append', action='store_true', 24 '-a', '--append', action='store_true',
23 help="Append coverage data to .coverage, otherwise it is started " 25 help="Append coverage data to .coverage, otherwise it is started clean with each run.",
24 "clean with each run." 26 )
25 )
26 branch = optparse.make_option( 27 branch = optparse.make_option(
27 '', '--branch', action='store_true', 28 '', '--branch', action='store_true',
28 help="Measure branch coverage in addition to statement coverage." 29 help="Measure branch coverage in addition to statement coverage.",
29 ) 30 )
30 CONCURRENCY_CHOICES = [ 31 CONCURRENCY_CHOICES = [
31 "thread", "gevent", "greenlet", "eventlet", "multiprocessing", 32 "thread", "gevent", "greenlet", "eventlet", "multiprocessing",
32 ] 33 ]
33 concurrency = optparse.make_option( 34 concurrency = optparse.make_option(
34 '', '--concurrency', action='store', metavar="LIB", 35 '', '--concurrency', action='store', metavar="LIB",
35 choices=CONCURRENCY_CHOICES, 36 choices=CONCURRENCY_CHOICES,
36 help="Properly measure code using a concurrency library. " 37 help=(
37 "Valid values are: %s." % ", ".join(CONCURRENCY_CHOICES) 38 "Properly measure code using a concurrency library. "
38 ) 39 "Valid values are: %s."
40 ) % ", ".join(CONCURRENCY_CHOICES),
41 )
39 debug = optparse.make_option( 42 debug = optparse.make_option(
40 '', '--debug', action='store', metavar="OPTS", 43 '', '--debug', action='store', metavar="OPTS",
41 help="Debug options, separated by commas" 44 help="Debug options, separated by commas",
42 ) 45 )
43 directory = optparse.make_option( 46 directory = optparse.make_option(
44 '-d', '--directory', action='store', metavar="DIR", 47 '-d', '--directory', action='store', metavar="DIR",
45 help="Write the output files to DIR." 48 help="Write the output files to DIR.",
46 ) 49 )
47 fail_under = optparse.make_option( 50 fail_under = optparse.make_option(
48 '', '--fail-under', action='store', metavar="MIN", type="int", 51 '', '--fail-under', action='store', metavar="MIN", type="int",
49 help="Exit with a status of 2 if the total coverage is less than MIN." 52 help="Exit with a status of 2 if the total coverage is less than MIN.",
50 ) 53 )
51 help = optparse.make_option( 54 help = optparse.make_option(
52 '-h', '--help', action='store_true', 55 '-h', '--help', action='store_true',
53 help="Get help on this command." 56 help="Get help on this command.",
54 ) 57 )
55 ignore_errors = optparse.make_option( 58 ignore_errors = optparse.make_option(
56 '-i', '--ignore-errors', action='store_true', 59 '-i', '--ignore-errors', action='store_true',
57 help="Ignore errors while reading source files." 60 help="Ignore errors while reading source files.",
58 ) 61 )
59 include = optparse.make_option( 62 include = optparse.make_option(
60 '', '--include', action='store', 63 '', '--include', action='store',
61 metavar="PAT1,PAT2,...", 64 metavar="PAT1,PAT2,...",
62 help="Include only files whose paths match one of these patterns. " 65 help=(
63 "Accepts shell-style wildcards, which must be quoted." 66 "Include only files whose paths match one of these patterns. "
64 ) 67 "Accepts shell-style wildcards, which must be quoted."
68 ),
69 )
65 pylib = optparse.make_option( 70 pylib = optparse.make_option(
66 '-L', '--pylib', action='store_true', 71 '-L', '--pylib', action='store_true',
67 help="Measure coverage even inside the Python installed library, " 72 help=(
68 "which isn't done by default." 73 "Measure coverage even inside the Python installed library, "
69 ) 74 "which isn't done by default."
75 ),
76 )
70 show_missing = optparse.make_option( 77 show_missing = optparse.make_option(
71 '-m', '--show-missing', action='store_true', 78 '-m', '--show-missing', action='store_true',
72 help="Show line numbers of statements in each module that weren't " 79 help="Show line numbers of statements in each module that weren't executed.",
73 "executed." 80 )
74 )
75 skip_covered = optparse.make_option( 81 skip_covered = optparse.make_option(
76 '--skip-covered', action='store_true', 82 '--skip-covered', action='store_true',
77 help="Skip files with 100% coverage." 83 help="Skip files with 100% coverage.",
78 ) 84 )
79 omit = optparse.make_option( 85 omit = optparse.make_option(
80 '', '--omit', action='store', 86 '', '--omit', action='store',
81 metavar="PAT1,PAT2,...", 87 metavar="PAT1,PAT2,...",
82 help="Omit files whose paths match one of these patterns. " 88 help=(
83 "Accepts shell-style wildcards, which must be quoted." 89 "Omit files whose paths match one of these patterns. "
84 ) 90 "Accepts shell-style wildcards, which must be quoted."
91 ),
92 )
85 output_xml = optparse.make_option( 93 output_xml = optparse.make_option(
86 '-o', '', action='store', dest="outfile", 94 '-o', '', action='store', dest="outfile",
87 metavar="OUTFILE", 95 metavar="OUTFILE",
88 help="Write the XML report to this file. Defaults to 'coverage.xml'" 96 help="Write the XML report to this file. Defaults to 'coverage.xml'",
89 ) 97 )
90 parallel_mode = optparse.make_option( 98 parallel_mode = optparse.make_option(
91 '-p', '--parallel-mode', action='store_true', 99 '-p', '--parallel-mode', action='store_true',
92 help="Append the machine name, process id and random number to the " 100 help=(
93 ".coverage data file name to simplify collecting data from " 101 "Append the machine name, process id and random number to the "
94 "many processes." 102 ".coverage data file name to simplify collecting data from "
95 ) 103 "many processes."
104 ),
105 )
96 module = optparse.make_option( 106 module = optparse.make_option(
97 '-m', '--module', action='store_true', 107 '-m', '--module', action='store_true',
98 help="<pyfile> is an importable Python module, not a script path, " 108 help=(
99 "to be run as 'python -m' would run it." 109 "<pyfile> is an importable Python module, not a script path, "
100 ) 110 "to be run as 'python -m' would run it."
111 ),
112 )
101 rcfile = optparse.make_option( 113 rcfile = optparse.make_option(
102 '', '--rcfile', action='store', 114 '', '--rcfile', action='store',
103 help="Specify configuration file. Defaults to '.coveragerc'" 115 help="Specify configuration file. Defaults to '.coveragerc'",
104 ) 116 )
105 source = optparse.make_option( 117 source = optparse.make_option(
106 '', '--source', action='store', metavar="SRC1,SRC2,...", 118 '', '--source', action='store', metavar="SRC1,SRC2,...",
107 help="A list of packages or directories of code to be measured." 119 help="A list of packages or directories of code to be measured.",
108 ) 120 )
109 timid = optparse.make_option( 121 timid = optparse.make_option(
110 '', '--timid', action='store_true', 122 '', '--timid', action='store_true',
111 help="Use a simpler but slower trace method. Try this if you get " 123 help=(
112 "seemingly impossible results!" 124 "Use a simpler but slower trace method. Try this if you get "
113 ) 125 "seemingly impossible results!"
126 ),
127 )
114 title = optparse.make_option( 128 title = optparse.make_option(
115 '', '--title', action='store', metavar="TITLE", 129 '', '--title', action='store', metavar="TITLE",
116 help="A text string to use as the title on the HTML." 130 help="A text string to use as the title on the HTML.",
117 ) 131 )
118 version = optparse.make_option( 132 version = optparse.make_option(
119 '', '--version', action='store_true', 133 '', '--version', action='store_true',
120 help="Display version information and exit." 134 help="Display version information and exit.",
121 ) 135 )
122 136
123 137
124 class CoverageOptionParser(optparse.OptionParser, object): 138 class CoverageOptionParser(optparse.OptionParser, object):
125 """Base OptionParser for coverage.py. 139 """Base OptionParser for coverage.py.
126 140
200 214
201 215
202 class CmdOptionParser(CoverageOptionParser): 216 class CmdOptionParser(CoverageOptionParser):
203 """Parse one of the new-style commands for coverage.py.""" 217 """Parse one of the new-style commands for coverage.py."""
204 218
205 def __init__(self, action, options=None, defaults=None, usage=None, 219 def __init__(self, action, options=None, defaults=None, usage=None, description=None):
206 description=None
207 ):
208 """Create an OptionParser for a coverage.py command. 220 """Create an OptionParser for a coverage.py command.
209 221
210 `action` is the slug to put into `options.action`. 222 `action` is the slug to put into `options.action`.
211 `options` is a list of Option's for the command. 223 `options` is a list of Option's for the command.
212 `defaults` is a dict of default value for options. 224 `defaults` is a dict of default value for options.
215 227
216 """ 228 """
217 if usage: 229 if usage:
218 usage = "%prog " + usage 230 usage = "%prog " + usage
219 super(CmdOptionParser, self).__init__( 231 super(CmdOptionParser, self).__init__(
220 prog="coverage %s" % action,
221 usage=usage, 232 usage=usage,
222 description=description, 233 description=description,
223 ) 234 )
224 self.set_defaults(action=action, **(defaults or {})) 235 self.set_defaults(action=action, **(defaults or {}))
225 if options: 236 if options:
229 def __eq__(self, other): 240 def __eq__(self, other):
230 # A convenience equality, so that I can put strings in unit test 241 # A convenience equality, so that I can put strings in unit test
231 # results, and they will compare equal to objects. 242 # results, and they will compare equal to objects.
232 return (other == "<CmdOptionParser:%s>" % self.cmd) 243 return (other == "<CmdOptionParser:%s>" % self.cmd)
233 244
245 def get_prog_name(self):
246 """Override of an undocumented function in optparse.OptionParser."""
247 program_name = super(CmdOptionParser, self).get_prog_name()
248
249 # Include the sub-command for this parser as part of the command.
250 return "%(command)s %(subcommand)s" % {'command': program_name, 'subcommand': self.cmd}
251
252
234 GLOBAL_ARGS = [ 253 GLOBAL_ARGS = [
235 Opts.debug, 254 Opts.debug,
236 Opts.help, 255 Opts.help,
237 Opts.rcfile, 256 Opts.rcfile,
238 ] 257 ]
239 258
240 CMDS = { 259 CMDS = {
241 'annotate': CmdOptionParser("annotate", 260 'annotate': CmdOptionParser(
261 "annotate",
242 [ 262 [
243 Opts.directory, 263 Opts.directory,
244 Opts.ignore_errors, 264 Opts.ignore_errors,
245 Opts.include, 265 Opts.include,
246 Opts.omit, 266 Opts.omit,
247 ] + GLOBAL_ARGS, 267 ] + GLOBAL_ARGS,
248 usage = "[options] [modules]", 268 usage="[options] [modules]",
249 description = "Make annotated copies of the given files, marking " 269 description=(
250 "statements that are executed with > and statements that are " 270 "Make annotated copies of the given files, marking statements that are executed "
251 "missed with !." 271 "with > and statements that are missed with !."
252 ), 272 ),
253 273 ),
254 'combine': CmdOptionParser("combine", GLOBAL_ARGS, 274
255 usage = "<path1> <path2> ... <pathN>", 275 'combine': CmdOptionParser(
256 description = "Combine data from multiple coverage files collected " 276 "combine",
277 GLOBAL_ARGS,
278 usage="<path1> <path2> ... <pathN>",
279 description=(
280 "Combine data from multiple coverage files collected "
257 "with 'run -p'. The combined results are written to a single " 281 "with 'run -p'. The combined results are written to a single "
258 "file representing the union of the data. The positional " 282 "file representing the union of the data. The positional "
259 "arguments are data files or directories containing data files. " 283 "arguments are data files or directories containing data files. "
260 "If no paths are provided, data files in the default data file's " 284 "If no paths are provided, data files in the default data file's "
261 "directory are combined." 285 "directory are combined."
262 ), 286 ),
263 287 ),
264 'debug': CmdOptionParser("debug", GLOBAL_ARGS, 288
265 usage = "<topic>", 289 'debug': CmdOptionParser(
266 description = "Display information on the internals of coverage.py, " 290 "debug", GLOBAL_ARGS,
291 usage="<topic>",
292 description=(
293 "Display information on the internals of coverage.py, "
267 "for diagnosing problems. " 294 "for diagnosing problems. "
268 "Topics are 'data' to show a summary of the collected data, " 295 "Topics are 'data' to show a summary of the collected data, "
269 "or 'sys' to show installation information." 296 "or 'sys' to show installation information."
270 ), 297 ),
271 298 ),
272 'erase': CmdOptionParser("erase", GLOBAL_ARGS, 299
273 usage = " ", 300 'erase': CmdOptionParser(
274 description = "Erase previously collected coverage data." 301 "erase", GLOBAL_ARGS,
275 ), 302 usage=" ",
276 303 description="Erase previously collected coverage data.",
277 'help': CmdOptionParser("help", GLOBAL_ARGS, 304 ),
278 usage = "[command]", 305
279 description = "Describe how to use coverage.py" 306 'help': CmdOptionParser(
280 ), 307 "help", GLOBAL_ARGS,
281 308 usage="[command]",
282 'html': CmdOptionParser("html", 309 description="Describe how to use coverage.py",
310 ),
311
312 'html': CmdOptionParser(
313 "html",
283 [ 314 [
284 Opts.directory, 315 Opts.directory,
285 Opts.fail_under, 316 Opts.fail_under,
286 Opts.ignore_errors, 317 Opts.ignore_errors,
287 Opts.include, 318 Opts.include,
288 Opts.omit, 319 Opts.omit,
289 Opts.title, 320 Opts.title,
290 ] + GLOBAL_ARGS, 321 ] + GLOBAL_ARGS,
291 usage = "[options] [modules]", 322 usage="[options] [modules]",
292 description = "Create an HTML report of the coverage of the files. " 323 description=(
324 "Create an HTML report of the coverage of the files. "
293 "Each file gets its own page, with the source decorated to show " 325 "Each file gets its own page, with the source decorated to show "
294 "executed, excluded, and missed lines." 326 "executed, excluded, and missed lines."
295 ), 327 ),
296 328 ),
297 'report': CmdOptionParser("report", 329
330 'report': CmdOptionParser(
331 "report",
298 [ 332 [
299 Opts.fail_under, 333 Opts.fail_under,
300 Opts.ignore_errors, 334 Opts.ignore_errors,
301 Opts.include, 335 Opts.include,
302 Opts.omit, 336 Opts.omit,
303 Opts.show_missing, 337 Opts.show_missing,
304 Opts.skip_covered, 338 Opts.skip_covered,
305 ] + GLOBAL_ARGS, 339 ] + GLOBAL_ARGS,
306 usage = "[options] [modules]", 340 usage="[options] [modules]",
307 description = "Report coverage statistics on modules." 341 description="Report coverage statistics on modules."
308 ), 342 ),
309 343
310 'run': CmdOptionParser("run", 344 'run': CmdOptionParser(
345 "run",
311 [ 346 [
312 Opts.append, 347 Opts.append,
313 Opts.branch, 348 Opts.branch,
314 Opts.concurrency, 349 Opts.concurrency,
315 Opts.include, 350 Opts.include,
318 Opts.pylib, 353 Opts.pylib,
319 Opts.parallel_mode, 354 Opts.parallel_mode,
320 Opts.source, 355 Opts.source,
321 Opts.timid, 356 Opts.timid,
322 ] + GLOBAL_ARGS, 357 ] + GLOBAL_ARGS,
323 usage = "[options] <pyfile> [program options]", 358 usage="[options] <pyfile> [program options]",
324 description = "Run a Python program, measuring code execution." 359 description="Run a Python program, measuring code execution."
325 ), 360 ),
326 361
327 'xml': CmdOptionParser("xml", 362 'xml': CmdOptionParser(
363 "xml",
328 [ 364 [
329 Opts.fail_under, 365 Opts.fail_under,
330 Opts.ignore_errors, 366 Opts.ignore_errors,
331 Opts.include, 367 Opts.include,
332 Opts.omit, 368 Opts.omit,
333 Opts.output_xml, 369 Opts.output_xml,
334 ] + GLOBAL_ARGS, 370 ] + GLOBAL_ARGS,
335 usage = "[options] [modules]", 371 usage="[options] [modules]",
336 description = "Generate an XML report of coverage results." 372 description="Generate an XML report of coverage results."
337 ), 373 ),
338 } 374 }
339 375
340 376
341 OK, ERR, FAIL_UNDER = 0, 1, 2 377 OK, ERR, FAIL_UNDER = 0, 1, 2
342 378
343 379
359 self.help_fn = _help_fn or self.help 395 self.help_fn = _help_fn or self.help
360 self.path_exists = _path_exists or os.path.exists 396 self.path_exists = _path_exists or os.path.exists
361 self.global_option = False 397 self.global_option = False
362 398
363 self.coverage = None 399 self.coverage = None
400
401 self.program_name = os.path.basename(sys.argv[0])
402 if env.WINDOWS:
403 # entry_points={'console_scripts':...} on Windows makes files
404 # called coverage.exe, coverage3.exe, and coverage-3.5.exe. These
405 # invoke coverage-script.py, coverage3-script.py, and
406 # coverage-3.5-script.py. argv[0] is the .py file, but we want to
407 # get back to the original form.
408 auto_suffix = "-script.py"
409 if self.program_name.endswith(auto_suffix):
410 self.program_name = self.program_name[:-len(auto_suffix)]
364 411
365 def command_line(self, argv): 412 def command_line(self, argv):
366 """The bulk of the command line interface to coverage.py. 413 """The bulk of the command line interface to coverage.py.
367 414
368 `argv` is the argument list to process. 415 `argv` is the argument list to process.
410 include = unshell_list(options.include) 457 include = unshell_list(options.include)
411 debug = unshell_list(options.debug) 458 debug = unshell_list(options.debug)
412 459
413 # Do something. 460 # Do something.
414 self.coverage = self.covpkg.coverage( 461 self.coverage = self.covpkg.coverage(
415 data_suffix = options.parallel_mode, 462 data_suffix=options.parallel_mode,
416 cover_pylib = options.pylib, 463 cover_pylib=options.pylib,
417 timid = options.timid, 464 timid=options.timid,
418 branch = options.branch, 465 branch=options.branch,
419 config_file = options.rcfile, 466 config_file=options.rcfile,
420 source = source, 467 source=source,
421 omit = omit, 468 omit=omit,
422 include = include, 469 include=include,
423 debug = debug, 470 debug=debug,
424 concurrency = options.concurrency, 471 concurrency=options.concurrency,
425 ) 472 )
426 473
427 if options.action == "debug": 474 if options.action == "debug":
428 return self.do_debug(args) 475 return self.do_debug(args)
429 476
441 self.coverage.save() 488 self.coverage.save()
442 return OK 489 return OK
443 490
444 # Remaining actions are reporting, with some common options. 491 # Remaining actions are reporting, with some common options.
445 report_args = dict( 492 report_args = dict(
446 morfs = unglob_args(args), 493 morfs=unglob_args(args),
447 ignore_errors = options.ignore_errors, 494 ignore_errors=options.ignore_errors,
448 omit = omit, 495 omit=omit,
449 include = include, 496 include=include,
450 ) 497 )
451 498
452 self.coverage.load() 499 self.coverage.load()
453 500
454 total = None 501 total = None
493 def help(self, error=None, topic=None, parser=None): 540 def help(self, error=None, topic=None, parser=None):
494 """Display an error message, or the named topic.""" 541 """Display an error message, or the named topic."""
495 assert error or topic or parser 542 assert error or topic or parser
496 if error: 543 if error:
497 print(error) 544 print(error)
498 print("Use 'coverage help' for help.") 545 print("Use '%s help' for help." % (self.program_name,))
499 elif parser: 546 elif parser:
500 print(parser.format_help().strip()) 547 print(parser.format_help().strip())
501 else: 548 else:
502 help_msg = HELP_TOPICS.get(topic, '').strip() 549 help_params = dict(self.covpkg.__dict__)
550 help_params['program_name'] = self.program_name
551 if CTracer is not None:
552 help_params['extension_modifier'] = 'with C extension'
553 else:
554 help_params['extension_modifier'] = 'without C extension'
555 help_msg = textwrap.dedent(HELP_TOPICS.get(topic, '')).strip()
503 if help_msg: 556 if help_msg:
504 print(help_msg % self.covpkg.__dict__) 557 print(help_msg.format(**help_params))
505 else: 558 else:
506 print("Don't know topic %r" % topic) 559 print("Don't know topic %r" % topic)
507 560
508 def do_help(self, options, args, parser): 561 def do_help(self, options, args, parser):
509 """Deal with help requests. 562 """Deal with help requests.
648 args = globbed 701 args = globbed
649 return args 702 return args
650 703
651 704
652 HELP_TOPICS = { 705 HELP_TOPICS = {
653 # ------------------------- 706 'help': """\
654 'help': """\ 707 Coverage.py, version {__version__} {extension_modifier}
655 Coverage.py, version %(__version__)s 708 Measure, collect, and report on code coverage in Python programs.
656 Measure, collect, and report on code coverage in Python programs. 709
657 710 usage: {program_name} <command> [options] [args]
658 usage: coverage <command> [options] [args] 711
659 712 Commands:
660 Commands: 713 annotate Annotate source files with execution information.
661 annotate Annotate source files with execution information. 714 combine Combine a number of data files.
662 combine Combine a number of data files. 715 erase Erase previously collected coverage data.
663 erase Erase previously collected coverage data. 716 help Get help on using coverage.py.
664 help Get help on using coverage.py. 717 html Create an HTML report.
665 html Create an HTML report. 718 report Report coverage stats on modules.
666 report Report coverage stats on modules. 719 run Run a Python program and measure code execution.
667 run Run a Python program and measure code execution. 720 xml Create an XML report of coverage results.
668 xml Create an XML report of coverage results. 721
669 722 Use "{program_name} help <command>" for detailed help on any command.
670 Use "coverage help <command>" for detailed help on any command. 723 For full documentation, see {__url__}
671 For full documentation, see %(__url__)s 724 """,
672 """, 725
673 # ------------------------- 726 'minimum_help': """\
674 'minimum_help': """\ 727 Code coverage for Python. Use '{program_name} help' for help.
675 Code coverage for Python. Use 'coverage help' for help. 728 """,
676 """, 729
677 # ------------------------- 730 'version': """\
678 'version': """\ 731 Coverage.py, version {__version__} {extension_modifier}
679 Coverage.py, version %(__version__)s. 732 Documentation at {__url__}
680 Documentation at %(__url__)s 733 """,
681 """,
682 } 734 }
683 735
684 736
685 def main(argv=None): 737 def main(argv=None):
686 """The main entry point to coverage.py. 738 """The main entry point to coverage.py.

eric ide

mercurial