|
1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 |
|
2 # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt |
|
3 |
|
4 """Command-line support for coverage.py.""" |
|
5 |
|
6 from __future__ import print_function |
|
7 |
|
8 import glob |
|
9 import optparse |
|
10 import os.path |
|
11 import sys |
|
12 import textwrap |
|
13 import traceback |
|
14 |
|
15 from coverage import env |
|
16 from coverage.collector import CTracer |
|
17 from coverage.debug import info_formatter, info_header |
|
18 from coverage.execfile import run_python_file, run_python_module |
|
19 from coverage.misc import BaseCoverageException, ExceptionDuringRun, NoSource |
|
20 from coverage.results import should_fail_under |
|
21 |
|
22 |
|
23 class Opts(object): |
|
24 """A namespace class for individual options we'll build parsers from.""" |
|
25 |
|
26 append = optparse.make_option( |
|
27 '-a', '--append', action='store_true', |
|
28 help="Append coverage data to .coverage, otherwise it starts clean each time.", |
|
29 ) |
|
30 branch = optparse.make_option( |
|
31 '', '--branch', action='store_true', |
|
32 help="Measure branch coverage in addition to statement coverage.", |
|
33 ) |
|
34 CONCURRENCY_CHOICES = [ |
|
35 "thread", "gevent", "greenlet", "eventlet", "multiprocessing", |
|
36 ] |
|
37 concurrency = optparse.make_option( |
|
38 '', '--concurrency', action='store', metavar="LIB", |
|
39 choices=CONCURRENCY_CHOICES, |
|
40 help=( |
|
41 "Properly measure code using a concurrency library. " |
|
42 "Valid values are: %s." |
|
43 ) % ", ".join(CONCURRENCY_CHOICES), |
|
44 ) |
|
45 debug = optparse.make_option( |
|
46 '', '--debug', action='store', metavar="OPTS", |
|
47 help="Debug options, separated by commas", |
|
48 ) |
|
49 directory = optparse.make_option( |
|
50 '-d', '--directory', action='store', metavar="DIR", |
|
51 help="Write the output files to DIR.", |
|
52 ) |
|
53 fail_under = optparse.make_option( |
|
54 '', '--fail-under', action='store', metavar="MIN", type="float", |
|
55 help="Exit with a status of 2 if the total coverage is less than MIN.", |
|
56 ) |
|
57 help = optparse.make_option( |
|
58 '-h', '--help', action='store_true', |
|
59 help="Get help on this command.", |
|
60 ) |
|
61 ignore_errors = optparse.make_option( |
|
62 '-i', '--ignore-errors', action='store_true', |
|
63 help="Ignore errors while reading source files.", |
|
64 ) |
|
65 include = optparse.make_option( |
|
66 '', '--include', action='store', |
|
67 metavar="PAT1,PAT2,...", |
|
68 help=( |
|
69 "Include only files whose paths match one of these patterns. " |
|
70 "Accepts shell-style wildcards, which must be quoted." |
|
71 ), |
|
72 ) |
|
73 pylib = optparse.make_option( |
|
74 '-L', '--pylib', action='store_true', |
|
75 help=( |
|
76 "Measure coverage even inside the Python installed library, " |
|
77 "which isn't done by default." |
|
78 ), |
|
79 ) |
|
80 show_missing = optparse.make_option( |
|
81 '-m', '--show-missing', action='store_true', |
|
82 help="Show line numbers of statements in each module that weren't executed.", |
|
83 ) |
|
84 skip_covered = optparse.make_option( |
|
85 '--skip-covered', action='store_true', |
|
86 help="Skip files with 100% coverage.", |
|
87 ) |
|
88 omit = optparse.make_option( |
|
89 '', '--omit', action='store', |
|
90 metavar="PAT1,PAT2,...", |
|
91 help=( |
|
92 "Omit files whose paths match one of these patterns. " |
|
93 "Accepts shell-style wildcards, which must be quoted." |
|
94 ), |
|
95 ) |
|
96 output_xml = optparse.make_option( |
|
97 '-o', '', action='store', dest="outfile", |
|
98 metavar="OUTFILE", |
|
99 help="Write the XML report to this file. Defaults to 'coverage.xml'", |
|
100 ) |
|
101 parallel_mode = optparse.make_option( |
|
102 '-p', '--parallel-mode', action='store_true', |
|
103 help=( |
|
104 "Append the machine name, process id and random number to the " |
|
105 ".coverage data file name to simplify collecting data from " |
|
106 "many processes." |
|
107 ), |
|
108 ) |
|
109 module = optparse.make_option( |
|
110 '-m', '--module', action='store_true', |
|
111 help=( |
|
112 "<pyfile> is an importable Python module, not a script path, " |
|
113 "to be run as 'python -m' would run it." |
|
114 ), |
|
115 ) |
|
116 rcfile = optparse.make_option( |
|
117 '', '--rcfile', action='store', |
|
118 help=( |
|
119 "Specify configuration file. " |
|
120 "By default '.coveragerc', 'setup.cfg' and 'tox.ini' are tried." |
|
121 ), |
|
122 ) |
|
123 source = optparse.make_option( |
|
124 '', '--source', action='store', metavar="SRC1,SRC2,...", |
|
125 help="A list of packages or directories of code to be measured.", |
|
126 ) |
|
127 timid = optparse.make_option( |
|
128 '', '--timid', action='store_true', |
|
129 help=( |
|
130 "Use a simpler but slower trace method. Try this if you get " |
|
131 "seemingly impossible results!" |
|
132 ), |
|
133 ) |
|
134 title = optparse.make_option( |
|
135 '', '--title', action='store', metavar="TITLE", |
|
136 help="A text string to use as the title on the HTML.", |
|
137 ) |
|
138 version = optparse.make_option( |
|
139 '', '--version', action='store_true', |
|
140 help="Display version information and exit.", |
|
141 ) |
|
142 |
|
143 |
|
144 class CoverageOptionParser(optparse.OptionParser, object): |
|
145 """Base OptionParser for coverage.py. |
|
146 |
|
147 Problems don't exit the program. |
|
148 Defaults are initialized for all options. |
|
149 |
|
150 """ |
|
151 |
|
152 def __init__(self, *args, **kwargs): |
|
153 super(CoverageOptionParser, self).__init__( |
|
154 add_help_option=False, *args, **kwargs |
|
155 ) |
|
156 self.set_defaults( |
|
157 action=None, |
|
158 append=None, |
|
159 branch=None, |
|
160 concurrency=None, |
|
161 debug=None, |
|
162 directory=None, |
|
163 fail_under=None, |
|
164 help=None, |
|
165 ignore_errors=None, |
|
166 include=None, |
|
167 module=None, |
|
168 omit=None, |
|
169 parallel_mode=None, |
|
170 pylib=None, |
|
171 rcfile=True, |
|
172 show_missing=None, |
|
173 skip_covered=None, |
|
174 source=None, |
|
175 timid=None, |
|
176 title=None, |
|
177 version=None, |
|
178 ) |
|
179 |
|
180 self.disable_interspersed_args() |
|
181 self.help_fn = self.help_noop |
|
182 |
|
183 def help_noop(self, error=None, topic=None, parser=None): |
|
184 """No-op help function.""" |
|
185 pass |
|
186 |
|
187 class OptionParserError(Exception): |
|
188 """Used to stop the optparse error handler ending the process.""" |
|
189 pass |
|
190 |
|
191 def parse_args_ok(self, args=None, options=None): |
|
192 """Call optparse.parse_args, but return a triple: |
|
193 |
|
194 (ok, options, args) |
|
195 |
|
196 """ |
|
197 try: |
|
198 options, args = \ |
|
199 super(CoverageOptionParser, self).parse_args(args, options) |
|
200 except self.OptionParserError: |
|
201 return False, None, None |
|
202 return True, options, args |
|
203 |
|
204 def error(self, msg): |
|
205 """Override optparse.error so sys.exit doesn't get called.""" |
|
206 self.help_fn(msg) |
|
207 raise self.OptionParserError |
|
208 |
|
209 |
|
210 class GlobalOptionParser(CoverageOptionParser): |
|
211 """Command-line parser for coverage.py global option arguments.""" |
|
212 |
|
213 def __init__(self): |
|
214 super(GlobalOptionParser, self).__init__() |
|
215 |
|
216 self.add_options([ |
|
217 Opts.help, |
|
218 Opts.version, |
|
219 ]) |
|
220 |
|
221 |
|
222 class CmdOptionParser(CoverageOptionParser): |
|
223 """Parse one of the new-style commands for coverage.py.""" |
|
224 |
|
225 def __init__(self, action, options, defaults=None, usage=None, description=None): |
|
226 """Create an OptionParser for a coverage.py command. |
|
227 |
|
228 `action` is the slug to put into `options.action`. |
|
229 `options` is a list of Option's for the command. |
|
230 `defaults` is a dict of default value for options. |
|
231 `usage` is the usage string to display in help. |
|
232 `description` is the description of the command, for the help text. |
|
233 |
|
234 """ |
|
235 if usage: |
|
236 usage = "%prog " + usage |
|
237 super(CmdOptionParser, self).__init__( |
|
238 usage=usage, |
|
239 description=description, |
|
240 ) |
|
241 self.set_defaults(action=action, **(defaults or {})) |
|
242 self.add_options(options) |
|
243 self.cmd = action |
|
244 |
|
245 def __eq__(self, other): |
|
246 # A convenience equality, so that I can put strings in unit test |
|
247 # results, and they will compare equal to objects. |
|
248 return (other == "<CmdOptionParser:%s>" % self.cmd) |
|
249 |
|
250 __hash__ = None # This object doesn't need to be hashed. |
|
251 |
|
252 def get_prog_name(self): |
|
253 """Override of an undocumented function in optparse.OptionParser.""" |
|
254 program_name = super(CmdOptionParser, self).get_prog_name() |
|
255 |
|
256 # Include the sub-command for this parser as part of the command. |
|
257 return "{command} {subcommand}".format(command=program_name, subcommand=self.cmd) |
|
258 |
|
259 |
|
260 GLOBAL_ARGS = [ |
|
261 Opts.debug, |
|
262 Opts.help, |
|
263 Opts.rcfile, |
|
264 ] |
|
265 |
|
266 CMDS = { |
|
267 'annotate': CmdOptionParser( |
|
268 "annotate", |
|
269 [ |
|
270 Opts.directory, |
|
271 Opts.ignore_errors, |
|
272 Opts.include, |
|
273 Opts.omit, |
|
274 ] + GLOBAL_ARGS, |
|
275 usage="[options] [modules]", |
|
276 description=( |
|
277 "Make annotated copies of the given files, marking statements that are executed " |
|
278 "with > and statements that are missed with !." |
|
279 ), |
|
280 ), |
|
281 |
|
282 'combine': CmdOptionParser( |
|
283 "combine", |
|
284 [ |
|
285 Opts.append, |
|
286 ] + GLOBAL_ARGS, |
|
287 usage="[options] <path1> <path2> ... <pathN>", |
|
288 description=( |
|
289 "Combine data from multiple coverage files collected " |
|
290 "with 'run -p'. The combined results are written to a single " |
|
291 "file representing the union of the data. The positional " |
|
292 "arguments are data files or directories containing data files. " |
|
293 "If no paths are provided, data files in the default data file's " |
|
294 "directory are combined." |
|
295 ), |
|
296 ), |
|
297 |
|
298 'debug': CmdOptionParser( |
|
299 "debug", GLOBAL_ARGS, |
|
300 usage="<topic>", |
|
301 description=( |
|
302 "Display information on the internals of coverage.py, " |
|
303 "for diagnosing problems. " |
|
304 "Topics are 'data' to show a summary of the collected data, " |
|
305 "or 'sys' to show installation information." |
|
306 ), |
|
307 ), |
|
308 |
|
309 'erase': CmdOptionParser( |
|
310 "erase", GLOBAL_ARGS, |
|
311 description="Erase previously collected coverage data.", |
|
312 ), |
|
313 |
|
314 'help': CmdOptionParser( |
|
315 "help", GLOBAL_ARGS, |
|
316 usage="[command]", |
|
317 description="Describe how to use coverage.py", |
|
318 ), |
|
319 |
|
320 'html': CmdOptionParser( |
|
321 "html", |
|
322 [ |
|
323 Opts.directory, |
|
324 Opts.fail_under, |
|
325 Opts.ignore_errors, |
|
326 Opts.include, |
|
327 Opts.omit, |
|
328 Opts.title, |
|
329 Opts.skip_covered, |
|
330 ] + GLOBAL_ARGS, |
|
331 usage="[options] [modules]", |
|
332 description=( |
|
333 "Create an HTML report of the coverage of the files. " |
|
334 "Each file gets its own page, with the source decorated to show " |
|
335 "executed, excluded, and missed lines." |
|
336 ), |
|
337 ), |
|
338 |
|
339 'report': CmdOptionParser( |
|
340 "report", |
|
341 [ |
|
342 Opts.fail_under, |
|
343 Opts.ignore_errors, |
|
344 Opts.include, |
|
345 Opts.omit, |
|
346 Opts.show_missing, |
|
347 Opts.skip_covered, |
|
348 ] + GLOBAL_ARGS, |
|
349 usage="[options] [modules]", |
|
350 description="Report coverage statistics on modules." |
|
351 ), |
|
352 |
|
353 'run': CmdOptionParser( |
|
354 "run", |
|
355 [ |
|
356 Opts.append, |
|
357 Opts.branch, |
|
358 Opts.concurrency, |
|
359 Opts.include, |
|
360 Opts.module, |
|
361 Opts.omit, |
|
362 Opts.pylib, |
|
363 Opts.parallel_mode, |
|
364 Opts.source, |
|
365 Opts.timid, |
|
366 ] + GLOBAL_ARGS, |
|
367 usage="[options] <pyfile> [program options]", |
|
368 description="Run a Python program, measuring code execution." |
|
369 ), |
|
370 |
|
371 'xml': CmdOptionParser( |
|
372 "xml", |
|
373 [ |
|
374 Opts.fail_under, |
|
375 Opts.ignore_errors, |
|
376 Opts.include, |
|
377 Opts.omit, |
|
378 Opts.output_xml, |
|
379 ] + GLOBAL_ARGS, |
|
380 usage="[options] [modules]", |
|
381 description="Generate an XML report of coverage results." |
|
382 ), |
|
383 } |
|
384 |
|
385 |
|
386 OK, ERR, FAIL_UNDER = 0, 1, 2 |
|
387 |
|
388 |
|
389 class CoverageScript(object): |
|
390 """The command-line interface to coverage.py.""" |
|
391 |
|
392 def __init__(self, _covpkg=None, _run_python_file=None, |
|
393 _run_python_module=None, _help_fn=None, _path_exists=None): |
|
394 # _covpkg is for dependency injection, so we can test this code. |
|
395 if _covpkg: |
|
396 self.covpkg = _covpkg |
|
397 else: |
|
398 import coverage |
|
399 self.covpkg = coverage |
|
400 |
|
401 # For dependency injection: |
|
402 self.run_python_file = _run_python_file or run_python_file |
|
403 self.run_python_module = _run_python_module or run_python_module |
|
404 self.help_fn = _help_fn or self.help |
|
405 self.path_exists = _path_exists or os.path.exists |
|
406 self.global_option = False |
|
407 |
|
408 self.coverage = None |
|
409 |
|
410 program_path = sys.argv[0] |
|
411 if program_path.endswith(os.path.sep + '__main__.py'): |
|
412 # The path is the main module of a package; get that path instead. |
|
413 program_path = os.path.dirname(program_path) |
|
414 self.program_name = os.path.basename(program_path) |
|
415 if env.WINDOWS: |
|
416 # entry_points={'console_scripts':...} on Windows makes files |
|
417 # called coverage.exe, coverage3.exe, and coverage-3.5.exe. These |
|
418 # invoke coverage-script.py, coverage3-script.py, and |
|
419 # coverage-3.5-script.py. argv[0] is the .py file, but we want to |
|
420 # get back to the original form. |
|
421 auto_suffix = "-script.py" |
|
422 if self.program_name.endswith(auto_suffix): |
|
423 self.program_name = self.program_name[:-len(auto_suffix)] |
|
424 |
|
425 def command_line(self, argv): |
|
426 """The bulk of the command line interface to coverage.py. |
|
427 |
|
428 `argv` is the argument list to process. |
|
429 |
|
430 Returns 0 if all is well, 1 if something went wrong. |
|
431 |
|
432 """ |
|
433 # Collect the command-line options. |
|
434 if not argv: |
|
435 self.help_fn(topic='minimum_help') |
|
436 return OK |
|
437 |
|
438 # The command syntax we parse depends on the first argument. Global |
|
439 # switch syntax always starts with an option. |
|
440 self.global_option = argv[0].startswith('-') |
|
441 if self.global_option: |
|
442 parser = GlobalOptionParser() |
|
443 else: |
|
444 parser = CMDS.get(argv[0]) |
|
445 if not parser: |
|
446 self.help_fn("Unknown command: '%s'" % argv[0]) |
|
447 return ERR |
|
448 argv = argv[1:] |
|
449 |
|
450 parser.help_fn = self.help_fn |
|
451 ok, options, args = parser.parse_args_ok(argv) |
|
452 if not ok: |
|
453 return ERR |
|
454 |
|
455 # Handle help and version. |
|
456 if self.do_help(options, args, parser): |
|
457 return OK |
|
458 |
|
459 # We need to be able to import from the current directory, because |
|
460 # plugins may try to, for example, to read Django settings. |
|
461 sys.path[0] = '' |
|
462 |
|
463 # Listify the list options. |
|
464 source = unshell_list(options.source) |
|
465 omit = unshell_list(options.omit) |
|
466 include = unshell_list(options.include) |
|
467 debug = unshell_list(options.debug) |
|
468 |
|
469 # Do something. |
|
470 self.coverage = self.covpkg.Coverage( |
|
471 data_suffix=options.parallel_mode, |
|
472 cover_pylib=options.pylib, |
|
473 timid=options.timid, |
|
474 branch=options.branch, |
|
475 config_file=options.rcfile, |
|
476 source=source, |
|
477 omit=omit, |
|
478 include=include, |
|
479 debug=debug, |
|
480 concurrency=options.concurrency, |
|
481 ) |
|
482 |
|
483 if options.action == "debug": |
|
484 return self.do_debug(args) |
|
485 |
|
486 elif options.action == "erase": |
|
487 self.coverage.erase() |
|
488 return OK |
|
489 |
|
490 elif options.action == "run": |
|
491 return self.do_run(options, args) |
|
492 |
|
493 elif options.action == "combine": |
|
494 if options.append: |
|
495 self.coverage.load() |
|
496 data_dirs = args or None |
|
497 self.coverage.combine(data_dirs, strict=True) |
|
498 self.coverage.save() |
|
499 return OK |
|
500 |
|
501 # Remaining actions are reporting, with some common options. |
|
502 report_args = dict( |
|
503 morfs=unglob_args(args), |
|
504 ignore_errors=options.ignore_errors, |
|
505 omit=omit, |
|
506 include=include, |
|
507 ) |
|
508 |
|
509 self.coverage.load() |
|
510 |
|
511 total = None |
|
512 if options.action == "report": |
|
513 total = self.coverage.report( |
|
514 show_missing=options.show_missing, |
|
515 skip_covered=options.skip_covered, **report_args) |
|
516 elif options.action == "annotate": |
|
517 self.coverage.annotate( |
|
518 directory=options.directory, **report_args) |
|
519 elif options.action == "html": |
|
520 total = self.coverage.html_report( |
|
521 directory=options.directory, title=options.title, |
|
522 skip_covered=options.skip_covered, **report_args) |
|
523 elif options.action == "xml": |
|
524 outfile = options.outfile |
|
525 total = self.coverage.xml_report(outfile=outfile, **report_args) |
|
526 |
|
527 if total is not None: |
|
528 # Apply the command line fail-under options, and then use the config |
|
529 # value, so we can get fail_under from the config file. |
|
530 if options.fail_under is not None: |
|
531 self.coverage.set_option("report:fail_under", options.fail_under) |
|
532 |
|
533 fail_under = self.coverage.get_option("report:fail_under") |
|
534 precision = self.coverage.get_option("report:precision") |
|
535 if should_fail_under(total, fail_under, precision): |
|
536 return FAIL_UNDER |
|
537 |
|
538 return OK |
|
539 |
|
540 def help(self, error=None, topic=None, parser=None): |
|
541 """Display an error message, or the named topic.""" |
|
542 assert error or topic or parser |
|
543 if error: |
|
544 print(error, file=sys.stderr) |
|
545 print("Use '%s help' for help." % (self.program_name,), file=sys.stderr) |
|
546 elif parser: |
|
547 print(parser.format_help().strip()) |
|
548 else: |
|
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() |
|
556 if help_msg: |
|
557 print(help_msg.format(**help_params)) |
|
558 else: |
|
559 print("Don't know topic %r" % topic) |
|
560 |
|
561 def do_help(self, options, args, parser): |
|
562 """Deal with help requests. |
|
563 |
|
564 Return True if it handled the request, False if not. |
|
565 |
|
566 """ |
|
567 # Handle help. |
|
568 if options.help: |
|
569 if self.global_option: |
|
570 self.help_fn(topic='help') |
|
571 else: |
|
572 self.help_fn(parser=parser) |
|
573 return True |
|
574 |
|
575 if options.action == "help": |
|
576 if args: |
|
577 for a in args: |
|
578 parser = CMDS.get(a) |
|
579 if parser: |
|
580 self.help_fn(parser=parser) |
|
581 else: |
|
582 self.help_fn(topic=a) |
|
583 else: |
|
584 self.help_fn(topic='help') |
|
585 return True |
|
586 |
|
587 # Handle version. |
|
588 if options.version: |
|
589 self.help_fn(topic='version') |
|
590 return True |
|
591 |
|
592 return False |
|
593 |
|
594 def do_run(self, options, args): |
|
595 """Implementation of 'coverage run'.""" |
|
596 |
|
597 if not args: |
|
598 self.help_fn("Nothing to do.") |
|
599 return ERR |
|
600 |
|
601 if options.append and self.coverage.get_option("run:parallel"): |
|
602 self.help_fn("Can't append to data files in parallel mode.") |
|
603 return ERR |
|
604 |
|
605 if options.concurrency == "multiprocessing": |
|
606 # Can't set other run-affecting command line options with |
|
607 # multiprocessing. |
|
608 for opt_name in ['branch', 'include', 'omit', 'pylib', 'source', 'timid']: |
|
609 # As it happens, all of these options have no default, meaning |
|
610 # they will be None if they have not been specified. |
|
611 if getattr(options, opt_name) is not None: |
|
612 self.help_fn( |
|
613 "Options affecting multiprocessing must be specified " |
|
614 "in a configuration file." |
|
615 ) |
|
616 return ERR |
|
617 |
|
618 if not self.coverage.get_option("run:parallel"): |
|
619 if not options.append: |
|
620 self.coverage.erase() |
|
621 |
|
622 # Run the script. |
|
623 self.coverage.start() |
|
624 code_ran = True |
|
625 try: |
|
626 if options.module: |
|
627 self.run_python_module(args[0], args) |
|
628 else: |
|
629 filename = args[0] |
|
630 self.run_python_file(filename, args) |
|
631 except NoSource: |
|
632 code_ran = False |
|
633 raise |
|
634 finally: |
|
635 self.coverage.stop() |
|
636 if code_ran: |
|
637 if options.append: |
|
638 data_file = self.coverage.get_option("run:data_file") |
|
639 if self.path_exists(data_file): |
|
640 self.coverage.combine(data_paths=[data_file]) |
|
641 self.coverage.save() |
|
642 |
|
643 return OK |
|
644 |
|
645 def do_debug(self, args): |
|
646 """Implementation of 'coverage debug'.""" |
|
647 |
|
648 if not args: |
|
649 self.help_fn("What information would you like: config, data, sys?") |
|
650 return ERR |
|
651 |
|
652 for info in args: |
|
653 if info == 'sys': |
|
654 sys_info = self.coverage.sys_info() |
|
655 print(info_header("sys")) |
|
656 for line in info_formatter(sys_info): |
|
657 print(" %s" % line) |
|
658 elif info == 'data': |
|
659 self.coverage.load() |
|
660 data = self.coverage.data |
|
661 print(info_header("data")) |
|
662 print("path: %s" % self.coverage.data_files.filename) |
|
663 if data: |
|
664 print("has_arcs: %r" % data.has_arcs()) |
|
665 summary = data.line_counts(fullpath=True) |
|
666 filenames = sorted(summary.keys()) |
|
667 print("\n%d files:" % len(filenames)) |
|
668 for f in filenames: |
|
669 line = "%s: %d lines" % (f, summary[f]) |
|
670 plugin = data.file_tracer(f) |
|
671 if plugin: |
|
672 line += " [%s]" % plugin |
|
673 print(line) |
|
674 else: |
|
675 print("No data collected") |
|
676 elif info == 'config': |
|
677 print(info_header("config")) |
|
678 config_info = self.coverage.config.__dict__.items() |
|
679 for line in info_formatter(config_info): |
|
680 print(" %s" % line) |
|
681 else: |
|
682 self.help_fn("Don't know what you mean by %r" % info) |
|
683 return ERR |
|
684 |
|
685 return OK |
|
686 |
|
687 |
|
688 def unshell_list(s): |
|
689 """Turn a command-line argument into a list.""" |
|
690 if not s: |
|
691 return None |
|
692 if env.WINDOWS: |
|
693 # When running coverage.py as coverage.exe, some of the behavior |
|
694 # of the shell is emulated: wildcards are expanded into a list of |
|
695 # file names. So you have to single-quote patterns on the command |
|
696 # line, but (not) helpfully, the single quotes are included in the |
|
697 # argument, so we have to strip them off here. |
|
698 s = s.strip("'") |
|
699 return s.split(',') |
|
700 |
|
701 |
|
702 def unglob_args(args): |
|
703 """Interpret shell wildcards for platforms that need it.""" |
|
704 if env.WINDOWS: |
|
705 globbed = [] |
|
706 for arg in args: |
|
707 if '?' in arg or '*' in arg: |
|
708 globbed.extend(glob.glob(arg)) |
|
709 else: |
|
710 globbed.append(arg) |
|
711 args = globbed |
|
712 return args |
|
713 |
|
714 |
|
715 HELP_TOPICS = { |
|
716 'help': """\ |
|
717 Coverage.py, version {__version__} {extension_modifier} |
|
718 Measure, collect, and report on code coverage in Python programs. |
|
719 |
|
720 usage: {program_name} <command> [options] [args] |
|
721 |
|
722 Commands: |
|
723 annotate Annotate source files with execution information. |
|
724 combine Combine a number of data files. |
|
725 erase Erase previously collected coverage data. |
|
726 help Get help on using coverage.py. |
|
727 html Create an HTML report. |
|
728 report Report coverage stats on modules. |
|
729 run Run a Python program and measure code execution. |
|
730 xml Create an XML report of coverage results. |
|
731 |
|
732 Use "{program_name} help <command>" for detailed help on any command. |
|
733 For full documentation, see {__url__} |
|
734 """, |
|
735 |
|
736 'minimum_help': """\ |
|
737 Code coverage for Python. Use '{program_name} help' for help. |
|
738 """, |
|
739 |
|
740 'version': """\ |
|
741 Coverage.py, version {__version__} {extension_modifier} |
|
742 Documentation at {__url__} |
|
743 """, |
|
744 } |
|
745 |
|
746 |
|
747 def main(argv=None): |
|
748 """The main entry point to coverage.py. |
|
749 |
|
750 This is installed as the script entry point. |
|
751 |
|
752 """ |
|
753 if argv is None: |
|
754 argv = sys.argv[1:] |
|
755 try: |
|
756 status = CoverageScript().command_line(argv) |
|
757 except ExceptionDuringRun as err: |
|
758 # An exception was caught while running the product code. The |
|
759 # sys.exc_info() return tuple is packed into an ExceptionDuringRun |
|
760 # exception. |
|
761 traceback.print_exception(*err.args) # pylint: disable=no-value-for-parameter |
|
762 status = ERR |
|
763 except BaseCoverageException as err: |
|
764 # A controlled error inside coverage.py: print the message to the user. |
|
765 print(err) |
|
766 status = ERR |
|
767 except SystemExit as err: |
|
768 # The user called `sys.exit()`. Exit with their argument, if any. |
|
769 if err.args: |
|
770 status = err.args[0] |
|
771 else: |
|
772 status = None |
|
773 return status |