--- a/DebugClients/Python3/coverage/cmdline.py Fri Apr 04 22:57:07 2014 +0200 +++ b/DebugClients/Python3/coverage/cmdline.py Thu Apr 10 23:02:20 2014 +0200 @@ -1,74 +1,107 @@ """Command-line support for Coverage.""" -import optparse, re, sys +import optparse, os, sys, time, traceback -from .execfile import run_python_file -from .misc import CoverageException +from .backward import sorted # pylint: disable=W0622 +from .execfile import run_python_file, run_python_module +from .misc import CoverageException, ExceptionDuringRun, NoSource +from .debug import info_formatter class Opts(object): """A namespace class for individual options we'll build parsers from.""" - append = optparse.Option( + append = optparse.make_option( '-a', '--append', action='store_false', dest="erase_first", help="Append coverage data to .coverage, otherwise it is started " "clean with each run." ) - branch = optparse.Option( + branch = optparse.make_option( '', '--branch', action='store_true', help="Measure branch coverage in addition to statement coverage." ) - directory = optparse.Option( - '-d', '--directory', action='store', - metavar="DIR", + debug = optparse.make_option( + '', '--debug', action='store', metavar="OPTS", + help="Debug options, separated by commas" + ) + directory = optparse.make_option( + '-d', '--directory', action='store', metavar="DIR", help="Write the output files to DIR." ) - help = optparse.Option( + fail_under = optparse.make_option( + '', '--fail-under', action='store', metavar="MIN", type="int", + help="Exit with a status of 2 if the total coverage is less than MIN." + ) + help = optparse.make_option( '-h', '--help', action='store_true', help="Get help on this command." ) - ignore_errors = optparse.Option( + ignore_errors = optparse.make_option( '-i', '--ignore-errors', action='store_true', help="Ignore errors while reading source files." ) - pylib = optparse.Option( + include = optparse.make_option( + '', '--include', action='store', + metavar="PAT1,PAT2,...", + help="Include files only when their filename path matches one of " + "these patterns. Usually needs quoting on the command line." + ) + pylib = optparse.make_option( '-L', '--pylib', action='store_true', help="Measure coverage even inside the Python installed library, " "which isn't done by default." ) - show_missing = optparse.Option( + show_missing = optparse.make_option( '-m', '--show-missing', action='store_true', help="Show line numbers of statements in each module that weren't " "executed." ) - old_omit = optparse.Option( + old_omit = optparse.make_option( '-o', '--omit', action='store', - metavar="PRE1,PRE2,...", - help="Omit files when their filename path starts with one of these " - "prefixes." + metavar="PAT1,PAT2,...", + help="Omit files when their filename matches one of these patterns. " + "Usually needs quoting on the command line." ) - omit = optparse.Option( + omit = optparse.make_option( '', '--omit', action='store', - metavar="PRE1,PRE2,...", - help="Omit files when their filename path starts with one of these " - "prefixes." + metavar="PAT1,PAT2,...", + help="Omit files when their filename matches one of these patterns. " + "Usually needs quoting on the command line." ) - output_xml = optparse.Option( + output_xml = optparse.make_option( '-o', '', action='store', dest="outfile", metavar="OUTFILE", help="Write the XML report to this file. Defaults to 'coverage.xml'" ) - parallel_mode = optparse.Option( + parallel_mode = optparse.make_option( '-p', '--parallel-mode', action='store_true', - help="Include the machine name and process id in the .coverage " - "data file name." + help="Append the machine name, process id and random number to the " + ".coverage data file name to simplify collecting data from " + "many processes." + ) + module = optparse.make_option( + '-m', '--module', action='store_true', + help="<pyfile> is an importable Python module, not a script path, " + "to be run as 'python -m' would run it." ) - timid = optparse.Option( + rcfile = optparse.make_option( + '', '--rcfile', action='store', + help="Specify configuration file. Defaults to '.coveragerc'" + ) + source = optparse.make_option( + '', '--source', action='store', metavar="SRC1,SRC2,...", + help="A list of packages or directories of code to be measured." + ) + timid = optparse.make_option( '', '--timid', action='store_true', help="Use a simpler but slower trace method. Try this if you get " "seemingly impossible results!" ) - version = optparse.Option( + title = optparse.make_option( + '', '--title', action='store', metavar="TITLE", + help="A text string to use as the title on the HTML." + ) + version = optparse.make_option( '', '--version', action='store_true', help="Display version information and exit." ) @@ -89,20 +122,31 @@ self.set_defaults( actions=[], branch=None, + debug=None, directory=None, + fail_under=None, help=None, ignore_errors=None, + include=None, omit=None, parallel_mode=None, + module=None, pylib=None, + rcfile=True, show_missing=None, + source=None, timid=None, + title=None, erase_first=None, version=None, ) self.disable_interspersed_args() - self.help_fn = lambda: None + self.help_fn = self.help_noop + + def help_noop(self, error=None, topic=None, parser=None): + """No-op help function.""" + pass class OptionParserError(Exception): """Used to stop the optparse error handler ending the process.""" @@ -197,6 +241,10 @@ # results, and they will compare equal to objects. return (other == "<CmdOptionParser:%s>" % self.cmd) +GLOBAL_ARGS = [ + Opts.rcfile, + Opts.help, + ] CMDS = { 'annotate': CmdOptionParser("annotate", @@ -204,15 +252,35 @@ Opts.directory, Opts.ignore_errors, Opts.omit, - Opts.help, - ], + Opts.include, + ] + GLOBAL_ARGS, usage = "[options] [modules]", description = "Make annotated copies of the given files, marking " "statements that are executed with > and statements that are " "missed with !." ), - 'help': CmdOptionParser("help", [Opts.help], + 'combine': CmdOptionParser("combine", GLOBAL_ARGS, + usage = " ", + description = "Combine data from multiple coverage files collected " + "with 'run -p'. The combined results are written to a single " + "file representing the union of the data." + ), + + 'debug': CmdOptionParser("debug", GLOBAL_ARGS, + usage = "<topic>", + description = "Display information on the internals of coverage.py, " + "for diagnosing problems. " + "Topics are 'data' to show a summary of the collected data, " + "or 'sys' to show installation information." + ), + + 'erase': CmdOptionParser("erase", GLOBAL_ARGS, + usage = " ", + description = "Erase previously collected coverage data." + ), + + 'help': CmdOptionParser("help", GLOBAL_ARGS, usage = "[command]", description = "Describe how to use coverage.py" ), @@ -220,43 +288,26 @@ 'html': CmdOptionParser("html", [ Opts.directory, + Opts.fail_under, Opts.ignore_errors, Opts.omit, - Opts.help, - ], + Opts.include, + Opts.title, + ] + GLOBAL_ARGS, usage = "[options] [modules]", description = "Create an HTML report of the coverage of the files. " "Each file gets its own page, with the source decorated to show " "executed, excluded, and missed lines." ), - 'combine': CmdOptionParser("combine", [Opts.help], - usage = " ", - description = "Combine data from multiple coverage files collected " - "with 'run -p'. The combined results are written to a single " - "file representing the union of the data." - ), - - 'debug': CmdOptionParser("debug", [Opts.help], - usage = "<topic>", - description = "Display information on the internals of coverage.py, " - "for diagnosing problems. " - "Topics are 'data' to show a summary of the collected data, " - "or 'sys' to show installation information." - ), - - 'erase': CmdOptionParser("erase", [Opts.help], - usage = " ", - description = "Erase previously collected coverage data." - ), - 'report': CmdOptionParser("report", [ + Opts.fail_under, Opts.ignore_errors, Opts.omit, + Opts.include, Opts.show_missing, - Opts.help, - ], + ] + GLOBAL_ARGS, usage = "[options] [modules]", description = "Report coverage statistics on modules." ), @@ -265,11 +316,15 @@ [ Opts.append, Opts.branch, + Opts.debug, Opts.pylib, Opts.parallel_mode, + Opts.module, Opts.timid, - Opts.help, - ], + Opts.source, + Opts.omit, + Opts.include, + ] + GLOBAL_ARGS, defaults = {'erase_first': True}, cmd = "run", usage = "[options] <pyfile> [program options]", @@ -278,59 +333,42 @@ 'xml': CmdOptionParser("xml", [ + Opts.fail_under, Opts.ignore_errors, Opts.omit, + Opts.include, Opts.output_xml, - Opts.help, - ], + ] + GLOBAL_ARGS, cmd = "xml", - defaults = {'outfile': 'coverage.xml'}, usage = "[options] [modules]", description = "Generate an XML report of coverage results." ), } -OK, ERR = 0, 1 +OK, ERR, FAIL_UNDER = 0, 1, 2 class CoverageScript(object): """The command-line interface to Coverage.""" - def __init__(self, _covpkg=None, _run_python_file=None, _help_fn=None): + def __init__(self, _covpkg=None, _run_python_file=None, + _run_python_module=None, _help_fn=None): # _covpkg is for dependency injection, so we can test this code. if _covpkg: self.covpkg = _covpkg else: - import coverage + from . import coverage self.covpkg = coverage - # _run_python_file is for dependency injection also. + # For dependency injection: self.run_python_file = _run_python_file or run_python_file - - # _help_fn is for dependency injection. + self.run_python_module = _run_python_module or run_python_module self.help_fn = _help_fn or self.help + self.classic = False self.coverage = None - def help(self, error=None, topic=None, parser=None): - """Display an error message, or the named topic.""" - assert error or topic or parser - if error: - print(error) - print("Use 'coverage help' for help.") - elif parser: - print(parser.format_help().strip()) - else: - # Parse out the topic we want from HELP_TOPICS - topic_list = re.split("(?m)^=+ (\w+) =+$", HELP_TOPICS) - topics = dict(zip(topic_list[1::2], topic_list[2::2])) - help_msg = topics.get(topic, '').strip() - if help_msg: - print(help_msg % self.covpkg.__dict__) - else: - print("Don't know topic %r" % topic) - def command_line(self, argv): """The bulk of the command line interface to Coverage. @@ -340,15 +378,14 @@ """ # Collect the command-line options. - if not argv: self.help_fn(topic='minimum_help') return OK # The command syntax we parse depends on the first argument. Classic # syntax always starts with an option. - classic = argv[0].startswith('-') - if classic: + self.classic = argv[0].startswith('-') + if self.classic: parser = ClassicOptionParser() else: parser = CMDS.get(argv[0]) @@ -362,13 +399,106 @@ if not ok: return ERR + # Handle help and version. + if self.do_help(options, args, parser): + return OK + + # Check for conflicts and problems in the options. + if not self.args_ok(options, args): + return ERR + + # Listify the list options. + source = unshell_list(options.source) + omit = unshell_list(options.omit) + include = unshell_list(options.include) + debug = unshell_list(options.debug) + + # Do something. + self.coverage = self.covpkg.coverage( + data_suffix = options.parallel_mode, + cover_pylib = options.pylib, + timid = options.timid, + branch = options.branch, + config_file = options.rcfile, + source = source, + omit = omit, + include = include, + debug = debug, + ) + + if 'debug' in options.actions: + return self.do_debug(args) + + if 'erase' in options.actions or options.erase_first: + self.coverage.erase() + else: + self.coverage.load() + + if 'execute' in options.actions: + self.do_execute(options, args) + + if 'combine' in options.actions: + self.coverage.combine() + self.coverage.save() + + # Remaining actions are reporting, with some common options. + report_args = dict( + morfs = args, + ignore_errors = options.ignore_errors, + omit = omit, + include = include, + ) + + if 'report' in options.actions: + total = self.coverage.report( + show_missing=options.show_missing, **report_args) + if 'annotate' in options.actions: + self.coverage.annotate( + directory=options.directory, **report_args) + if 'html' in options.actions: + total = self.coverage.html_report( + directory=options.directory, title=options.title, + **report_args) + if 'xml' in options.actions: + outfile = options.outfile + total = self.coverage.xml_report(outfile=outfile, **report_args) + + if options.fail_under is not None: + if total >= options.fail_under: + return OK + else: + return FAIL_UNDER + else: + return OK + + def help(self, error=None, topic=None, parser=None): + """Display an error message, or the named topic.""" + assert error or topic or parser + if error: + print(error) + print("Use 'coverage help' for help.") + elif parser: + print(parser.format_help().strip()) + else: + help_msg = HELP_TOPICS.get(topic, '').strip() + if help_msg: + print(help_msg % self.covpkg.__dict__) + else: + print("Don't know topic %r" % topic) + + def do_help(self, options, args, parser): + """Deal with help requests. + + Return True if it handled the request, False if not. + + """ # Handle help. if options.help: - if classic: + if self.classic: self.help_fn(topic='help') else: self.help_fn(parser=parser) - return OK + return True if "help" in options.actions: if args: @@ -380,26 +510,33 @@ self.help_fn(topic=a) else: self.help_fn(topic='help') - return OK + return True # Handle version. if options.version: self.help_fn(topic='version') - return OK + return True + + return False - # Check for conflicts and problems in the options. + def args_ok(self, options, args): + """Check for conflicts and problems in the options. + + Returns True if everything is ok, or False if not. + + """ for i in ['erase', 'execute']: for j in ['annotate', 'html', 'report', 'combine']: if (i in options.actions) and (j in options.actions): self.help_fn("You can't specify the '%s' and '%s' " "options at the same time." % (i, j)) - return ERR + return False if not options.actions: self.help_fn( "You must specify at least one of -e, -x, -c, -r, -a, or -b." ) - return ERR + return False args_allowed = ( 'execute' in options.actions or 'annotate' in options.actions or @@ -410,103 +547,91 @@ ) if not args_allowed and args: self.help_fn("Unexpected arguments: %s" % " ".join(args)) - return ERR + return False if 'execute' in options.actions and not args: self.help_fn("Nothing to do.") - return ERR + return False + + return True - # Do something. - self.coverage = self.covpkg.coverage( - data_suffix = bool(options.parallel_mode), - cover_pylib = options.pylib, - timid = options.timid, - branch = options.branch, - ) + def do_execute(self, options, args): + """Implementation of 'coverage run'.""" + + # Set the first path element properly. + old_path0 = sys.path[0] - if 'debug' in options.actions: - if not args: - self.help_fn("What information would you like: data, sys?") - return ERR - for info in args: - if info == 'sys': - print("-- sys ----------------------------------------") - for label, info in self.coverage.sysinfo(): - if isinstance(info, list): - print("%15s:" % label) - for e in info: - print("%15s %s" % ("", e)) - else: - print("%15s: %s" % (label, info)) - elif info == 'data': - print("-- data ---------------------------------------") - self.coverage.load() - print("path: %s" % self.coverage.data.filename) - print("has_arcs: %r" % self.coverage.data.has_arcs()) - summary = self.coverage.data.summary(fullpath=True) - if summary: - filenames = sorted(summary.keys()) - print("\n%d files:" % len(filenames)) - for f in filenames: - print("%s: %d lines" % (f, summary[f])) - else: - print("No data collected") + # Run the script. + self.coverage.start() + code_ran = True + try: + try: + if options.module: + sys.path[0] = '' + self.run_python_module(args[0], args) else: - self.help_fn("Don't know what you mean by %r" % info) - return ERR - return OK - - if 'erase' in options.actions or options.erase_first: - self.coverage.erase() - else: - self.coverage.load() - - if 'execute' in options.actions: - # Run the script. - self.coverage.start() - try: - self.run_python_file(args[0], args) - finally: - self.coverage.stop() + filename = args[0] + sys.path[0] = os.path.abspath(os.path.dirname(filename)) + self.run_python_file(filename, args) + except NoSource: + code_ran = False + raise + finally: + self.coverage.stop() + if code_ran: self.coverage.save() - if 'combine' in options.actions: - self.coverage.combine() - self.coverage.save() + # Restore the old path + sys.path[0] = old_path0 - # Remaining actions are reporting, with some common options. - report_args = { - 'morfs': args, - 'ignore_errors': options.ignore_errors, - } - - omit = None - if options.omit: - omit = options.omit.split(',') - report_args['omit_prefixes'] = omit + def do_debug(self, args): + """Implementation of 'coverage debug'.""" - if 'report' in options.actions: - self.coverage.report( - show_missing=options.show_missing, **report_args) - if 'annotate' in options.actions: - self.coverage.annotate( - directory=options.directory, **report_args) - if 'html' in options.actions: - self.coverage.html_report( - directory=options.directory, **report_args) - if 'xml' in options.actions: - outfile = options.outfile - if outfile == '-': - outfile = None - self.coverage.xml_report(outfile=outfile, **report_args) - + if not args: + self.help_fn("What information would you like: data, sys?") + return ERR + for info in args: + if info == 'sys': + print("-- sys ----------------------------------------") + for line in info_formatter(self.coverage.sysinfo()): + print(" %s" % line) + elif info == 'data': + print("-- data ---------------------------------------") + self.coverage.load() + print("path: %s" % self.coverage.data.filename) + print("has_arcs: %r" % self.coverage.data.has_arcs()) + summary = self.coverage.data.summary(fullpath=True) + if summary: + filenames = sorted(summary.keys()) + print("\n%d files:" % len(filenames)) + for f in filenames: + print("%s: %d lines" % (f, summary[f])) + else: + print("No data collected") + else: + self.help_fn("Don't know what you mean by %r" % info) + return ERR return OK -HELP_TOPICS = r""" +def unshell_list(s): + """Turn a command-line argument into a list.""" + if not s: + return None + if sys.platform == 'win32': + # When running coverage as coverage.exe, some of the behavior + # of the shell is emulated: wildcards are expanded into a list of + # filenames. So you have to single-quote patterns on the command + # line, but (not) helpfully, the single quotes are included in the + # argument, so we have to strip them off here. + s = s.strip("'") + return s.split(',') -== classic ==================================================================== -Coverage.py version %(__version__)s + +HELP_TOPICS = { +# ------------------------- +'classic': +r"""Coverage.py version %(__version__)s Measure, collect, and report on code coverage in Python programs. Usage: @@ -550,8 +675,9 @@ Coverage data is saved in the file .coverage by default. Set the COVERAGE_FILE environment variable to save it somewhere else. - -== help ======================================================================= +""", +# ------------------------- +'help': """\ Coverage.py, version %(__version__)s Measure, collect, and report on code coverage in Python programs. @@ -570,26 +696,49 @@ Use "coverage help <command>" for detailed help on any command. Use "coverage help classic" for help on older command syntax. For more information, see %(__url__)s - -== minimum_help =============================================================== +""", +# ------------------------- +'minimum_help': """\ Code coverage for Python. Use 'coverage help' for help. - -== version ==================================================================== +""", +# ------------------------- +'version': """\ Coverage.py, version %(__version__)s. %(__url__)s - -""" +""", +} -def main(): - """The main entrypoint to Coverage. +def main(argv=None): + """The main entry point to Coverage. - This is installed as the script entrypoint. + This is installed as the script entry point. """ + if argv is None: + argv = sys.argv[1:] try: - status = CoverageScript().command_line(sys.argv[1:]) + start = time.clock() + status = CoverageScript().command_line(argv) + end = time.clock() + if 0: + print("time: %.3fs" % (end - start)) + except ExceptionDuringRun: + # An exception was caught while running the product code. The + # sys.exc_info() return tuple is packed into an ExceptionDuringRun + # exception. + _, err, _ = sys.exc_info() + traceback.print_exception(*err.args) + status = ERR except CoverageException: + # A controlled error inside coverage.py: print the message to the user. _, err, _ = sys.exc_info() print(err) status = ERR - return status \ No newline at end of file + except SystemExit: + # The user called `sys.exit()`. Exit with their argument, if any. + _, err, _ = sys.exc_info() + if err.args: + status = err.args[0] + else: + status = None + return status