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

Side by Side Diff: third_party/pycoverage/coverage/cmdline.py

Issue 727003004: Add python coverage module to third_party (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 1 month 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
OLDNEW
(Empty)
1 """Command-line support for Coverage."""
2
3 import optparse, os, sys, time, 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 from coverage.debug import info_formatter
9
10
11 class Opts(object):
12 """A namespace class for individual options we'll build parsers from."""
13
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."
18 )
19 branch = optparse.make_option(
20 '', '--branch', action='store_true',
21 help="Measure branch coverage in addition to statement coverage."
22 )
23 debug = optparse.make_option(
24 '', '--debug', action='store', metavar="OPTS",
25 help="Debug options, separated by commas"
26 )
27 directory = optparse.make_option(
28 '-d', '--directory', action='store', metavar="DIR",
29 help="Write the output files to DIR."
30 )
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."
34 )
35 help = optparse.make_option(
36 '-h', '--help', action='store_true',
37 help="Get help on this command."
38 )
39 ignore_errors = optparse.make_option(
40 '-i', '--ignore-errors', action='store_true',
41 help="Ignore errors while reading source files."
42 )
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."
48 )
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."
53 )
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."
58 )
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."
64 )
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."
70 )
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'"
75 )
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."
81 )
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."
86 )
87 rcfile = optparse.make_option(
88 '', '--rcfile', action='store',
89 help="Specify configuration file. Defaults to '.coveragerc'"
90 )
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."
94 )
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!"
99 )
100 title = optparse.make_option(
101 '', '--title', action='store', metavar="TITLE",
102 help="A text string to use as the title on the HTML."
103 )
104 version = optparse.make_option(
105 '', '--version', action='store_true',
106 help="Display version information and exit."
107 )
108
109
110 class CoverageOptionParser(optparse.OptionParser, object):
111 """Base OptionParser for coverage.
112
113 Problems don't exit the program.
114 Defaults are initialized for all options.
115
116 """
117
118 def __init__(self, *args, **kwargs):
119 super(CoverageOptionParser, self).__init__(
120 add_help_option=False, *args, **kwargs
121 )
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,
142 )
143
144 self.disable_interspersed_args()
145 self.help_fn = self.help_noop
146
147 def help_noop(self, error=None, topic=None, parser=None):
148 """No-op help function."""
149 pass
150
151 class OptionParserError(Exception):
152 """Used to stop the optparse error handler ending the process."""
153 pass
154
155 def parse_args(self, args=None, options=None):
156 """Call optparse.parse_args, but return a triple:
157
158 (ok, options, args)
159
160 """
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
167
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
172
173
174 class ClassicOptionParser(CoverageOptionParser):
175 """Command-line parser for coverage.py classic arguments."""
176
177 def __init__(self):
178 super(ClassicOptionParser, self).__init__()
179
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')
186
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,
197 ])
198
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
203 )
204 option.action_code = action_code
205
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)
209
210
211 class CmdOptionParser(CoverageOptionParser):
212 """Parse one of the new-style commands for coverage.py."""
213
214 def __init__(self, action, options=None, defaults=None, usage=None,
215 cmd=None, description=None
216 ):
217 """Create an OptionParser for a coverage command.
218
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.
225
226 """
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,
233 )
234 self.set_defaults(actions=[action], **(defaults or {}))
235 if options:
236 self.add_options(options)
237 self.cmd = cmd or action
238
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)
243
244 GLOBAL_ARGS = [
245 Opts.rcfile,
246 Opts.help,
247 ]
248
249 CMDS = {
250 'annotate': CmdOptionParser("annotate",
251 [
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 !."
261 ),
262
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."
268 ),
269
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."
276 ),
277
278 'erase': CmdOptionParser("erase", GLOBAL_ARGS,
279 usage = " ",
280 description = "Erase previously collected coverage data."
281 ),
282
283 'help': CmdOptionParser("help", GLOBAL_ARGS,
284 usage = "[command]",
285 description = "Describe how to use coverage.py"
286 ),
287
288 'html': CmdOptionParser("html",
289 [
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."
301 ),
302
303 'report': CmdOptionParser("report",
304 [
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."
313 ),
314
315 'run': CmdOptionParser("execute",
316 [
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."
332 ),
333
334 'xml': CmdOptionParser("xml",
335 [
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."
345 ),
346 }
347
348
349 OK, ERR, FAIL_UNDER = 0, 1, 2
350
351
352 class CoverageScript(object):
353 """The command-line interface to Coverage."""
354
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
363
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
369
370 self.coverage = None
371
372 def command_line(self, argv):
373 """The bulk of the command line interface to Coverage.
374
375 `argv` is the argument list to process.
376
377 Returns 0 if all is well, 1 if something went wrong.
378
379 """
380 # Collect the command-line options.
381 if not argv:
382 self.help_fn(topic='minimum_help')
383 return OK
384
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:]
396
397 parser.help_fn = self.help_fn
398 ok, options, args = parser.parse_args(argv)
399 if not ok:
400 return ERR
401
402 # Handle help and version.
403 if self.do_help(options, args, parser):
404 return OK
405
406 # Check for conflicts and problems in the options.
407 if not self.args_ok(options, args):
408 return ERR
409
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)
415
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,
427 )
428
429 if 'debug' in options.actions:
430 return self.do_debug(args)
431
432 if 'erase' in options.actions or options.erase_first:
433 self.coverage.erase()
434 else:
435 self.coverage.load()
436
437 if 'execute' in options.actions:
438 self.do_execute(options, args)
439
440 if 'combine' in options.actions:
441 self.coverage.combine()
442 self.coverage.save()
443
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,
450 )
451
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)
465
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
473
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)
488
489 def do_help(self, options, args, parser):
490 """Deal with help requests.
491
492 Return True if it handled the request, False if not.
493
494 """
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
502
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
514
515 # Handle version.
516 if options.version:
517 self.help_fn(topic='version')
518 return True
519
520 return False
521
522 def args_ok(self, options, args):
523 """Check for conflicts and problems in the options.
524
525 Returns True if everything is ok, or False if not.
526
527 """
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
534
535 if not options.actions:
536 self.help_fn(
537 "You must specify at least one of -e, -x, -c, -r, -a, or -b."
538 )
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
547 )
548 if not args_allowed and args:
549 self.help_fn("Unexpected arguments: %s" % " ".join(args))
550 return False
551
552 if 'execute' in options.actions and not args:
553 self.help_fn("Nothing to do.")
554 return False
555
556 return True
557
558 def do_execute(self, options, args):
559 """Implementation of 'coverage run'."""
560
561 # Set the first path element properly.
562 old_path0 = sys.path[0]
563
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()
583
584 # Restore the old path
585 sys.path[0] = old_path0
586
587 def do_debug(self, args):
588 """Implementation of 'coverage debug'."""
589
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
615
616
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(',')
629
630
631 HELP_TOPICS = {
632 # -------------------------
633 'classic':
634 r"""Coverage.py version %(__version__)s
635 Measure, collect, and report on code coverage in Python programs.
636
637 Usage:
638
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.
645
646 coverage -e
647 Erase collected coverage data.
648
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.
652
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.
656
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.
661
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 !.
665
666 -d DIR
667 Write output files for -b or -a to this directory.
668
669 -i Ignore errors while reporting or annotating.
670
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
675
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.
683
684 usage: coverage <command> [options] [args]
685
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.
695
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 """,
708 }
709
710
711 def main(argv=None):
712 """The main entry point to Coverage.
713
714 This is installed as the script entry point.
715
716 """
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
OLDNEW
« no previous file with comments | « third_party/pycoverage/coverage/bytecode.py ('k') | third_party/pycoverage/coverage/codeunit.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698