Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(27)

Side by Side Diff: third_party/coverage-3.6/coverage/cmdline.py

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

Powered by Google App Engine
This is Rietveld 408576698