src/eric7/DebugClients/Python/coverage/cmdline.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9099
0e511e0e94a3
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2 # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
3
4 """Command-line support for coverage.py."""
5
6 import glob
7 import optparse # pylint: disable=deprecated-module
8 import os
9 import os.path
10 import shlex
11 import sys
12 import textwrap
13 import traceback
14
15 import coverage
16 from coverage import Coverage
17 from coverage import env
18 from coverage.collector import CTracer
19 from coverage.config import CoverageConfig
20 from coverage.control import DEFAULT_DATAFILE
21 from coverage.data import combinable_files, debug_data_file
22 from coverage.debug import info_header, short_stack, write_formatted_info
23 from coverage.exceptions import _BaseCoverageException, _ExceptionDuringRun, NoSource
24 from coverage.execfile import PyRunner
25 from coverage.results import Numbers, should_fail_under
26
27 # When adding to this file, alphabetization is important. Look for
28 # "alphabetize" comments throughout.
29
30 class Opts:
31 """A namespace class for individual options we'll build parsers from."""
32
33 # Keep these entries alphabetized (roughly) by the option name as it
34 # appears on the command line.
35
36 append = optparse.make_option(
37 '-a', '--append', action='store_true',
38 help="Append coverage data to .coverage, otherwise it starts clean each time.",
39 )
40 keep = optparse.make_option(
41 '', '--keep', action='store_true',
42 help="Keep original coverage files, otherwise they are deleted.",
43 )
44 branch = optparse.make_option(
45 '', '--branch', action='store_true',
46 help="Measure branch coverage in addition to statement coverage.",
47 )
48 concurrency = optparse.make_option(
49 '', '--concurrency', action='store', metavar="LIBS",
50 help=(
51 "Properly measure code using a concurrency library. " +
52 "Valid values are: {}, or a comma-list of them."
53 ).format(", ".join(sorted(CoverageConfig.CONCURRENCY_CHOICES))),
54 )
55 context = optparse.make_option(
56 '', '--context', action='store', metavar="LABEL",
57 help="The context label to record for this coverage run.",
58 )
59 contexts = optparse.make_option(
60 '', '--contexts', action='store', metavar="REGEX1,REGEX2,...",
61 help=(
62 "Only display data from lines covered in the given contexts. " +
63 "Accepts Python regexes, which must be quoted."
64 ),
65 )
66 combine_datafile = optparse.make_option(
67 '', '--data-file', action='store', metavar="DATAFILE",
68 help=(
69 "Base name of the data files to operate on. " +
70 "Defaults to '.coverage'. [env: COVERAGE_FILE]"
71 ),
72 )
73 input_datafile = optparse.make_option(
74 '', '--data-file', action='store', metavar="INFILE",
75 help=(
76 "Read coverage data for report generation from this file. " +
77 "Defaults to '.coverage'. [env: COVERAGE_FILE]"
78 ),
79 )
80 output_datafile = optparse.make_option(
81 '', '--data-file', action='store', metavar="OUTFILE",
82 help=(
83 "Write the recorded coverage data to this file. " +
84 "Defaults to '.coverage'. [env: COVERAGE_FILE]"
85 ),
86 )
87 debug = optparse.make_option(
88 '', '--debug', action='store', metavar="OPTS",
89 help="Debug options, separated by commas. [env: COVERAGE_DEBUG]",
90 )
91 directory = optparse.make_option(
92 '-d', '--directory', action='store', metavar="DIR",
93 help="Write the output files to DIR.",
94 )
95 fail_under = optparse.make_option(
96 '', '--fail-under', action='store', metavar="MIN", type="float",
97 help="Exit with a status of 2 if the total coverage is less than MIN.",
98 )
99 help = optparse.make_option(
100 '-h', '--help', action='store_true',
101 help="Get help on this command.",
102 )
103 ignore_errors = optparse.make_option(
104 '-i', '--ignore-errors', action='store_true',
105 help="Ignore errors while reading source files.",
106 )
107 include = optparse.make_option(
108 '', '--include', action='store', metavar="PAT1,PAT2,...",
109 help=(
110 "Include only files whose paths match one of these patterns. " +
111 "Accepts shell-style wildcards, which must be quoted."
112 ),
113 )
114 pylib = optparse.make_option(
115 '-L', '--pylib', action='store_true',
116 help=(
117 "Measure coverage even inside the Python installed library, " +
118 "which isn't done by default."
119 ),
120 )
121 show_missing = optparse.make_option(
122 '-m', '--show-missing', action='store_true',
123 help="Show line numbers of statements in each module that weren't executed.",
124 )
125 module = optparse.make_option(
126 '-m', '--module', action='store_true',
127 help=(
128 "<pyfile> is an importable Python module, not a script path, " +
129 "to be run as 'python -m' would run it."
130 ),
131 )
132 omit = optparse.make_option(
133 '', '--omit', action='store', metavar="PAT1,PAT2,...",
134 help=(
135 "Omit files whose paths match one of these patterns. " +
136 "Accepts shell-style wildcards, which must be quoted."
137 ),
138 )
139 output_xml = optparse.make_option(
140 '-o', '', action='store', dest="outfile", metavar="OUTFILE",
141 help="Write the XML report to this file. Defaults to 'coverage.xml'",
142 )
143 output_json = optparse.make_option(
144 '-o', '', action='store', dest="outfile", metavar="OUTFILE",
145 help="Write the JSON report to this file. Defaults to 'coverage.json'",
146 )
147 output_lcov = optparse.make_option(
148 '-o', '', action='store', dest='outfile', metavar="OUTFILE",
149 help="Write the LCOV report to this file. Defaults to 'coverage.lcov'",
150 )
151 json_pretty_print = optparse.make_option(
152 '', '--pretty-print', action='store_true',
153 help="Format the JSON for human readers.",
154 )
155 parallel_mode = optparse.make_option(
156 '-p', '--parallel-mode', action='store_true',
157 help=(
158 "Append the machine name, process id and random number to the " +
159 "data file name to simplify collecting data from " +
160 "many processes."
161 ),
162 )
163 precision = optparse.make_option(
164 '', '--precision', action='store', metavar='N', type=int,
165 help=(
166 "Number of digits after the decimal point to display for " +
167 "reported coverage percentages."
168 ),
169 )
170 quiet = optparse.make_option(
171 '-q', '--quiet', action='store_true',
172 help="Don't print messages about what is happening.",
173 )
174 rcfile = optparse.make_option(
175 '', '--rcfile', action='store',
176 help=(
177 "Specify configuration file. " +
178 "By default '.coveragerc', 'setup.cfg', 'tox.ini', and " +
179 "'pyproject.toml' are tried. [env: COVERAGE_RCFILE]"
180 ),
181 )
182 show_contexts = optparse.make_option(
183 '--show-contexts', action='store_true',
184 help="Show contexts for covered lines.",
185 )
186 skip_covered = optparse.make_option(
187 '--skip-covered', action='store_true',
188 help="Skip files with 100% coverage.",
189 )
190 no_skip_covered = optparse.make_option(
191 '--no-skip-covered', action='store_false', dest='skip_covered',
192 help="Disable --skip-covered.",
193 )
194 skip_empty = optparse.make_option(
195 '--skip-empty', action='store_true',
196 help="Skip files with no code.",
197 )
198 sort = optparse.make_option(
199 '--sort', action='store', metavar='COLUMN',
200 help=(
201 "Sort the report by the named column: name, stmts, miss, branch, brpart, or cover. " +
202 "Default is name."
203 ),
204 )
205 source = optparse.make_option(
206 '', '--source', action='store', metavar="SRC1,SRC2,...",
207 help="A list of directories or importable names of code to measure.",
208 )
209 timid = optparse.make_option(
210 '', '--timid', action='store_true',
211 help=(
212 "Use a simpler but slower trace method. Try this if you get " +
213 "seemingly impossible results!"
214 ),
215 )
216 title = optparse.make_option(
217 '', '--title', action='store', metavar="TITLE",
218 help="A text string to use as the title on the HTML.",
219 )
220 version = optparse.make_option(
221 '', '--version', action='store_true',
222 help="Display version information and exit.",
223 )
224
225
226 class CoverageOptionParser(optparse.OptionParser):
227 """Base OptionParser for coverage.py.
228
229 Problems don't exit the program.
230 Defaults are initialized for all options.
231
232 """
233
234 def __init__(self, *args, **kwargs):
235 super().__init__(add_help_option=False, *args, **kwargs)
236 self.set_defaults(
237 # Keep these arguments alphabetized by their names.
238 action=None,
239 append=None,
240 branch=None,
241 concurrency=None,
242 context=None,
243 contexts=None,
244 data_file=None,
245 debug=None,
246 directory=None,
247 fail_under=None,
248 help=None,
249 ignore_errors=None,
250 include=None,
251 keep=None,
252 module=None,
253 omit=None,
254 parallel_mode=None,
255 precision=None,
256 pylib=None,
257 quiet=None,
258 rcfile=True,
259 show_contexts=None,
260 show_missing=None,
261 skip_covered=None,
262 skip_empty=None,
263 sort=None,
264 source=None,
265 timid=None,
266 title=None,
267 version=None,
268 )
269
270 self.disable_interspersed_args()
271
272 class OptionParserError(Exception):
273 """Used to stop the optparse error handler ending the process."""
274 pass
275
276 def parse_args_ok(self, args=None, options=None):
277 """Call optparse.parse_args, but return a triple:
278
279 (ok, options, args)
280
281 """
282 try:
283 options, args = super().parse_args(args, options)
284 except self.OptionParserError:
285 return False, None, None
286 return True, options, args
287
288 def error(self, msg):
289 """Override optparse.error so sys.exit doesn't get called."""
290 show_help(msg)
291 raise self.OptionParserError
292
293
294 class GlobalOptionParser(CoverageOptionParser):
295 """Command-line parser for coverage.py global option arguments."""
296
297 def __init__(self):
298 super().__init__()
299
300 self.add_options([
301 Opts.help,
302 Opts.version,
303 ])
304
305
306 class CmdOptionParser(CoverageOptionParser):
307 """Parse one of the new-style commands for coverage.py."""
308
309 def __init__(self, action, options, defaults=None, usage=None, description=None):
310 """Create an OptionParser for a coverage.py command.
311
312 `action` is the slug to put into `options.action`.
313 `options` is a list of Option's for the command.
314 `defaults` is a dict of default value for options.
315 `usage` is the usage string to display in help.
316 `description` is the description of the command, for the help text.
317
318 """
319 if usage:
320 usage = "%prog " + usage
321 super().__init__(
322 usage=usage,
323 description=description,
324 )
325 self.set_defaults(action=action, **(defaults or {}))
326 self.add_options(options)
327 self.cmd = action
328
329 def __eq__(self, other):
330 # A convenience equality, so that I can put strings in unit test
331 # results, and they will compare equal to objects.
332 return (other == f"<CmdOptionParser:{self.cmd}>")
333
334 __hash__ = None # This object doesn't need to be hashed.
335
336 def get_prog_name(self):
337 """Override of an undocumented function in optparse.OptionParser."""
338 program_name = super().get_prog_name()
339
340 # Include the sub-command for this parser as part of the command.
341 return f"{program_name} {self.cmd}"
342
343 # In lists of Opts, keep them alphabetized by the option names as they appear
344 # on the command line, since these lists determine the order of the options in
345 # the help output.
346 #
347 # In COMMANDS, keep the keys (command names) alphabetized.
348
349 GLOBAL_ARGS = [
350 Opts.debug,
351 Opts.help,
352 Opts.rcfile,
353 ]
354
355 COMMANDS = {
356 'annotate': CmdOptionParser(
357 "annotate",
358 [
359 Opts.directory,
360 Opts.input_datafile,
361 Opts.ignore_errors,
362 Opts.include,
363 Opts.omit,
364 ] + GLOBAL_ARGS,
365 usage="[options] [modules]",
366 description=(
367 "Make annotated copies of the given files, marking statements that are executed " +
368 "with > and statements that are missed with !."
369 ),
370 ),
371
372 'combine': CmdOptionParser(
373 "combine",
374 [
375 Opts.append,
376 Opts.combine_datafile,
377 Opts.keep,
378 Opts.quiet,
379 ] + GLOBAL_ARGS,
380 usage="[options] <path1> <path2> ... <pathN>",
381 description=(
382 "Combine data from multiple coverage files collected " +
383 "with 'run -p'. The combined results are written to a single " +
384 "file representing the union of the data. The positional " +
385 "arguments are data files or directories containing data files. " +
386 "If no paths are provided, data files in the default data file's " +
387 "directory are combined."
388 ),
389 ),
390
391 'debug': CmdOptionParser(
392 "debug", GLOBAL_ARGS,
393 usage="<topic>",
394 description=(
395 "Display information about the internals of coverage.py, " +
396 "for diagnosing problems. " +
397 "Topics are: " +
398 "'data' to show a summary of the collected data; " +
399 "'sys' to show installation information; " +
400 "'config' to show the configuration; " +
401 "'premain' to show what is calling coverage; " +
402 "'pybehave' to show internal flags describing Python behavior."
403 ),
404 ),
405
406 'erase': CmdOptionParser(
407 "erase",
408 [
409 Opts.combine_datafile
410 ] + GLOBAL_ARGS,
411 description="Erase previously collected coverage data.",
412 ),
413
414 'help': CmdOptionParser(
415 "help", GLOBAL_ARGS,
416 usage="[command]",
417 description="Describe how to use coverage.py",
418 ),
419
420 'html': CmdOptionParser(
421 "html",
422 [
423 Opts.contexts,
424 Opts.directory,
425 Opts.input_datafile,
426 Opts.fail_under,
427 Opts.ignore_errors,
428 Opts.include,
429 Opts.omit,
430 Opts.precision,
431 Opts.quiet,
432 Opts.show_contexts,
433 Opts.skip_covered,
434 Opts.no_skip_covered,
435 Opts.skip_empty,
436 Opts.title,
437 ] + GLOBAL_ARGS,
438 usage="[options] [modules]",
439 description=(
440 "Create an HTML report of the coverage of the files. " +
441 "Each file gets its own page, with the source decorated to show " +
442 "executed, excluded, and missed lines."
443 ),
444 ),
445
446 'json': CmdOptionParser(
447 "json",
448 [
449 Opts.contexts,
450 Opts.input_datafile,
451 Opts.fail_under,
452 Opts.ignore_errors,
453 Opts.include,
454 Opts.omit,
455 Opts.output_json,
456 Opts.json_pretty_print,
457 Opts.quiet,
458 Opts.show_contexts,
459 ] + GLOBAL_ARGS,
460 usage="[options] [modules]",
461 description="Generate a JSON report of coverage results.",
462 ),
463
464 'lcov': CmdOptionParser(
465 "lcov",
466 [
467 Opts.input_datafile,
468 Opts.fail_under,
469 Opts.ignore_errors,
470 Opts.include,
471 Opts.output_lcov,
472 Opts.omit,
473 Opts.quiet,
474 ] + GLOBAL_ARGS,
475 usage="[options] [modules]",
476 description="Generate an LCOV report of coverage results.",
477 ),
478
479 'report': CmdOptionParser(
480 "report",
481 [
482 Opts.contexts,
483 Opts.input_datafile,
484 Opts.fail_under,
485 Opts.ignore_errors,
486 Opts.include,
487 Opts.omit,
488 Opts.precision,
489 Opts.sort,
490 Opts.show_missing,
491 Opts.skip_covered,
492 Opts.no_skip_covered,
493 Opts.skip_empty,
494 ] + GLOBAL_ARGS,
495 usage="[options] [modules]",
496 description="Report coverage statistics on modules.",
497 ),
498
499 'run': CmdOptionParser(
500 "run",
501 [
502 Opts.append,
503 Opts.branch,
504 Opts.concurrency,
505 Opts.context,
506 Opts.output_datafile,
507 Opts.include,
508 Opts.module,
509 Opts.omit,
510 Opts.pylib,
511 Opts.parallel_mode,
512 Opts.source,
513 Opts.timid,
514 ] + GLOBAL_ARGS,
515 usage="[options] <pyfile> [program options]",
516 description="Run a Python program, measuring code execution.",
517 ),
518
519 'xml': CmdOptionParser(
520 "xml",
521 [
522 Opts.input_datafile,
523 Opts.fail_under,
524 Opts.ignore_errors,
525 Opts.include,
526 Opts.omit,
527 Opts.output_xml,
528 Opts.quiet,
529 Opts.skip_empty,
530 ] + GLOBAL_ARGS,
531 usage="[options] [modules]",
532 description="Generate an XML report of coverage results.",
533 ),
534 }
535
536
537 def show_help(error=None, topic=None, parser=None):
538 """Display an error message, or the named topic."""
539 assert error or topic or parser
540
541 program_path = sys.argv[0]
542 if program_path.endswith(os.path.sep + '__main__.py'):
543 # The path is the main module of a package; get that path instead.
544 program_path = os.path.dirname(program_path)
545 program_name = os.path.basename(program_path)
546 if env.WINDOWS:
547 # entry_points={'console_scripts':...} on Windows makes files
548 # called coverage.exe, coverage3.exe, and coverage-3.5.exe. These
549 # invoke coverage-script.py, coverage3-script.py, and
550 # coverage-3.5-script.py. argv[0] is the .py file, but we want to
551 # get back to the original form.
552 auto_suffix = "-script.py"
553 if program_name.endswith(auto_suffix):
554 program_name = program_name[:-len(auto_suffix)]
555
556 help_params = dict(coverage.__dict__)
557 help_params['program_name'] = program_name
558 if CTracer is not None:
559 help_params['extension_modifier'] = 'with C extension'
560 else:
561 help_params['extension_modifier'] = 'without C extension'
562
563 if error:
564 print(error, file=sys.stderr)
565 print(f"Use '{program_name} help' for help.", file=sys.stderr)
566 elif parser:
567 print(parser.format_help().strip())
568 print()
569 else:
570 help_msg = textwrap.dedent(HELP_TOPICS.get(topic, '')).strip()
571 if help_msg:
572 print(help_msg.format(**help_params))
573 else:
574 print(f"Don't know topic {topic!r}")
575 print("Full documentation is at {__url__}".format(**help_params))
576
577
578 OK, ERR, FAIL_UNDER = 0, 1, 2
579
580
581 class CoverageScript:
582 """The command-line interface to coverage.py."""
583
584 def __init__(self):
585 self.global_option = False
586 self.coverage = None
587
588 def command_line(self, argv):
589 """The bulk of the command line interface to coverage.py.
590
591 `argv` is the argument list to process.
592
593 Returns 0 if all is well, 1 if something went wrong.
594
595 """
596 # Collect the command-line options.
597 if not argv:
598 show_help(topic='minimum_help')
599 return OK
600
601 # The command syntax we parse depends on the first argument. Global
602 # switch syntax always starts with an option.
603 self.global_option = argv[0].startswith('-')
604 if self.global_option:
605 parser = GlobalOptionParser()
606 else:
607 parser = COMMANDS.get(argv[0])
608 if not parser:
609 show_help(f"Unknown command: {argv[0]!r}")
610 return ERR
611 argv = argv[1:]
612
613 ok, options, args = parser.parse_args_ok(argv)
614 if not ok:
615 return ERR
616
617 # Handle help and version.
618 if self.do_help(options, args, parser):
619 return OK
620
621 # Listify the list options.
622 source = unshell_list(options.source)
623 omit = unshell_list(options.omit)
624 include = unshell_list(options.include)
625 debug = unshell_list(options.debug)
626 contexts = unshell_list(options.contexts)
627
628 if options.concurrency is not None:
629 concurrency = options.concurrency.split(",")
630 else:
631 concurrency = None
632
633 # Do something.
634 self.coverage = Coverage(
635 data_file=options.data_file or DEFAULT_DATAFILE,
636 data_suffix=options.parallel_mode,
637 cover_pylib=options.pylib,
638 timid=options.timid,
639 branch=options.branch,
640 config_file=options.rcfile,
641 source=source,
642 omit=omit,
643 include=include,
644 debug=debug,
645 concurrency=concurrency,
646 check_preimported=True,
647 context=options.context,
648 messages=not options.quiet,
649 )
650
651 if options.action == "debug":
652 return self.do_debug(args)
653
654 elif options.action == "erase":
655 self.coverage.erase()
656 return OK
657
658 elif options.action == "run":
659 return self.do_run(options, args)
660
661 elif options.action == "combine":
662 if options.append:
663 self.coverage.load()
664 data_paths = args or None
665 self.coverage.combine(data_paths, strict=True, keep=bool(options.keep))
666 self.coverage.save()
667 return OK
668
669 # Remaining actions are reporting, with some common options.
670 report_args = dict(
671 morfs=unglob_args(args),
672 ignore_errors=options.ignore_errors,
673 omit=omit,
674 include=include,
675 contexts=contexts,
676 )
677
678 # We need to be able to import from the current directory, because
679 # plugins may try to, for example, to read Django settings.
680 sys.path.insert(0, '')
681
682 self.coverage.load()
683
684 total = None
685 if options.action == "report":
686 total = self.coverage.report(
687 precision=options.precision,
688 show_missing=options.show_missing,
689 skip_covered=options.skip_covered,
690 skip_empty=options.skip_empty,
691 sort=options.sort,
692 **report_args
693 )
694 elif options.action == "annotate":
695 self.coverage.annotate(directory=options.directory, **report_args)
696 elif options.action == "html":
697 total = self.coverage.html_report(
698 directory=options.directory,
699 precision=options.precision,
700 skip_covered=options.skip_covered,
701 skip_empty=options.skip_empty,
702 show_contexts=options.show_contexts,
703 title=options.title,
704 **report_args
705 )
706 elif options.action == "xml":
707 total = self.coverage.xml_report(
708 outfile=options.outfile,
709 skip_empty=options.skip_empty,
710 **report_args
711 )
712 elif options.action == "json":
713 total = self.coverage.json_report(
714 outfile=options.outfile,
715 pretty_print=options.pretty_print,
716 show_contexts=options.show_contexts,
717 **report_args
718 )
719 elif options.action == "lcov":
720 total = self.coverage.lcov_report(
721 outfile=options.outfile,
722 **report_args
723 )
724 else:
725 # There are no other possible actions.
726 raise AssertionError
727
728 if total is not None:
729 # Apply the command line fail-under options, and then use the config
730 # value, so we can get fail_under from the config file.
731 if options.fail_under is not None:
732 self.coverage.set_option("report:fail_under", options.fail_under)
733 if options.precision is not None:
734 self.coverage.set_option("report:precision", options.precision)
735
736 fail_under = self.coverage.get_option("report:fail_under")
737 precision = self.coverage.get_option("report:precision")
738 if should_fail_under(total, fail_under, precision):
739 msg = "total of {total} is less than fail-under={fail_under:.{p}f}".format(
740 total=Numbers(precision=precision).display_covered(total),
741 fail_under=fail_under,
742 p=precision,
743 )
744 print("Coverage failure:", msg)
745 return FAIL_UNDER
746
747 return OK
748
749 def do_help(self, options, args, parser):
750 """Deal with help requests.
751
752 Return True if it handled the request, False if not.
753
754 """
755 # Handle help.
756 if options.help:
757 if self.global_option:
758 show_help(topic='help')
759 else:
760 show_help(parser=parser)
761 return True
762
763 if options.action == "help":
764 if args:
765 for a in args:
766 parser = COMMANDS.get(a)
767 if parser:
768 show_help(parser=parser)
769 else:
770 show_help(topic=a)
771 else:
772 show_help(topic='help')
773 return True
774
775 # Handle version.
776 if options.version:
777 show_help(topic='version')
778 return True
779
780 return False
781
782 def do_run(self, options, args):
783 """Implementation of 'coverage run'."""
784
785 if not args:
786 if options.module:
787 # Specified -m with nothing else.
788 show_help("No module specified for -m")
789 return ERR
790 command_line = self.coverage.get_option("run:command_line")
791 if command_line is not None:
792 args = shlex.split(command_line)
793 if args and args[0] in {"-m", "--module"}:
794 options.module = True
795 args = args[1:]
796 if not args:
797 show_help("Nothing to do.")
798 return ERR
799
800 if options.append and self.coverage.get_option("run:parallel"):
801 show_help("Can't append to data files in parallel mode.")
802 return ERR
803
804 if options.concurrency == "multiprocessing":
805 # Can't set other run-affecting command line options with
806 # multiprocessing.
807 for opt_name in ['branch', 'include', 'omit', 'pylib', 'source', 'timid']:
808 # As it happens, all of these options have no default, meaning
809 # they will be None if they have not been specified.
810 if getattr(options, opt_name) is not None:
811 show_help(
812 "Options affecting multiprocessing must only be specified " +
813 "in a configuration file.\n" +
814 f"Remove --{opt_name} from the command line."
815 )
816 return ERR
817
818 os.environ["COVERAGE_RUN"] = "true"
819
820 runner = PyRunner(args, as_module=bool(options.module))
821 runner.prepare()
822
823 if options.append:
824 self.coverage.load()
825
826 # Run the script.
827 self.coverage.start()
828 code_ran = True
829 try:
830 runner.run()
831 except NoSource:
832 code_ran = False
833 raise
834 finally:
835 self.coverage.stop()
836 if code_ran:
837 self.coverage.save()
838
839 return OK
840
841 def do_debug(self, args):
842 """Implementation of 'coverage debug'."""
843
844 if not args:
845 show_help("What information would you like: config, data, sys, premain, pybehave?")
846 return ERR
847 if args[1:]:
848 show_help("Only one topic at a time, please")
849 return ERR
850
851 if args[0] == "sys":
852 write_formatted_info(print, "sys", self.coverage.sys_info())
853 elif args[0] == "data":
854 print(info_header("data"))
855 data_file = self.coverage.config.data_file
856 debug_data_file(data_file)
857 for filename in combinable_files(data_file):
858 print("-----")
859 debug_data_file(filename)
860 elif args[0] == "config":
861 write_formatted_info(print, "config", self.coverage.config.debug_info())
862 elif args[0] == "premain":
863 print(info_header("premain"))
864 print(short_stack())
865 elif args[0] == "pybehave":
866 write_formatted_info(print, "pybehave", env.debug_info())
867 else:
868 show_help(f"Don't know what you mean by {args[0]!r}")
869 return ERR
870
871 return OK
872
873
874 def unshell_list(s):
875 """Turn a command-line argument into a list."""
876 if not s:
877 return None
878 if env.WINDOWS:
879 # When running coverage.py as coverage.exe, some of the behavior
880 # of the shell is emulated: wildcards are expanded into a list of
881 # file names. So you have to single-quote patterns on the command
882 # line, but (not) helpfully, the single quotes are included in the
883 # argument, so we have to strip them off here.
884 s = s.strip("'")
885 return s.split(',')
886
887
888 def unglob_args(args):
889 """Interpret shell wildcards for platforms that need it."""
890 if env.WINDOWS:
891 globbed = []
892 for arg in args:
893 if '?' in arg or '*' in arg:
894 globbed.extend(glob.glob(arg))
895 else:
896 globbed.append(arg)
897 args = globbed
898 return args
899
900
901 HELP_TOPICS = {
902 'help': """\
903 Coverage.py, version {__version__} {extension_modifier}
904 Measure, collect, and report on code coverage in Python programs.
905
906 usage: {program_name} <command> [options] [args]
907
908 Commands:
909 annotate Annotate source files with execution information.
910 combine Combine a number of data files.
911 debug Display information about the internals of coverage.py
912 erase Erase previously collected coverage data.
913 help Get help on using coverage.py.
914 html Create an HTML report.
915 json Create a JSON report of coverage results.
916 lcov Create an LCOV report of coverage results.
917 report Report coverage stats on modules.
918 run Run a Python program and measure code execution.
919 xml Create an XML report of coverage results.
920
921 Use "{program_name} help <command>" for detailed help on any command.
922 """,
923
924 'minimum_help': """\
925 Code coverage for Python, version {__version__} {extension_modifier}. Use '{program_name} help' for help.
926 """,
927
928 'version': """\
929 Coverage.py, version {__version__} {extension_modifier}
930 """,
931 }
932
933
934 def main(argv=None):
935 """The main entry point to coverage.py.
936
937 This is installed as the script entry point.
938
939 """
940 if argv is None:
941 argv = sys.argv[1:]
942 try:
943 status = CoverageScript().command_line(argv)
944 except _ExceptionDuringRun as err:
945 # An exception was caught while running the product code. The
946 # sys.exc_info() return tuple is packed into an _ExceptionDuringRun
947 # exception.
948 traceback.print_exception(*err.args) # pylint: disable=no-value-for-parameter
949 status = ERR
950 except _BaseCoverageException as err:
951 # A controlled error inside coverage.py: print the message to the user.
952 msg = err.args[0]
953 print(msg)
954 status = ERR
955 except SystemExit as err:
956 # The user called `sys.exit()`. Exit with their argument, if any.
957 if err.args:
958 status = err.args[0]
959 else:
960 status = None
961 return status
962
963 # Profiling using ox_profile. Install it from GitHub:
964 # pip install git+https://github.com/emin63/ox_profile.git
965 #
966 # $set_env.py: COVERAGE_PROFILE - Set to use ox_profile.
967 _profile = os.environ.get("COVERAGE_PROFILE", "")
968 if _profile: # pragma: debugging
969 from ox_profile.core.launchers import SimpleLauncher # pylint: disable=import-error
970 original_main = main
971
972 def main(argv=None): # pylint: disable=function-redefined
973 """A wrapper around main that profiles."""
974 profiler = SimpleLauncher.launch()
975 try:
976 return original_main(argv)
977 finally:
978 data, _ = profiler.query(re_filter='coverage', max_records=100)
979 print(profiler.show(query=data, limit=100, sep='', col=''))
980 profiler.cancel()

eric ide

mercurial