1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 |
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 |
2 # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt |
3 |
3 |
4 """Command-line support for coverage.py.""" |
4 """Command-line support for coverage.py.""" |
5 |
5 |
6 from __future__ import print_function |
6 from __future__ import print_function |
7 |
7 |
8 import glob |
8 import glob |
9 import optparse |
9 import optparse |
10 import os.path |
10 import os.path |
|
11 import shlex |
11 import sys |
12 import sys |
12 import textwrap |
13 import textwrap |
13 import traceback |
14 import traceback |
14 |
15 |
|
16 import coverage |
|
17 from coverage import Coverage |
15 from coverage import env |
18 from coverage import env |
16 from coverage.collector import CTracer |
19 from coverage.collector import CTracer |
|
20 from coverage.data import line_counts |
17 from coverage.debug import info_formatter, info_header |
21 from coverage.debug import info_formatter, info_header |
18 from coverage.execfile import run_python_file, run_python_module |
22 from coverage.execfile import PyRunner |
19 from coverage.misc import BaseCoverageException, ExceptionDuringRun, NoSource |
23 from coverage.misc import BaseCoverageException, ExceptionDuringRun, NoSource, output_encoding |
20 from coverage.results import should_fail_under |
24 from coverage.results import should_fail_under |
21 |
25 |
22 |
26 |
23 class Opts(object): |
27 class Opts(object): |
24 """A namespace class for individual options we'll build parsers from.""" |
28 """A namespace class for individual options we'll build parsers from.""" |
40 help=( |
44 help=( |
41 "Properly measure code using a concurrency library. " |
45 "Properly measure code using a concurrency library. " |
42 "Valid values are: %s." |
46 "Valid values are: %s." |
43 ) % ", ".join(CONCURRENCY_CHOICES), |
47 ) % ", ".join(CONCURRENCY_CHOICES), |
44 ) |
48 ) |
|
49 context = optparse.make_option( |
|
50 '', '--context', action='store', metavar="LABEL", |
|
51 help="The context label to record for this coverage run.", |
|
52 ) |
45 debug = optparse.make_option( |
53 debug = optparse.make_option( |
46 '', '--debug', action='store', metavar="OPTS", |
54 '', '--debug', action='store', metavar="OPTS", |
47 help="Debug options, separated by commas", |
55 help="Debug options, separated by commas. [env: COVERAGE_DEBUG]", |
48 ) |
56 ) |
49 directory = optparse.make_option( |
57 directory = optparse.make_option( |
50 '-d', '--directory', action='store', metavar="DIR", |
58 '-d', '--directory', action='store', metavar="DIR", |
51 help="Write the output files to DIR.", |
59 help="Write the output files to DIR.", |
52 ) |
60 ) |
83 ) |
91 ) |
84 skip_covered = optparse.make_option( |
92 skip_covered = optparse.make_option( |
85 '--skip-covered', action='store_true', |
93 '--skip-covered', action='store_true', |
86 help="Skip files with 100% coverage.", |
94 help="Skip files with 100% coverage.", |
87 ) |
95 ) |
|
96 skip_empty = optparse.make_option( |
|
97 '--skip-empty', action='store_true', |
|
98 help="Skip files with no code.", |
|
99 ) |
|
100 show_contexts = optparse.make_option( |
|
101 '--show-contexts', action='store_true', |
|
102 help="Show contexts for covered lines.", |
|
103 ) |
88 omit = optparse.make_option( |
104 omit = optparse.make_option( |
89 '', '--omit', action='store', |
105 '', '--omit', action='store', |
90 metavar="PAT1,PAT2,...", |
106 metavar="PAT1,PAT2,...", |
91 help=( |
107 help=( |
92 "Omit files whose paths match one of these patterns. " |
108 "Omit files whose paths match one of these patterns. " |
93 "Accepts shell-style wildcards, which must be quoted." |
109 "Accepts shell-style wildcards, which must be quoted." |
94 ), |
110 ), |
95 ) |
111 ) |
|
112 contexts = optparse.make_option( |
|
113 '', '--contexts', action='store', |
|
114 metavar="REGEX1,REGEX2,...", |
|
115 help=( |
|
116 "Only display data from lines covered in the given contexts. " |
|
117 "Accepts Python regexes, which must be quoted." |
|
118 ), |
|
119 ) |
96 output_xml = optparse.make_option( |
120 output_xml = optparse.make_option( |
97 '-o', '', action='store', dest="outfile", |
121 '-o', '', action='store', dest="outfile", |
98 metavar="OUTFILE", |
122 metavar="OUTFILE", |
99 help="Write the XML report to this file. Defaults to 'coverage.xml'", |
123 help="Write the XML report to this file. Defaults to 'coverage.xml'", |
|
124 ) |
|
125 output_json = optparse.make_option( |
|
126 '-o', '', action='store', dest="outfile", |
|
127 metavar="OUTFILE", |
|
128 help="Write the JSON report to this file. Defaults to 'coverage.json'", |
|
129 ) |
|
130 json_pretty_print = optparse.make_option( |
|
131 '', '--pretty-print', action='store_true', |
|
132 help="Format the JSON for human readers.", |
100 ) |
133 ) |
101 parallel_mode = optparse.make_option( |
134 parallel_mode = optparse.make_option( |
102 '-p', '--parallel-mode', action='store_true', |
135 '-p', '--parallel-mode', action='store_true', |
103 help=( |
136 help=( |
104 "Append the machine name, process id and random number to the " |
137 "Append the machine name, process id and random number to the " |
114 ), |
147 ), |
115 ) |
148 ) |
116 rcfile = optparse.make_option( |
149 rcfile = optparse.make_option( |
117 '', '--rcfile', action='store', |
150 '', '--rcfile', action='store', |
118 help=( |
151 help=( |
119 "Specify configuration file. " |
152 "Specify configuration file. " |
120 "By default '.coveragerc', 'setup.cfg' and 'tox.ini' are tried." |
153 "By default '.coveragerc', 'setup.cfg', 'tox.ini', and " |
|
154 "'pyproject.toml' are tried. [env: COVERAGE_RCFILE]" |
121 ), |
155 ), |
122 ) |
156 ) |
123 source = optparse.make_option( |
157 source = optparse.make_option( |
124 '', '--source', action='store', metavar="SRC1,SRC2,...", |
158 '', '--source', action='store', metavar="SRC1,SRC2,...", |
125 help="A list of packages or directories of code to be measured.", |
159 help="A list of packages or directories of code to be measured.", |
126 ) |
160 ) |
127 timid = optparse.make_option( |
161 timid = optparse.make_option( |
128 '', '--timid', action='store_true', |
162 '', '--timid', action='store_true', |
129 help=( |
163 help=( |
130 "Use a simpler but slower trace method. Try this if you get " |
164 "Use a simpler but slower trace method. Try this if you get " |
131 "seemingly impossible results!" |
165 "seemingly impossible results!" |
132 ), |
166 ), |
133 ) |
167 ) |
134 title = optparse.make_option( |
168 title = optparse.make_option( |
135 '', '--title', action='store', metavar="TITLE", |
169 '', '--title', action='store', metavar="TITLE", |
381 description="Generate an XML report of coverage results." |
435 description="Generate an XML report of coverage results." |
382 ), |
436 ), |
383 } |
437 } |
384 |
438 |
385 |
439 |
|
440 def show_help(error=None, topic=None, parser=None): |
|
441 """Display an error message, or the named topic.""" |
|
442 assert error or topic or parser |
|
443 |
|
444 program_path = sys.argv[0] |
|
445 if program_path.endswith(os.path.sep + '__main__.py'): |
|
446 # The path is the main module of a package; get that path instead. |
|
447 program_path = os.path.dirname(program_path) |
|
448 program_name = os.path.basename(program_path) |
|
449 if env.WINDOWS: |
|
450 # entry_points={'console_scripts':...} on Windows makes files |
|
451 # called coverage.exe, coverage3.exe, and coverage-3.5.exe. These |
|
452 # invoke coverage-script.py, coverage3-script.py, and |
|
453 # coverage-3.5-script.py. argv[0] is the .py file, but we want to |
|
454 # get back to the original form. |
|
455 auto_suffix = "-script.py" |
|
456 if program_name.endswith(auto_suffix): |
|
457 program_name = program_name[:-len(auto_suffix)] |
|
458 |
|
459 help_params = dict(coverage.__dict__) |
|
460 help_params['program_name'] = program_name |
|
461 if CTracer is not None: |
|
462 help_params['extension_modifier'] = 'with C extension' |
|
463 else: |
|
464 help_params['extension_modifier'] = 'without C extension' |
|
465 |
|
466 if error: |
|
467 print(error, file=sys.stderr) |
|
468 print("Use '%s help' for help." % (program_name,), file=sys.stderr) |
|
469 elif parser: |
|
470 print(parser.format_help().strip()) |
|
471 print() |
|
472 else: |
|
473 help_msg = textwrap.dedent(HELP_TOPICS.get(topic, '')).strip() |
|
474 if help_msg: |
|
475 print(help_msg.format(**help_params)) |
|
476 else: |
|
477 print("Don't know topic %r" % topic) |
|
478 print("Full documentation is at {__url__}".format(**help_params)) |
|
479 |
|
480 |
386 OK, ERR, FAIL_UNDER = 0, 1, 2 |
481 OK, ERR, FAIL_UNDER = 0, 1, 2 |
387 |
482 |
388 |
483 |
389 class CoverageScript(object): |
484 class CoverageScript(object): |
390 """The command-line interface to coverage.py.""" |
485 """The command-line interface to coverage.py.""" |
391 |
486 |
392 def __init__(self, _covpkg=None, _run_python_file=None, |
487 def __init__(self): |
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 |
488 self.global_option = False |
407 |
|
408 self.coverage = None |
489 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 |
490 |
425 def command_line(self, argv): |
491 def command_line(self, argv): |
426 """The bulk of the command line interface to coverage.py. |
492 """The bulk of the command line interface to coverage.py. |
427 |
493 |
428 `argv` is the argument list to process. |
494 `argv` is the argument list to process. |
430 Returns 0 if all is well, 1 if something went wrong. |
496 Returns 0 if all is well, 1 if something went wrong. |
431 |
497 |
432 """ |
498 """ |
433 # Collect the command-line options. |
499 # Collect the command-line options. |
434 if not argv: |
500 if not argv: |
435 self.help_fn(topic='minimum_help') |
501 show_help(topic='minimum_help') |
436 return OK |
502 return OK |
437 |
503 |
438 # The command syntax we parse depends on the first argument. Global |
504 # The command syntax we parse depends on the first argument. Global |
439 # switch syntax always starts with an option. |
505 # switch syntax always starts with an option. |
440 self.global_option = argv[0].startswith('-') |
506 self.global_option = argv[0].startswith('-') |
441 if self.global_option: |
507 if self.global_option: |
442 parser = GlobalOptionParser() |
508 parser = GlobalOptionParser() |
443 else: |
509 else: |
444 parser = CMDS.get(argv[0]) |
510 parser = CMDS.get(argv[0]) |
445 if not parser: |
511 if not parser: |
446 self.help_fn("Unknown command: '%s'" % argv[0]) |
512 show_help("Unknown command: '%s'" % argv[0]) |
447 return ERR |
513 return ERR |
448 argv = argv[1:] |
514 argv = argv[1:] |
449 |
515 |
450 parser.help_fn = self.help_fn |
|
451 ok, options, args = parser.parse_args_ok(argv) |
516 ok, options, args = parser.parse_args_ok(argv) |
452 if not ok: |
517 if not ok: |
453 return ERR |
518 return ERR |
454 |
519 |
455 # Handle help and version. |
520 # Handle help and version. |
456 if self.do_help(options, args, parser): |
521 if self.do_help(options, args, parser): |
457 return OK |
522 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 |
523 |
463 # Listify the list options. |
524 # Listify the list options. |
464 source = unshell_list(options.source) |
525 source = unshell_list(options.source) |
465 omit = unshell_list(options.omit) |
526 omit = unshell_list(options.omit) |
466 include = unshell_list(options.include) |
527 include = unshell_list(options.include) |
467 debug = unshell_list(options.debug) |
528 debug = unshell_list(options.debug) |
|
529 contexts = unshell_list(options.contexts) |
468 |
530 |
469 # Do something. |
531 # Do something. |
470 self.coverage = self.covpkg.Coverage( |
532 self.coverage = Coverage( |
471 data_suffix=options.parallel_mode, |
533 data_suffix=options.parallel_mode, |
472 cover_pylib=options.pylib, |
534 cover_pylib=options.pylib, |
473 timid=options.timid, |
535 timid=options.timid, |
474 branch=options.branch, |
536 branch=options.branch, |
475 config_file=options.rcfile, |
537 config_file=options.rcfile, |
476 source=source, |
538 source=source, |
477 omit=omit, |
539 omit=omit, |
478 include=include, |
540 include=include, |
479 debug=debug, |
541 debug=debug, |
480 concurrency=options.concurrency, |
542 concurrency=options.concurrency, |
|
543 check_preimported=True, |
|
544 context=options.context, |
481 ) |
545 ) |
482 |
546 |
483 if options.action == "debug": |
547 if options.action == "debug": |
484 return self.do_debug(args) |
548 return self.do_debug(args) |
485 |
549 |
502 report_args = dict( |
566 report_args = dict( |
503 morfs=unglob_args(args), |
567 morfs=unglob_args(args), |
504 ignore_errors=options.ignore_errors, |
568 ignore_errors=options.ignore_errors, |
505 omit=omit, |
569 omit=omit, |
506 include=include, |
570 include=include, |
|
571 contexts=contexts, |
507 ) |
572 ) |
|
573 |
|
574 # We need to be able to import from the current directory, because |
|
575 # plugins may try to, for example, to read Django settings. |
|
576 sys.path.insert(0, '') |
508 |
577 |
509 self.coverage.load() |
578 self.coverage.load() |
510 |
579 |
511 total = None |
580 total = None |
512 if options.action == "report": |
581 if options.action == "report": |
513 total = self.coverage.report( |
582 total = self.coverage.report( |
514 show_missing=options.show_missing, |
583 show_missing=options.show_missing, |
515 skip_covered=options.skip_covered, **report_args) |
584 skip_covered=options.skip_covered, |
|
585 skip_empty=options.skip_empty, |
|
586 **report_args |
|
587 ) |
516 elif options.action == "annotate": |
588 elif options.action == "annotate": |
517 self.coverage.annotate( |
589 self.coverage.annotate(directory=options.directory, **report_args) |
518 directory=options.directory, **report_args) |
|
519 elif options.action == "html": |
590 elif options.action == "html": |
520 total = self.coverage.html_report( |
591 total = self.coverage.html_report( |
521 directory=options.directory, title=options.title, |
592 directory=options.directory, |
522 skip_covered=options.skip_covered, **report_args) |
593 title=options.title, |
|
594 skip_covered=options.skip_covered, |
|
595 skip_empty=options.skip_empty, |
|
596 show_contexts=options.show_contexts, |
|
597 **report_args |
|
598 ) |
523 elif options.action == "xml": |
599 elif options.action == "xml": |
524 outfile = options.outfile |
600 outfile = options.outfile |
525 total = self.coverage.xml_report(outfile=outfile, **report_args) |
601 total = self.coverage.xml_report(outfile=outfile, **report_args) |
|
602 elif options.action == "json": |
|
603 outfile = options.outfile |
|
604 total = self.coverage.json_report( |
|
605 outfile=outfile, |
|
606 pretty_print=options.pretty_print, |
|
607 show_contexts=options.show_contexts, |
|
608 **report_args |
|
609 ) |
526 |
610 |
527 if total is not None: |
611 if total is not None: |
528 # Apply the command line fail-under options, and then use the config |
612 # Apply the command line fail-under options, and then use the config |
529 # value, so we can get fail_under from the config file. |
613 # value, so we can get fail_under from the config file. |
530 if options.fail_under is not None: |
614 if options.fail_under is not None: |
535 if should_fail_under(total, fail_under, precision): |
619 if should_fail_under(total, fail_under, precision): |
536 return FAIL_UNDER |
620 return FAIL_UNDER |
537 |
621 |
538 return OK |
622 return OK |
539 |
623 |
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): |
624 def do_help(self, options, args, parser): |
562 """Deal with help requests. |
625 """Deal with help requests. |
563 |
626 |
564 Return True if it handled the request, False if not. |
627 Return True if it handled the request, False if not. |
565 |
628 |
566 """ |
629 """ |
567 # Handle help. |
630 # Handle help. |
568 if options.help: |
631 if options.help: |
569 if self.global_option: |
632 if self.global_option: |
570 self.help_fn(topic='help') |
633 show_help(topic='help') |
571 else: |
634 else: |
572 self.help_fn(parser=parser) |
635 show_help(parser=parser) |
573 return True |
636 return True |
574 |
637 |
575 if options.action == "help": |
638 if options.action == "help": |
576 if args: |
639 if args: |
577 for a in args: |
640 for a in args: |
578 parser = CMDS.get(a) |
641 parser = CMDS.get(a) |
579 if parser: |
642 if parser: |
580 self.help_fn(parser=parser) |
643 show_help(parser=parser) |
581 else: |
644 else: |
582 self.help_fn(topic=a) |
645 show_help(topic=a) |
583 else: |
646 else: |
584 self.help_fn(topic='help') |
647 show_help(topic='help') |
585 return True |
648 return True |
586 |
649 |
587 # Handle version. |
650 # Handle version. |
588 if options.version: |
651 if options.version: |
589 self.help_fn(topic='version') |
652 show_help(topic='version') |
590 return True |
653 return True |
591 |
654 |
592 return False |
655 return False |
593 |
656 |
594 def do_run(self, options, args): |
657 def do_run(self, options, args): |
595 """Implementation of 'coverage run'.""" |
658 """Implementation of 'coverage run'.""" |
596 |
659 |
597 if not args: |
660 if not args: |
598 self.help_fn("Nothing to do.") |
661 if options.module: |
|
662 # Specified -m with nothing else. |
|
663 show_help("No module specified for -m") |
|
664 return ERR |
|
665 command_line = self.coverage.get_option("run:command_line") |
|
666 if command_line is not None: |
|
667 args = shlex.split(command_line) |
|
668 if args and args[0] == "-m": |
|
669 options.module = True |
|
670 args = args[1:] |
|
671 if not args: |
|
672 show_help("Nothing to do.") |
599 return ERR |
673 return ERR |
600 |
674 |
601 if options.append and self.coverage.get_option("run:parallel"): |
675 if options.append and self.coverage.get_option("run:parallel"): |
602 self.help_fn("Can't append to data files in parallel mode.") |
676 show_help("Can't append to data files in parallel mode.") |
603 return ERR |
677 return ERR |
604 |
678 |
605 if options.concurrency == "multiprocessing": |
679 if options.concurrency == "multiprocessing": |
606 # Can't set other run-affecting command line options with |
680 # Can't set other run-affecting command line options with |
607 # multiprocessing. |
681 # multiprocessing. |
608 for opt_name in ['branch', 'include', 'omit', 'pylib', 'source', 'timid']: |
682 for opt_name in ['branch', 'include', 'omit', 'pylib', 'source', 'timid']: |
609 # As it happens, all of these options have no default, meaning |
683 # As it happens, all of these options have no default, meaning |
610 # they will be None if they have not been specified. |
684 # they will be None if they have not been specified. |
611 if getattr(options, opt_name) is not None: |
685 if getattr(options, opt_name) is not None: |
612 self.help_fn( |
686 show_help( |
613 "Options affecting multiprocessing must be specified " |
687 "Options affecting multiprocessing must only be specified " |
614 "in a configuration file." |
688 "in a configuration file.\n" |
|
689 "Remove --{} from the command line.".format(opt_name) |
615 ) |
690 ) |
616 return ERR |
691 return ERR |
617 |
692 |
618 if not self.coverage.get_option("run:parallel"): |
693 runner = PyRunner(args, as_module=bool(options.module)) |
619 if not options.append: |
694 runner.prepare() |
620 self.coverage.erase() |
695 |
|
696 if options.append: |
|
697 self.coverage.load() |
621 |
698 |
622 # Run the script. |
699 # Run the script. |
623 self.coverage.start() |
700 self.coverage.start() |
624 code_ran = True |
701 code_ran = True |
625 try: |
702 try: |
626 if options.module: |
703 runner.run() |
627 self.run_python_module(args[0], args) |
|
628 else: |
|
629 filename = args[0] |
|
630 self.run_python_file(filename, args) |
|
631 except NoSource: |
704 except NoSource: |
632 code_ran = False |
705 code_ran = False |
633 raise |
706 raise |
634 finally: |
707 finally: |
635 self.coverage.stop() |
708 self.coverage.stop() |
636 if code_ran: |
709 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() |
710 self.coverage.save() |
642 |
711 |
643 return OK |
712 return OK |
644 |
713 |
645 def do_debug(self, args): |
714 def do_debug(self, args): |
646 """Implementation of 'coverage debug'.""" |
715 """Implementation of 'coverage debug'.""" |
647 |
716 |
648 if not args: |
717 if not args: |
649 self.help_fn("What information would you like: config, data, sys?") |
718 show_help("What information would you like: config, data, sys, premain?") |
650 return ERR |
719 return ERR |
651 |
720 |
652 for info in args: |
721 for info in args: |
653 if info == 'sys': |
722 if info == 'sys': |
654 sys_info = self.coverage.sys_info() |
723 sys_info = self.coverage.sys_info() |
655 print(info_header("sys")) |
724 print(info_header("sys")) |
656 for line in info_formatter(sys_info): |
725 for line in info_formatter(sys_info): |
657 print(" %s" % line) |
726 print(" %s" % line) |
658 elif info == 'data': |
727 elif info == 'data': |
659 self.coverage.load() |
728 self.coverage.load() |
660 data = self.coverage.data |
729 data = self.coverage.get_data() |
661 print(info_header("data")) |
730 print(info_header("data")) |
662 print("path: %s" % self.coverage.data_files.filename) |
731 print("path: %s" % self.coverage.get_data().data_filename()) |
663 if data: |
732 if data: |
664 print("has_arcs: %r" % data.has_arcs()) |
733 print("has_arcs: %r" % data.has_arcs()) |
665 summary = data.line_counts(fullpath=True) |
734 summary = line_counts(data, fullpath=True) |
666 filenames = sorted(summary.keys()) |
735 filenames = sorted(summary.keys()) |
667 print("\n%d files:" % len(filenames)) |
736 print("\n%d files:" % len(filenames)) |
668 for f in filenames: |
737 for f in filenames: |
669 line = "%s: %d lines" % (f, summary[f]) |
738 line = "%s: %d lines" % (f, summary[f]) |
670 plugin = data.file_tracer(f) |
739 plugin = data.file_tracer(f) |
723 annotate Annotate source files with execution information. |
796 annotate Annotate source files with execution information. |
724 combine Combine a number of data files. |
797 combine Combine a number of data files. |
725 erase Erase previously collected coverage data. |
798 erase Erase previously collected coverage data. |
726 help Get help on using coverage.py. |
799 help Get help on using coverage.py. |
727 html Create an HTML report. |
800 html Create an HTML report. |
|
801 json Create a JSON report of coverage results. |
728 report Report coverage stats on modules. |
802 report Report coverage stats on modules. |
729 run Run a Python program and measure code execution. |
803 run Run a Python program and measure code execution. |
730 xml Create an XML report of coverage results. |
804 xml Create an XML report of coverage results. |
731 |
805 |
732 Use "{program_name} help <command>" for detailed help on any command. |
806 Use "{program_name} help <command>" for detailed help on any command. |
733 For full documentation, see {__url__} |
|
734 """, |
807 """, |
735 |
808 |
736 'minimum_help': """\ |
809 'minimum_help': """\ |
737 Code coverage for Python. Use '{program_name} help' for help. |
810 Code coverage for Python. Use '{program_name} help' for help. |
738 """, |
811 """, |
739 |
812 |
740 'version': """\ |
813 'version': """\ |
741 Coverage.py, version {__version__} {extension_modifier} |
814 Coverage.py, version {__version__} {extension_modifier} |
742 Documentation at {__url__} |
|
743 """, |
815 """, |
744 } |
816 } |
745 |
817 |
746 |
818 |
747 def main(argv=None): |
819 def main(argv=None): |
760 # exception. |
832 # exception. |
761 traceback.print_exception(*err.args) # pylint: disable=no-value-for-parameter |
833 traceback.print_exception(*err.args) # pylint: disable=no-value-for-parameter |
762 status = ERR |
834 status = ERR |
763 except BaseCoverageException as err: |
835 except BaseCoverageException as err: |
764 # A controlled error inside coverage.py: print the message to the user. |
836 # A controlled error inside coverage.py: print the message to the user. |
765 print(err) |
837 msg = err.args[0] |
|
838 if env.PY2: |
|
839 msg = msg.encode(output_encoding()) |
|
840 print(msg) |
766 status = ERR |
841 status = ERR |
767 except SystemExit as err: |
842 except SystemExit as err: |
768 # The user called `sys.exit()`. Exit with their argument, if any. |
843 # The user called `sys.exit()`. Exit with their argument, if any. |
769 if err.args: |
844 if err.args: |
770 status = err.args[0] |
845 status = err.args[0] |
771 else: |
846 else: |
772 status = None |
847 status = None |
773 return status |
848 return status |
|
849 |
|
850 # Profiling using ox_profile. Install it from GitHub: |
|
851 # pip install git+https://github.com/emin63/ox_profile.git |
|
852 # |
|
853 # $set_env.py: COVERAGE_PROFILE - Set to use ox_profile. |
|
854 _profile = os.environ.get("COVERAGE_PROFILE", "") |
|
855 if _profile: # pragma: debugging |
|
856 from ox_profile.core.launchers import SimpleLauncher # pylint: disable=import-error |
|
857 original_main = main |
|
858 |
|
859 def main(argv=None): # pylint: disable=function-redefined |
|
860 """A wrapper around main that profiles.""" |
|
861 try: |
|
862 profiler = SimpleLauncher.launch() |
|
863 return original_main(argv) |
|
864 finally: |
|
865 data, _ = profiler.query(re_filter='coverage', max_records=100) |
|
866 print(profiler.show(query=data, limit=100, sep='', col='')) |
|
867 profiler.cancel() |