1 """Command-line support for Coverage.""" |
1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 |
2 |
2 # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt |
3 import optparse, os, sys, time, traceback |
3 |
4 |
4 """Command-line support for coverage.py.""" |
5 from .backward import sorted # pylint: disable=W0622 |
5 |
6 from .execfile import run_python_file, run_python_module |
6 import glob |
7 from .misc import CoverageException, ExceptionDuringRun, NoSource |
7 import optparse |
8 from .debug import info_formatter |
8 import os.path |
|
9 import sys |
|
10 import traceback |
|
11 |
|
12 from coverage import env |
|
13 from coverage.execfile import run_python_file, run_python_module |
|
14 from coverage.misc import CoverageException, ExceptionDuringRun, NoSource |
|
15 from coverage.debug import info_formatter, info_header |
9 |
16 |
10 |
17 |
11 class Opts(object): |
18 class Opts(object): |
12 """A namespace class for individual options we'll build parsers from.""" |
19 """A namespace class for individual options we'll build parsers from.""" |
13 |
20 |
14 append = optparse.make_option( |
21 append = optparse.make_option( |
15 '-a', '--append', action='store_false', dest="erase_first", |
22 '-a', '--append', action='store_true', |
16 help="Append coverage data to .coverage, otherwise it is started " |
23 help="Append coverage data to .coverage, otherwise it is started " |
17 "clean with each run." |
24 "clean with each run." |
18 ) |
25 ) |
19 branch = optparse.make_option( |
26 branch = optparse.make_option( |
20 '', '--branch', action='store_true', |
27 '', '--branch', action='store_true', |
21 help="Measure branch coverage in addition to statement coverage." |
28 help="Measure branch coverage in addition to statement coverage." |
22 ) |
29 ) |
|
30 CONCURRENCY_CHOICES = [ |
|
31 "thread", "gevent", "greenlet", "eventlet", "multiprocessing", |
|
32 ] |
|
33 concurrency = optparse.make_option( |
|
34 '', '--concurrency', action='store', metavar="LIB", |
|
35 choices=CONCURRENCY_CHOICES, |
|
36 help="Properly measure code using a concurrency library. " |
|
37 "Valid values are: %s." % ", ".join(CONCURRENCY_CHOICES) |
|
38 ) |
23 debug = optparse.make_option( |
39 debug = optparse.make_option( |
24 '', '--debug', action='store', metavar="OPTS", |
40 '', '--debug', action='store', metavar="OPTS", |
25 help="Debug options, separated by commas" |
41 help="Debug options, separated by commas" |
26 ) |
42 ) |
27 directory = optparse.make_option( |
43 directory = optparse.make_option( |
54 show_missing = optparse.make_option( |
70 show_missing = optparse.make_option( |
55 '-m', '--show-missing', action='store_true', |
71 '-m', '--show-missing', action='store_true', |
56 help="Show line numbers of statements in each module that weren't " |
72 help="Show line numbers of statements in each module that weren't " |
57 "executed." |
73 "executed." |
58 ) |
74 ) |
59 old_omit = optparse.make_option( |
75 skip_covered = optparse.make_option( |
60 '-o', '--omit', action='store', |
76 '--skip-covered', action='store_true', |
61 metavar="PAT1,PAT2,...", |
77 help="Skip files with 100% coverage." |
62 help="Omit files when their filename matches one of these patterns. " |
|
63 "Usually needs quoting on the command line." |
|
64 ) |
78 ) |
65 omit = optparse.make_option( |
79 omit = optparse.make_option( |
66 '', '--omit', action='store', |
80 '', '--omit', action='store', |
67 metavar="PAT1,PAT2,...", |
81 metavar="PAT1,PAT2,...", |
68 help="Omit files when their filename matches one of these patterns. " |
82 help="Omit files whose paths match one of these patterns. " |
69 "Usually needs quoting on the command line." |
83 "Accepts shell-style wildcards, which must be quoted." |
70 ) |
84 ) |
71 output_xml = optparse.make_option( |
85 output_xml = optparse.make_option( |
72 '-o', '', action='store', dest="outfile", |
86 '-o', '', action='store', dest="outfile", |
73 metavar="OUTFILE", |
87 metavar="OUTFILE", |
74 help="Write the XML report to this file. Defaults to 'coverage.xml'" |
88 help="Write the XML report to this file. Defaults to 'coverage.xml'" |
169 """Override optparse.error so sys.exit doesn't get called.""" |
185 """Override optparse.error so sys.exit doesn't get called.""" |
170 self.help_fn(msg) |
186 self.help_fn(msg) |
171 raise self.OptionParserError |
187 raise self.OptionParserError |
172 |
188 |
173 |
189 |
174 class ClassicOptionParser(CoverageOptionParser): |
190 class GlobalOptionParser(CoverageOptionParser): |
175 """Command-line parser for coverage.py classic arguments.""" |
191 """Command-line parser for coverage.py global option arguments.""" |
176 |
192 |
177 def __init__(self): |
193 def __init__(self): |
178 super(ClassicOptionParser, self).__init__() |
194 super(GlobalOptionParser, 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 |
195 |
187 self.add_options([ |
196 self.add_options([ |
188 Opts.directory, |
|
189 Opts.help, |
197 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, |
198 Opts.version, |
197 ]) |
199 ]) |
198 |
200 |
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 |
201 |
211 class CmdOptionParser(CoverageOptionParser): |
202 class CmdOptionParser(CoverageOptionParser): |
212 """Parse one of the new-style commands for coverage.py.""" |
203 """Parse one of the new-style commands for coverage.py.""" |
213 |
204 |
214 def __init__(self, action, options=None, defaults=None, usage=None, |
205 def __init__(self, action, options=None, defaults=None, usage=None, |
215 cmd=None, description=None |
206 description=None |
216 ): |
207 ): |
217 """Create an OptionParser for a coverage command. |
208 """Create an OptionParser for a coverage.py command. |
218 |
209 |
219 `action` is the slug to put into `options.actions`. |
210 `action` is the slug to put into `options.action`. |
220 `options` is a list of Option's for the command. |
211 `options` is a list of Option's for the command. |
221 `defaults` is a dict of default value for options. |
212 `defaults` is a dict of default value for options. |
222 `usage` is the usage string to display in help. |
213 `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. |
214 `description` is the description of the command, for the help text. |
225 |
215 |
226 """ |
216 """ |
227 if usage: |
217 if usage: |
228 usage = "%prog " + usage |
218 usage = "%prog " + usage |
229 super(CmdOptionParser, self).__init__( |
219 super(CmdOptionParser, self).__init__( |
230 prog="coverage %s" % (cmd or action), |
220 prog="coverage %s" % action, |
231 usage=usage, |
221 usage=usage, |
232 description=description, |
222 description=description, |
233 ) |
223 ) |
234 self.set_defaults(actions=[action], **(defaults or {})) |
224 self.set_defaults(action=action, **(defaults or {})) |
235 if options: |
225 if options: |
236 self.add_options(options) |
226 self.add_options(options) |
237 self.cmd = cmd or action |
227 self.cmd = action |
238 |
228 |
239 def __eq__(self, other): |
229 def __eq__(self, other): |
240 # A convenience equality, so that I can put strings in unit test |
230 # A convenience equality, so that I can put strings in unit test |
241 # results, and they will compare equal to objects. |
231 # results, and they will compare equal to objects. |
242 return (other == "<CmdOptionParser:%s>" % self.cmd) |
232 return (other == "<CmdOptionParser:%s>" % self.cmd) |
243 |
233 |
244 GLOBAL_ARGS = [ |
234 GLOBAL_ARGS = [ |
|
235 Opts.debug, |
|
236 Opts.help, |
245 Opts.rcfile, |
237 Opts.rcfile, |
246 Opts.help, |
|
247 ] |
238 ] |
248 |
239 |
249 CMDS = { |
240 CMDS = { |
250 'annotate': CmdOptionParser("annotate", |
241 'annotate': CmdOptionParser("annotate", |
251 [ |
242 [ |
252 Opts.directory, |
243 Opts.directory, |
253 Opts.ignore_errors, |
244 Opts.ignore_errors, |
|
245 Opts.include, |
254 Opts.omit, |
246 Opts.omit, |
255 Opts.include, |
|
256 ] + GLOBAL_ARGS, |
247 ] + GLOBAL_ARGS, |
257 usage = "[options] [modules]", |
248 usage = "[options] [modules]", |
258 description = "Make annotated copies of the given files, marking " |
249 description = "Make annotated copies of the given files, marking " |
259 "statements that are executed with > and statements that are " |
250 "statements that are executed with > and statements that are " |
260 "missed with !." |
251 "missed with !." |
261 ), |
252 ), |
262 |
253 |
263 'combine': CmdOptionParser("combine", GLOBAL_ARGS, |
254 'combine': CmdOptionParser("combine", GLOBAL_ARGS, |
264 usage = " ", |
255 usage = "<path1> <path2> ... <pathN>", |
265 description = "Combine data from multiple coverage files collected " |
256 description = "Combine data from multiple coverage files collected " |
266 "with 'run -p'. The combined results are written to a single " |
257 "with 'run -p'. The combined results are written to a single " |
267 "file representing the union of the data." |
258 "file representing the union of the data. The positional " |
|
259 "arguments are data files or directories containing data files. " |
|
260 "If no paths are provided, data files in the default data file's " |
|
261 "directory are combined." |
268 ), |
262 ), |
269 |
263 |
270 'debug': CmdOptionParser("debug", GLOBAL_ARGS, |
264 'debug': CmdOptionParser("debug", GLOBAL_ARGS, |
271 usage = "<topic>", |
265 usage = "<topic>", |
272 description = "Display information on the internals of coverage.py, " |
266 description = "Display information on the internals of coverage.py, " |
302 |
296 |
303 'report': CmdOptionParser("report", |
297 'report': CmdOptionParser("report", |
304 [ |
298 [ |
305 Opts.fail_under, |
299 Opts.fail_under, |
306 Opts.ignore_errors, |
300 Opts.ignore_errors, |
|
301 Opts.include, |
307 Opts.omit, |
302 Opts.omit, |
308 Opts.include, |
|
309 Opts.show_missing, |
303 Opts.show_missing, |
|
304 Opts.skip_covered, |
310 ] + GLOBAL_ARGS, |
305 ] + GLOBAL_ARGS, |
311 usage = "[options] [modules]", |
306 usage = "[options] [modules]", |
312 description = "Report coverage statistics on modules." |
307 description = "Report coverage statistics on modules." |
313 ), |
308 ), |
314 |
309 |
315 'run': CmdOptionParser("execute", |
310 'run': CmdOptionParser("run", |
316 [ |
311 [ |
317 Opts.append, |
312 Opts.append, |
318 Opts.branch, |
313 Opts.branch, |
319 Opts.debug, |
314 Opts.concurrency, |
|
315 Opts.include, |
|
316 Opts.module, |
|
317 Opts.omit, |
320 Opts.pylib, |
318 Opts.pylib, |
321 Opts.parallel_mode, |
319 Opts.parallel_mode, |
322 Opts.module, |
320 Opts.source, |
323 Opts.timid, |
321 Opts.timid, |
324 Opts.source, |
|
325 Opts.omit, |
|
326 Opts.include, |
|
327 ] + GLOBAL_ARGS, |
322 ] + GLOBAL_ARGS, |
328 defaults = {'erase_first': True}, |
|
329 cmd = "run", |
|
330 usage = "[options] <pyfile> [program options]", |
323 usage = "[options] <pyfile> [program options]", |
331 description = "Run a Python program, measuring code execution." |
324 description = "Run a Python program, measuring code execution." |
332 ), |
325 ), |
333 |
326 |
334 'xml': CmdOptionParser("xml", |
327 'xml': CmdOptionParser("xml", |
335 [ |
328 [ |
336 Opts.fail_under, |
329 Opts.fail_under, |
337 Opts.ignore_errors, |
330 Opts.ignore_errors, |
|
331 Opts.include, |
338 Opts.omit, |
332 Opts.omit, |
339 Opts.include, |
|
340 Opts.output_xml, |
333 Opts.output_xml, |
341 ] + GLOBAL_ARGS, |
334 ] + GLOBAL_ARGS, |
342 cmd = "xml", |
|
343 usage = "[options] [modules]", |
335 usage = "[options] [modules]", |
344 description = "Generate an XML report of coverage results." |
336 description = "Generate an XML report of coverage results." |
345 ), |
337 ), |
346 } |
338 } |
347 |
339 |
348 |
340 |
349 OK, ERR, FAIL_UNDER = 0, 1, 2 |
341 OK, ERR, FAIL_UNDER = 0, 1, 2 |
350 |
342 |
351 |
343 |
352 class CoverageScript(object): |
344 class CoverageScript(object): |
353 """The command-line interface to Coverage.""" |
345 """The command-line interface to coverage.py.""" |
354 |
346 |
355 def __init__(self, _covpkg=None, _run_python_file=None, |
347 def __init__(self, _covpkg=None, _run_python_file=None, |
356 _run_python_module=None, _help_fn=None): |
348 _run_python_module=None, _help_fn=None, _path_exists=None): |
357 # _covpkg is for dependency injection, so we can test this code. |
349 # _covpkg is for dependency injection, so we can test this code. |
358 if _covpkg: |
350 if _covpkg: |
359 self.covpkg = _covpkg |
351 self.covpkg = _covpkg |
360 else: |
352 else: |
361 from . import coverage |
353 import coverage |
362 self.covpkg = coverage |
354 self.covpkg = coverage |
363 |
355 |
364 # For dependency injection: |
356 # For dependency injection: |
365 self.run_python_file = _run_python_file or run_python_file |
357 self.run_python_file = _run_python_file or run_python_file |
366 self.run_python_module = _run_python_module or run_python_module |
358 self.run_python_module = _run_python_module or run_python_module |
367 self.help_fn = _help_fn or self.help |
359 self.help_fn = _help_fn or self.help |
368 self.classic = False |
360 self.path_exists = _path_exists or os.path.exists |
|
361 self.global_option = False |
369 |
362 |
370 self.coverage = None |
363 self.coverage = None |
371 |
364 |
372 def command_line(self, argv): |
365 def command_line(self, argv): |
373 """The bulk of the command line interface to Coverage. |
366 """The bulk of the command line interface to coverage.py. |
374 |
367 |
375 `argv` is the argument list to process. |
368 `argv` is the argument list to process. |
376 |
369 |
377 Returns 0 if all is well, 1 if something went wrong. |
370 Returns 0 if all is well, 1 if something went wrong. |
378 |
371 |
380 # Collect the command-line options. |
373 # Collect the command-line options. |
381 if not argv: |
374 if not argv: |
382 self.help_fn(topic='minimum_help') |
375 self.help_fn(topic='minimum_help') |
383 return OK |
376 return OK |
384 |
377 |
385 # The command syntax we parse depends on the first argument. Classic |
378 # The command syntax we parse depends on the first argument. Global |
386 # syntax always starts with an option. |
379 # switch syntax always starts with an option. |
387 self.classic = argv[0].startswith('-') |
380 self.global_option = argv[0].startswith('-') |
388 if self.classic: |
381 if self.global_option: |
389 parser = ClassicOptionParser() |
382 parser = GlobalOptionParser() |
390 else: |
383 else: |
391 parser = CMDS.get(argv[0]) |
384 parser = CMDS.get(argv[0]) |
392 if not parser: |
385 if not parser: |
393 self.help_fn("Unknown command: '%s'" % argv[0]) |
386 self.help_fn("Unknown command: '%s'" % argv[0]) |
394 return ERR |
387 return ERR |
395 argv = argv[1:] |
388 argv = argv[1:] |
396 |
389 |
397 parser.help_fn = self.help_fn |
390 parser.help_fn = self.help_fn |
398 ok, options, args = parser.parse_args(argv) |
391 ok, options, args = parser.parse_args_ok(argv) |
399 if not ok: |
392 if not ok: |
400 return ERR |
393 return ERR |
401 |
394 |
402 # Handle help and version. |
395 # Handle help and version. |
403 if self.do_help(options, args, parser): |
396 if self.do_help(options, args, parser): |
404 return OK |
397 return OK |
405 |
398 |
406 # Check for conflicts and problems in the options. |
399 # Check for conflicts and problems in the options. |
407 if not self.args_ok(options, args): |
400 if not self.args_ok(options, args): |
408 return ERR |
401 return ERR |
|
402 |
|
403 # We need to be able to import from the current directory, because |
|
404 # plugins may try to, for example, to read Django settings. |
|
405 sys.path[0] = '' |
409 |
406 |
410 # Listify the list options. |
407 # Listify the list options. |
411 source = unshell_list(options.source) |
408 source = unshell_list(options.source) |
412 omit = unshell_list(options.omit) |
409 omit = unshell_list(options.omit) |
413 include = unshell_list(options.include) |
410 include = unshell_list(options.include) |
422 config_file = options.rcfile, |
419 config_file = options.rcfile, |
423 source = source, |
420 source = source, |
424 omit = omit, |
421 omit = omit, |
425 include = include, |
422 include = include, |
426 debug = debug, |
423 debug = debug, |
|
424 concurrency = options.concurrency, |
427 ) |
425 ) |
428 |
426 |
429 if 'debug' in options.actions: |
427 if options.action == "debug": |
430 return self.do_debug(args) |
428 return self.do_debug(args) |
431 |
429 |
432 if 'erase' in options.actions or options.erase_first: |
430 elif options.action == "erase": |
433 self.coverage.erase() |
431 self.coverage.erase() |
434 else: |
432 return OK |
|
433 |
|
434 elif options.action == "run": |
|
435 return self.do_run(options, args) |
|
436 |
|
437 elif options.action == "combine": |
435 self.coverage.load() |
438 self.coverage.load() |
436 |
439 data_dirs = args or None |
437 if 'execute' in options.actions: |
440 self.coverage.combine(data_dirs) |
438 self.do_execute(options, args) |
|
439 |
|
440 if 'combine' in options.actions: |
|
441 self.coverage.combine() |
|
442 self.coverage.save() |
441 self.coverage.save() |
|
442 return OK |
443 |
443 |
444 # Remaining actions are reporting, with some common options. |
444 # Remaining actions are reporting, with some common options. |
445 report_args = dict( |
445 report_args = dict( |
446 morfs = args, |
446 morfs = unglob_args(args), |
447 ignore_errors = options.ignore_errors, |
447 ignore_errors = options.ignore_errors, |
448 omit = omit, |
448 omit = omit, |
449 include = include, |
449 include = include, |
450 ) |
450 ) |
451 |
451 |
452 if 'report' in options.actions: |
452 self.coverage.load() |
|
453 |
|
454 total = None |
|
455 if options.action == "report": |
453 total = self.coverage.report( |
456 total = self.coverage.report( |
454 show_missing=options.show_missing, **report_args) |
457 show_missing=options.show_missing, |
455 if 'annotate' in options.actions: |
458 skip_covered=options.skip_covered, **report_args) |
|
459 elif options.action == "annotate": |
456 self.coverage.annotate( |
460 self.coverage.annotate( |
457 directory=options.directory, **report_args) |
461 directory=options.directory, **report_args) |
458 if 'html' in options.actions: |
462 elif options.action == "html": |
459 total = self.coverage.html_report( |
463 total = self.coverage.html_report( |
460 directory=options.directory, title=options.title, |
464 directory=options.directory, title=options.title, |
461 **report_args) |
465 **report_args) |
462 if 'xml' in options.actions: |
466 elif options.action == "xml": |
463 outfile = options.outfile |
467 outfile = options.outfile |
464 total = self.coverage.xml_report(outfile=outfile, **report_args) |
468 total = self.coverage.xml_report(outfile=outfile, **report_args) |
465 |
469 |
466 if options.fail_under is not None: |
470 if total is not None: |
467 if total >= options.fail_under: |
471 # Apply the command line fail-under options, and then use the config |
468 return OK |
472 # value, so we can get fail_under from the config file. |
469 else: |
473 if options.fail_under is not None: |
470 return FAIL_UNDER |
474 self.coverage.set_option("report:fail_under", options.fail_under) |
471 else: |
475 |
472 return OK |
476 if self.coverage.get_option("report:fail_under"): |
|
477 |
|
478 # Total needs to be rounded, but be careful of 0 and 100. |
|
479 if 0 < total < 1: |
|
480 total = 1 |
|
481 elif 99 < total < 100: |
|
482 total = 99 |
|
483 else: |
|
484 total = round(total) |
|
485 |
|
486 if total >= self.coverage.get_option("report:fail_under"): |
|
487 return OK |
|
488 else: |
|
489 return FAIL_UNDER |
|
490 |
|
491 return OK |
473 |
492 |
474 def help(self, error=None, topic=None, parser=None): |
493 def help(self, error=None, topic=None, parser=None): |
475 """Display an error message, or the named topic.""" |
494 """Display an error message, or the named topic.""" |
476 assert error or topic or parser |
495 assert error or topic or parser |
477 if error: |
496 if error: |
520 return False |
539 return False |
521 |
540 |
522 def args_ok(self, options, args): |
541 def args_ok(self, options, args): |
523 """Check for conflicts and problems in the options. |
542 """Check for conflicts and problems in the options. |
524 |
543 |
525 Returns True if everything is ok, or False if not. |
544 Returns True if everything is OK, or False if not. |
526 |
545 |
527 """ |
546 """ |
528 for i in ['erase', 'execute']: |
547 if options.action == "run" and not args: |
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.") |
548 self.help_fn("Nothing to do.") |
554 return False |
549 return False |
555 |
550 |
556 return True |
551 return True |
557 |
552 |
558 def do_execute(self, options, args): |
553 def do_run(self, options, args): |
559 """Implementation of 'coverage run'.""" |
554 """Implementation of 'coverage run'.""" |
560 |
555 |
561 # Set the first path element properly. |
556 if options.append and self.coverage.get_option("run:parallel"): |
562 old_path0 = sys.path[0] |
557 self.help_fn("Can't append to data files in parallel mode.") |
|
558 return ERR |
|
559 |
|
560 if not self.coverage.get_option("run:parallel"): |
|
561 if not options.append: |
|
562 self.coverage.erase() |
563 |
563 |
564 # Run the script. |
564 # Run the script. |
565 self.coverage.start() |
565 self.coverage.start() |
566 code_ran = True |
566 code_ran = True |
567 try: |
567 try: |
568 try: |
568 if options.module: |
569 if options.module: |
569 self.run_python_module(args[0], args) |
570 sys.path[0] = '' |
570 else: |
571 self.run_python_module(args[0], args) |
571 filename = args[0] |
572 else: |
572 self.run_python_file(filename, args) |
573 filename = args[0] |
573 except NoSource: |
574 sys.path[0] = os.path.abspath(os.path.dirname(filename)) |
574 code_ran = False |
575 self.run_python_file(filename, args) |
575 raise |
576 except NoSource: |
|
577 code_ran = False |
|
578 raise |
|
579 finally: |
576 finally: |
580 self.coverage.stop() |
577 self.coverage.stop() |
581 if code_ran: |
578 if code_ran: |
|
579 if options.append: |
|
580 data_file = self.coverage.get_option("run:data_file") |
|
581 if self.path_exists(data_file): |
|
582 self.coverage.combine(data_paths=[data_file]) |
582 self.coverage.save() |
583 self.coverage.save() |
583 |
584 |
584 # Restore the old path |
585 return OK |
585 sys.path[0] = old_path0 |
|
586 |
586 |
587 def do_debug(self, args): |
587 def do_debug(self, args): |
588 """Implementation of 'coverage debug'.""" |
588 """Implementation of 'coverage debug'.""" |
589 |
589 |
590 if not args: |
590 if not args: |
591 self.help_fn("What information would you like: data, sys?") |
591 self.help_fn("What information would you like: data, sys?") |
592 return ERR |
592 return ERR |
|
593 |
593 for info in args: |
594 for info in args: |
594 if info == 'sys': |
595 if info == 'sys': |
595 print("-- sys ----------------------------------------") |
596 sys_info = self.coverage.sys_info() |
596 for line in info_formatter(self.coverage.sysinfo()): |
597 print(info_header("sys")) |
|
598 for line in info_formatter(sys_info): |
597 print(" %s" % line) |
599 print(" %s" % line) |
598 elif info == 'data': |
600 elif info == 'data': |
599 print("-- data ---------------------------------------") |
|
600 self.coverage.load() |
601 self.coverage.load() |
601 print("path: %s" % self.coverage.data.filename) |
602 data = self.coverage.data |
602 print("has_arcs: %r" % self.coverage.data.has_arcs()) |
603 print(info_header("data")) |
603 summary = self.coverage.data.summary(fullpath=True) |
604 print("path: %s" % self.coverage.data_files.filename) |
604 if summary: |
605 if data: |
|
606 print("has_arcs: %r" % data.has_arcs()) |
|
607 summary = data.line_counts(fullpath=True) |
605 filenames = sorted(summary.keys()) |
608 filenames = sorted(summary.keys()) |
606 print("\n%d files:" % len(filenames)) |
609 print("\n%d files:" % len(filenames)) |
607 for f in filenames: |
610 for f in filenames: |
608 print("%s: %d lines" % (f, summary[f])) |
611 line = "%s: %d lines" % (f, summary[f]) |
|
612 plugin = data.file_tracer(f) |
|
613 if plugin: |
|
614 line += " [%s]" % plugin |
|
615 print(line) |
609 else: |
616 else: |
610 print("No data collected") |
617 print("No data collected") |
611 else: |
618 else: |
612 self.help_fn("Don't know what you mean by %r" % info) |
619 self.help_fn("Don't know what you mean by %r" % info) |
613 return ERR |
620 return ERR |
|
621 |
614 return OK |
622 return OK |
615 |
623 |
616 |
624 |
617 def unshell_list(s): |
625 def unshell_list(s): |
618 """Turn a command-line argument into a list.""" |
626 """Turn a command-line argument into a list.""" |
619 if not s: |
627 if not s: |
620 return None |
628 return None |
621 if sys.platform == 'win32': |
629 if env.WINDOWS: |
622 # When running coverage as coverage.exe, some of the behavior |
630 # When running coverage.py as coverage.exe, some of the behavior |
623 # of the shell is emulated: wildcards are expanded into a list of |
631 # of the shell is emulated: wildcards are expanded into a list of |
624 # filenames. So you have to single-quote patterns on the command |
632 # file names. So you have to single-quote patterns on the command |
625 # line, but (not) helpfully, the single quotes are included in the |
633 # line, but (not) helpfully, the single quotes are included in the |
626 # argument, so we have to strip them off here. |
634 # argument, so we have to strip them off here. |
627 s = s.strip("'") |
635 s = s.strip("'") |
628 return s.split(',') |
636 return s.split(',') |
629 |
637 |
630 |
638 |
|
639 def unglob_args(args): |
|
640 """Interpret shell wildcards for platforms that need it.""" |
|
641 if env.WINDOWS: |
|
642 globbed = [] |
|
643 for arg in args: |
|
644 if '?' in arg or '*' in arg: |
|
645 globbed.extend(glob.glob(arg)) |
|
646 else: |
|
647 globbed.append(arg) |
|
648 args = globbed |
|
649 return args |
|
650 |
|
651 |
631 HELP_TOPICS = { |
652 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 # ------------------------- |
653 # ------------------------- |
680 'help': """\ |
654 'help': """\ |
681 Coverage.py, version %(__version__)s |
655 Coverage.py, version %(__version__)s |
682 Measure, collect, and report on code coverage in Python programs. |
656 Measure, collect, and report on code coverage in Python programs. |
683 |
657 |
692 report Report coverage stats on modules. |
666 report Report coverage stats on modules. |
693 run Run a Python program and measure code execution. |
667 run Run a Python program and measure code execution. |
694 xml Create an XML report of coverage results. |
668 xml Create an XML report of coverage results. |
695 |
669 |
696 Use "coverage help <command>" for detailed help on any command. |
670 Use "coverage help <command>" for detailed help on any command. |
697 Use "coverage help classic" for help on older command syntax. |
671 For full documentation, see %(__url__)s |
698 For more information, see %(__url__)s |
|
699 """, |
672 """, |
700 # ------------------------- |
673 # ------------------------- |
701 'minimum_help': """\ |
674 'minimum_help': """\ |
702 Code coverage for Python. Use 'coverage help' for help. |
675 Code coverage for Python. Use 'coverage help' for help. |
703 """, |
676 """, |
704 # ------------------------- |
677 # ------------------------- |
705 'version': """\ |
678 'version': """\ |
706 Coverage.py, version %(__version__)s. %(__url__)s |
679 Coverage.py, version %(__version__)s. |
|
680 Documentation at %(__url__)s |
707 """, |
681 """, |
708 } |
682 } |
709 |
683 |
710 |
684 |
711 def main(argv=None): |
685 def main(argv=None): |
712 """The main entry point to Coverage. |
686 """The main entry point to coverage.py. |
713 |
687 |
714 This is installed as the script entry point. |
688 This is installed as the script entry point. |
715 |
689 |
716 """ |
690 """ |
717 if argv is None: |
691 if argv is None: |
718 argv = sys.argv[1:] |
692 argv = sys.argv[1:] |
719 try: |
693 try: |
720 start = time.clock() |
|
721 status = CoverageScript().command_line(argv) |
694 status = CoverageScript().command_line(argv) |
722 end = time.clock() |
695 except ExceptionDuringRun as err: |
723 if 0: |
|
724 print("time: %.3fs" % (end - start)) |
|
725 except ExceptionDuringRun: |
|
726 # An exception was caught while running the product code. The |
696 # An exception was caught while running the product code. The |
727 # sys.exc_info() return tuple is packed into an ExceptionDuringRun |
697 # sys.exc_info() return tuple is packed into an ExceptionDuringRun |
728 # exception. |
698 # exception. |
729 _, err, _ = sys.exc_info() |
|
730 traceback.print_exception(*err.args) |
699 traceback.print_exception(*err.args) |
731 status = ERR |
700 status = ERR |
732 except CoverageException: |
701 except CoverageException as err: |
733 # A controlled error inside coverage.py: print the message to the user. |
702 # A controlled error inside coverage.py: print the message to the user. |
734 _, err, _ = sys.exc_info() |
|
735 print(err) |
703 print(err) |
736 status = ERR |
704 status = ERR |
737 except SystemExit: |
705 except SystemExit as err: |
738 # The user called `sys.exit()`. Exit with their argument, if any. |
706 # The user called `sys.exit()`. Exit with their argument, if any. |
739 _, err, _ = sys.exc_info() |
|
740 if err.args: |
707 if err.args: |
741 status = err.args[0] |
708 status = err.args[0] |
742 else: |
709 else: |
743 status = None |
710 status = None |
744 return status |
711 return status |
745 |
|
746 # |
|
747 # eflag: FileType = Python2 |
|