Sun, 24 Jul 2016 12:01:01 +0200
Updated coverage.py to version 4.1.0.
--- a/DataViews/PyCoverageDialog.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DataViews/PyCoverageDialog.py Sun Jul 24 12:01:01 2016 +0200 @@ -22,7 +22,7 @@ from .Ui_PyCoverageDialog import Ui_PyCoverageDialog import Utilities -from coverage import coverage +from coverage import Coverage from coverage.misc import CoverageException @@ -168,7 +168,7 @@ self.path = os.path.dirname(cfn) files.sort() - cover = coverage(data_file=self.cfn) + cover = Coverage(data_file=self.cfn) cover.load() # set the exclude pattern @@ -315,7 +315,7 @@ itm = self.resultList.currentItem() fn = itm.text(0) - cover = coverage(data_file=self.cfn) + cover = Coverage(data_file=self.cfn) cover.exclude(self.excludeList[0]) cover.load() cover.annotate([fn], None, True) @@ -337,7 +337,7 @@ itm = self.resultList.topLevelItem(index) files.append(itm.text(0)) - cover = coverage(data_file=self.cfn) + cover = Coverage(data_file=self.cfn) cover.exclude(self.excludeList[0]) cover.load() @@ -365,7 +365,7 @@ This method erases the collected coverage data that is stored in the .coverage file. """ - cover = coverage(data_file=self.cfn) + cover = Coverage(data_file=self.cfn) cover.load() cover.erase()
--- a/DebugClients/Python/coverage/__init__.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/__init__.py Sun Jul 24 12:01:01 2016 +0200 @@ -14,6 +14,7 @@ from coverage.data import CoverageData from coverage.misc import CoverageException from coverage.plugin import CoveragePlugin, FileTracer, FileReporter +from coverage.pytracer import PyTracer # Backward compatibility. coverage = Coverage
--- a/DebugClients/Python/coverage/annotate.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/annotate.py Sun Jul 24 12:01:01 2016 +0200 @@ -8,8 +8,12 @@ import re from coverage.files import flat_rootname +from coverage.misc import isolate_module from coverage.report import Reporter +os = isolate_module(os) + + class AnnotateReporter(Reporter): """Generate annotated source files showing line coverage.
--- a/DebugClients/Python/coverage/backunittest.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/backunittest.py Sun Jul 24 12:01:01 2016 +0200 @@ -40,3 +40,6 @@ if not unittest_has('assertRegex'): def assertRegex(self, *args, **kwargs): return self.assertRegexpMatches(*args, **kwargs) + +# +# eflag: FileType = Python2
--- a/DebugClients/Python/coverage/backward.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/backward.py Sun Jul 24 12:01:01 2016 +0200 @@ -49,6 +49,15 @@ except NameError: range = range +# shlex.quote is new, but there's an undocumented implementation in "pipes", +# who knew!? +try: + from shlex import quote as shlex_quote +except ImportError: + # Useful function, available under a different (undocumented) name + # in Python versions earlier than 3.3. + from pipes import quote as shlex_quote + # A function to iterate listlessly over a dict's items. try: {}.iteritems @@ -84,10 +93,6 @@ """Produce a byte string with the ints from `byte_values`.""" return bytes(byte_values) - def byte_to_int(byte_value): - """Turn an element of a bytes object into an int.""" - return byte_value - def bytes_to_ints(bytes_value): """Turn a bytes object into a sequence of ints.""" # In Python 3, iterating bytes gives ints. @@ -102,10 +107,6 @@ """Produce a byte string with the ints from `byte_values`.""" return "".join(chr(b) for b in byte_values) - def byte_to_int(byte_value): - """Turn an element of a bytes object into an int.""" - return ord(byte_value) - def bytes_to_ints(bytes_value): """Turn a bytes object into a sequence of ints.""" for byte in bytes_value: @@ -142,11 +143,12 @@ PYC_MAGIC_NUMBER = imp.get_magic() -def import_local_file(modname): +def import_local_file(modname, modfile=None): """Import a local file as a module. Opens a file in the current directory named `modname`.py, imports it - as `modname`, and returns the module object. + as `modname`, and returns the module object. `modfile` is the file to + import if it isn't in the current directory. """ try: @@ -154,7 +156,8 @@ except ImportError: SourceFileLoader = None - modfile = modname + '.py' + if modfile is None: + modfile = modname + '.py' if SourceFileLoader: mod = SourceFileLoader(modname, modfile).load_module() else:
--- a/DebugClients/Python/coverage/bytecode.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/bytecode.py Sun Jul 24 12:01:01 2016 +0200 @@ -3,73 +3,8 @@ """Bytecode manipulation for coverage.py""" -import opcode import types -from coverage.backward import byte_to_int - - -class ByteCode(object): - """A single bytecode.""" - def __init__(self): - # The offset of this bytecode in the code object. - self.offset = -1 - - # The opcode, defined in the `opcode` module. - self.op = -1 - - # The argument, a small integer, whose meaning depends on the opcode. - self.arg = -1 - - # The offset in the code object of the next bytecode. - self.next_offset = -1 - - # The offset to jump to. - self.jump_to = -1 - - -class ByteCodes(object): - """Iterator over byte codes in `code`. - - This handles the logic of EXTENDED_ARG byte codes internally. Those byte - codes are not returned by this iterator. - - Returns `ByteCode` objects. - - """ - def __init__(self, code): - self.code = code - - def __getitem__(self, i): - return byte_to_int(self.code[i]) - - def __iter__(self): - offset = 0 - ext_arg = 0 - while offset < len(self.code): - bc = ByteCode() - bc.op = self[offset] - bc.offset = offset - - next_offset = offset+1 - if bc.op >= opcode.HAVE_ARGUMENT: - bc.arg = ext_arg + self[offset+1] + 256*self[offset+2] - next_offset += 2 - - label = -1 - if bc.op in opcode.hasjrel: - label = next_offset + bc.arg - elif bc.op in opcode.hasjabs: - label = bc.arg - bc.jump_to = label - - bc.next_offset = offset = next_offset - if bc.op == opcode.EXTENDED_ARG: - ext_arg = bc.arg * 256*256 - else: - ext_arg = 0 - yield bc - class CodeObjects(object): """Iterate over all the code objects in `code`."""
--- a/DebugClients/Python/coverage/cmdline.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/cmdline.py Sun Jul 24 12:01:01 2016 +0200 @@ -7,9 +7,11 @@ import optparse import os.path import sys +import textwrap import traceback from coverage import env +from coverage.collector import CTracer from coverage.execfile import run_python_file, run_python_module from coverage.misc import CoverageException, ExceptionDuringRun, NoSource from coverage.debug import info_formatter, info_header @@ -20,105 +22,117 @@ append = optparse.make_option( '-a', '--append', action='store_true', - help="Append coverage data to .coverage, otherwise it is started " - "clean with each run." - ) + help="Append coverage data to .coverage, otherwise it is started clean with each run.", + ) branch = optparse.make_option( '', '--branch', action='store_true', - help="Measure branch coverage in addition to statement coverage." - ) + help="Measure branch coverage in addition to statement coverage.", + ) CONCURRENCY_CHOICES = [ "thread", "gevent", "greenlet", "eventlet", "multiprocessing", ] concurrency = optparse.make_option( '', '--concurrency', action='store', metavar="LIB", choices=CONCURRENCY_CHOICES, - help="Properly measure code using a concurrency library. " - "Valid values are: %s." % ", ".join(CONCURRENCY_CHOICES) - ) + help=( + "Properly measure code using a concurrency library. " + "Valid values are: %s." + ) % ", ".join(CONCURRENCY_CHOICES), + ) debug = optparse.make_option( '', '--debug', action='store', metavar="OPTS", - help="Debug options, separated by commas" - ) + help="Debug options, separated by commas", + ) directory = optparse.make_option( '-d', '--directory', action='store', metavar="DIR", - help="Write the output files to DIR." - ) + help="Write the output files to DIR.", + ) 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="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." - ) + help="Get help on this command.", + ) ignore_errors = optparse.make_option( '-i', '--ignore-errors', action='store_true', - help="Ignore errors while reading source files." - ) + help="Ignore errors while reading source files.", + ) include = optparse.make_option( '', '--include', action='store', metavar="PAT1,PAT2,...", - help="Include only files whose paths match one of these patterns. " - "Accepts shell-style wildcards, which must be quoted." - ) + help=( + "Include only files whose paths match one of these patterns. " + "Accepts shell-style wildcards, which must be quoted." + ), + ) pylib = optparse.make_option( '-L', '--pylib', action='store_true', - help="Measure coverage even inside the Python installed library, " - "which isn't done by default." - ) + help=( + "Measure coverage even inside the Python installed library, " + "which isn't done by default." + ), + ) show_missing = optparse.make_option( '-m', '--show-missing', action='store_true', - help="Show line numbers of statements in each module that weren't " - "executed." - ) + help="Show line numbers of statements in each module that weren't executed.", + ) skip_covered = optparse.make_option( '--skip-covered', action='store_true', - help="Skip files with 100% coverage." - ) + help="Skip files with 100% coverage.", + ) omit = optparse.make_option( '', '--omit', action='store', metavar="PAT1,PAT2,...", - help="Omit files whose paths match one of these patterns. " - "Accepts shell-style wildcards, which must be quoted." - ) + help=( + "Omit files whose paths match one of these patterns. " + "Accepts shell-style wildcards, which must be quoted." + ), + ) output_xml = optparse.make_option( '-o', '', action='store', dest="outfile", metavar="OUTFILE", - help="Write the XML report to this file. Defaults to 'coverage.xml'" - ) + help="Write the XML report to this file. Defaults to 'coverage.xml'", + ) parallel_mode = optparse.make_option( '-p', '--parallel-mode', action='store_true', - help="Append the machine name, process id and random number to the " - ".coverage data file name to simplify collecting data from " - "many processes." - ) + 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." - ) + help=( + "<pyfile> is an importable Python module, not a script path, " + "to be run as 'python -m' would run it." + ), + ) rcfile = optparse.make_option( '', '--rcfile', action='store', - help="Specify configuration file. Defaults to '.coveragerc'" - ) + 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." - ) + 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!" - ) + help=( + "Use a simpler but slower trace method. Try this if you get " + "seemingly impossible results!" + ), + ) title = optparse.make_option( '', '--title', action='store', metavar="TITLE", - help="A text string to use as the title on the HTML." - ) + 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." - ) + help="Display version information and exit.", + ) class CoverageOptionParser(optparse.OptionParser, object): @@ -202,9 +216,7 @@ class CmdOptionParser(CoverageOptionParser): """Parse one of the new-style commands for coverage.py.""" - def __init__(self, action, options=None, defaults=None, usage=None, - description=None - ): + def __init__(self, action, options=None, defaults=None, usage=None, description=None): """Create an OptionParser for a coverage.py command. `action` is the slug to put into `options.action`. @@ -217,7 +229,6 @@ if usage: usage = "%prog " + usage super(CmdOptionParser, self).__init__( - prog="coverage %s" % action, usage=usage, description=description, ) @@ -231,6 +242,14 @@ # results, and they will compare equal to objects. return (other == "<CmdOptionParser:%s>" % self.cmd) + def get_prog_name(self): + """Override of an undocumented function in optparse.OptionParser.""" + program_name = super(CmdOptionParser, self).get_prog_name() + + # Include the sub-command for this parser as part of the command. + return "%(command)s %(subcommand)s" % {'command': program_name, 'subcommand': self.cmd} + + GLOBAL_ARGS = [ Opts.debug, Opts.help, @@ -238,48 +257,60 @@ ] CMDS = { - 'annotate': CmdOptionParser("annotate", + 'annotate': CmdOptionParser( + "annotate", [ Opts.directory, Opts.ignore_errors, Opts.include, Opts.omit, ] + 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 !." + usage="[options] [modules]", + description=( + "Make annotated copies of the given files, marking statements that are executed " + "with > and statements that are missed with !." ), + ), - 'combine': CmdOptionParser("combine", GLOBAL_ARGS, - usage = "<path1> <path2> ... <pathN>", - description = "Combine data from multiple coverage files collected " + 'combine': CmdOptionParser( + "combine", + GLOBAL_ARGS, + usage="<path1> <path2> ... <pathN>", + 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. The positional " "arguments are data files or directories containing data files. " "If no paths are provided, data files in the default data file's " "directory are combined." ), + ), - 'debug': CmdOptionParser("debug", GLOBAL_ARGS, - usage = "<topic>", - description = "Display information on the internals of coverage.py, " + '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." - ), + '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" - ), + 'help': CmdOptionParser( + "help", GLOBAL_ARGS, + usage="[command]", + description="Describe how to use coverage.py", + ), - 'html': CmdOptionParser("html", + 'html': CmdOptionParser( + "html", [ Opts.directory, Opts.fail_under, @@ -288,13 +319,16 @@ Opts.omit, Opts.title, ] + GLOBAL_ARGS, - usage = "[options] [modules]", - description = "Create an HTML report of the coverage of the files. " + 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." ), + ), - 'report': CmdOptionParser("report", + 'report': CmdOptionParser( + "report", [ Opts.fail_under, Opts.ignore_errors, @@ -303,11 +337,12 @@ Opts.show_missing, Opts.skip_covered, ] + GLOBAL_ARGS, - usage = "[options] [modules]", - description = "Report coverage statistics on modules." - ), + usage="[options] [modules]", + description="Report coverage statistics on modules." + ), - 'run': CmdOptionParser("run", + 'run': CmdOptionParser( + "run", [ Opts.append, Opts.branch, @@ -320,11 +355,12 @@ Opts.source, Opts.timid, ] + GLOBAL_ARGS, - usage = "[options] <pyfile> [program options]", - description = "Run a Python program, measuring code execution." - ), + usage="[options] <pyfile> [program options]", + description="Run a Python program, measuring code execution." + ), - 'xml': CmdOptionParser("xml", + 'xml': CmdOptionParser( + "xml", [ Opts.fail_under, Opts.ignore_errors, @@ -332,10 +368,10 @@ Opts.omit, Opts.output_xml, ] + GLOBAL_ARGS, - usage = "[options] [modules]", - description = "Generate an XML report of coverage results." - ), - } + usage="[options] [modules]", + description="Generate an XML report of coverage results." + ), +} OK, ERR, FAIL_UNDER = 0, 1, 2 @@ -362,6 +398,17 @@ self.coverage = None + self.program_name = os.path.basename(sys.argv[0]) + if env.WINDOWS: + # entry_points={'console_scripts':...} on Windows makes files + # called coverage.exe, coverage3.exe, and coverage-3.5.exe. These + # invoke coverage-script.py, coverage3-script.py, and + # coverage-3.5-script.py. argv[0] is the .py file, but we want to + # get back to the original form. + auto_suffix = "-script.py" + if self.program_name.endswith(auto_suffix): + self.program_name = self.program_name[:-len(auto_suffix)] + def command_line(self, argv): """The bulk of the command line interface to coverage.py. @@ -412,16 +459,16 @@ # 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, - concurrency = options.concurrency, + 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, + concurrency=options.concurrency, ) if options.action == "debug": @@ -443,10 +490,10 @@ # Remaining actions are reporting, with some common options. report_args = dict( - morfs = unglob_args(args), - ignore_errors = options.ignore_errors, - omit = omit, - include = include, + morfs=unglob_args(args), + ignore_errors=options.ignore_errors, + omit=omit, + include=include, ) self.coverage.load() @@ -495,13 +542,19 @@ assert error or topic or parser if error: print(error) - print("Use 'coverage help' for help.") + print("Use '%s help' for help." % (self.program_name,)) elif parser: print(parser.format_help().strip()) else: - help_msg = HELP_TOPICS.get(topic, '').strip() + help_params = dict(self.covpkg.__dict__) + help_params['program_name'] = self.program_name + if CTracer is not None: + help_params['extension_modifier'] = 'with C extension' + else: + help_params['extension_modifier'] = 'without C extension' + help_msg = textwrap.dedent(HELP_TOPICS.get(topic, '')).strip() if help_msg: - print(help_msg % self.covpkg.__dict__) + print(help_msg.format(**help_params)) else: print("Don't know topic %r" % topic) @@ -650,35 +703,34 @@ HELP_TOPICS = { -# ------------------------- -'help': """\ -Coverage.py, version %(__version__)s -Measure, collect, and report on code coverage in Python programs. + 'help': """\ + Coverage.py, version {__version__} {extension_modifier} + Measure, collect, and report on code coverage in Python programs. -usage: coverage <command> [options] [args] + usage: {program_name} <command> [options] [args] -Commands: - annotate Annotate source files with execution information. - combine Combine a number of data files. - erase Erase previously collected coverage data. - help Get help on using coverage.py. - html Create an HTML report. - report Report coverage stats on modules. - run Run a Python program and measure code execution. - xml Create an XML report of coverage results. + Commands: + annotate Annotate source files with execution information. + combine Combine a number of data files. + erase Erase previously collected coverage data. + help Get help on using coverage.py. + html Create an HTML report. + report Report coverage stats on modules. + run Run a Python program and measure code execution. + xml Create an XML report of coverage results. -Use "coverage help <command>" for detailed help on any command. -For full documentation, see %(__url__)s -""", -# ------------------------- -'minimum_help': """\ -Code coverage for Python. Use 'coverage help' for help. -""", -# ------------------------- -'version': """\ -Coverage.py, version %(__version__)s. -Documentation at %(__url__)s -""", + Use "{program_name} help <command>" for detailed help on any command. + For full documentation, see {__url__} + """, + + 'minimum_help': """\ + Code coverage for Python. Use '{program_name} help' for help. + """, + + 'version': """\ + Coverage.py, version {__version__} {extension_modifier} + Documentation at {__url__} + """, }
--- a/DebugClients/Python/coverage/collector.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/collector.py Sun Jul 24 12:01:01 2016 +0200 @@ -3,14 +3,18 @@ """Raw data collector for coverage.py.""" -import os, sys +import os +import sys from coverage import env from coverage.backward import iitems from coverage.files import abs_file -from coverage.misc import CoverageException +from coverage.misc import CoverageException, isolate_module from coverage.pytracer import PyTracer +os = isolate_module(os) + + try: # Use the C extension code when we can, for speed. from coverage.tracer import CTracer, CFileDisposition # pylint: disable=no-name-in-module @@ -33,6 +37,13 @@ pass +def should_start_context(frame): + """Who-Tests-What hack: Determine whether this frame begins a new who-context.""" + fn_name = frame.f_code.co_name + if fn_name.startswith("test"): + return fn_name + + class Collector(object): """Collects trace data. @@ -112,6 +123,10 @@ "Couldn't trace with concurrency=%s, the module isn't installed." % concurrency ) + # Who-Tests-What is just a hack at the moment, so turn it on with an + # environment variable. + self.wtw = int(os.getenv('COVERAGE_WTW', 0)) + self.reset() if timid: @@ -143,6 +158,10 @@ # pairs as keys (if branch coverage). self.data = {} + # A dict mapping contexts to data dictionaries. + self.contexts = {} + self.contexts[None] = self.data + # A dictionary mapping file names to file tracer plugin names that will # handle them. self.file_tracers = {} @@ -202,6 +221,11 @@ tracer.threading = self.threading if hasattr(tracer, 'check_include'): tracer.check_include = self.check_include + if self.wtw: + if hasattr(tracer, 'should_start_context'): + tracer.should_start_context = should_start_context + if hasattr(tracer, 'switch_context'): + tracer.switch_context = self.switch_context fn = tracer.start() self.tracers.append(tracer) @@ -290,7 +314,7 @@ if stats: print("\nCoverage.py tracer stats:") for k in sorted(stats.keys()): - print("%16s: %s" % (k, stats[k])) + print("%20s: %s" % (k, stats[k])) if self.threading: self.threading.settrace(None) @@ -303,6 +327,14 @@ else: self._start_tracer() + def switch_context(self, new_context): + """Who-Tests-What hack: switch to a new who-context.""" + # Make a new data dict, or find the existing one, and switch all the + # tracers to use it. + data = self.contexts.setdefault(new_context, {}) + for tracer in self.tracers: + tracer.data = data + def save_data(self, covdata): """Save the collected data to a `CoverageData`. @@ -319,6 +351,13 @@ covdata.add_lines(abs_file_dict(self.data)) covdata.add_file_tracers(abs_file_dict(self.file_tracers)) + if self.wtw: + # Just a hack, so just hack it. + import pprint + out_file = "coverage_wtw_{:06}.py".format(os.getpid()) + with open(out_file, "w") as wtw_out: + pprint.pprint(self.contexts, wtw_out) + self.reset() #
--- a/DebugClients/Python/coverage/config.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/config.py Sun Jul 24 12:01:01 2016 +0200 @@ -9,7 +9,9 @@ import sys from coverage.backward import configparser, iitems, string_class -from coverage.misc import CoverageException +from coverage.misc import CoverageException, isolate_module + +os = isolate_module(os) class HandyConfigParser(configparser.RawConfigParser):
--- a/DebugClients/Python/coverage/control.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/control.py Sun Jul 24 12:01:01 2016 +0200 @@ -7,6 +7,7 @@ import inspect import os import platform +import re import sys import traceback @@ -22,7 +23,7 @@ from coverage.files import ModuleMatcher, abs_file from coverage.html import HtmlReporter from coverage.misc import CoverageException, bool_or_none, join_regex -from coverage.misc import file_be_gone +from coverage.misc import file_be_gone, isolate_module from coverage.monkey import patch_multiprocessing from coverage.plugin import FileReporter from coverage.plugin_support import Plugins @@ -31,6 +32,7 @@ from coverage.summary import SummaryReporter from coverage.xmlreport import XmlReporter +os = isolate_module(os) # Pypy has some unusual stuff in the "stdlib". Consider those locations # when deciding where the stdlib is. @@ -108,8 +110,8 @@ `concurrency` is a string indicating the concurrency library being used in the measured code. Without this, coverage.py will get incorrect - results. Valid strings are "greenlet", "eventlet", "gevent", or - "thread" (the default). + results. Valid strings are "greenlet", "eventlet", "gevent", + "multiprocessing", or "thread" (the default). .. versionadded:: 4.0 The `concurrency` parameter. @@ -279,7 +281,7 @@ # data file will be written into the directory where the process # started rather than wherever the process eventually chdir'd to. self.data = CoverageData(debug=self.debug) - self.data_files = CoverageDataFiles(basename=self.config.data_file) + self.data_files = CoverageDataFiles(basename=self.config.data_file, warn=self._warn) # The directories for files considered "installed with the interpreter". self.pylib_dirs = set() @@ -289,7 +291,7 @@ # environments (virtualenv, for example), these modules may be # spread across a few locations. Look at all the candidate modules # we've imported, and take all the different ones. - for m in (atexit, inspect, os, platform, _structseq, traceback): + for m in (atexit, inspect, os, platform, re, _structseq, traceback): if m is not None and hasattr(m, "__file__"): self.pylib_dirs.add(self._canonical_dir(m)) if _structseq and not hasattr(_structseq, '__file__'): @@ -475,6 +477,11 @@ # can't do anything with the data later anyway. return nope(disp, "not a real file name") + # pyexpat does a dumb thing, calling the trace function explicitly from + # C code with a C file name. + if re.search(r"[/\\]Modules[/\\]pyexpat.c", filename): + return nope(disp, "pyexpat lies about itself") + # Jython reports the .class file to the tracer, use the source file. if filename.endswith("$py.class"): filename = filename[:-9] + ".py" @@ -798,7 +805,7 @@ """ self._init() if not self._measured: - return + return self.data self.collector.save_data(self.data) @@ -832,15 +839,6 @@ self.data.touch_file(py_file) - # Add run information. - self.data.add_run_info( - brief_sys=" ".join([ - platform.python_implementation(), - platform.python_version(), - platform.system(), - ]) - ) - if self.config.note: self.data.add_run_info(note=self.config.note) @@ -943,9 +941,9 @@ return file_reporters def report( - self, morfs=None, show_missing=True, ignore_errors=None, + self, morfs=None, show_missing=None, ignore_errors=None, file=None, # pylint: disable=redefined-builtin - omit=None, include=None, skip_covered=False, + omit=None, include=None, skip_covered=None, ): """Write a summary report to `file`. @@ -1049,7 +1047,10 @@ output_dir = os.path.dirname(self.config.xml_output) if output_dir and not os.path.isdir(output_dir): os.makedirs(output_dir) - outfile = open(self.config.xml_output, "w") + open_kwargs = {} + if env.PY3: + open_kwargs['encoding'] = 'utf8' + outfile = open(self.config.xml_output, "w", **open_kwargs) file_to_close = outfile try: reporter = XmlReporter(self, self.config) @@ -1165,11 +1166,14 @@ import coverage; coverage.process_startup() + Returns the :class:`Coverage` instance that was started, or None if it was + not started by this call. + """ cps = os.environ.get("COVERAGE_PROCESS_START") if not cps: # No request for coverage, nothing to do. - return + return None # This function can be called more than once in a process. This happens # because some virtualenv configurations make the same directory visible @@ -1184,7 +1188,7 @@ if hasattr(process_startup, "done"): # We've annotated this function before, so we must have already # started coverage.py in this process. Nothing to do. - return + return None process_startup.done = True cov = Coverage(config_file=cps, auto_data=True) @@ -1192,5 +1196,7 @@ cov._warn_no_data = False cov._warn_unimported_source = False + return cov + # # eflag: FileType = Python2
--- a/DebugClients/Python/coverage/data.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/data.py Sun Jul 24 12:01:01 2016 +0200 @@ -4,6 +4,7 @@ """Coverage data for coverage.py.""" import glob +import itertools import json import optparse import os @@ -11,13 +12,14 @@ import random import re import socket -import sys from coverage import env from coverage.backward import iitems, string_class from coverage.debug import _TEST_NAME_FILE from coverage.files import PathAliases -from coverage.misc import CoverageException, file_be_gone +from coverage.misc import CoverageException, file_be_gone, isolate_module + +os = isolate_module(os) class CoverageData(object): @@ -177,11 +179,12 @@ """ if self._arcs is not None: - if filename in self._arcs: - return [s for s, __ in self._arcs[filename] if s > 0] + arcs = self._arcs.get(filename) + if arcs is not None: + all_lines = itertools.chain.from_iterable(arcs) + return list(set(l for l in all_lines if l > 0)) elif self._lines is not None: - if filename in self._lines: - return self._lines[filename] + return self._lines.get(filename) return None def arcs(self, filename): @@ -270,15 +273,10 @@ self._lines = self._arcs = None if 'lines' in data: - self._lines = dict( - (fname.encode(sys.getfilesystemencoding()), linenos) - for fname, linenos in iitems(data['lines']) - ) - + self._lines = data['lines'] if 'arcs' in data: self._arcs = dict( - (fname.encode(sys.getfilesystemencoding()), - [tuple(pair) for pair in arcs]) + (fname, [tuple(pair) for pair in arcs]) for fname, arcs in iitems(data['arcs']) ) self._file_tracers = data.get('file_tracers', {}) @@ -290,8 +288,15 @@ """Read the coverage data from `filename` into this object.""" if self._debug and self._debug.should('dataio'): self._debug.write("Reading data from %r" % (filename,)) - with self._open_for_reading(filename) as f: - self.read_fileobj(f) + try: + with self._open_for_reading(filename) as f: + self.read_fileobj(f) + except Exception as exc: + raise CoverageException( + "Couldn't read data from '%s': %s: %s" % ( + filename, exc.__class__.__name__, exc, + ) + ) _GO_AWAY = "!coverage.py: This is a private format, don't read it directly!" @@ -433,17 +438,10 @@ file_data = {} if self._has_arcs(): - file_data['arcs'] = dict( - (fname.decode(sys.getfilesystemencoding()), - [tuple(pair) for pair in self._arcs]) - for fname, arcs in iitems(data['arcs']) - ) + file_data['arcs'] = self._arcs if self._has_lines(): - file_data['lines'] = dict( - (fname.decode(sys.getfilesystemencoding()), linenos) - for fname, linenos in iitems(self._lines) - ) + file_data['lines'] = self._lines if self._file_tracers: file_data['file_tracers'] = self._file_tracers @@ -580,7 +578,7 @@ def add_to_hash(self, filename, hasher): """Contribute `filename`'s data to the `hasher`. - `hasher` is a :class:`coverage.misc.Hasher` instance to be updated with + `hasher` is a `coverage.misc.Hasher` instance to be updated with the file's data. It should only get the results data, not the run data. @@ -607,12 +605,15 @@ class CoverageDataFiles(object): """Manage the use of coverage data files.""" - def __init__(self, basename=None): + def __init__(self, basename=None, warn=None): """Create a CoverageDataFiles to manage data files. + `warn` is the warning function to use. + `basename` is the name of the file to use for storing data. """ + self.warn = warn # Construct the file name that will be used for data storage. self.filename = os.path.abspath(basename or ".coverage") @@ -681,7 +682,9 @@ If `data_paths` is not provided, then the directory portion of `self.filename` is used as the directory to search for data files. - Every data file found and combined is then deleted from disk. + Every data file found and combined is then deleted from disk. If a file + cannot be read, a warning will be issued, and the file will not be + deleted. """ # Because of the os.path.abspath in the constructor, data_dir will @@ -702,9 +705,16 @@ for f in files_to_combine: new_data = CoverageData() - new_data.read_file(f) - data.update(new_data, aliases=aliases) - file_be_gone(f) + try: + new_data.read_file(f) + except CoverageException as exc: + if self.warn: + # The CoverageException has the file name in it, so just + # use the message as the warning. + self.warn(str(exc)) + else: + data.update(new_data, aliases=aliases) + file_be_gone(f) def canonicalize_json_data(data): @@ -754,6 +764,7 @@ if __name__ == '__main__': + import sys debug_main(sys.argv[1:]) #
--- a/DebugClients/Python/coverage/debug.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/debug.py Sun Jul 24 12:01:01 2016 +0200 @@ -7,6 +7,10 @@ import os import sys +from coverage.misc import isolate_module + +os = isolate_module(os) + # When debugging, it can be helpful to force some options, especially when # debugging the configuration mechanisms you usually use to control debugging! @@ -38,7 +42,7 @@ msg = "pid %5d: %s" % (os.getpid(), msg) self.output.write(msg+"\n") if self.should('callers'): - dump_stack_frames(self.output) + dump_stack_frames(out=self.output) self.output.flush() def write_formatted_info(self, header, info): @@ -76,7 +80,7 @@ yield "%*s: %s" % (label_len, label, data) -def short_stack(): # pragma: debugging +def short_stack(limit=None): # pragma: debugging """Return a string summarizing the call stack. The string is multi-line, with one line per stack frame. Each line shows @@ -88,15 +92,17 @@ import_local_file : /Users/ned/coverage/trunk/coverage/backward.py @159 ... + `limit` is the number of frames to include, defaulting to all of them. + """ - stack = inspect.stack()[:0:-1] + stack = inspect.stack()[limit:0:-1] return "\n".join("%30s : %s @%d" % (t[3], t[1], t[2]) for t in stack) -def dump_stack_frames(out=None): # pragma: debugging +def dump_stack_frames(limit=None, out=None): # pragma: debugging """Print a summary of the stack to stdout, or some place else.""" out = out or sys.stdout - out.write(short_stack()) + out.write(short_stack(limit=limit)) out.write("\n") #
--- a/DebugClients/Python/coverage/doc/AUTHORS.txt Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/doc/AUTHORS.txt Sun Jul 24 12:01:01 2016 +0200 @@ -7,6 +7,7 @@ Alex Gaynor Alexander Todorov Anthony Sottile +Arcadiy Ivanov Ben Finney Bill Hart Brandon Rhodes @@ -26,6 +27,7 @@ David Stanek Detlev Offenbach Devin Jeanpierre +Dmitry Shishov Dmitry Trofimov Eduardo Schettino Edward Loper @@ -34,6 +36,7 @@ George Song Greg Rogers Guillaume Chazarain +Ilia Meerovich Imri Goldberg Ionel Cristian Mărieș JT Olds @@ -49,11 +52,13 @@ Mark van der Wal Martin Fuzzey Matthew Desmarais +Max Linke Mickie Betz Noel O'Boyle Pablo Carballo Patrick Mezard Peter Portante +Rodrigue Cloutier Roger Hu Ross Lawley Sandra Martocchia
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DebugClients/Python/coverage/doc/CHANGES.rst Sun Jul 24 12:01:01 2016 +0200 @@ -0,0 +1,1654 @@ +.. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +.. For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt + +============================== +Change history for Coverage.py +============================== + + +Version 4.1 --- 2016-05-21 +-------------------------- + +- The internal attribute `Reporter.file_reporters` was removed in 4.1b3. It + should have come has no surprise that there were third-party tools out there + using that attribute. It has been restored, but with a deprecation warning. + + +Version 4.1b3 --- 2016-05-10 +---------------------------- + +- When running your program, execution can jump from an ``except X:`` line to + some other line when an exception other than ``X`` happens. This jump is no + longer considered a branch when measuring branch coverage. + +- When measuring branch coverage, ``yield`` statements that were never resumed + were incorrectly marked as missing, as reported in `issue 440`_. This is now + fixed. + +- During branch coverage of single-line callables like lambdas and generator + expressions, coverage.py can now distinguish between them never being called, + or being called but not completed. Fixes `issue 90`_, `issue 460`_ and + `issue 475`_. + +- The HTML report now has a map of the file along the rightmost edge of the + page, giving an overview of where the missed lines are. Thanks, Dmitry + Shishov. + +- The HTML report now uses different monospaced fonts, favoring Consolas over + Courier. Along the way, `issue 472`_ about not properly handling one-space + indents was fixed. The index page also has slightly different styling, to + try to make the clickable detail pages more apparent. + +- Missing branches reported with ``coverage report -m`` will now say ``->exit`` + for missed branches to the exit of a function, rather than a negative number. + Fixes `issue 469`_. + +- ``coverage --help`` and ``coverage --version`` now mention which tracer is + installed, to help diagnose problems. The docs mention which features need + the C extension. (`issue 479`_) + +- Officially support PyPy 5.1, which required no changes, just updates to the + docs. + +- The `Coverage.report` function had two parameters with non-None defaults, + which have been changed. `show_missing` used to default to True, but now + defaults to None. If you had been calling `Coverage.report` without + specifying `show_missing`, you'll need to explicitly set it to True to keep + the same behavior. `skip_covered` used to default to False. It is now None, + which doesn't change the behavior. This fixes `issue 485`_. + +- It's never been possible to pass a namespace module to one of the analysis + functions, but now at least we raise a more specific error message, rather + than getting confused. (`issue 456`_) + +- The `coverage.process_startup` function now returns the `Coverage` instance + it creates, as suggested in `issue 481`_. + +- Make a small tweak to how we compare threads, to avoid buggy custom + comparison code in thread classes. (`issue 245`_) + +.. _issue 90: https://bitbucket.org/ned/coveragepy/issues/90/lambda-expression-confuses-branch +.. _issue 245: https://bitbucket.org/ned/coveragepy/issues/245/change-solution-for-issue-164 +.. _issue 440: https://bitbucket.org/ned/coveragepy/issues/440/yielded-twisted-failure-marked-as-missed +.. _issue 456: https://bitbucket.org/ned/coveragepy/issues/456/coverage-breaks-with-implicit-namespaces +.. _issue 460: https://bitbucket.org/ned/coveragepy/issues/460/confusing-html-report-for-certain-partial +.. _issue 469: https://bitbucket.org/ned/coveragepy/issues/469/strange-1-line-number-in-branch-coverage +.. _issue 472: https://bitbucket.org/ned/coveragepy/issues/472/html-report-indents-incorrectly-for-one +.. _issue 475: https://bitbucket.org/ned/coveragepy/issues/475/generator-expression-is-marked-as-not +.. _issue 479: https://bitbucket.org/ned/coveragepy/issues/479/clarify-the-need-for-the-c-extension +.. _issue 481: https://bitbucket.org/ned/coveragepy/issues/481/asyncioprocesspoolexecutor-tracing-not +.. _issue 485: https://bitbucket.org/ned/coveragepy/issues/485/coveragereport-ignores-show_missing-and + + +Version 4.1b2 --- 2016-01-23 +---------------------------- + +- Problems with the new branch measurement in 4.1 beta 1 were fixed: + + - Class docstrings were considered executable. Now they no longer are. + + - ``yield from`` and ``await`` were considered returns from functions, since + they could tranfer control to the caller. This produced unhelpful "missing + branch" reports in a number of circumstances. Now they no longer are + considered returns. + + - In unusual situations, a missing branch to a negative number was reported. + This has been fixed, closing `issue 466`_. + +- The XML report now produces correct package names for modules found in + directories specified with ``source=``. Fixes `issue 465`_. + +- ``coverage report`` won't produce trailing whitespace. + +.. _issue 465: https://bitbucket.org/ned/coveragepy/issues/465/coveragexml-produces-package-names-with-an +.. _issue 466: https://bitbucket.org/ned/coveragepy/issues/466/impossible-missed-branch-to-a-negative + + +Version 4.1b1 --- 2016-01-10 +---------------------------- + +- Branch analysis has been rewritten: it used to be based on bytecode, but now + uses AST analysis. This has changed a number of things: + + - More code paths are now considered runnable, especially in + ``try``/``except`` structures. This may mean that coverage.py will + identify more code paths as uncovered. This could either raise or lower + your overall coverage number. + + - Python 3.5's ``async`` and ``await`` keywords are properly supported, + fixing `issue 434`_. + + - Some long-standing branch coverage bugs were fixed: + + - `issue 129`_: functions with only a docstring for a body would + incorrectly report a missing branch on the ``def`` line. + + - `issue 212`_: code in an ``except`` block could be incorrectly marked as + a missing branch. + + - `issue 146`_: context managers (``with`` statements) in a loop or ``try`` + block could confuse the branch measurement, reporting incorrect partial + branches. + + - `issue 422`_: in Python 3.5, an actual partial branch could be marked as + complete. + +- Pragmas to disable coverage measurement can now be used on decorator lines, + and they will apply to the entire function or class being decorated. This + implements the feature requested in `issue 131`_. + +- Multiprocessing support is now available on Windows. Thanks, Rodrigue + Cloutier. + +- Files with two encoding declarations are properly supported, fixing + `issue 453`_. Thanks, Max Linke. + +- Non-ascii characters in regexes in the configuration file worked in 3.7, but + stopped working in 4.0. Now they work again, closing `issue 455`_. + +- Form-feed characters would prevent accurate determination of the beginning of + statements in the rest of the file. This is now fixed, closing `issue 461`_. + +.. _issue 129: https://bitbucket.org/ned/coveragepy/issues/129/misleading-branch-coverage-of-empty +.. _issue 131: https://bitbucket.org/ned/coveragepy/issues/131/pragma-on-a-decorator-line-should-affect +.. _issue 146: https://bitbucket.org/ned/coveragepy/issues/146/context-managers-confuse-branch-coverage +.. _issue 212: https://bitbucket.org/ned/coveragepy/issues/212/coverage-erroneously-reports-partial +.. _issue 422: https://bitbucket.org/ned/coveragepy/issues/422/python35-partial-branch-marked-as-fully +.. _issue 434: https://bitbucket.org/ned/coveragepy/issues/434/indexerror-in-python-35 +.. _issue 453: https://bitbucket.org/ned/coveragepy/issues/453/source-code-encoding-can-only-be-specified +.. _issue 455: https://bitbucket.org/ned/coveragepy/issues/455/unusual-exclusions-stopped-working-in +.. _issue 461: https://bitbucket.org/ned/coveragepy/issues/461/multiline-asserts-need-too-many-pragma + + +Version 4.0.3 --- 2015-11-24 +---------------------------- + +- Fixed a mysterious problem that manifested in different ways: sometimes + hanging the process (`issue 420`_), sometimes making database connections + fail (`issue 445`_). + +- The XML report now has correct ``<source>`` elements when using a + ``--source=`` option somewhere besides the current directory. This fixes + `issue 439`_. Thanks, Arcady Ivanov. + +- Fixed an unusual edge case of detecting source encodings, described in + `issue 443`_. + +- Help messages that mention the command to use now properly use the actual + command name, which might be different than "coverage". Thanks to Ben + Finney, this closes `issue 438`_. + +.. _issue 420: https://bitbucket.org/ned/coveragepy/issues/420/coverage-40-hangs-indefinitely-on-python27 +.. _issue 438: https://bitbucket.org/ned/coveragepy/issues/438/parameterise-coverage-command-name +.. _issue 439: https://bitbucket.org/ned/coveragepy/issues/439/incorrect-cobertura-file-sources-generated +.. _issue 443: https://bitbucket.org/ned/coveragepy/issues/443/coverage-gets-confused-when-encoding +.. _issue 445: https://bitbucket.org/ned/coveragepy/issues/445/django-app-cannot-connect-to-cassandra + + +Version 4.0.2 --- 2015-11-04 +---------------------------- + +- More work on supporting unusually encoded source. Fixed `issue 431`_. + +- Files or directories with non-ASCII characters are now handled properly, + fixing `issue 432`_. + +- Setting a trace function with sys.settrace was broken by a change in 4.0.1, + as reported in `issue 436`_. This is now fixed. + +- Officially support PyPy 4.0, which required no changes, just updates to the + docs. + +.. _issue 431: https://bitbucket.org/ned/coveragepy/issues/431/couldnt-parse-python-file-with-cp1252 +.. _issue 432: https://bitbucket.org/ned/coveragepy/issues/432/path-with-unicode-characters-various +.. _issue 436: https://bitbucket.org/ned/coveragepy/issues/436/disabled-coverage-ctracer-may-rise-from + + +Version 4.0.1 --- 2015-10-13 +---------------------------- + +- When combining data files, unreadable files will now generate a warning + instead of failing the command. This is more in line with the older + coverage.py v3.7.1 behavior, which silently ignored unreadable files. + Prompted by `issue 418`_. + +- The --skip-covered option would skip reporting on 100% covered files, but + also skipped them when calculating total coverage. This was wrong, it should + only remove lines from the report, not change the final answer. This is now + fixed, closing `issue 423`_. + +- In 4.0, the data file recorded a summary of the system on which it was run. + Combined data files would keep all of those summaries. This could lead to + enormous data files consisting of mostly repetitive useless information. That + summary is now gone, fixing `issue 415`_. If you want summary information, + get in touch, and we'll figure out a better way to do it. + +- Test suites that mocked os.path.exists would experience strange failures, due + to coverage.py using their mock inadvertently. This is now fixed, closing + `issue 416`_. + +- Importing a ``__init__`` module explicitly would lead to an error: + ``AttributeError: 'module' object has no attribute '__path__'``, as reported + in `issue 410`_. This is now fixed. + +- Code that uses ``sys.settrace(sys.gettrace())`` used to incur a more than 2x + speed penalty. Now there's no penalty at all. Fixes `issue 397`_. + +- Pyexpat C code will no longer be recorded as a source file, fixing + `issue 419`_. + +- The source kit now contains all of the files needed to have a complete source + tree, re-fixing `issue 137`_ and closing `issue 281`_. + +.. _issue 281: https://bitbucket.org/ned/coveragepy/issues/281/supply-scripts-for-testing-in-the +.. _issue 397: https://bitbucket.org/ned/coveragepy/issues/397/stopping-and-resuming-coverage-with +.. _issue 410: https://bitbucket.org/ned/coveragepy/issues/410/attributeerror-module-object-has-no +.. _issue 415: https://bitbucket.org/ned/coveragepy/issues/415/repeated-coveragedataupdates-cause +.. _issue 416: https://bitbucket.org/ned/coveragepy/issues/416/mocking-ospathexists-causes-failures +.. _issue 418: https://bitbucket.org/ned/coveragepy/issues/418/json-parse-error +.. _issue 419: https://bitbucket.org/ned/coveragepy/issues/419/nosource-no-source-for-code-path-to-c +.. _issue 423: https://bitbucket.org/ned/coveragepy/issues/423/skip_covered-changes-reported-total + + +Version 4.0 --- 2015-09-20 +-------------------------- + +No changes from 4.0b3 + + +Version 4.0b3 --- 2015-09-07 +---------------------------- + +- Reporting on an unmeasured file would fail with a traceback. This is now + fixed, closing `issue 403`_. + +- The Jenkins ShiningPanda plugin looks for an obsolete file name to find the + HTML reports to publish, so it was failing under coverage.py 4.0. Now we + create that file if we are running under Jenkins, to keep things working + smoothly. `issue 404`_. + +- Kits used to include tests and docs, but didn't install them anywhere, or + provide all of the supporting tools to make them useful. Kits no longer + include tests and docs. If you were using them from the older packages, get + in touch and help me understand how. + +.. _issue 403: https://bitbucket.org/ned/coveragepy/issues/403/hasherupdate-fails-with-typeerror-nonetype +.. _issue 404: https://bitbucket.org/ned/coveragepy/issues/404/shiningpanda-jenkins-plugin-cant-find-html + + + +Version 4.0b2 --- 2015-08-22 +---------------------------- + +- 4.0b1 broke ``--append`` creating new data files. This is now fixed, closing + `issue 392`_. + +- ``py.test --cov`` can write empty data, then touch files due to ``--source``, + which made coverage.py mistakenly force the data file to record lines instead + of arcs. This would lead to a "Can't combine line data with arc data" error + message. This is now fixed, and changed some method names in the + CoverageData interface. Fixes `issue 399`_. + +- `CoverageData.read_fileobj` and `CoverageData.write_fileobj` replace the + `.read` and `.write` methods, and are now properly inverses of each other. + +- When using ``report --skip-covered``, a message will now be included in the + report output indicating how many files were skipped, and if all files are + skipped, coverage.py won't accidentally scold you for having no data to + report. Thanks, Krystian Kichewko. + +- A new conversion utility has been added: ``python -m coverage.pickle2json`` + will convert v3.x pickle data files to v4.x JSON data files. Thanks, + Alexander Todorov. Closes `issue 395`_. + +- A new version identifier is available, `coverage.version_info`, a plain tuple + of values similar to `sys.version_info`_. + +.. _issue 392: https://bitbucket.org/ned/coveragepy/issues/392/run-append-doesnt-create-coverage-file +.. _issue 395: https://bitbucket.org/ned/coveragepy/issues/395/rfe-read-pickled-files-as-well-for +.. _issue 399: https://bitbucket.org/ned/coveragepy/issues/399/coverageexception-cant-combine-line-data +.. _sys.version_info: https://docs.python.org/3/library/sys.html#sys.version_info + + +Version 4.0b1 --- 2015-08-02 +---------------------------- + +- Coverage.py is now licensed under the Apache 2.0 license. See NOTICE.txt for + details. Closes `issue 313`_. + +- The data storage has been completely revamped. The data file is now + JSON-based instead of a pickle, closing `issue 236`_. The `CoverageData` + class is now a public supported documented API to the data file. + +- A new configuration option, ``[run] note``, lets you set a note that will be + stored in the `runs` section of the data file. You can use this to annotate + the data file with any information you like. + +- Unrecognized configuration options will now print an error message and stop + coverage.py. This should help prevent configuration mistakes from passing + silently. Finishes `issue 386`_. + +- In parallel mode, ``coverage erase`` will now delete all of the data files, + fixing `issue 262`_. + +- Coverage.py now accepts a directory name for ``coverage run`` and will run a + ``__main__.py`` found there, just like Python will. Fixes `issue 252`_. + Thanks, Dmitry Trofimov. + +- The XML report now includes a ``missing-branches`` attribute. Thanks, Steve + Peak. This is not a part of the Cobertura DTD, so the XML report no longer + references the DTD. + +- Missing branches in the HTML report now have a bit more information in the + right-hand annotations. Hopefully this will make their meaning clearer. + +- All the reporting functions now behave the same if no data had been + collected, exiting with a status code of 1. Fixed ``fail_under`` to be + applied even when the report is empty. Thanks, Ionel Cristian Mărieș. + +- Plugins are now initialized differently. Instead of looking for a class + called ``Plugin``, coverage.py looks for a function called ``coverage_init``. + +- A file-tracing plugin can now ask to have built-in Python reporting by + returning `"python"` from its `file_reporter()` method. + +- Code that was executed with `exec` would be mis-attributed to the file that + called it. This is now fixed, closing `issue 380`_. + +- The ability to use item access on `Coverage.config` (introduced in 4.0a2) has + been changed to a more explicit `Coverage.get_option` and + `Coverage.set_option` API. + +- The ``Coverage.use_cache`` method is no longer supported. + +- The private method ``Coverage._harvest_data`` is now called + ``Coverage.get_data``, and returns the ``CoverageData`` containing the + collected data. + +- The project is consistently referred to as "coverage.py" throughout the code + and the documentation, closing `issue 275`_. + +- Combining data files with an explicit configuration file was broken in 4.0a6, + but now works again, closing `issue 385`_. + +- ``coverage combine`` now accepts files as well as directories. + +- The speed is back to 3.7.1 levels, after having slowed down due to plugin + support, finishing up `issue 387`_. + +.. _issue 236: https://bitbucket.org/ned/coveragepy/issues/236/pickles-are-bad-and-you-should-feel-bad +.. _issue 252: https://bitbucket.org/ned/coveragepy/issues/252/coverage-wont-run-a-program-with +.. _issue 262: https://bitbucket.org/ned/coveragepy/issues/262/when-parallel-true-erase-should-erase-all +.. _issue 275: https://bitbucket.org/ned/coveragepy/issues/275/refer-consistently-to-project-as-coverage +.. _issue 313: https://bitbucket.org/ned/coveragepy/issues/313/add-license-file-containing-2-3-or-4 +.. _issue 380: https://bitbucket.org/ned/coveragepy/issues/380/code-executed-by-exec-excluded-from +.. _issue 385: https://bitbucket.org/ned/coveragepy/issues/385/coverage-combine-doesnt-work-with-rcfile +.. _issue 386: https://bitbucket.org/ned/coveragepy/issues/386/error-on-unrecognised-configuration +.. _issue 387: https://bitbucket.org/ned/coveragepy/issues/387/performance-degradation-from-371-to-40 + +.. 40 issues closed in 4.0 below here + + +Version 4.0a6 --- 2015-06-21 +---------------------------- + +- Python 3.5b2 and PyPy 2.6.0 are supported. + +- The original module-level function interface to coverage.py is no longer + supported. You must now create a ``coverage.Coverage`` object, and use + methods on it. + +- The ``coverage combine`` command now accepts any number of directories as + arguments, and will combine all the data files from those directories. This + means you don't have to copy the files to one directory before combining. + Thanks, Christine Lytwynec. Finishes `issue 354`_. + +- Branch coverage couldn't properly handle certain extremely long files. This + is now fixed (`issue 359`_). + +- Branch coverage didn't understand yield statements properly. Mickie Betz + persisted in pursuing this despite Ned's pessimism. Fixes `issue 308`_ and + `issue 324`_. + +- The COVERAGE_DEBUG environment variable can be used to set the ``[run] debug`` + configuration option to control what internal operations are logged. + +- HTML reports were truncated at formfeed characters. This is now fixed + (`issue 360`_). It's always fun when the problem is due to a `bug in the + Python standard library <http://bugs.python.org/issue19035>`_. + +- Files with incorrect encoding declaration comments are no longer ignored by + the reporting commands, fixing `issue 351`_. + +- HTML reports now include a timestamp in the footer, closing `issue 299`_. + Thanks, Conrad Ho. + +- HTML reports now begrudgingly use double-quotes rather than single quotes, + because there are "software engineers" out there writing tools that read HTML + and somehow have no idea that single quotes exist. Capitulates to the absurd + `issue 361`_. Thanks, Jon Chappell. + +- The ``coverage annotate`` command now handles non-ASCII characters properly, + closing `issue 363`_. Thanks, Leonardo Pistone. + +- Drive letters on Windows were not normalized correctly, now they are. Thanks, + Ionel Cristian Mărieș. + +- Plugin support had some bugs fixed, closing `issue 374`_ and `issue 375`_. + Thanks, Stefan Behnel. + +.. _issue 299: https://bitbucket.org/ned/coveragepy/issue/299/inserted-created-on-yyyy-mm-dd-hh-mm-in +.. _issue 308: https://bitbucket.org/ned/coveragepy/issue/308/yield-lambda-branch-coverage +.. _issue 324: https://bitbucket.org/ned/coveragepy/issue/324/yield-in-loop-confuses-branch-coverage +.. _issue 351: https://bitbucket.org/ned/coveragepy/issue/351/files-with-incorrect-encoding-are-ignored +.. _issue 354: https://bitbucket.org/ned/coveragepy/issue/354/coverage-combine-should-take-a-list-of +.. _issue 359: https://bitbucket.org/ned/coveragepy/issue/359/xml-report-chunk-error +.. _issue 360: https://bitbucket.org/ned/coveragepy/issue/360/html-reports-get-confused-by-l-in-the-code +.. _issue 361: https://bitbucket.org/ned/coveragepy/issue/361/use-double-quotes-in-html-output-to +.. _issue 363: https://bitbucket.org/ned/coveragepy/issue/363/annotate-command-hits-unicode-happy-fun +.. _issue 374: https://bitbucket.org/ned/coveragepy/issue/374/c-tracer-lookups-fail-in +.. _issue 375: https://bitbucket.org/ned/coveragepy/issue/375/ctracer_handle_return-reads-byte-code + + +Version 4.0a5 --- 2015-02-16 +---------------------------- + +- Plugin support is now implemented in the C tracer instead of the Python + tracer. This greatly improves the speed of tracing projects using plugins. + +- Coverage.py now always adds the current directory to sys.path, so that + plugins can import files in the current directory (`issue 358`_). + +- If the `config_file` argument to the Coverage constructor is specified as + ".coveragerc", it is treated as if it were True. This means setup.cfg is + also examined, and a missing file is not considered an error (`issue 357`_). + +- Wildly experimental: support for measuring processes started by the + multiprocessing module. To use, set ``--concurrency=multiprocessing``, + either on the command line or in the .coveragerc file (`issue 117`_). Thanks, + Eduardo Schettino. Currently, this does not work on Windows. + +- A new warning is possible, if a desired file isn't measured because it was + imported before coverage.py was started (`issue 353`_). + +- The `coverage.process_startup` function now will start coverage measurement + only once, no matter how many times it is called. This fixes problems due + to unusual virtualenv configurations (`issue 340`_). + +- Added 3.5.0a1 to the list of supported CPython versions. + +.. _issue 117: https://bitbucket.org/ned/coveragepy/issue/117/enable-coverage-measurement-of-code-run-by +.. _issue 340: https://bitbucket.org/ned/coveragepy/issue/340/keyerror-subpy +.. _issue 353: https://bitbucket.org/ned/coveragepy/issue/353/40a3-introduces-an-unexpected-third-case +.. _issue 357: https://bitbucket.org/ned/coveragepy/issue/357/behavior-changed-when-coveragerc-is +.. _issue 358: https://bitbucket.org/ned/coveragepy/issue/358/all-coverage-commands-should-adjust + + +Version 4.0a4 --- 2015-01-25 +---------------------------- + +- Plugins can now provide sys_info for debugging output. + +- Started plugins documentation. + +- Prepared to move the docs to readthedocs.org. + + +Version 4.0a3 --- 2015-01-20 +---------------------------- + +- Reports now use file names with extensions. Previously, a report would + describe a/b/c.py as "a/b/c". Now it is shown as "a/b/c.py". This allows + for better support of non-Python files, and also fixed `issue 69`_. + +- The XML report now reports each directory as a package again. This was a bad + regression, I apologize. This was reported in `issue 235`_, which is now + fixed. + +- A new configuration option for the XML report: ``[xml] package_depth`` + controls which directories are identified as packages in the report. + Directories deeper than this depth are not reported as packages. + The default is that all directories are reported as packages. + Thanks, Lex Berezhny. + +- When looking for the source for a frame, check if the file exists. On + Windows, .pyw files are no longer recorded as .py files. Along the way, this + fixed `issue 290`_. + +- Empty files are now reported as 100% covered in the XML report, not 0% + covered (`issue 345`_). + +- Regexes in the configuration file are now compiled as soon as they are read, + to provide error messages earlier (`issue 349`_). + +.. _issue 69: https://bitbucket.org/ned/coveragepy/issue/69/coverage-html-overwrite-files-that-doesnt +.. _issue 235: https://bitbucket.org/ned/coveragepy/issue/235/package-name-is-missing-in-xml-report +.. _issue 290: https://bitbucket.org/ned/coveragepy/issue/290/running-programmatically-with-pyw-files +.. _issue 345: https://bitbucket.org/ned/coveragepy/issue/345/xml-reports-line-rate-0-for-empty-files +.. _issue 349: https://bitbucket.org/ned/coveragepy/issue/349/bad-regex-in-config-should-get-an-earlier + + +Version 4.0a2 --- 2015-01-14 +---------------------------- + +- Officially support PyPy 2.4, and PyPy3 2.4. Drop support for + CPython 3.2 and older versions of PyPy. The code won't work on CPython 3.2. + It will probably still work on older versions of PyPy, but I'm not testing + against them. + +- Plugins! + +- The original command line switches (`-x` to run a program, etc) are no + longer supported. + +- A new option: `coverage report --skip-covered` will reduce the number of + files reported by skipping files with 100% coverage. Thanks, Krystian + Kichewko. This means that empty `__init__.py` files will be skipped, since + they are 100% covered, closing `issue 315`_. + +- You can now specify the ``--fail-under`` option in the ``.coveragerc`` file + as the ``[report] fail_under`` option. This closes `issue 314`_. + +- The ``COVERAGE_OPTIONS`` environment variable is no longer supported. It was + a hack for ``--timid`` before configuration files were available. + +- The HTML report now has filtering. Type text into the Filter box on the + index page, and only modules with that text in the name will be shown. + Thanks, Danny Allen. + +- The textual report and the HTML report used to report partial branches + differently for no good reason. Now the text report's "missing branches" + column is a "partial branches" column so that both reports show the same + numbers. This closes `issue 342`_. + +- If you specify a ``--rcfile`` that cannot be read, you will get an error + message. Fixes `issue 343`_. + +- The ``--debug`` switch can now be used on any command. + +- You can now programmatically adjust the configuration of coverage.py by + setting items on `Coverage.config` after construction. + +- A module run with ``-m`` can be used as the argument to ``--source``, fixing + `issue 328`_. Thanks, Buck Evan. + +- The regex for matching exclusion pragmas has been fixed to allow more kinds + of whitespace, fixing `issue 334`_. + +- Made some PyPy-specific tweaks to improve speed under PyPy. Thanks, Alex + Gaynor. + +- In some cases, with a source file missing a final newline, coverage.py would + count statements incorrectly. This is now fixed, closing `issue 293`_. + +- The status.dat file that HTML reports use to avoid re-creating files that + haven't changed is now a JSON file instead of a pickle file. This obviates + `issue 287`_ and `issue 237`_. + +.. _issue 237: https://bitbucket.org/ned/coveragepy/issue/237/htmlcov-with-corrupt-statusdat +.. _issue 287: https://bitbucket.org/ned/coveragepy/issue/287/htmlpy-doesnt-specify-pickle-protocol +.. _issue 293: https://bitbucket.org/ned/coveragepy/issue/293/number-of-statement-detection-wrong-if-no +.. _issue 314: https://bitbucket.org/ned/coveragepy/issue/314/fail_under-param-not-working-in-coveragerc +.. _issue 315: https://bitbucket.org/ned/coveragepy/issue/315/option-to-omit-empty-files-eg-__init__py +.. _issue 328: https://bitbucket.org/ned/coveragepy/issue/328/misbehavior-in-run-source +.. _issue 334: https://bitbucket.org/ned/coveragepy/issue/334/pragma-not-recognized-if-tab-character +.. _issue 342: https://bitbucket.org/ned/coveragepy/issue/342/console-and-html-coverage-reports-differ +.. _issue 343: https://bitbucket.org/ned/coveragepy/issue/343/an-explicitly-named-non-existent-config + + +Version 4.0a1 --- 2014-09-27 +---------------------------- + +- Python versions supported are now CPython 2.6, 2.7, 3.2, 3.3, and 3.4, and + PyPy 2.2. + +- Gevent, eventlet, and greenlet are now supported, closing `issue 149`_. + The ``concurrency`` setting specifies the concurrency library in use. Huge + thanks to Peter Portante for initial implementation, and to Joe Jevnik for + the final insight that completed the work. + +- Options are now also read from a setup.cfg file, if any. Sections are + prefixed with "coverage:", so the ``[run]`` options will be read from the + ``[coverage:run]`` section of setup.cfg. Finishes `issue 304`_. + +- The ``report -m`` command can now show missing branches when reporting on + branch coverage. Thanks, Steve Leonard. Closes `issue 230`_. + +- The XML report now contains a <source> element, fixing `issue 94`_. Thanks + Stan Hu. + +- The class defined in the coverage module is now called ``Coverage`` instead + of ``coverage``, though the old name still works, for backward compatibility. + +- The ``fail-under`` value is now rounded the same as reported results, + preventing paradoxical results, fixing `issue 284`_. + +- The XML report will now create the output directory if need be, fixing + `issue 285`_. Thanks, Chris Rose. + +- HTML reports no longer raise UnicodeDecodeError if a Python file has + undecodable characters, fixing `issue 303`_ and `issue 331`_. + +- The annotate command will now annotate all files, not just ones relative to + the current directory, fixing `issue 57`_. + +- The coverage module no longer causes deprecation warnings on Python 3.4 by + importing the imp module, fixing `issue 305`_. + +- Encoding declarations in source files are only considered if they are truly + comments. Thanks, Anthony Sottile. + +.. _issue 57: https://bitbucket.org/ned/coveragepy/issue/57/annotate-command-fails-to-annotate-many +.. _issue 94: https://bitbucket.org/ned/coveragepy/issue/94/coverage-xml-doesnt-produce-sources +.. _issue 149: https://bitbucket.org/ned/coveragepy/issue/149/coverage-gevent-looks-broken +.. _issue 230: https://bitbucket.org/ned/coveragepy/issue/230/show-line-no-for-missing-branches-in +.. _issue 284: https://bitbucket.org/ned/coveragepy/issue/284/fail-under-should-show-more-precision +.. _issue 285: https://bitbucket.org/ned/coveragepy/issue/285/xml-report-fails-if-output-file-directory +.. _issue 303: https://bitbucket.org/ned/coveragepy/issue/303/unicodedecodeerror +.. _issue 304: https://bitbucket.org/ned/coveragepy/issue/304/attempt-to-get-configuration-from-setupcfg +.. _issue 305: https://bitbucket.org/ned/coveragepy/issue/305/pendingdeprecationwarning-the-imp-module +.. _issue 331: https://bitbucket.org/ned/coveragepy/issue/331/failure-of-encoding-detection-on-python2 + + +Version 3.7.1 --- 2013-12-13 +---------------------------- + +- Improved the speed of HTML report generation by about 20%. + +- Fixed the mechanism for finding OS-installed static files for the HTML report + so that it will actually find OS-installed static files. + + +Version 3.7 --- 2013-10-06 +-------------------------- + +- Added the ``--debug`` switch to ``coverage run``. It accepts a list of + options indicating the type of internal activity to log to stderr. + +- Improved the branch coverage facility, fixing `issue 92`_ and `issue 175`_. + +- Running code with ``coverage run -m`` now behaves more like Python does, + setting sys.path properly, which fixes `issue 207`_ and `issue 242`_. + +- Coverage.py can now run .pyc files directly, closing `issue 264`_. + +- Coverage.py properly supports .pyw files, fixing `issue 261`_. + +- Omitting files within a tree specified with the ``source`` option would + cause them to be incorrectly marked as unexecuted, as described in + `issue 218`_. This is now fixed. + +- When specifying paths to alias together during data combining, you can now + specify relative paths, fixing `issue 267`_. + +- Most file paths can now be specified with username expansion (``~/src``, or + ``~build/src``, for example), and with environment variable expansion + (``build/$BUILDNUM/src``). + +- Trying to create an XML report with no files to report on, would cause a + ZeroDivideError, but no longer does, fixing `issue 250`_. + +- When running a threaded program under the Python tracer, coverage.py no + longer issues a spurious warning about the trace function changing: "Trace + function changed, measurement is likely wrong: None." This fixes `issue + 164`_. + +- Static files necessary for HTML reports are found in system-installed places, + to ease OS-level packaging of coverage.py. Closes `issue 259`_. + +- Source files with encoding declarations, but a blank first line, were not + decoded properly. Now they are. Thanks, Roger Hu. + +- The source kit now includes the ``__main__.py`` file in the root coverage + directory, fixing `issue 255`_. + +.. _issue 92: https://bitbucket.org/ned/coveragepy/issue/92/finally-clauses-arent-treated-properly-in +.. _issue 164: https://bitbucket.org/ned/coveragepy/issue/164/trace-function-changed-warning-when-using +.. _issue 175: https://bitbucket.org/ned/coveragepy/issue/175/branch-coverage-gets-confused-in-certain +.. _issue 207: https://bitbucket.org/ned/coveragepy/issue/207/run-m-cannot-find-module-or-package-in +.. _issue 242: https://bitbucket.org/ned/coveragepy/issue/242/running-a-two-level-package-doesnt-work +.. _issue 218: https://bitbucket.org/ned/coveragepy/issue/218/run-command-does-not-respect-the-omit-flag +.. _issue 250: https://bitbucket.org/ned/coveragepy/issue/250/uncaught-zerodivisionerror-when-generating +.. _issue 255: https://bitbucket.org/ned/coveragepy/issue/255/directory-level-__main__py-not-included-in +.. _issue 259: https://bitbucket.org/ned/coveragepy/issue/259/allow-use-of-system-installed-third-party +.. _issue 261: https://bitbucket.org/ned/coveragepy/issue/261/pyw-files-arent-reported-properly +.. _issue 264: https://bitbucket.org/ned/coveragepy/issue/264/coverage-wont-run-pyc-files +.. _issue 267: https://bitbucket.org/ned/coveragepy/issue/267/relative-path-aliases-dont-work + + +Version 3.6 --- 2013-01-05 +-------------------------- + +- Added a page to the docs about troublesome situations, closing `issue 226`_, + and added some info to the TODO file, closing `issue 227`_. + +.. _issue 226: https://bitbucket.org/ned/coveragepy/issue/226/make-readme-section-to-describe-when +.. _issue 227: https://bitbucket.org/ned/coveragepy/issue/227/update-todo + + +Version 3.6b3 --- 2012-12-29 +---------------------------- + +- Beta 2 broke the nose plugin. It's fixed again, closing `issue 224`_. + +.. _issue 224: https://bitbucket.org/ned/coveragepy/issue/224/36b2-breaks-nosexcover + + +Version 3.6b2 --- 2012-12-23 +---------------------------- + +- Coverage.py runs on Python 2.3 and 2.4 again. It was broken in 3.6b1. + +- The C extension is optionally compiled using a different more widely-used + technique, taking another stab at fixing `issue 80`_ once and for all. + +- Combining data files would create entries for phantom files if used with + ``source`` and path aliases. It no longer does. + +- ``debug sys`` now shows the configuration file path that was read. + +- If an oddly-behaved package claims that code came from an empty-string + file name, coverage.py no longer associates it with the directory name, + fixing `issue 221`_. + +.. _issue 221: https://bitbucket.org/ned/coveragepy/issue/221/coveragepy-incompatible-with-pyratemp + + +Version 3.6b1 --- 2012-11-28 +---------------------------- + +- Wildcards in ``include=`` and ``omit=`` arguments were not handled properly + in reporting functions, though they were when running. Now they are handled + uniformly, closing `issue 143`_ and `issue 163`_. **NOTE**: it is possible + that your configurations may now be incorrect. If you use ``include`` or + ``omit`` during reporting, whether on the command line, through the API, or + in a configuration file, please check carefully that you were not relying on + the old broken behavior. + +- The **report**, **html**, and **xml** commands now accept a ``--fail-under`` + switch that indicates in the exit status whether the coverage percentage was + less than a particular value. Closes `issue 139`_. + +- The reporting functions coverage.report(), coverage.html_report(), and + coverage.xml_report() now all return a float, the total percentage covered + measurement. + +- The HTML report's title can now be set in the configuration file, with the + ``--title`` switch on the command line, or via the API. + +- Configuration files now support substitution of environment variables, using + syntax like ``${WORD}``. Closes `issue 97`_. + +- Embarrassingly, the ``[xml] output=`` setting in the .coveragerc file simply + didn't work. Now it does. + +- The XML report now consistently uses file names for the file name attribute, + rather than sometimes using module names. Fixes `issue 67`_. + Thanks, Marcus Cobden. + +- Coverage percentage metrics are now computed slightly differently under + branch coverage. This means that completely unexecuted files will now + correctly have 0% coverage, fixing `issue 156`_. This also means that your + total coverage numbers will generally now be lower if you are measuring + branch coverage. + +- When installing, now in addition to creating a "coverage" command, two new + aliases are also installed. A "coverage2" or "coverage3" command will be + created, depending on whether you are installing in Python 2.x or 3.x. + A "coverage-X.Y" command will also be created corresponding to your specific + version of Python. Closes `issue 111`_. + +- The coverage.py installer no longer tries to bootstrap setuptools or + Distribute. You must have one of them installed first, as `issue 202`_ + recommended. + +- The coverage.py kit now includes docs (closing `issue 137`_) and tests. + +- On Windows, files are now reported in their correct case, fixing `issue 89`_ + and `issue 203`_. + +- If a file is missing during reporting, the path shown in the error message + is now correct, rather than an incorrect path in the current directory. + Fixes `issue 60`_. + +- Running an HTML report in Python 3 in the same directory as an old Python 2 + HTML report would fail with a UnicodeDecodeError. This issue (`issue 193`_) + is now fixed. + +- Fixed yet another error trying to parse non-Python files as Python, this + time an IndentationError, closing `issue 82`_ for the fourth time... + +- If `coverage xml` fails because there is no data to report, it used to + create a zero-length XML file. Now it doesn't, fixing `issue 210`_. + +- Jython files now work with the ``--source`` option, fixing `issue 100`_. + +- Running coverage.py under a debugger is unlikely to work, but it shouldn't + fail with "TypeError: 'NoneType' object is not iterable". Fixes `issue + 201`_. + +- On some Linux distributions, when installed with the OS package manager, + coverage.py would report its own code as part of the results. Now it won't, + fixing `issue 214`_, though this will take some time to be repackaged by the + operating systems. + +- Docstrings for the legacy singleton methods are more helpful. Thanks Marius + Gedminas. Closes `issue 205`_. + +- The pydoc tool can now show documentation for the class `coverage.coverage`. + Closes `issue 206`_. + +- Added a page to the docs about contributing to coverage.py, closing + `issue 171`_. + +- When coverage.py ended unsuccessfully, it may have reported odd errors like + ``'NoneType' object has no attribute 'isabs'``. It no longer does, + so kiss `issue 153`_ goodbye. + +.. _issue 60: https://bitbucket.org/ned/coveragepy/issue/60/incorrect-path-to-orphaned-pyc-files +.. _issue 67: https://bitbucket.org/ned/coveragepy/issue/67/xml-report-filenames-may-be-generated +.. _issue 89: https://bitbucket.org/ned/coveragepy/issue/89/on-windows-all-packages-are-reported-in +.. _issue 97: https://bitbucket.org/ned/coveragepy/issue/97/allow-environment-variables-to-be +.. _issue 100: https://bitbucket.org/ned/coveragepy/issue/100/source-directive-doesnt-work-for-packages +.. _issue 111: https://bitbucket.org/ned/coveragepy/issue/111/when-installing-coverage-with-pip-not +.. _issue 137: https://bitbucket.org/ned/coveragepy/issue/137/provide-docs-with-source-distribution +.. _issue 139: https://bitbucket.org/ned/coveragepy/issue/139/easy-check-for-a-certain-coverage-in-tests +.. _issue 143: https://bitbucket.org/ned/coveragepy/issue/143/omit-doesnt-seem-to-work-in-coverage +.. _issue 153: https://bitbucket.org/ned/coveragepy/issue/153/non-existent-filename-triggers +.. _issue 156: https://bitbucket.org/ned/coveragepy/issue/156/a-completely-unexecuted-file-shows-14 +.. _issue 163: https://bitbucket.org/ned/coveragepy/issue/163/problem-with-include-and-omit-filename +.. _issue 171: https://bitbucket.org/ned/coveragepy/issue/171/how-to-contribute-and-run-tests +.. _issue 193: https://bitbucket.org/ned/coveragepy/issue/193/unicodedecodeerror-on-htmlpy +.. _issue 201: https://bitbucket.org/ned/coveragepy/issue/201/coverage-using-django-14-with-pydb-on +.. _issue 202: https://bitbucket.org/ned/coveragepy/issue/202/get-rid-of-ez_setuppy-and +.. _issue 203: https://bitbucket.org/ned/coveragepy/issue/203/duplicate-filenames-reported-when-filename +.. _issue 205: https://bitbucket.org/ned/coveragepy/issue/205/make-pydoc-coverage-more-friendly +.. _issue 206: https://bitbucket.org/ned/coveragepy/issue/206/pydoc-coveragecoverage-fails-with-an-error +.. _issue 210: https://bitbucket.org/ned/coveragepy/issue/210/if-theres-no-coverage-data-coverage-xml +.. _issue 214: https://bitbucket.org/ned/coveragepy/issue/214/coveragepy-measures-itself-on-precise + + +Version 3.5.3 --- 2012-09-29 +---------------------------- + +- Line numbers in the HTML report line up better with the source lines, fixing + `issue 197`_, thanks Marius Gedminas. + +- When specifying a directory as the source= option, the directory itself no + longer needs to have a ``__init__.py`` file, though its sub-directories do, + to be considered as source files. + +- Files encoded as UTF-8 with a BOM are now properly handled, fixing + `issue 179`_. Thanks, Pablo Carballo. + +- Fixed more cases of non-Python files being reported as Python source, and + then not being able to parse them as Python. Closes `issue 82`_ (again). + Thanks, Julian Berman. + +- Fixed memory leaks under Python 3, thanks, Brett Cannon. Closes `issue 147`_. + +- Optimized .pyo files may not have been handled correctly, `issue 195`_. + Thanks, Marius Gedminas. + +- Certain unusually named file paths could have been mangled during reporting, + `issue 194`_. Thanks, Marius Gedminas. + +- Try to do a better job of the impossible task of detecting when we can't + build the C extension, fixing `issue 183`_. + +- Testing is now done with `tox`_, thanks, Marc Abramowitz. + +.. _issue 147: https://bitbucket.org/ned/coveragepy/issue/147/massive-memory-usage-by-ctracer +.. _issue 179: https://bitbucket.org/ned/coveragepy/issue/179/htmlreporter-fails-when-source-file-is +.. _issue 183: https://bitbucket.org/ned/coveragepy/issue/183/install-fails-for-python-23 +.. _issue 194: https://bitbucket.org/ned/coveragepy/issue/194/filelocatorrelative_filename-could-mangle +.. _issue 195: https://bitbucket.org/ned/coveragepy/issue/195/pyo-file-handling-in-codeunit +.. _issue 197: https://bitbucket.org/ned/coveragepy/issue/197/line-numbers-in-html-report-do-not-align +.. _tox: http://tox.readthedocs.org/ + + +Version 3.5.2 --- 2012-05-04 +---------------------------- + +No changes since 3.5.2.b1 + + +Version 3.5.2b1 --- 2012-04-29 +------------------------------ + +- The HTML report has slightly tweaked controls: the buttons at the top of + the page are color-coded to the source lines they affect. + +- Custom CSS can be applied to the HTML report by specifying a CSS file as + the ``extra_css`` configuration value in the ``[html]`` section. + +- Source files with custom encodings declared in a comment at the top are now + properly handled during reporting on Python 2. Python 3 always handled them + properly. This fixes `issue 157`_. + +- Backup files left behind by editors are no longer collected by the source= + option, fixing `issue 168`_. + +- If a file doesn't parse properly as Python, we don't report it as an error + if the file name seems like maybe it wasn't meant to be Python. This is a + pragmatic fix for `issue 82`_. + +- The ``-m`` switch on ``coverage report``, which includes missing line numbers + in the summary report, can now be specified as ``show_missing`` in the + config file. Closes `issue 173`_. + +- When running a module with ``coverage run -m <modulename>``, certain details + of the execution environment weren't the same as for + ``python -m <modulename>``. This had the unfortunate side-effect of making + ``coverage run -m unittest discover`` not work if you had tests in a + directory named "test". This fixes `issue 155`_ and `issue 142`_. + +- Now the exit status of your product code is properly used as the process + status when running ``python -m coverage run ...``. Thanks, JT Olds. + +- When installing into pypy, we no longer attempt (and fail) to compile + the C tracer function, closing `issue 166`_. + +.. _issue 142: https://bitbucket.org/ned/coveragepy/issue/142/executing-python-file-syspath-is-replaced +.. _issue 155: https://bitbucket.org/ned/coveragepy/issue/155/cant-use-coverage-run-m-unittest-discover +.. _issue 157: https://bitbucket.org/ned/coveragepy/issue/157/chokes-on-source-files-with-non-utf-8 +.. _issue 166: https://bitbucket.org/ned/coveragepy/issue/166/dont-try-to-compile-c-extension-on-pypy +.. _issue 168: https://bitbucket.org/ned/coveragepy/issue/168/dont-be-alarmed-by-emacs-droppings +.. _issue 173: https://bitbucket.org/ned/coveragepy/issue/173/theres-no-way-to-specify-show-missing-in + + +Version 3.5.1 --- 2011-09-23 +---------------------------- + +- The ``[paths]`` feature unfortunately didn't work in real world situations + where you wanted to, you know, report on the combined data. Now all paths + stored in the combined file are canonicalized properly. + + +Version 3.5.1b1 --- 2011-08-28 +------------------------------ + +- When combining data files from parallel runs, you can now instruct + coverage.py about which directories are equivalent on different machines. A + ``[paths]`` section in the configuration file lists paths that are to be + considered equivalent. Finishes `issue 17`_. + +- for-else constructs are understood better, and don't cause erroneous partial + branch warnings. Fixes `issue 122`_. + +- Branch coverage for ``with`` statements is improved, fixing `issue 128`_. + +- The number of partial branches reported on the HTML summary page was + different than the number reported on the individual file pages. This is + now fixed. + +- An explicit include directive to measure files in the Python installation + wouldn't work because of the standard library exclusion. Now the include + directive takes precedence, and the files will be measured. Fixes + `issue 138`_. + +- The HTML report now handles Unicode characters in Python source files + properly. This fixes `issue 124`_ and `issue 144`_. Thanks, Devin + Jeanpierre. + +- In order to help the core developers measure the test coverage of the + standard library, Brandon Rhodes devised an aggressive hack to trick Python + into running some coverage.py code before anything else in the process. + See the coverage/fullcoverage directory if you are interested. + +.. _issue 17: http://bitbucket.org/ned/coveragepy/issue/17/support-combining-coverage-data-from +.. _issue 122: http://bitbucket.org/ned/coveragepy/issue/122/for-else-always-reports-missing-branch +.. _issue 124: http://bitbucket.org/ned/coveragepy/issue/124/no-arbitrary-unicode-in-html-reports-in +.. _issue 128: http://bitbucket.org/ned/coveragepy/issue/128/branch-coverage-of-with-statement-in-27 +.. _issue 138: http://bitbucket.org/ned/coveragepy/issue/138/include-should-take-precedence-over-is +.. _issue 144: http://bitbucket.org/ned/coveragepy/issue/144/failure-generating-html-output-for + + +Version 3.5 --- 2011-06-29 +-------------------------- + +- The HTML report hotkeys now behave slightly differently when the current + chunk isn't visible at all: a chunk on the screen will be selected, + instead of the old behavior of jumping to the literal next chunk. + The hotkeys now work in Google Chrome. Thanks, Guido van Rossum. + + +Version 3.5b1 --- 2011-06-05 +---------------------------- + +- The HTML report now has hotkeys. Try ``n``, ``s``, ``m``, ``x``, ``b``, + ``p``, and ``c`` on the overview page to change the column sorting. + On a file page, ``r``, ``m``, ``x``, and ``p`` toggle the run, missing, + excluded, and partial line markings. You can navigate the highlighted + sections of code by using the ``j`` and ``k`` keys for next and previous. + The ``1`` (one) key jumps to the first highlighted section in the file, + and ``0`` (zero) scrolls to the top of the file. + +- The ``--omit`` and ``--include`` switches now interpret their values more + usefully. If the value starts with a wildcard character, it is used as-is. + If it does not, it is interpreted relative to the current directory. + Closes `issue 121`_. + +- Partial branch warnings can now be pragma'd away. The configuration option + ``partial_branches`` is a list of regular expressions. Lines matching any of + those expressions will never be marked as a partial branch. In addition, + there's a built-in list of regular expressions marking statements which should + never be marked as partial. This list includes ``while True:``, ``while 1:``, + ``if 1:``, and ``if 0:``. + +- The ``coverage()`` constructor accepts single strings for the ``omit=`` and + ``include=`` arguments, adapting to a common error in programmatic use. + +- Modules can now be run directly using ``coverage run -m modulename``, to + mirror Python's ``-m`` flag. Closes `issue 95`_, thanks, Brandon Rhodes. + +- ``coverage run`` didn't emulate Python accurately in one small detail: the + current directory inserted into ``sys.path`` was relative rather than + absolute. This is now fixed. + +- HTML reporting is now incremental: a record is kept of the data that + produced the HTML reports, and only files whose data has changed will + be generated. This should make most HTML reporting faster. + +- Pathological code execution could disable the trace function behind our + backs, leading to incorrect code measurement. Now if this happens, + coverage.py will issue a warning, at least alerting you to the problem. + Closes `issue 93`_. Thanks to Marius Gedminas for the idea. + +- The C-based trace function now behaves properly when saved and restored + with ``sys.gettrace()`` and ``sys.settrace()``. This fixes `issue 125`_ + and `issue 123`_. Thanks, Devin Jeanpierre. + +- Source files are now opened with Python 3.2's ``tokenize.open()`` where + possible, to get the best handling of Python source files with encodings. + Closes `issue 107`_, thanks, Brett Cannon. + +- Syntax errors in supposed Python files can now be ignored during reporting + with the ``-i`` switch just like other source errors. Closes `issue 115`_. + +- Installation from source now succeeds on machines without a C compiler, + closing `issue 80`_. + +- Coverage.py can now be run directly from a working tree by specifying + the directory name to python: ``python coverage_py_working_dir run ...``. + Thanks, Brett Cannon. + +- A little bit of Jython support: `coverage run` can now measure Jython + execution by adapting when $py.class files are traced. Thanks, Adi Roiban. + Jython still doesn't provide the Python libraries needed to make + coverage reporting work, unfortunately. + +- Internally, files are now closed explicitly, fixing `issue 104`_. Thanks, + Brett Cannon. + +.. _issue 80: https://bitbucket.org/ned/coveragepy/issue/80/is-there-a-duck-typing-way-to-know-we-cant +.. _issue 93: http://bitbucket.org/ned/coveragepy/issue/93/copying-a-mock-object-breaks-coverage +.. _issue 95: https://bitbucket.org/ned/coveragepy/issue/95/run-subcommand-should-take-a-module-name +.. _issue 104: https://bitbucket.org/ned/coveragepy/issue/104/explicitly-close-files +.. _issue 107: https://bitbucket.org/ned/coveragepy/issue/107/codeparser-not-opening-source-files-with +.. _issue 115: https://bitbucket.org/ned/coveragepy/issue/115/fail-gracefully-when-reporting-on-file +.. _issue 121: https://bitbucket.org/ned/coveragepy/issue/121/filename-patterns-are-applied-stupidly +.. _issue 123: https://bitbucket.org/ned/coveragepy/issue/123/pyeval_settrace-used-in-way-that-breaks +.. _issue 125: https://bitbucket.org/ned/coveragepy/issue/125/coverage-removes-decoratortoolss-tracing + + +Version 3.4 --- 2010-09-19 +-------------------------- + +- The XML report is now sorted by package name, fixing `issue 88`_. + +- Programs that exited with ``sys.exit()`` with no argument weren't handled + properly, producing a coverage.py stack trace. That is now fixed. + +.. _issue 88: http://bitbucket.org/ned/coveragepy/issue/88/xml-report-lists-packages-in-random-order + + +Version 3.4b2 --- 2010-09-06 +---------------------------- + +- Completely unexecuted files can now be included in coverage results, reported + as 0% covered. This only happens if the --source option is specified, since + coverage.py needs guidance about where to look for source files. + +- The XML report output now properly includes a percentage for branch coverage, + fixing `issue 65`_ and `issue 81`_. + +- Coverage percentages are now displayed uniformly across reporting methods. + Previously, different reports could round percentages differently. Also, + percentages are only reported as 0% or 100% if they are truly 0 or 100, and + are rounded otherwise. Fixes `issue 41`_ and `issue 70`_. + +- The precision of reported coverage percentages can be set with the + ``[report] precision`` config file setting. Completes `issue 16`_. + +- Threads derived from ``threading.Thread`` with an overridden `run` method + would report no coverage for the `run` method. This is now fixed, closing + `issue 85`_. + +.. _issue 16: http://bitbucket.org/ned/coveragepy/issue/16/allow-configuration-of-accuracy-of-percentage-totals +.. _issue 41: http://bitbucket.org/ned/coveragepy/issue/41/report-says-100-when-it-isnt-quite-there +.. _issue 65: http://bitbucket.org/ned/coveragepy/issue/65/branch-option-not-reported-in-cobertura +.. _issue 70: http://bitbucket.org/ned/coveragepy/issue/70/text-report-and-html-report-disagree-on-coverage +.. _issue 81: http://bitbucket.org/ned/coveragepy/issue/81/xml-report-does-not-have-condition-coverage-attribute-for-lines-with-a +.. _issue 85: http://bitbucket.org/ned/coveragepy/issue/85/threadrun-isnt-measured + + +Version 3.4b1 --- 2010-08-21 +---------------------------- + +- BACKWARD INCOMPATIBILITY: the ``--omit`` and ``--include`` switches now take + file patterns rather than file prefixes, closing `issue 34`_ and `issue 36`_. + +- BACKWARD INCOMPATIBILITY: the `omit_prefixes` argument is gone throughout + coverage.py, replaced with `omit`, a list of file name patterns suitable for + `fnmatch`. A parallel argument `include` controls what files are included. + +- The run command now has a ``--source`` switch, a list of directories or + module names. If provided, coverage.py will only measure execution in those + source files. + +- Various warnings are printed to stderr for problems encountered during data + measurement: if a ``--source`` module has no Python source to measure, or is + never encountered at all, or if no data is collected. + +- The reporting commands (report, annotate, html, and xml) now have an + ``--include`` switch to restrict reporting to modules matching those file + patterns, similar to the existing ``--omit`` switch. Thanks, Zooko. + +- The run command now supports ``--include`` and ``--omit`` to control what + modules it measures. This can speed execution and reduce the amount of data + during reporting. Thanks Zooko. + +- Since coverage.py 3.1, using the Python trace function has been slower than + it needs to be. A cache of tracing decisions was broken, but has now been + fixed. + +- Python 2.7 and 3.2 have introduced new opcodes that are now supported. + +- Python files with no statements, for example, empty ``__init__.py`` files, + are now reported as having zero statements instead of one. Fixes `issue 1`_. + +- Reports now have a column of missed line counts rather than executed line + counts, since developers should focus on reducing the missed lines to zero, + rather than increasing the executed lines to varying targets. Once + suggested, this seemed blindingly obvious. + +- Line numbers in HTML source pages are clickable, linking directly to that + line, which is highlighted on arrival. Added a link back to the index page + at the bottom of each HTML page. + +- Programs that call ``os.fork`` will properly collect data from both the child + and parent processes. Use ``coverage run -p`` to get two data files that can + be combined with ``coverage combine``. Fixes `issue 56`_. + +- Coverage.py is now runnable as a module: ``python -m coverage``. Thanks, + Brett Cannon. + +- When measuring code running in a virtualenv, most of the system library was + being measured when it shouldn't have been. This is now fixed. + +- Doctest text files are no longer recorded in the coverage data, since they + can't be reported anyway. Fixes `issue 52`_ and `issue 61`_. + +- Jinja HTML templates compile into Python code using the HTML file name, + which confused coverage.py. Now these files are no longer traced, fixing + `issue 82`_. + +- Source files can have more than one dot in them (foo.test.py), and will be + treated properly while reporting. Fixes `issue 46`_. + +- Source files with DOS line endings are now properly tokenized for syntax + coloring on non-DOS machines. Fixes `issue 53`_. + +- Unusual code structure that confused exits from methods with exits from + classes is now properly analyzed. See `issue 62`_. + +- Asking for an HTML report with no files now shows a nice error message rather + than a cryptic failure ('int' object is unsubscriptable). Fixes `issue 59`_. + +.. _issue 1: http://bitbucket.org/ned/coveragepy/issue/1/empty-__init__py-files-are-reported-as-1-executable +.. _issue 34: http://bitbucket.org/ned/coveragepy/issue/34/enhanced-omit-globbing-handling +.. _issue 36: http://bitbucket.org/ned/coveragepy/issue/36/provide-regex-style-omit +.. _issue 46: http://bitbucket.org/ned/coveragepy/issue/46 +.. _issue 53: http://bitbucket.org/ned/coveragepy/issue/53 +.. _issue 52: http://bitbucket.org/ned/coveragepy/issue/52/doctesttestfile-confuses-source-detection +.. _issue 56: http://bitbucket.org/ned/coveragepy/issue/56 +.. _issue 61: http://bitbucket.org/ned/coveragepy/issue/61/annotate-i-doesnt-work +.. _issue 62: http://bitbucket.org/ned/coveragepy/issue/62 +.. _issue 59: http://bitbucket.org/ned/coveragepy/issue/59/html-report-fails-with-int-object-is +.. _issue 82: http://bitbucket.org/ned/coveragepy/issue/82/tokenerror-when-generating-html-report + + +Version 3.3.1 --- 2010-03-06 +---------------------------- + +- Using `parallel=True` in .coveragerc file prevented reporting, but now does + not, fixing `issue 49`_. + +- When running your code with "coverage run", if you call `sys.exit()`, + coverage.py will exit with that status code, fixing `issue 50`_. + +.. _issue 49: http://bitbucket.org/ned/coveragepy/issue/49 +.. _issue 50: http://bitbucket.org/ned/coveragepy/issue/50 + + +Version 3.3 --- 2010-02-24 +-------------------------- + +- Settings are now read from a .coveragerc file. A specific file can be + specified on the command line with --rcfile=FILE. The name of the file can + be programmatically set with the `config_file` argument to the coverage() + constructor, or reading a config file can be disabled with + `config_file=False`. + +- Fixed a problem with nested loops having their branch possibilities + mischaracterized: `issue 39`_. + +- Added coverage.process_start to enable coverage measurement when Python + starts. + +- Parallel data file names now have a random number appended to them in + addition to the machine name and process id. + +- Parallel data files combined with "coverage combine" are deleted after + they're combined, to clean up unneeded files. Fixes `issue 40`_. + +- Exceptions thrown from product code run with "coverage run" are now displayed + without internal coverage.py frames, so the output is the same as when the + code is run without coverage.py. + +- The `data_suffix` argument to the coverage constructor is now appended with + an added dot rather than simply appended, so that .coveragerc files will not + be confused for data files. + +- Python source files that don't end with a newline can now be executed, fixing + `issue 47`_. + +- Added an AUTHORS.txt file. + +.. _issue 39: http://bitbucket.org/ned/coveragepy/issue/39 +.. _issue 40: http://bitbucket.org/ned/coveragepy/issue/40 +.. _issue 47: http://bitbucket.org/ned/coveragepy/issue/47 + + +Version 3.2 --- 2009-12-05 +-------------------------- + +- Added a ``--version`` option on the command line. + + +Version 3.2b4 --- 2009-12-01 +---------------------------- + +- Branch coverage improvements: + + - The XML report now includes branch information. + +- Click-to-sort HTML report columns are now persisted in a cookie. Viewing + a report will sort it first the way you last had a coverage report sorted. + Thanks, `Chris Adams`_. + +- On Python 3.x, setuptools has been replaced by `Distribute`_. + +.. _Distribute: http://packages.python.org/distribute/ + + +Version 3.2b3 --- 2009-11-23 +---------------------------- + +- Fixed a memory leak in the C tracer that was introduced in 3.2b1. + +- Branch coverage improvements: + + - Branches to excluded code are ignored. + +- The table of contents in the HTML report is now sortable: click the headers + on any column. Thanks, `Chris Adams`_. + +.. _Chris Adams: http://improbable.org/chris/ + + +Version 3.2b2 --- 2009-11-19 +---------------------------- + +- Branch coverage improvements: + + - Classes are no longer incorrectly marked as branches: `issue 32`_. + + - "except" clauses with types are no longer incorrectly marked as branches: + `issue 35`_. + +- Fixed some problems syntax coloring sources with line continuations and + source with tabs: `issue 30`_ and `issue 31`_. + +- The --omit option now works much better than before, fixing `issue 14`_ and + `issue 33`_. Thanks, Danek Duvall. + +.. _issue 14: http://bitbucket.org/ned/coveragepy/issue/14 +.. _issue 30: http://bitbucket.org/ned/coveragepy/issue/30 +.. _issue 31: http://bitbucket.org/ned/coveragepy/issue/31 +.. _issue 32: http://bitbucket.org/ned/coveragepy/issue/32 +.. _issue 33: http://bitbucket.org/ned/coveragepy/issue/33 +.. _issue 35: http://bitbucket.org/ned/coveragepy/issue/35 + + +Version 3.2b1 --- 2009-11-10 +---------------------------- + +- Branch coverage! + +- XML reporting has file paths that let Cobertura find the source code. + +- The tracer code has changed, it's a few percent faster. + +- Some exceptions reported by the command line interface have been cleaned up + so that tracebacks inside coverage.py aren't shown. Fixes `issue 23`_. + +.. _issue 23: http://bitbucket.org/ned/coveragepy/issue/23 + + +Version 3.1 --- 2009-10-04 +-------------------------- + +- Source code can now be read from eggs. Thanks, Ross Lawley. Fixes + `issue 25`_. + +.. _issue 25: http://bitbucket.org/ned/coveragepy/issue/25 + + +Version 3.1b1 --- 2009-09-27 +---------------------------- + +- Python 3.1 is now supported. + +- Coverage.py has a new command line syntax with sub-commands. This expands + the possibilities for adding features and options in the future. The old + syntax is still supported. Try "coverage help" to see the new commands. + Thanks to Ben Finney for early help. + +- Added an experimental "coverage xml" command for producing coverage reports + in a Cobertura-compatible XML format. Thanks, Bill Hart. + +- Added the --timid option to enable a simpler slower trace function that works + for DecoratorTools projects, including TurboGears. Fixed `issue 12`_ and + `issue 13`_. + +- HTML reports show modules from other directories. Fixed `issue 11`_. + +- HTML reports now display syntax-colored Python source. + +- Programs that change directory will still write .coverage files in the + directory where execution started. Fixed `issue 24`_. + +- Added a "coverage debug" command for getting diagnostic information about the + coverage.py installation. + +.. _issue 11: http://bitbucket.org/ned/coveragepy/issue/11 +.. _issue 12: http://bitbucket.org/ned/coveragepy/issue/12 +.. _issue 13: http://bitbucket.org/ned/coveragepy/issue/13 +.. _issue 24: http://bitbucket.org/ned/coveragepy/issue/24 + + +Version 3.0.1 --- 2009-07-07 +---------------------------- + +- Removed the recursion limit in the tracer function. Previously, code that + ran more than 500 frames deep would crash. Fixed `issue 9`_. + +- Fixed a bizarre problem involving pyexpat, whereby lines following XML parser + invocations could be overlooked. Fixed `issue 10`_. + +- On Python 2.3, coverage.py could mis-measure code with exceptions being + raised. This is now fixed. + +- The coverage.py code itself will now not be measured by coverage.py, and no + coverage.py modules will be mentioned in the nose --with-cover plug-in. + Fixed `issue 8`_. + +- When running source files, coverage.py now opens them in universal newline + mode just like Python does. This lets it run Windows files on Mac, for + example. + +.. _issue 9: http://bitbucket.org/ned/coveragepy/issue/9 +.. _issue 10: http://bitbucket.org/ned/coveragepy/issue/10 +.. _issue 8: http://bitbucket.org/ned/coveragepy/issue/8 + + +Version 3.0 --- 2009-06-13 +-------------------------- + +- Fixed the way the Python library was ignored. Too much code was being + excluded the old way. + +- Tabs are now properly converted in HTML reports. Previously indentation was + lost. Fixed `issue 6`_. + +- Nested modules now get a proper flat_rootname. Thanks, Christian Heimes. + +.. _issue 6: http://bitbucket.org/ned/coveragepy/issue/6 + + +Version 3.0b3 --- 2009-05-16 +---------------------------- + +- Added parameters to coverage.__init__ for options that had been set on the + coverage object itself. + +- Added clear_exclude() and get_exclude_list() methods for programmatic + manipulation of the exclude regexes. + +- Added coverage.load() to read previously-saved data from the data file. + +- Improved the finding of code files. For example, .pyc files that have been + installed after compiling are now located correctly. Thanks, Detlev + Offenbach. + +- When using the object API (that is, constructing a coverage() object), data + is no longer saved automatically on process exit. You can re-enable it with + the auto_data=True parameter on the coverage() constructor. The module-level + interface still uses automatic saving. + + +Version 3.0b --- 2009-04-30 +--------------------------- + +HTML reporting, and continued refactoring. + +- HTML reports and annotation of source files: use the new -b (browser) switch. + Thanks to George Song for code, inspiration and guidance. + +- Code in the Python standard library is not measured by default. If you need + to measure standard library code, use the -L command-line switch during + execution, or the cover_pylib=True argument to the coverage() constructor. + +- Source annotation into a directory (-a -d) behaves differently. The + annotated files are named with their hierarchy flattened so that same-named + files from different directories no longer collide. Also, only files in the + current tree are included. + +- coverage.annotate_file is no longer available. + +- Programs executed with -x now behave more as they should, for example, + __file__ has the correct value. + +- .coverage data files have a new pickle-based format designed for better + extensibility. + +- Removed the undocumented cache_file argument to coverage.usecache(). + + +Version 3.0b1 --- 2009-03-07 +---------------------------- + +Major overhaul. + +- Coverage.py is now a package rather than a module. Functionality has been + split into classes. + +- The trace function is implemented in C for speed. Coverage.py runs are now + much faster. Thanks to David Christian for productive micro-sprints and + other encouragement. + +- Executable lines are identified by reading the line number tables in the + compiled code, removing a great deal of complicated analysis code. + +- Precisely which lines are considered executable has changed in some cases. + Therefore, your coverage stats may also change slightly. + +- The singleton coverage object is only created if the module-level functions + are used. This maintains the old interface while allowing better + programmatic use of Coverage.py. + +- The minimum supported Python version is 2.3. + + +Version 2.85 --- 2008-09-14 +--------------------------- + +- Add support for finding source files in eggs. Don't check for + morf's being instances of ModuleType, instead use duck typing so that + pseudo-modules can participate. Thanks, Imri Goldberg. + +- Use os.realpath as part of the fixing of file names so that symlinks won't + confuse things. Thanks, Patrick Mezard. + + +Version 2.80 --- 2008-05-25 +--------------------------- + +- Open files in rU mode to avoid line ending craziness. Thanks, Edward Loper. + + +Version 2.78 --- 2007-09-30 +--------------------------- + +- Don't try to predict whether a file is Python source based on the extension. + Extension-less files are often Pythons scripts. Instead, simply parse the file + and catch the syntax errors. Hat tip to Ben Finney. + + +Version 2.77 --- 2007-07-29 +--------------------------- + +- Better packaging. + + +Version 2.76 --- 2007-07-23 +--------------------------- + +- Now Python 2.5 is *really* fully supported: the body of the new with + statement is counted as executable. + + +Version 2.75 --- 2007-07-22 +--------------------------- + +- Python 2.5 now fully supported. The method of dealing with multi-line + statements is now less sensitive to the exact line that Python reports during + execution. Pass statements are handled specially so that their disappearance + during execution won't throw off the measurement. + + +Version 2.7 --- 2007-07-21 +-------------------------- + +- "#pragma: nocover" is excluded by default. + +- Properly ignore docstrings and other constant expressions that appear in the + middle of a function, a problem reported by Tim Leslie. + +- coverage.erase() shouldn't clobber the exclude regex. Change how parallel + mode is invoked, and fix erase() so that it erases the cache when called + programmatically. + +- In reports, ignore code executed from strings, since we can't do anything + useful with it anyway. + +- Better file handling on Linux, thanks Guillaume Chazarain. + +- Better shell support on Windows, thanks Noel O'Boyle. + +- Python 2.2 support maintained, thanks Catherine Proulx. + +- Minor changes to avoid lint warnings. + + +Version 2.6 --- 2006-08-23 +-------------------------- + +- Applied Joseph Tate's patch for function decorators. + +- Applied Sigve Tjora and Mark van der Wal's fixes for argument handling. + +- Applied Geoff Bache's parallel mode patch. + +- Refactorings to improve testability. Fixes to command-line logic for parallel + mode and collect. + + +Version 2.5 --- 2005-12-04 +-------------------------- + +- Call threading.settrace so that all threads are measured. Thanks Martin + Fuzzey. + +- Add a file argument to report so that reports can be captured to a different + destination. + +- Coverage.py can now measure itself. + +- Adapted Greg Rogers' patch for using relative file names, and sorting and + omitting files to report on. + + +Version 2.2 --- 2004-12-31 +-------------------------- + +- Allow for keyword arguments in the module global functions. Thanks, Allen. + + +Version 2.1 --- 2004-12-14 +-------------------------- + +- Return 'analysis' to its original behavior and add 'analysis2'. Add a global + for 'annotate', and factor it, adding 'annotate_file'. + + +Version 2.0 --- 2004-12-12 +-------------------------- + +Significant code changes. + +- Finding executable statements has been rewritten so that docstrings and + other quirks of Python execution aren't mistakenly identified as missing + lines. + +- Lines can be excluded from consideration, even entire suites of lines. + +- The file system cache of covered lines can be disabled programmatically. + +- Modernized the code. + + +Earlier History +--------------- + +2001-12-04 GDR Created. + +2001-12-06 GDR Added command-line interface and source code annotation. + +2001-12-09 GDR Moved design and interface to separate documents. + +2001-12-10 GDR Open cache file as binary on Windows. Allow simultaneous -e and +-x, or -a and -r. + +2001-12-12 GDR Added command-line help. Cache analysis so that it only needs to +be done once when you specify -a and -r. + +2001-12-13 GDR Improved speed while recording. Portable between Python 1.5.2 +and 2.1.1. + +2002-01-03 GDR Module-level functions work correctly. + +2002-01-07 GDR Update sys.path when running a file with the -x option, so that +it matches the value the program would get if it were run on its own.
--- a/DebugClients/Python/coverage/doc/CHANGES.txt Sat Jul 23 13:33:54 2016 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1411 +0,0 @@ -.. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -.. For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt - -============================== -Change history for Coverage.py -============================== - - -Version 4.0 --- 20 September 2015 ---------------------------------- - -No changes from 4.0b3 - - -Version 4.0b3 --- 7 September 2015 ----------------------------------- - -- Reporting on an unmeasured file would fail with a traceback. This is now - fixed, closing `issue 403`_. - -- The Jenkins ShiningPanda plugin looks for an obsolete file name to find the - HTML reports to publish, so it was failing under coverage.py 4.0. Now we - create that file if we are running under Jenkins, to keep things working - smoothly. `issue 404`_. - -- Kits used to include tests and docs, but didn't install them anywhere, or - provide all of the supporting tools to make them useful. Kits no longer - include tests and docs. If you were using them from the older packages, get - in touch and help me understand how. - -.. _issue 403: https://bitbucket.org/ned/coveragepy/issues/403/hasherupdate-fails-with-typeerror-nonetype -.. _issue 404: https://bitbucket.org/ned/coveragepy/issues/404/shiningpanda-jenkins-plugin-cant-find-html - - - -Version 4.0b2 --- 22 August 2015 --------------------------------- - -- 4.0b1 broke --append creating new data files. This is now fixed, closing - `issue 392`_. - -- ``py.test --cov`` can write empty data, then touch files due to ``--source``, - which made coverage.py mistakenly force the data file to record lines instead - of arcs. This would lead to a "Can't combine line data with arc data" error - message. This is now fixed, and changed some method names in the - CoverageData interface. Fixes `issue 399`_. - -- `CoverageData.read_fileobj` and `CoverageData.write_fileobj` replace the - `.read` and `.write` methods, and are now properly inverses of each other. - -- When using ``report --skip-covered``, a message will now be included in the - report output indicating how many files were skipped, and if all files are - skipped, coverage.py won't accidentally scold you for having no data to - report. Thanks, Krystian Kichewko. - -- A new conversion utility has been added: ``python -m coverage.pickle2json`` - will convert v3.x pickle data files to v4.x JSON data files. Thanks, - Alexander Todorov. Closes `issue 395`_. - -- A new version identifier is available, `coverage.version_info`, a plain tuple - of values similar to `sys.version_info`_. - -.. _issue 392: https://bitbucket.org/ned/coveragepy/issues/392/run-append-doesnt-create-coverage-file -.. _issue 395: https://bitbucket.org/ned/coveragepy/issues/395/rfe-read-pickled-files-as-well-for -.. _issue 399: https://bitbucket.org/ned/coveragepy/issues/399/coverageexception-cant-combine-line-data -.. _sys.version_info: https://docs.python.org/3/library/sys.html#sys.version_info - - -Version 4.0b1 --- 2 August 2015 -------------------------------- - -- Coverage.py is now licensed under the Apache 2.0 license. See NOTICE.txt for - details. Closes `issue 313`_. - -- The data storage has been completely revamped. The data file is now - JSON-based instead of a pickle, closing `issue 236`_. The `CoverageData` - class is now a public supported documented API to the data file. - -- A new configuration option, ``[run] note``, lets you set a note that will be - stored in the `runs` section of the data file. You can use this to annotate - the data file with any information you like. - -- Unrecognized configuration options will now print an error message and stop - coverage.py. This should help prevent configuration mistakes from passing - silently. Finishes `issue 386`_. - -- In parallel mode, ``coverage erase`` will now delete all of the data files, - fixing `issue 262`_. - -- Coverage.py now accepts a directory name for ``coverage run`` and will run a - ``__main__.py`` found there, just like Python will. Fixes `issue 252`_. - Thanks, Dmitry Trofimov. - -- The XML report now includes a ``missing-branches`` attribute. Thanks, Steve - Peak. This is not a part of the Cobertura DTD, so the XML report no longer - references the DTD. - -- Missing branches in the HTML report now have a bit more information in the - right-hand annotations. Hopefully this will make their meaning clearer. - -- All the reporting functions now behave the same if no data had been - collected, exiting with a status code of 1. Fixed ``fail_under`` to be - applied even when the report is empty. Thanks, Ionel Cristian Mărieș. - -- Plugins are now initialized differently. Instead of looking for a class - called ``Plugin``, coverage.py looks for a function called ``coverage_init``. - -- A file-tracing plugin can now ask to have built-in Python reporting by - returning `"python"` from its `file_reporter()` method. - -- Code that was executed with `exec` would be mis-attributed to the file that - called it. This is now fixed, closing `issue 380`_. - -- The ability to use item access on `Coverage.config` (introduced in 4.0a2) has - been changed to a more explicit `Coverage.get_option` and - `Coverage.set_option` API. - -- The ``Coverage.use_cache`` method is no longer supported. - -- The private method ``Coverage._harvest_data`` is now called - ``Coverage.get_data``, and returns the ``CoverageData`` containing the - collected data. - -- The project is consistently referred to as "coverage.py" throughout the code - and the documentation, closing `issue 275`_. - -- Combining data files with an explicit configuration file was broken in 4.0a6, - but now works again, closing `issue 385`_. - -- ``coverage combine`` now accepts files as well as directories. - -- The speed is back to 3.7.1 levels, after having slowed down due to plugin - support, finishing up `issue 387`_. - -.. _issue 236: https://bitbucket.org/ned/coveragepy/issues/236/pickles-are-bad-and-you-should-feel-bad -.. _issue 252: https://bitbucket.org/ned/coveragepy/issues/252/coverage-wont-run-a-program-with -.. _issue 262: https://bitbucket.org/ned/coveragepy/issues/262/when-parallel-true-erase-should-erase-all -.. _issue 275: https://bitbucket.org/ned/coveragepy/issues/275/refer-consistently-to-project-as-coverage -.. _issue 313: https://bitbucket.org/ned/coveragepy/issues/313/add-license-file-containing-2-3-or-4 -.. _issue 380: https://bitbucket.org/ned/coveragepy/issues/380/code-executed-by-exec-excluded-from -.. _issue 385: https://bitbucket.org/ned/coveragepy/issues/385/coverage-combine-doesnt-work-with-rcfile -.. _issue 386: https://bitbucket.org/ned/coveragepy/issues/386/error-on-unrecognised-configuration -.. _issue 387: https://bitbucket.org/ned/coveragepy/issues/387/performance-degradation-from-371-to-40 - -.. 40 issues closed in 4.0 below here - - -Version 4.0a6 --- 21 June 2015 ------------------------------- - -- Python 3.5b2 and PyPy 2.6.0 are supported. - -- The original module-level function interface to coverage.py is no longer - supported. You must now create a ``coverage.Coverage`` object, and use - methods on it. - -- The ``coverage combine`` command now accepts any number of directories as - arguments, and will combine all the data files from those directories. This - means you don't have to copy the files to one directory before combining. - Thanks, Christine Lytwynec. Finishes `issue 354`_. - -- Branch coverage couldn't properly handle certain extremely long files. This - is now fixed (`issue 359`_). - -- Branch coverage didn't understand yield statements properly. Mickie Betz - persisted in pursuing this despite Ned's pessimism. Fixes `issue 308`_ and - `issue 324`_. - -- The COVERAGE_DEBUG environment variable can be used to set the ``[run] debug`` - configuration option to control what internal operations are logged. - -- HTML reports were truncated at formfeed characters. This is now fixed - (`issue 360`_). It's always fun when the problem is due to a `bug in the - Python standard library <http://bugs.python.org/issue19035>`_. - -- Files with incorrect encoding declaration comments are no longer ignored by - the reporting commands, fixing `issue 351`_. - -- HTML reports now include a timestamp in the footer, closing `issue 299`_. - Thanks, Conrad Ho. - -- HTML reports now begrudgingly use double-quotes rather than single quotes, - because there are "software engineers" out there writing tools that read HTML - and somehow have no idea that single quotes exist. Capitulates to the absurd - `issue 361`_. Thanks, Jon Chappell. - -- The ``coverage annotate`` command now handles non-ASCII characters properly, - closing `issue 363`_. Thanks, Leonardo Pistone. - -- Drive letters on Windows were not normalized correctly, now they are. Thanks, - Ionel Cristian Mărieș. - -- Plugin support had some bugs fixed, closing `issue 374`_ and `issue 375`_. - Thanks, Stefan Behnel. - -.. _issue 299: https://bitbucket.org/ned/coveragepy/issue/299/inserted-created-on-yyyy-mm-dd-hh-mm-in -.. _issue 308: https://bitbucket.org/ned/coveragepy/issue/308/yield-lambda-branch-coverage -.. _issue 324: https://bitbucket.org/ned/coveragepy/issue/324/yield-in-loop-confuses-branch-coverage -.. _issue 351: https://bitbucket.org/ned/coveragepy/issue/351/files-with-incorrect-encoding-are-ignored -.. _issue 354: https://bitbucket.org/ned/coveragepy/issue/354/coverage-combine-should-take-a-list-of -.. _issue 359: https://bitbucket.org/ned/coveragepy/issue/359/xml-report-chunk-error -.. _issue 360: https://bitbucket.org/ned/coveragepy/issue/360/html-reports-get-confused-by-l-in-the-code -.. _issue 361: https://bitbucket.org/ned/coveragepy/issue/361/use-double-quotes-in-html-output-to -.. _issue 363: https://bitbucket.org/ned/coveragepy/issue/363/annotate-command-hits-unicode-happy-fun -.. _issue 374: https://bitbucket.org/ned/coveragepy/issue/374/c-tracer-lookups-fail-in -.. _issue 375: https://bitbucket.org/ned/coveragepy/issue/375/ctracer_handle_return-reads-byte-code - - -Version 4.0a5 --- 16 February 2015 ----------------------------------- - -- Plugin support is now implemented in the C tracer instead of the Python - tracer. This greatly improves the speed of tracing projects using plugins. - -- Coverage.py now always adds the current directory to sys.path, so that - plugins can import files in the current directory (`issue 358`_). - -- If the `config_file` argument to the Coverage constructor is specified as - ".coveragerc", it is treated as if it were True. This means setup.cfg is - also examined, and a missing file is not considered an error (`issue 357`_). - -- Wildly experimental: support for measuring processes started by the - multiprocessing module. To use, set ``--concurrency=multiprocessing``, - either on the command line or in the .coveragerc file (`issue 117`_). Thanks, - Eduardo Schettino. Currently, this does not work on Windows. - -- A new warning is possible, if a desired file isn't measured because it was - imported before coverage.py was started (`issue 353`_). - -- The `coverage.process_startup` function now will start coverage measurement - only once, no matter how many times it is called. This fixes problems due - to unusual virtualenv configurations (`issue 340`_). - -- Added 3.5.0a1 to the list of supported CPython versions. - -.. _issue 117: https://bitbucket.org/ned/coveragepy/issue/117/enable-coverage-measurement-of-code-run-by -.. _issue 340: https://bitbucket.org/ned/coveragepy/issue/340/keyerror-subpy -.. _issue 353: https://bitbucket.org/ned/coveragepy/issue/353/40a3-introduces-an-unexpected-third-case -.. _issue 357: https://bitbucket.org/ned/coveragepy/issue/357/behavior-changed-when-coveragerc-is -.. _issue 358: https://bitbucket.org/ned/coveragepy/issue/358/all-coverage-commands-should-adjust - - -Version 4.0a4 --- 25 January 2015 ---------------------------------- - -- Plugins can now provide sys_info for debugging output. - -- Started plugins documentation. - -- Prepared to move the docs to readthedocs.org. - - -Version 4.0a3 --- 20 January 2015 ---------------------------------- - -- Reports now use file names with extensions. Previously, a report would - describe a/b/c.py as "a/b/c". Now it is shown as "a/b/c.py". This allows - for better support of non-Python files, and also fixed `issue 69`_. - -- The XML report now reports each directory as a package again. This was a bad - regression, I apologize. This was reported in `issue 235`_, which is now - fixed. - -- A new configuration option for the XML report: ``[xml] package_depth`` - controls which directories are identified as packages in the report. - Directories deeper than this depth are not reported as packages. - The default is that all directories are reported as packages. - Thanks, Lex Berezhny. - -- When looking for the source for a frame, check if the file exists. On - Windows, .pyw files are no longer recorded as .py files. Along the way, this - fixed `issue 290`_. - -- Empty files are now reported as 100% covered in the XML report, not 0% - covered (`issue 345`_). - -- Regexes in the configuration file are now compiled as soon as they are read, - to provide error messages earlier (`issue 349`_). - -.. _issue 69: https://bitbucket.org/ned/coveragepy/issue/69/coverage-html-overwrite-files-that-doesnt -.. _issue 235: https://bitbucket.org/ned/coveragepy/issue/235/package-name-is-missing-in-xml-report -.. _issue 290: https://bitbucket.org/ned/coveragepy/issue/290/running-programmatically-with-pyw-files -.. _issue 345: https://bitbucket.org/ned/coveragepy/issue/345/xml-reports-line-rate-0-for-empty-files -.. _issue 349: https://bitbucket.org/ned/coveragepy/issue/349/bad-regex-in-config-should-get-an-earlier - - -Version 4.0a2 --- 14 January 2015 ---------------------------------- - -- Officially support PyPy 2.4, and PyPy3 2.4. Drop support for - CPython 3.2 and older versions of PyPy. The code won't work on CPython 3.2. - It will probably still work on older versions of PyPy, but I'm not testing - against them. - -- Plugins! - -- The original command line switches (`-x` to run a program, etc) are no - longer supported. - -- A new option: `coverage report --skip-covered` will reduce the number of - files reported by skipping files with 100% coverage. Thanks, Krystian - Kichewko. This means that empty `__init__.py` files will be skipped, since - they are 100% covered, closing `issue 315`_. - -- You can now specify the ``--fail-under`` option in the ``.coveragerc`` file - as the ``[report] fail_under`` option. This closes `issue 314`_. - -- The ``COVERAGE_OPTIONS`` environment variable is no longer supported. It was - a hack for ``--timid`` before configuration files were available. - -- The HTML report now has filtering. Type text into the Filter box on the - index page, and only modules with that text in the name will be shown. - Thanks, Danny Allen. - -- The textual report and the HTML report used to report partial branches - differently for no good reason. Now the text report's "missing branches" - column is a "partial branches" column so that both reports show the same - numbers. This closes `issue 342`_. - -- If you specify a ``--rcfile`` that cannot be read, you will get an error - message. Fixes `issue 343`_. - -- The ``--debug`` switch can now be used on any command. - -- You can now programmatically adjust the configuration of coverage.py by - setting items on `Coverage.config` after construction. - -- A module run with ``-m`` can be used as the argument to ``--source``, fixing - `issue 328`_. Thanks, Buck Evan. - -- The regex for matching exclusion pragmas has been fixed to allow more kinds - of whitespace, fixing `issue 334`_. - -- Made some PyPy-specific tweaks to improve speed under PyPy. Thanks, Alex - Gaynor. - -- In some cases, with a source file missing a final newline, coverage.py would - count statements incorrectly. This is now fixed, closing `issue 293`_. - -- The status.dat file that HTML reports use to avoid re-creating files that - haven't changed is now a JSON file instead of a pickle file. This obviates - `issue 287`_ and `issue 237`_. - -.. _issue 237: https://bitbucket.org/ned/coveragepy/issue/237/htmlcov-with-corrupt-statusdat -.. _issue 287: https://bitbucket.org/ned/coveragepy/issue/287/htmlpy-doesnt-specify-pickle-protocol -.. _issue 293: https://bitbucket.org/ned/coveragepy/issue/293/number-of-statement-detection-wrong-if-no -.. _issue 314: https://bitbucket.org/ned/coveragepy/issue/314/fail_under-param-not-working-in-coveragerc -.. _issue 315: https://bitbucket.org/ned/coveragepy/issue/315/option-to-omit-empty-files-eg-__init__py -.. _issue 328: https://bitbucket.org/ned/coveragepy/issue/328/misbehavior-in-run-source -.. _issue 334: https://bitbucket.org/ned/coveragepy/issue/334/pragma-not-recognized-if-tab-character -.. _issue 342: https://bitbucket.org/ned/coveragepy/issue/342/console-and-html-coverage-reports-differ -.. _issue 343: https://bitbucket.org/ned/coveragepy/issue/343/an-explicitly-named-non-existent-config - - -Version 4.0a1 --- 27 September 2014 ------------------------------------ - -- Python versions supported are now CPython 2.6, 2.7, 3.2, 3.3, and 3.4, and - PyPy 2.2. - -- Gevent, eventlet, and greenlet are now supported, closing `issue 149`_. - The ``concurrency`` setting specifies the concurrency library in use. Huge - thanks to Peter Portante for initial implementation, and to Joe Jevnik for - the final insight that completed the work. - -- Options are now also read from a setup.cfg file, if any. Sections are - prefixed with "coverage:", so the ``[run]`` options will be read from the - ``[coverage:run]`` section of setup.cfg. Finishes `issue 304`_. - -- The ``report -m`` command can now show missing branches when reporting on - branch coverage. Thanks, Steve Leonard. Closes `issue 230`_. - -- The XML report now contains a <source> element, fixing `issue 94`_. Thanks - Stan Hu. - -- The class defined in the coverage module is now called ``Coverage`` instead - of ``coverage``, though the old name still works, for backward compatibility. - -- The ``fail-under`` value is now rounded the same as reported results, - preventing paradoxical results, fixing `issue 284`_. - -- The XML report will now create the output directory if need be, fixing - `issue 285`_. Thanks, Chris Rose. - -- HTML reports no longer raise UnicodeDecodeError if a Python file has - undecodable characters, fixing `issue 303`_ and `issue 331`_. - -- The annotate command will now annotate all files, not just ones relative to - the current directory, fixing `issue 57`_. - -- The coverage module no longer causes deprecation warnings on Python 3.4 by - importing the imp module, fixing `issue 305`_. - -- Encoding declarations in source files are only considered if they are truly - comments. Thanks, Anthony Sottile. - -.. _issue 57: https://bitbucket.org/ned/coveragepy/issue/57/annotate-command-fails-to-annotate-many -.. _issue 94: https://bitbucket.org/ned/coveragepy/issue/94/coverage-xml-doesnt-produce-sources -.. _issue 149: https://bitbucket.org/ned/coveragepy/issue/149/coverage-gevent-looks-broken -.. _issue 230: https://bitbucket.org/ned/coveragepy/issue/230/show-line-no-for-missing-branches-in -.. _issue 284: https://bitbucket.org/ned/coveragepy/issue/284/fail-under-should-show-more-precision -.. _issue 285: https://bitbucket.org/ned/coveragepy/issue/285/xml-report-fails-if-output-file-directory -.. _issue 303: https://bitbucket.org/ned/coveragepy/issue/303/unicodedecodeerror -.. _issue 304: https://bitbucket.org/ned/coveragepy/issue/304/attempt-to-get-configuration-from-setupcfg -.. _issue 305: https://bitbucket.org/ned/coveragepy/issue/305/pendingdeprecationwarning-the-imp-module -.. _issue 331: https://bitbucket.org/ned/coveragepy/issue/331/failure-of-encoding-detection-on-python2 - - -Version 3.7.1 --- 13 December 2013 ----------------------------------- - -- Improved the speed of HTML report generation by about 20%. - -- Fixed the mechanism for finding OS-installed static files for the HTML report - so that it will actually find OS-installed static files. - - -Version 3.7 --- 6 October 2013 ------------------------------- - -- Added the ``--debug`` switch to ``coverage run``. It accepts a list of - options indicating the type of internal activity to log to stderr. - -- Improved the branch coverage facility, fixing `issue 92`_ and `issue 175`_. - -- Running code with ``coverage run -m`` now behaves more like Python does, - setting sys.path properly, which fixes `issue 207`_ and `issue 242`_. - -- Coverage.py can now run .pyc files directly, closing `issue 264`_. - -- Coverage.py properly supports .pyw files, fixing `issue 261`_. - -- Omitting files within a tree specified with the ``source`` option would - cause them to be incorrectly marked as unexecuted, as described in - `issue 218`_. This is now fixed. - -- When specifying paths to alias together during data combining, you can now - specify relative paths, fixing `issue 267`_. - -- Most file paths can now be specified with username expansion (``~/src``, or - ``~build/src``, for example), and with environment variable expansion - (``build/$BUILDNUM/src``). - -- Trying to create an XML report with no files to report on, would cause a - ZeroDivideError, but no longer does, fixing `issue 250`_. - -- When running a threaded program under the Python tracer, coverage.py no - longer issues a spurious warning about the trace function changing: "Trace - function changed, measurement is likely wrong: None." This fixes `issue - 164`_. - -- Static files necessary for HTML reports are found in system-installed places, - to ease OS-level packaging of coverage.py. Closes `issue 259`_. - -- Source files with encoding declarations, but a blank first line, were not - decoded properly. Now they are. Thanks, Roger Hu. - -- The source kit now includes the ``__main__.py`` file in the root coverage - directory, fixing `issue 255`_. - -.. _issue 92: https://bitbucket.org/ned/coveragepy/issue/92/finally-clauses-arent-treated-properly-in -.. _issue 164: https://bitbucket.org/ned/coveragepy/issue/164/trace-function-changed-warning-when-using -.. _issue 175: https://bitbucket.org/ned/coveragepy/issue/175/branch-coverage-gets-confused-in-certain -.. _issue 207: https://bitbucket.org/ned/coveragepy/issue/207/run-m-cannot-find-module-or-package-in -.. _issue 242: https://bitbucket.org/ned/coveragepy/issue/242/running-a-two-level-package-doesnt-work -.. _issue 218: https://bitbucket.org/ned/coveragepy/issue/218/run-command-does-not-respect-the-omit-flag -.. _issue 250: https://bitbucket.org/ned/coveragepy/issue/250/uncaught-zerodivisionerror-when-generating -.. _issue 255: https://bitbucket.org/ned/coveragepy/issue/255/directory-level-__main__py-not-included-in -.. _issue 259: https://bitbucket.org/ned/coveragepy/issue/259/allow-use-of-system-installed-third-party -.. _issue 261: https://bitbucket.org/ned/coveragepy/issue/261/pyw-files-arent-reported-properly -.. _issue 264: https://bitbucket.org/ned/coveragepy/issue/264/coverage-wont-run-pyc-files -.. _issue 267: https://bitbucket.org/ned/coveragepy/issue/267/relative-path-aliases-dont-work - - -Version 3.6 --- 5 January 2013 ------------------------------- - -- Added a page to the docs about troublesome situations, closing `issue 226`_, - and added some info to the TODO file, closing `issue 227`_. - -.. _issue 226: https://bitbucket.org/ned/coveragepy/issue/226/make-readme-section-to-describe-when -.. _issue 227: https://bitbucket.org/ned/coveragepy/issue/227/update-todo - - -Version 3.6b3 --- 29 December 2012 ----------------------------------- - -- Beta 2 broke the nose plugin. It's fixed again, closing `issue 224`_. - -.. _issue 224: https://bitbucket.org/ned/coveragepy/issue/224/36b2-breaks-nosexcover - - -Version 3.6b2 --- 23 December 2012 ----------------------------------- - -- Coverage.py runs on Python 2.3 and 2.4 again. It was broken in 3.6b1. - -- The C extension is optionally compiled using a different more widely-used - technique, taking another stab at fixing `issue 80`_ once and for all. - -- Combining data files would create entries for phantom files if used with - ``source`` and path aliases. It no longer does. - -- ``debug sys`` now shows the configuration file path that was read. - -- If an oddly-behaved package claims that code came from an empty-string - file name, coverage.py no longer associates it with the directory name, - fixing `issue 221`_. - -.. _issue 80: https://bitbucket.org/ned/coveragepy/issue/80/is-there-a-duck-typing-way-to-know-we-cant -.. _issue 221: https://bitbucket.org/ned/coveragepy/issue/221/coveragepy-incompatible-with-pyratemp - - -Version 3.6b1 --- 28 November 2012 ----------------------------------- - -- Wildcards in ``include=`` and ``omit=`` arguments were not handled properly - in reporting functions, though they were when running. Now they are handled - uniformly, closing `issue 143`_ and `issue 163`_. **NOTE**: it is possible - that your configurations may now be incorrect. If you use ``include`` or - ``omit`` during reporting, whether on the command line, through the API, or - in a configuration file, please check carefully that you were not relying on - the old broken behavior. - -- The **report**, **html**, and **xml** commands now accept a ``--fail-under`` - switch that indicates in the exit status whether the coverage percentage was - less than a particular value. Closes `issue 139`_. - -- The reporting functions coverage.report(), coverage.html_report(), and - coverage.xml_report() now all return a float, the total percentage covered - measurement. - -- The HTML report's title can now be set in the configuration file, with the - ``--title`` switch on the command line, or via the API. - -- Configuration files now support substitution of environment variables, using - syntax like ``${WORD}``. Closes `issue 97`_. - -- Embarrassingly, the ``[xml] output=`` setting in the .coveragerc file simply - didn't work. Now it does. - -- The XML report now consistently uses file names for the file name attribute, - rather than sometimes using module names. Fixes `issue 67`_. - Thanks, Marcus Cobden. - -- Coverage percentage metrics are now computed slightly differently under - branch coverage. This means that completely unexecuted files will now - correctly have 0% coverage, fixing `issue 156`_. This also means that your - total coverage numbers will generally now be lower if you are measuring - branch coverage. - -- When installing, now in addition to creating a "coverage" command, two new - aliases are also installed. A "coverage2" or "coverage3" command will be - created, depending on whether you are installing in Python 2.x or 3.x. - A "coverage-X.Y" command will also be created corresponding to your specific - version of Python. Closes `issue 111`_. - -- The coverage.py installer no longer tries to bootstrap setuptools or - Distribute. You must have one of them installed first, as `issue 202`_ - recommended. - -- The coverage.py kit now includes docs (closing `issue 137`_) and tests. - -- On Windows, files are now reported in their correct case, fixing `issue 89`_ - and `issue 203`_. - -- If a file is missing during reporting, the path shown in the error message - is now correct, rather than an incorrect path in the current directory. - Fixes `issue 60`_. - -- Running an HTML report in Python 3 in the same directory as an old Python 2 - HTML report would fail with a UnicodeDecodeError. This issue (`issue 193`_) - is now fixed. - -- Fixed yet another error trying to parse non-Python files as Python, this - time an IndentationError, closing `issue 82`_ for the fourth time... - -- If `coverage xml` fails because there is no data to report, it used to - create a zero-length XML file. Now it doesn't, fixing `issue 210`_. - -- Jython files now work with the ``--source`` option, fixing `issue 100`_. - -- Running coverage.py under a debugger is unlikely to work, but it shouldn't - fail with "TypeError: 'NoneType' object is not iterable". Fixes `issue - 201`_. - -- On some Linux distributions, when installed with the OS package manager, - coverage.py would report its own code as part of the results. Now it won't, - fixing `issue 214`_, though this will take some time to be repackaged by the - operating systems. - -- Docstrings for the legacy singleton methods are more helpful. Thanks Marius - Gedminas. Closes `issue 205`_. - -- The pydoc tool can now show documentation for the class `coverage.coverage`. - Closes `issue 206`_. - -- Added a page to the docs about contributing to coverage.py, closing - `issue 171`_. - -- When coverage.py ended unsuccessfully, it may have reported odd errors like - ``'NoneType' object has no attribute 'isabs'``. It no longer does, - so kiss `issue 153`_ goodbye. - -.. _issue 60: https://bitbucket.org/ned/coveragepy/issue/60/incorrect-path-to-orphaned-pyc-files -.. _issue 67: https://bitbucket.org/ned/coveragepy/issue/67/xml-report-filenames-may-be-generated -.. _issue 89: https://bitbucket.org/ned/coveragepy/issue/89/on-windows-all-packages-are-reported-in -.. _issue 97: https://bitbucket.org/ned/coveragepy/issue/97/allow-environment-variables-to-be -.. _issue 100: https://bitbucket.org/ned/coveragepy/issue/100/source-directive-doesnt-work-for-packages -.. _issue 111: https://bitbucket.org/ned/coveragepy/issue/111/when-installing-coverage-with-pip-not -.. _issue 137: https://bitbucket.org/ned/coveragepy/issue/137/provide-docs-with-source-distribution -.. _issue 139: https://bitbucket.org/ned/coveragepy/issue/139/easy-check-for-a-certain-coverage-in-tests -.. _issue 143: https://bitbucket.org/ned/coveragepy/issue/143/omit-doesnt-seem-to-work-in-coverage -.. _issue 153: https://bitbucket.org/ned/coveragepy/issue/153/non-existent-filename-triggers -.. _issue 156: https://bitbucket.org/ned/coveragepy/issue/156/a-completely-unexecuted-file-shows-14 -.. _issue 163: https://bitbucket.org/ned/coveragepy/issue/163/problem-with-include-and-omit-filename -.. _issue 171: https://bitbucket.org/ned/coveragepy/issue/171/how-to-contribute-and-run-tests -.. _issue 193: https://bitbucket.org/ned/coveragepy/issue/193/unicodedecodeerror-on-htmlpy -.. _issue 201: https://bitbucket.org/ned/coveragepy/issue/201/coverage-using-django-14-with-pydb-on -.. _issue 202: https://bitbucket.org/ned/coveragepy/issue/202/get-rid-of-ez_setuppy-and -.. _issue 203: https://bitbucket.org/ned/coveragepy/issue/203/duplicate-filenames-reported-when-filename -.. _issue 205: https://bitbucket.org/ned/coveragepy/issue/205/make-pydoc-coverage-more-friendly -.. _issue 206: https://bitbucket.org/ned/coveragepy/issue/206/pydoc-coveragecoverage-fails-with-an-error -.. _issue 210: https://bitbucket.org/ned/coveragepy/issue/210/if-theres-no-coverage-data-coverage-xml -.. _issue 214: https://bitbucket.org/ned/coveragepy/issue/214/coveragepy-measures-itself-on-precise - - -Version 3.5.3 --- 29 September 2012 ------------------------------------ - -- Line numbers in the HTML report line up better with the source lines, fixing - `issue 197`_, thanks Marius Gedminas. - -- When specifying a directory as the source= option, the directory itself no - longer needs to have a ``__init__.py`` file, though its sub-directories do, - to be considered as source files. - -- Files encoded as UTF-8 with a BOM are now properly handled, fixing - `issue 179`_. Thanks, Pablo Carballo. - -- Fixed more cases of non-Python files being reported as Python source, and - then not being able to parse them as Python. Closes `issue 82`_ (again). - Thanks, Julian Berman. - -- Fixed memory leaks under Python 3, thanks, Brett Cannon. Closes `issue 147`_. - -- Optimized .pyo files may not have been handled correctly, `issue 195`_. - Thanks, Marius Gedminas. - -- Certain unusually named file paths could have been mangled during reporting, - `issue 194`_. Thanks, Marius Gedminas. - -- Try to do a better job of the impossible task of detecting when we can't - build the C extension, fixing `issue 183`_. - -- Testing is now done with `tox`_, thanks, Marc Abramowitz. - -.. _issue 147: https://bitbucket.org/ned/coveragepy/issue/147/massive-memory-usage-by-ctracer -.. _issue 179: https://bitbucket.org/ned/coveragepy/issue/179/htmlreporter-fails-when-source-file-is -.. _issue 183: https://bitbucket.org/ned/coveragepy/issue/183/install-fails-for-python-23 -.. _issue 194: https://bitbucket.org/ned/coveragepy/issue/194/filelocatorrelative_filename-could-mangle -.. _issue 195: https://bitbucket.org/ned/coveragepy/issue/195/pyo-file-handling-in-codeunit -.. _issue 197: https://bitbucket.org/ned/coveragepy/issue/197/line-numbers-in-html-report-do-not-align -.. _tox: http://tox.readthedocs.org/ - - -Version 3.5.2 --- 4 May 2012 ----------------------------- - -No changes since 3.5.2.b1 - - -Version 3.5.2b1 --- 29 April 2012 ---------------------------------- - -- The HTML report has slightly tweaked controls: the buttons at the top of - the page are color-coded to the source lines they affect. - -- Custom CSS can be applied to the HTML report by specifying a CSS file as - the ``extra_css`` configuration value in the ``[html]`` section. - -- Source files with custom encodings declared in a comment at the top are now - properly handled during reporting on Python 2. Python 3 always handled them - properly. This fixes `issue 157`_. - -- Backup files left behind by editors are no longer collected by the source= - option, fixing `issue 168`_. - -- If a file doesn't parse properly as Python, we don't report it as an error - if the file name seems like maybe it wasn't meant to be Python. This is a - pragmatic fix for `issue 82`_. - -- The ``-m`` switch on ``coverage report``, which includes missing line numbers - in the summary report, can now be specified as ``show_missing`` in the - config file. Closes `issue 173`_. - -- When running a module with ``coverage run -m <modulename>``, certain details - of the execution environment weren't the same as for - ``python -m <modulename>``. This had the unfortunate side-effect of making - ``coverage run -m unittest discover`` not work if you had tests in a - directory named "test". This fixes `issue 155`_ and `issue 142`_. - -- Now the exit status of your product code is properly used as the process - status when running ``python -m coverage run ...``. Thanks, JT Olds. - -- When installing into pypy, we no longer attempt (and fail) to compile - the C tracer function, closing `issue 166`_. - -.. _issue 142: https://bitbucket.org/ned/coveragepy/issue/142/executing-python-file-syspath-is-replaced -.. _issue 155: https://bitbucket.org/ned/coveragepy/issue/155/cant-use-coverage-run-m-unittest-discover -.. _issue 157: https://bitbucket.org/ned/coveragepy/issue/157/chokes-on-source-files-with-non-utf-8 -.. _issue 166: https://bitbucket.org/ned/coveragepy/issue/166/dont-try-to-compile-c-extension-on-pypy -.. _issue 168: https://bitbucket.org/ned/coveragepy/issue/168/dont-be-alarmed-by-emacs-droppings -.. _issue 173: https://bitbucket.org/ned/coveragepy/issue/173/theres-no-way-to-specify-show-missing-in - - -Version 3.5.1 --- 23 September 2011 ------------------------------------ - -- The ``[paths]`` feature unfortunately didn't work in real world situations - where you wanted to, you know, report on the combined data. Now all paths - stored in the combined file are canonicalized properly. - - -Version 3.5.1b1 --- 28 August 2011 ----------------------------------- - -- When combining data files from parallel runs, you can now instruct - coverage.py about which directories are equivalent on different machines. A - ``[paths]`` section in the configuration file lists paths that are to be - considered equivalent. Finishes `issue 17`_. - -- for-else constructs are understood better, and don't cause erroneous partial - branch warnings. Fixes `issue 122`_. - -- Branch coverage for ``with`` statements is improved, fixing `issue 128`_. - -- The number of partial branches reported on the HTML summary page was - different than the number reported on the individual file pages. This is - now fixed. - -- An explicit include directive to measure files in the Python installation - wouldn't work because of the standard library exclusion. Now the include - directive takes precedence, and the files will be measured. Fixes - `issue 138`_. - -- The HTML report now handles Unicode characters in Python source files - properly. This fixes `issue 124`_ and `issue 144`_. Thanks, Devin - Jeanpierre. - -- In order to help the core developers measure the test coverage of the - standard library, Brandon Rhodes devised an aggressive hack to trick Python - into running some coverage.py code before anything else in the process. - See the coverage/fullcoverage directory if you are interested. - -.. _issue 17: http://bitbucket.org/ned/coveragepy/issue/17/support-combining-coverage-data-from -.. _issue 122: http://bitbucket.org/ned/coveragepy/issue/122/for-else-always-reports-missing-branch -.. _issue 124: http://bitbucket.org/ned/coveragepy/issue/124/no-arbitrary-unicode-in-html-reports-in -.. _issue 128: http://bitbucket.org/ned/coveragepy/issue/128/branch-coverage-of-with-statement-in-27 -.. _issue 138: http://bitbucket.org/ned/coveragepy/issue/138/include-should-take-precedence-over-is -.. _issue 144: http://bitbucket.org/ned/coveragepy/issue/144/failure-generating-html-output-for - - -Version 3.5 --- 29 June 2011 ----------------------------- - -- The HTML report hotkeys now behave slightly differently when the current - chunk isn't visible at all: a chunk on the screen will be selected, - instead of the old behavior of jumping to the literal next chunk. - The hotkeys now work in Google Chrome. Thanks, Guido van Rossum. - - -Version 3.5b1 --- 5 June 2011 ------------------------------ - -- The HTML report now has hotkeys. Try ``n``, ``s``, ``m``, ``x``, ``b``, - ``p``, and ``c`` on the overview page to change the column sorting. - On a file page, ``r``, ``m``, ``x``, and ``p`` toggle the run, missing, - excluded, and partial line markings. You can navigate the highlighted - sections of code by using the ``j`` and ``k`` keys for next and previous. - The ``1`` (one) key jumps to the first highlighted section in the file, - and ``0`` (zero) scrolls to the top of the file. - -- The ``--omit`` and ``--include`` switches now interpret their values more - usefully. If the value starts with a wildcard character, it is used as-is. - If it does not, it is interpreted relative to the current directory. - Closes `issue 121`_. - -- Partial branch warnings can now be pragma'd away. The configuration option - ``partial_branches`` is a list of regular expressions. Lines matching any of - those expressions will never be marked as a partial branch. In addition, - there's a built-in list of regular expressions marking statements which should - never be marked as partial. This list includes ``while True:``, ``while 1:``, - ``if 1:``, and ``if 0:``. - -- The ``coverage()`` constructor accepts single strings for the ``omit=`` and - ``include=`` arguments, adapting to a common error in programmatic use. - -- Modules can now be run directly using ``coverage run -m modulename``, to - mirror Python's ``-m`` flag. Closes `issue 95`_, thanks, Brandon Rhodes. - -- ``coverage run`` didn't emulate Python accurately in one small detail: the - current directory inserted into ``sys.path`` was relative rather than - absolute. This is now fixed. - -- HTML reporting is now incremental: a record is kept of the data that - produced the HTML reports, and only files whose data has changed will - be generated. This should make most HTML reporting faster. - -- Pathological code execution could disable the trace function behind our - backs, leading to incorrect code measurement. Now if this happens, - coverage.py will issue a warning, at least alerting you to the problem. - Closes `issue 93`_. Thanks to Marius Gedminas for the idea. - -- The C-based trace function now behaves properly when saved and restored - with ``sys.gettrace()`` and ``sys.settrace()``. This fixes `issue 125`_ - and `issue 123`_. Thanks, Devin Jeanpierre. - -- Source files are now opened with Python 3.2's ``tokenize.open()`` where - possible, to get the best handling of Python source files with encodings. - Closes `issue 107`_, thanks, Brett Cannon. - -- Syntax errors in supposed Python files can now be ignored during reporting - with the ``-i`` switch just like other source errors. Closes `issue 115`_. - -- Installation from source now succeeds on machines without a C compiler, - closing `issue 80`_. - -- Coverage.py can now be run directly from a working tree by specifying - the directory name to python: ``python coverage_py_working_dir run ...``. - Thanks, Brett Cannon. - -- A little bit of Jython support: `coverage run` can now measure Jython - execution by adapting when $py.class files are traced. Thanks, Adi Roiban. - Jython still doesn't provide the Python libraries needed to make - coverage reporting work, unfortunately. - -- Internally, files are now closed explicitly, fixing `issue 104`_. Thanks, - Brett Cannon. - -.. _issue 80: https://bitbucket.org/ned/coveragepy/issue/80/is-there-a-duck-typing-way-to-know-we-cant -.. _issue 93: http://bitbucket.org/ned/coveragepy/issue/93/copying-a-mock-object-breaks-coverage -.. _issue 95: https://bitbucket.org/ned/coveragepy/issue/95/run-subcommand-should-take-a-module-name -.. _issue 104: https://bitbucket.org/ned/coveragepy/issue/104/explicitly-close-files -.. _issue 107: https://bitbucket.org/ned/coveragepy/issue/107/codeparser-not-opening-source-files-with -.. _issue 115: https://bitbucket.org/ned/coveragepy/issue/115/fail-gracefully-when-reporting-on-file -.. _issue 121: https://bitbucket.org/ned/coveragepy/issue/121/filename-patterns-are-applied-stupidly -.. _issue 123: https://bitbucket.org/ned/coveragepy/issue/123/pyeval_settrace-used-in-way-that-breaks -.. _issue 125: https://bitbucket.org/ned/coveragepy/issue/125/coverage-removes-decoratortoolss-tracing - - -Version 3.4 --- 19 September 2010 ---------------------------------- - -- The XML report is now sorted by package name, fixing `issue 88`_. - -- Programs that exited with ``sys.exit()`` with no argument weren't handled - properly, producing a coverage.py stack trace. That is now fixed. - -.. _issue 88: http://bitbucket.org/ned/coveragepy/issue/88/xml-report-lists-packages-in-random-order - - -Version 3.4b2 --- 6 September 2010 ----------------------------------- - -- Completely unexecuted files can now be included in coverage results, reported - as 0% covered. This only happens if the --source option is specified, since - coverage.py needs guidance about where to look for source files. - -- The XML report output now properly includes a percentage for branch coverage, - fixing `issue 65`_ and `issue 81`_. - -- Coverage percentages are now displayed uniformly across reporting methods. - Previously, different reports could round percentages differently. Also, - percentages are only reported as 0% or 100% if they are truly 0 or 100, and - are rounded otherwise. Fixes `issue 41`_ and `issue 70`_. - -- The precision of reported coverage percentages can be set with the - ``[report] precision`` config file setting. Completes `issue 16`_. - -- Threads derived from ``threading.Thread`` with an overridden `run` method - would report no coverage for the `run` method. This is now fixed, closing - `issue 85`_. - -.. _issue 16: http://bitbucket.org/ned/coveragepy/issue/16/allow-configuration-of-accuracy-of-percentage-totals -.. _issue 41: http://bitbucket.org/ned/coveragepy/issue/41/report-says-100-when-it-isnt-quite-there -.. _issue 65: http://bitbucket.org/ned/coveragepy/issue/65/branch-option-not-reported-in-cobertura -.. _issue 70: http://bitbucket.org/ned/coveragepy/issue/70/text-report-and-html-report-disagree-on-coverage -.. _issue 81: http://bitbucket.org/ned/coveragepy/issue/81/xml-report-does-not-have-condition-coverage-attribute-for-lines-with-a -.. _issue 85: http://bitbucket.org/ned/coveragepy/issue/85/threadrun-isnt-measured - - -Version 3.4b1 --- 21 August 2010 --------------------------------- - -- BACKWARD INCOMPATIBILITY: the ``--omit`` and ``--include`` switches now take - file patterns rather than file prefixes, closing `issue 34`_ and `issue 36`_. - -- BACKWARD INCOMPATIBILITY: the `omit_prefixes` argument is gone throughout - coverage.py, replaced with `omit`, a list of file name patterns suitable for - `fnmatch`. A parallel argument `include` controls what files are included. - -- The run command now has a ``--source`` switch, a list of directories or - module names. If provided, coverage.py will only measure execution in those - source files. - -- Various warnings are printed to stderr for problems encountered during data - measurement: if a ``--source`` module has no Python source to measure, or is - never encountered at all, or if no data is collected. - -- The reporting commands (report, annotate, html, and xml) now have an - ``--include`` switch to restrict reporting to modules matching those file - patterns, similar to the existing ``--omit`` switch. Thanks, Zooko. - -- The run command now supports ``--include`` and ``--omit`` to control what - modules it measures. This can speed execution and reduce the amount of data - during reporting. Thanks Zooko. - -- Since coverage.py 3.1, using the Python trace function has been slower than - it needs to be. A cache of tracing decisions was broken, but has now been - fixed. - -- Python 2.7 and 3.2 have introduced new opcodes that are now supported. - -- Python files with no statements, for example, empty ``__init__.py`` files, - are now reported as having zero statements instead of one. Fixes `issue 1`_. - -- Reports now have a column of missed line counts rather than executed line - counts, since developers should focus on reducing the missed lines to zero, - rather than increasing the executed lines to varying targets. Once - suggested, this seemed blindingly obvious. - -- Line numbers in HTML source pages are clickable, linking directly to that - line, which is highlighted on arrival. Added a link back to the index page - at the bottom of each HTML page. - -- Programs that call ``os.fork`` will properly collect data from both the child - and parent processes. Use ``coverage run -p`` to get two data files that can - be combined with ``coverage combine``. Fixes `issue 56`_. - -- Coverage.py is now runnable as a module: ``python -m coverage``. Thanks, - Brett Cannon. - -- When measuring code running in a virtualenv, most of the system library was - being measured when it shouldn't have been. This is now fixed. - -- Doctest text files are no longer recorded in the coverage data, since they - can't be reported anyway. Fixes `issue 52`_ and `issue 61`_. - -- Jinja HTML templates compile into Python code using the HTML file name, - which confused coverage.py. Now these files are no longer traced, fixing - `issue 82`_. - -- Source files can have more than one dot in them (foo.test.py), and will be - treated properly while reporting. Fixes `issue 46`_. - -- Source files with DOS line endings are now properly tokenized for syntax - coloring on non-DOS machines. Fixes `issue 53`_. - -- Unusual code structure that confused exits from methods with exits from - classes is now properly analyzed. See `issue 62`_. - -- Asking for an HTML report with no files now shows a nice error message rather - than a cryptic failure ('int' object is unsubscriptable). Fixes `issue 59`_. - -.. _issue 1: http://bitbucket.org/ned/coveragepy/issue/1/empty-__init__py-files-are-reported-as-1-executable -.. _issue 34: http://bitbucket.org/ned/coveragepy/issue/34/enhanced-omit-globbing-handling -.. _issue 36: http://bitbucket.org/ned/coveragepy/issue/36/provide-regex-style-omit -.. _issue 46: http://bitbucket.org/ned/coveragepy/issue/46 -.. _issue 53: http://bitbucket.org/ned/coveragepy/issue/53 -.. _issue 52: http://bitbucket.org/ned/coveragepy/issue/52/doctesttestfile-confuses-source-detection -.. _issue 56: http://bitbucket.org/ned/coveragepy/issue/56 -.. _issue 61: http://bitbucket.org/ned/coveragepy/issue/61/annotate-i-doesnt-work -.. _issue 62: http://bitbucket.org/ned/coveragepy/issue/62 -.. _issue 59: http://bitbucket.org/ned/coveragepy/issue/59/html-report-fails-with-int-object-is -.. _issue 82: http://bitbucket.org/ned/coveragepy/issue/82/tokenerror-when-generating-html-report - - -Version 3.3.1 --- 6 March 2010 ------------------------------- - -- Using `parallel=True` in .coveragerc file prevented reporting, but now does - not, fixing `issue 49`_. - -- When running your code with "coverage run", if you call `sys.exit()`, - coverage.py will exit with that status code, fixing `issue 50`_. - -.. _issue 49: http://bitbucket.org/ned/coveragepy/issue/49 -.. _issue 50: http://bitbucket.org/ned/coveragepy/issue/50 - - -Version 3.3 --- 24 February 2010 --------------------------------- - -- Settings are now read from a .coveragerc file. A specific file can be - specified on the command line with --rcfile=FILE. The name of the file can - be programmatically set with the `config_file` argument to the coverage() - constructor, or reading a config file can be disabled with - `config_file=False`. - -- Fixed a problem with nested loops having their branch possibilities - mischaracterized: `issue 39`_. - -- Added coverage.process_start to enable coverage measurement when Python - starts. - -- Parallel data file names now have a random number appended to them in - addition to the machine name and process id. - -- Parallel data files combined with "coverage combine" are deleted after - they're combined, to clean up unneeded files. Fixes `issue 40`_. - -- Exceptions thrown from product code run with "coverage run" are now displayed - without internal coverage.py frames, so the output is the same as when the - code is run without coverage.py. - -- The `data_suffix` argument to the coverage constructor is now appended with - an added dot rather than simply appended, so that .coveragerc files will not - be confused for data files. - -- Python source files that don't end with a newline can now be executed, fixing - `issue 47`_. - -- Added an AUTHORS.txt file. - -.. _issue 39: http://bitbucket.org/ned/coveragepy/issue/39 -.. _issue 40: http://bitbucket.org/ned/coveragepy/issue/40 -.. _issue 47: http://bitbucket.org/ned/coveragepy/issue/47 - - -Version 3.2 --- 5 December 2009 -------------------------------- - -- Added a ``--version`` option on the command line. - - -Version 3.2b4 --- 1 December 2009 ---------------------------------- - -- Branch coverage improvements: - - - The XML report now includes branch information. - -- Click-to-sort HTML report columns are now persisted in a cookie. Viewing - a report will sort it first the way you last had a coverage report sorted. - Thanks, `Chris Adams`_. - -- On Python 3.x, setuptools has been replaced by `Distribute`_. - -.. _Distribute: http://packages.python.org/distribute/ - - -Version 3.2b3 --- 23 November 2009 ----------------------------------- - -- Fixed a memory leak in the C tracer that was introduced in 3.2b1. - -- Branch coverage improvements: - - - Branches to excluded code are ignored. - -- The table of contents in the HTML report is now sortable: click the headers - on any column. Thanks, `Chris Adams`_. - -.. _Chris Adams: http://improbable.org/chris/ - - -Version 3.2b2 --- 19 November 2009 ----------------------------------- - -- Branch coverage improvements: - - - Classes are no longer incorrectly marked as branches: `issue 32`_. - - - "except" clauses with types are no longer incorrectly marked as branches: - `issue 35`_. - -- Fixed some problems syntax coloring sources with line continuations and - source with tabs: `issue 30`_ and `issue 31`_. - -- The --omit option now works much better than before, fixing `issue 14`_ and - `issue 33`_. Thanks, Danek Duvall. - -.. _issue 14: http://bitbucket.org/ned/coveragepy/issue/14 -.. _issue 30: http://bitbucket.org/ned/coveragepy/issue/30 -.. _issue 31: http://bitbucket.org/ned/coveragepy/issue/31 -.. _issue 32: http://bitbucket.org/ned/coveragepy/issue/32 -.. _issue 33: http://bitbucket.org/ned/coveragepy/issue/33 -.. _issue 35: http://bitbucket.org/ned/coveragepy/issue/35 - - -Version 3.2b1 --- 10 November 2009 ----------------------------------- - -- Branch coverage! - -- XML reporting has file paths that let Cobertura find the source code. - -- The tracer code has changed, it's a few percent faster. - -- Some exceptions reported by the command line interface have been cleaned up - so that tracebacks inside coverage.py aren't shown. Fixes `issue 23`_. - -.. _issue 23: http://bitbucket.org/ned/coveragepy/issue/23 - - -Version 3.1 --- 4 October 2009 ------------------------------- - -- Source code can now be read from eggs. Thanks, Ross Lawley. Fixes - `issue 25`_. - -.. _issue 25: http://bitbucket.org/ned/coveragepy/issue/25 - - -Version 3.1b1 --- 27 September 2009 ------------------------------------ - -- Python 3.1 is now supported. - -- Coverage.py has a new command line syntax with sub-commands. This expands - the possibilities for adding features and options in the future. The old - syntax is still supported. Try "coverage help" to see the new commands. - Thanks to Ben Finney for early help. - -- Added an experimental "coverage xml" command for producing coverage reports - in a Cobertura-compatible XML format. Thanks, Bill Hart. - -- Added the --timid option to enable a simpler slower trace function that works - for DecoratorTools projects, including TurboGears. Fixed `issue 12`_ and - `issue 13`_. - -- HTML reports show modules from other directories. Fixed `issue 11`_. - -- HTML reports now display syntax-colored Python source. - -- Programs that change directory will still write .coverage files in the - directory where execution started. Fixed `issue 24`_. - -- Added a "coverage debug" command for getting diagnostic information about the - coverage.py installation. - -.. _issue 11: http://bitbucket.org/ned/coveragepy/issue/11 -.. _issue 12: http://bitbucket.org/ned/coveragepy/issue/12 -.. _issue 13: http://bitbucket.org/ned/coveragepy/issue/13 -.. _issue 24: http://bitbucket.org/ned/coveragepy/issue/24 - - -Version 3.0.1 --- 7 July 2009 ------------------------------ - -- Removed the recursion limit in the tracer function. Previously, code that - ran more than 500 frames deep would crash. Fixed `issue 9`_. - -- Fixed a bizarre problem involving pyexpat, whereby lines following XML parser - invocations could be overlooked. Fixed `issue 10`_. - -- On Python 2.3, coverage.py could mis-measure code with exceptions being - raised. This is now fixed. - -- The coverage.py code itself will now not be measured by coverage.py, and no - coverage.py modules will be mentioned in the nose --with-cover plug-in. - Fixed `issue 8`_. - -- When running source files, coverage.py now opens them in universal newline - mode just like Python does. This lets it run Windows files on Mac, for - example. - -.. _issue 9: http://bitbucket.org/ned/coveragepy/issue/9 -.. _issue 10: http://bitbucket.org/ned/coveragepy/issue/10 -.. _issue 8: http://bitbucket.org/ned/coveragepy/issue/8 - - -Version 3.0 --- 13 June 2009 ----------------------------- - -- Fixed the way the Python library was ignored. Too much code was being - excluded the old way. - -- Tabs are now properly converted in HTML reports. Previously indentation was - lost. Fixed `issue 6`_. - -- Nested modules now get a proper flat_rootname. Thanks, Christian Heimes. - -.. _issue 6: http://bitbucket.org/ned/coveragepy/issue/6 - - -Version 3.0b3 --- 16 May 2009 ------------------------------ - -- Added parameters to coverage.__init__ for options that had been set on the - coverage object itself. - -- Added clear_exclude() and get_exclude_list() methods for programmatic - manipulation of the exclude regexes. - -- Added coverage.load() to read previously-saved data from the data file. - -- Improved the finding of code files. For example, .pyc files that have been - installed after compiling are now located correctly. Thanks, Detlev - Offenbach. - -- When using the object API (that is, constructing a coverage() object), data - is no longer saved automatically on process exit. You can re-enable it with - the auto_data=True parameter on the coverage() constructor. The module-level - interface still uses automatic saving. - - -Version 3.0b --- 30 April 2009 ------------------------------- - -HTML reporting, and continued refactoring. - -- HTML reports and annotation of source files: use the new -b (browser) switch. - Thanks to George Song for code, inspiration and guidance. - -- Code in the Python standard library is not measured by default. If you need - to measure standard library code, use the -L command-line switch during - execution, or the cover_pylib=True argument to the coverage() constructor. - -- Source annotation into a directory (-a -d) behaves differently. The - annotated files are named with their hierarchy flattened so that same-named - files from different directories no longer collide. Also, only files in the - current tree are included. - -- coverage.annotate_file is no longer available. - -- Programs executed with -x now behave more as they should, for example, - __file__ has the correct value. - -- .coverage data files have a new pickle-based format designed for better - extensibility. - -- Removed the undocumented cache_file argument to coverage.usecache(). - - -Version 3.0b1 --- 7 March 2009 ------------------------------- - -Major overhaul. - -- Coverage.py is now a package rather than a module. Functionality has been - split into classes. - -- The trace function is implemented in C for speed. Coverage.py runs are now - much faster. Thanks to David Christian for productive micro-sprints and - other encouragement. - -- Executable lines are identified by reading the line number tables in the - compiled code, removing a great deal of complicated analysis code. - -- Precisely which lines are considered executable has changed in some cases. - Therefore, your coverage stats may also change slightly. - -- The singleton coverage object is only created if the module-level functions - are used. This maintains the old interface while allowing better - programmatic use of Coverage.py. - -- The minimum supported Python version is 2.3. - - -Version 2.85 --- 14 September 2008 ----------------------------------- - -- Add support for finding source files in eggs. Don't check for - morf's being instances of ModuleType, instead use duck typing so that - pseudo-modules can participate. Thanks, Imri Goldberg. - -- Use os.realpath as part of the fixing of file names so that symlinks won't - confuse things. Thanks, Patrick Mezard. - - -Version 2.80 --- 25 May 2008 ----------------------------- - -- Open files in rU mode to avoid line ending craziness. Thanks, Edward Loper. - - -Version 2.78 --- 30 September 2007 ----------------------------------- - -- Don't try to predict whether a file is Python source based on the extension. - Extension-less files are often Pythons scripts. Instead, simply parse the file - and catch the syntax errors. Hat tip to Ben Finney. - - -Version 2.77 --- 29 July 2007 ------------------------------ - -- Better packaging. - - -Version 2.76 --- 23 July 2007 ------------------------------ - -- Now Python 2.5 is *really* fully supported: the body of the new with - statement is counted as executable. - - -Version 2.75 --- 22 July 2007 ------------------------------ - -- Python 2.5 now fully supported. The method of dealing with multi-line - statements is now less sensitive to the exact line that Python reports during - execution. Pass statements are handled specially so that their disappearance - during execution won't throw off the measurement. - - -Version 2.7 --- 21 July 2007 ----------------------------- - -- "#pragma: nocover" is excluded by default. - -- Properly ignore docstrings and other constant expressions that appear in the - middle of a function, a problem reported by Tim Leslie. - -- coverage.erase() shouldn't clobber the exclude regex. Change how parallel - mode is invoked, and fix erase() so that it erases the cache when called - programmatically. - -- In reports, ignore code executed from strings, since we can't do anything - useful with it anyway. - -- Better file handling on Linux, thanks Guillaume Chazarain. - -- Better shell support on Windows, thanks Noel O'Boyle. - -- Python 2.2 support maintained, thanks Catherine Proulx. - -- Minor changes to avoid lint warnings. - - -Version 2.6 --- 23 August 2006 ------------------------------- - -- Applied Joseph Tate's patch for function decorators. - -- Applied Sigve Tjora and Mark van der Wal's fixes for argument handling. - -- Applied Geoff Bache's parallel mode patch. - -- Refactorings to improve testability. Fixes to command-line logic for parallel - mode and collect. - - -Version 2.5 --- 4 December 2005 -------------------------------- - -- Call threading.settrace so that all threads are measured. Thanks Martin - Fuzzey. - -- Add a file argument to report so that reports can be captured to a different - destination. - -- Coverage.py can now measure itself. - -- Adapted Greg Rogers' patch for using relative file names, and sorting and - omitting files to report on. - - -Version 2.2 --- 31 December 2004 --------------------------------- - -- Allow for keyword arguments in the module global functions. Thanks, Allen. - - -Version 2.1 --- 14 December 2004 --------------------------------- - -- Return 'analysis' to its original behavior and add 'analysis2'. Add a global - for 'annotate', and factor it, adding 'annotate_file'. - - -Version 2.0 --- 12 December 2004 --------------------------------- - -Significant code changes. - -- Finding executable statements has been rewritten so that docstrings and - other quirks of Python execution aren't mistakenly identified as missing - lines. - -- Lines can be excluded from consideration, even entire suites of lines. - -- The file system cache of covered lines can be disabled programmatically. - -- Modernized the code. - - -Earlier History ---------------- - -2001-12-04 GDR Created. - -2001-12-06 GDR Added command-line interface and source code annotation. - -2001-12-09 GDR Moved design and interface to separate documents. - -2001-12-10 GDR Open cache file as binary on Windows. Allow simultaneous -e and --x, or -a and -r. - -2001-12-12 GDR Added command-line help. Cache analysis so that it only needs to -be done once when you specify -a and -r. - -2001-12-13 GDR Improved speed while recording. Portable between Python 1.5.2 -and 2.1.1. - -2002-01-03 GDR Module-level functions work correctly. - -2002-01-07 GDR Update sys.path when running a file with the -x option, so that -it matches the value the program would get if it were run on its own.
--- a/DebugClients/Python/coverage/doc/README.rst Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/doc/README.rst Sun Jul 24 12:01:01 2016 +0200 @@ -15,20 +15,23 @@ the code analysis tools and tracing hooks provided in the Python standard library to determine which lines are executable, and which have been executed. -Coverage.py runs on CPython 2.6, 2.7, 3.3, 3.4 or 3.5, PyPy 2.6, and PyPy3 2.4. +Coverage.py runs on CPython 2.6, 2.7, and 3.3 through 3.6; PyPy 4.0 and 5.1; +and PyPy3 2.4. -Documentation is on `Read the Docs <http://coverage.readthedocs.org>`_. +Documentation is on `Read the Docs <http://coverage.readthedocs.io>`_. Code repository and issue tracker are on `Bitbucket <http://bitbucket.org/ned/coveragepy>`_, with a mirrored repository on `GitHub <https://github.com/nedbat/coveragepy>`_. -**New in 4.0:** ``--concurrency``, plugins for non-Python files, setup.cfg +**New in 4.1:** much-improved branch coverage. + +New in 4.0: ``--concurrency``, plugins for non-Python files, setup.cfg support, --skip-covered, HTML filtering, and more than 50 issues closed. -Quick Start ------------ +Getting Started +--------------- -See the `quick start <http://coverage.readthedocs.org/#quick-start>`_ +See the `quick start <http://coverage.readthedocs.io/#quick-start>`_ section of the docs. @@ -46,7 +49,7 @@ :target: https://ci.appveyor.com/project/nedbat/coveragepy :alt: Windows build status .. |docs| image:: https://readthedocs.org/projects/coverage/badge/?version=latest&style=flat - :target: http://coverage.readthedocs.org + :target: http://coverage.readthedocs.io :alt: Documentation .. |reqs| image:: https://requires.io/github/nedbat/coveragepy/requirements.svg?branch=master :target: https://requires.io/github/nedbat/coveragepy/requirements/?branch=master @@ -57,9 +60,9 @@ .. |format| image:: https://img.shields.io/pypi/format/coverage.svg :target: https://pypi.python.org/pypi/coverage :alt: Kit format -.. |downloads| image:: https://img.shields.io/pypi/dd/coverage.svg +.. |downloads| image:: https://img.shields.io/pypi/dw/coverage.svg :target: https://pypi.python.org/pypi/coverage - :alt: Daily PyPI downloads + :alt: Weekly PyPI downloads .. |versions| image:: https://img.shields.io/pypi/pyversions/coverage.svg :target: https://pypi.python.org/pypi/coverage :alt: Python versions supported
--- a/DebugClients/Python/coverage/env.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/env.py Sun Jul 24 12:01:01 2016 +0200 @@ -30,3 +30,6 @@ # Even when running tests, you can use COVERAGE_TESTING=0 to disable the # test-specific behavior like contracts. TESTING = os.getenv('COVERAGE_TESTING', '') == 'True' + +# +# eflag: FileType = Python2
--- a/DebugClients/Python/coverage/execfile.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/execfile.py Sun Jul 24 12:01:01 2016 +0200 @@ -10,10 +10,12 @@ from coverage.backward import BUILTINS from coverage.backward import PYC_MAGIC_NUMBER, imp, importlib_util_find_spec -from coverage.misc import ExceptionDuringRun, NoCode, NoSource +from coverage.misc import ExceptionDuringRun, NoCode, NoSource, isolate_module from coverage.phystokens import compile_unicode from coverage.python import get_python_source +os = isolate_module(os) + class DummyLoader(object): """A shim for the pep302 __loader__, emulating pkgutil.ImpLoader. @@ -39,7 +41,7 @@ raise NoSource("No module named %r" % (modulename,)) pathname = spec.origin packagename = spec.name - if pathname.endswith("__init__.py"): + if pathname.endswith("__init__.py") and not modulename.endswith("__init__"): mod_main = modulename + ".__main__" spec = importlib_util_find_spec(mod_main) if not spec:
--- a/DebugClients/Python/coverage/files.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/files.py Sun Jul 24 12:01:01 2016 +0200 @@ -13,11 +13,10 @@ from coverage import env from coverage.backward import unicode_class -from coverage.misc import CoverageException, join_regex +from coverage.misc import contract, CoverageException, join_regex, isolate_module -RELATIVE_DIR = None -CANONICAL_FILENAME_CACHE = {} +os = isolate_module(os) def set_relative_directory(): @@ -31,10 +30,13 @@ # avoid duplicating work. CANONICAL_FILENAME_CACHE = {} + def relative_directory(): """Return the directory that `relative_filename` is relative to.""" return RELATIVE_DIR + +@contract(returns='unicode') def relative_filename(filename): """Return the relative form of `filename`. @@ -45,8 +47,10 @@ fnorm = os.path.normcase(filename) if fnorm.startswith(RELATIVE_DIR): filename = filename[len(RELATIVE_DIR):] - return filename + return unicode_filename(filename) + +@contract(returns='unicode') def canonical_filename(filename): """Return a canonical file name for `filename`. @@ -58,7 +62,7 @@ for path in [os.curdir] + sys.path: if path is None: continue - f = path + os.sep + filename + f = os.path.join(path, filename) if os.path.exists(f): filename = f break @@ -114,7 +118,7 @@ if os.path.normcase(f) == normtail: tail = f break - actpath = head.strip(os.sep) + os.sep + tail + actpath = os.path.join(head, tail) _ACTUAL_PATH_CACHE[path] = actpath return actpath @@ -124,14 +128,36 @@ return filename +if env.PY2: + @contract(returns='unicode') + def unicode_filename(filename): + """Return a Unicode version of `filename`.""" + if isinstance(filename, str): + encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() + filename = filename.decode(encoding, "replace") + return filename +else: + @contract(filename='unicode', returns='unicode') + def unicode_filename(filename): + """Return a Unicode version of `filename`.""" + return filename + + +@contract(returns='unicode') def abs_file(filename): """Return the absolute normalized form of `filename`.""" path = os.path.expandvars(os.path.expanduser(filename)) path = os.path.abspath(os.path.realpath(path)) path = actual_path(path) + path = unicode_filename(path) return path +RELATIVE_DIR = None +CANONICAL_FILENAME_CACHE = None +set_relative_directory() + + def isabs_anywhere(filename): """Is `filename` an absolute path on any OS?""" return ntpath.isabs(filename) or posixpath.isabs(filename) @@ -349,7 +375,7 @@ # files: Must end with .py or .pyw, and must not have certain funny # characters that probably mean they are editor junk. if re.match(r"^[^.#~!$@%^&*()+=,]+\.pyw?$", filename): - yield dirpath + os.sep + filename + yield os.path.join(dirpath, filename) # # eflag: FileType = Python2
--- a/DebugClients/Python/coverage/html.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/html.py Sun Jul 24 12:01:01 2016 +0200 @@ -6,18 +6,19 @@ import datetime import json import os -import re import shutil import coverage from coverage import env from coverage.backward import iitems from coverage.files import flat_rootname -from coverage.misc import CoverageException, Hasher +from coverage.misc import CoverageException, Hasher, isolate_module from coverage.report import Reporter from coverage.results import Numbers from coverage.templite import Templite +os = isolate_module(os) + # Static files are looked for in a list of places. STATIC_PATH = [ @@ -57,12 +58,18 @@ ) -def data(fname): +def read_data(fname): """Return the contents of a data file of ours.""" with open(data_filename(fname)) as data_file: return data_file.read() +def write_html(fname, html): + """Write `html` to `fname`, properly encoded.""" + with open(fname, "wb") as fout: + fout.write(html.encode('ascii', 'xmlcharrefreplace')) + + class HtmlReporter(Reporter): """HTML reporting.""" @@ -93,9 +100,7 @@ '__url__': coverage.__url__, '__version__': coverage.__version__, } - self.source_tmpl = Templite( - data("pyfile.html"), self.template_globals - ) + self.source_tmpl = Templite(read_data("pyfile.html"), self.template_globals) self.coverage = cov @@ -157,11 +162,6 @@ os.path.join(self.directory, self.extra_css) ) - def write_html(self, fname, html): - """Write `html` to `fname`, properly encoded.""" - with open(fname, "wb") as fout: - fout.write(html.encode('ascii', 'xmlcharrefreplace')) - def file_hash(self, source, fr): """Compute a hash that changes if the file needs to be re-reported.""" m = Hasher() @@ -189,6 +189,7 @@ if self.has_arcs: missing_branch_arcs = analysis.missing_branch_arcs() + arcs_executed = analysis.arcs_executed() # These classes determine which lines are highlighted by default. c_run = "run hide_run" @@ -202,7 +203,7 @@ # Figure out how to mark this line. line_class = [] annotate_html = "" - annotate_title = "" + annotate_long = "" if lineno in analysis.statements: line_class.append("stm") if lineno in analysis.excluded: @@ -216,23 +217,22 @@ for b in missing_branch_arcs[lineno]: if b < 0: shorts.append("exit") - longs.append("the function exit") else: shorts.append(b) - longs.append("line %d" % b) + longs.append(fr.missing_arc_description(lineno, b, arcs_executed)) # 202F is NARROW NO-BREAK SPACE. # 219B is RIGHTWARDS ARROW WITH STROKE. short_fmt = "%s ↛ %s" annotate_html = ", ".join(short_fmt % (lineno, d) for d in shorts) - annotate_html += " [?]" - annotate_title = "Line %d was executed, but never jumped to " % lineno if len(longs) == 1: - annotate_title += longs[0] - elif len(longs) == 2: - annotate_title += longs[0] + " or " + longs[1] + annotate_long = longs[0] else: - annotate_title += ", ".join(longs[:-1]) + ", or " + longs[-1] + annotate_long = "%d missed branches: %s" % ( + len(longs), + ", ".join("%d) %s" % (num, ann_long) + for num, ann_long in enumerate(longs, start=1)), + ) elif lineno in analysis.statements: line_class.append(c_run) @@ -252,21 +252,26 @@ 'number': lineno, 'class': ' '.join(line_class) or "pln", 'annotate': annotate_html, - 'annotate_title': annotate_title, + 'annotate_long': annotate_long, }) # Write the HTML page for this file. - template_values = { - 'c_exc': c_exc, 'c_mis': c_mis, 'c_par': c_par, 'c_run': c_run, - 'has_arcs': self.has_arcs, 'extra_css': self.extra_css, - 'fr': fr, 'nums': nums, 'lines': lines, + html = self.source_tmpl.render({ + 'c_exc': c_exc, + 'c_mis': c_mis, + 'c_par': c_par, + 'c_run': c_run, + 'has_arcs': self.has_arcs, + 'extra_css': self.extra_css, + 'fr': fr, + 'nums': nums, + 'lines': lines, 'time_stamp': self.time_stamp, - } - html = spaceless(self.source_tmpl.render(template_values)) + }) html_filename = rootname + ".html" html_path = os.path.join(self.directory, html_filename) - self.write_html(html_path, html) + write_html(html_path, html) # Save this file's information for the index file. index_info = { @@ -279,7 +284,7 @@ def index_file(self): """Write the index.html file for this report.""" - index_tmpl = Templite(data("index.html"), self.template_globals) + index_tmpl = Templite(read_data("index.html"), self.template_globals) self.totals = sum(f['nums'] for f in self.files) @@ -291,7 +296,7 @@ 'time_stamp': self.time_stamp, }) - self.write_html(os.path.join(self.directory, "index.html"), html) + write_html(os.path.join(self.directory, "index.html"), html) # Write the latest hashes for next time. self.status.write(self.directory) @@ -416,29 +421,13 @@ # Helpers for templates and generating HTML def escape(t): - """HTML-escape the text in `t`.""" - return ( - t - # Convert HTML special chars into HTML entities. - .replace("&", "&").replace("<", "<").replace(">", ">") - .replace("'", "'").replace('"', """) - # Convert runs of spaces: "......" -> " . . ." - .replace(" ", " ") - # To deal with odd-length runs, convert the final pair of spaces - # so that "....." -> " . ." - .replace(" ", " ") - ) + """HTML-escape the text in `t`. - -def spaceless(html): - """Squeeze out some annoying extra space from an HTML string. - - Nicely-formatted templates mean lots of extra space in the result. - Get rid of some. + This is only suitable for HTML text, not attributes. """ - html = re.sub(r">\s+<p ", ">\n<p ", html) - return html + # Convert HTML special chars into HTML entities. + return t.replace("&", "&").replace("<", "<") def pair(ratio):
--- a/DebugClients/Python/coverage/misc.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/misc.py Sun Jul 24 12:01:01 2016 +0200 @@ -6,27 +6,57 @@ import errno import hashlib import inspect +import locale import os +import sys +import types from coverage import env from coverage.backward import string_class, to_bytes, unicode_class +ISOLATED_MODULES = {} + + +def isolate_module(mod): + """Copy a module so that we are isolated from aggressive mocking. + + If a test suite mocks os.path.exists (for example), and then we need to use + it during the test, everything will get tangled up if we use their mock. + Making a copy of the module when we import it will isolate coverage.py from + those complications. + """ + if mod not in ISOLATED_MODULES: + new_mod = types.ModuleType(mod.__name__) + ISOLATED_MODULES[mod] = new_mod + for name in dir(mod): + value = getattr(mod, name) + if isinstance(value, types.ModuleType): + value = isolate_module(value) + setattr(new_mod, name, value) + return ISOLATED_MODULES[mod] + +os = isolate_module(os) + # Use PyContracts for assertion testing on parameters and returns, but only if # we are running our own test suite. if env.TESTING: from contracts import contract # pylint: disable=unused-import - from contracts import new_contract + from contracts import new_contract as raw_new_contract - try: - # Define contract words that PyContract doesn't have. - new_contract('bytes', lambda v: isinstance(v, bytes)) - if env.PY3: - new_contract('unicode', lambda v: isinstance(v, unicode_class)) - except ValueError: - # During meta-coverage, this module is imported twice, and PyContracts - # doesn't like redefining contracts. It's OK. - pass + def new_contract(*args, **kwargs): + """A proxy for contracts.new_contract that doesn't mind happening twice.""" + try: + return raw_new_contract(*args, **kwargs) + except ValueError: + # During meta-coverage, this module is imported twice, and + # PyContracts doesn't like redefining contracts. It's OK. + pass + + # Define contract words that PyContract doesn't have. + new_contract('bytes', lambda v: isinstance(v, bytes)) + if env.PY3: + new_contract('unicode', lambda v: isinstance(v, unicode_class)) else: # pragma: not covered # We aren't using real PyContracts, so just define a no-op decorator as a # stunt double. @@ -34,6 +64,10 @@ """Dummy no-op implementation of `contract`.""" return lambda func: func + def new_contract(*args_unused, **kwargs_unused): + """Dummy no-op implementation of `new_contract`.""" + pass + def nice_pair(pair): """Make a nice string representation of a pair of numbers. @@ -125,6 +159,18 @@ raise +def output_encoding(outfile=None): + """Determine the encoding to use for output written to `outfile` or stdout.""" + if outfile is None: + outfile = sys.stdout + encoding = ( + getattr(outfile, "encoding", None) or + getattr(sys.__stdout__, "encoding", None) or + locale.getpreferredencoding() + ) + return encoding + + class Hasher(object): """Hashes Python data into md5.""" def __init__(self):
--- a/DebugClients/Python/coverage/monkey.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/monkey.py Sun Jul 24 12:01:01 2016 +0200 @@ -11,6 +11,36 @@ # monkey-patched. PATCHED_MARKER = "_coverage$patched" +if sys.version_info >= (3, 4): + klass = multiprocessing.process.BaseProcess +else: + klass = multiprocessing.Process + +original_bootstrap = klass._bootstrap + + +class ProcessWithCoverage(klass): + """A replacement for multiprocess.Process that starts coverage.""" + def _bootstrap(self): + """Wrapper around _bootstrap to start coverage.""" + from coverage import Coverage + cov = Coverage(data_suffix=True) + cov.start() + try: + return original_bootstrap(self) + finally: + cov.stop() + cov.save() + + +class Stowaway(object): + """An object to pickle, so when it is unpickled, it can apply the monkey-patch.""" + def __getstate__(self): + return {} + + def __setstate__(self, state_unused): + patch_multiprocessing() + def patch_multiprocessing(): """Monkey-patch the multiprocessing module. @@ -23,28 +53,31 @@ return if sys.version_info >= (3, 4): - klass = multiprocessing.process.BaseProcess - else: - klass = multiprocessing.Process - - original_bootstrap = klass._bootstrap - - class ProcessWithCoverage(klass): - """A replacement for multiprocess.Process that starts coverage.""" - def _bootstrap(self): - """Wrapper around _bootstrap to start coverage.""" - from coverage import Coverage - cov = Coverage(data_suffix=True) - cov.start() - try: - return original_bootstrap(self) - finally: - cov.stop() - cov.save() - - if sys.version_info >= (3, 4): klass._bootstrap = ProcessWithCoverage._bootstrap else: multiprocessing.Process = ProcessWithCoverage + # When spawning processes rather than forking them, we have no state in the + # new process. We sneak in there with a Stowaway: we stuff one of our own + # objects into the data that gets pickled and sent to the sub-process. When + # the Stowaway is unpickled, it's __setstate__ method is called, which + # re-applies the monkey-patch. + # Windows only spawns, so this is needed to keep Windows working. + try: + from multiprocessing import spawn # pylint: disable=no-name-in-module + original_get_preparation_data = spawn.get_preparation_data + except (ImportError, AttributeError): + pass + else: + def get_preparation_data_with_stowaway(name): + """Get the original preparation data, and also insert our stowaway.""" + d = original_get_preparation_data(name) + d['stowaway'] = Stowaway() + return d + + spawn.get_preparation_data = get_preparation_data_with_stowaway + setattr(multiprocessing, PATCHED_MARKER, True) + +# +# eflag: FileType = Python2
--- a/DebugClients/Python/coverage/parser.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/parser.py Sun Jul 24 12:01:01 2016 +0200 @@ -3,23 +3,30 @@ """Code parsing for coverage.py.""" +import ast import collections -import dis +import os import re import token import tokenize +from coverage import env from coverage.backward import range # pylint: disable=redefined-builtin -from coverage.backward import bytes_to_ints -from coverage.bytecode import ByteCodes, CodeObjects -from coverage.misc import contract, nice_pair, expensive, join_regex +from coverage.backward import bytes_to_ints, string_class +from coverage.bytecode import CodeObjects +from coverage.debug import short_stack +from coverage.misc import contract, new_contract, nice_pair, join_regex from coverage.misc import CoverageException, NoSource, NotPython -from coverage.phystokens import compile_unicode, generate_tokens +from coverage.phystokens import compile_unicode, generate_tokens, neuter_encoding_declaration class PythonParser(object): - """Parse code to find executable lines, excluded lines, etc.""" + """Parse code to find executable lines, excluded lines, etc. + This information is all based on static analysis: no code execution is + involved. + + """ @contract(text='unicode|None') def __init__(self, text=None, filename=None, exclude=None): """ @@ -42,29 +49,45 @@ self.exclude = exclude - self.show_tokens = False - # The text lines of the parsed code. self.lines = self.text.split('\n') - # The line numbers of excluded lines of code. + # The normalized line numbers of the statements in the code. Exclusions + # are taken into account, and statements are adjusted to their first + # lines. + self.statements = set() + + # The normalized line numbers of the excluded lines in the code, + # adjusted to their first lines. self.excluded = set() + # The raw_* attributes are only used in this class, and in + # lab/parser.py to show how this class is working. + + # The line numbers that start statements, as reported by the line + # number table in the bytecode. + self.raw_statements = set() + + # The raw line numbers of excluded lines of code, as marked by pragmas. + self.raw_excluded = set() + + # The line numbers of class and function definitions. + self.raw_classdefs = set() + # The line numbers of docstring lines. - self.docstrings = set() + self.raw_docstrings = set() - # The line numbers of class definitions. - self.classdefs = set() + # Internal detail, used by lab/parser.py. + self.show_tokens = False - # A dict mapping line numbers to (lo,hi) for multi-line statements. - self.multiline = {} + # A dict mapping line numbers to lexical statement starts for + # multi-line statements. + self._multiline = {} - # The line numbers that start statements. - self.statement_starts = set() - - # Lazily-created ByteParser and arc data. + # Lazily-created ByteParser, arc data, and missing arc descriptions. self._byte_parser = None self._all_arcs = None + self._missing_arc_fragments = None @property def byte_parser(self): @@ -81,7 +104,10 @@ part of it. """ - regex_c = re.compile(join_regex(regexes)) + combined = join_regex(regexes) + if env.PY2: + combined = combined.decode("utf8") + regex_c = re.compile(combined) matches = set() for i, ltext in enumerate(self.lines, start=1): if regex_c.search(ltext): @@ -91,21 +117,23 @@ def _raw_parse(self): """Parse the source to find the interesting facts about its lines. - A handful of member fields are updated. + A handful of attributes are updated. """ # Find lines which match an exclusion pattern. if self.exclude: - self.excluded = self.lines_matching(self.exclude) + self.raw_excluded = self.lines_matching(self.exclude) # Tokenize, to find excluded suites, to find docstrings, and to find # multi-line statements. indent = 0 exclude_indent = 0 excluding = False + excluding_decorators = False prev_toktype = token.INDENT first_line = None empty = True + first_on_line = True tokgen = generate_tokens(self.text) for toktype, ttext, (slineno, _), (elineno, _), ltext in tokgen: @@ -118,32 +146,44 @@ indent += 1 elif toktype == token.DEDENT: indent -= 1 - elif toktype == token.NAME and ttext == 'class': - # Class definitions look like branches in the byte code, so - # we need to exclude them. The simplest way is to note the - # lines with the 'class' keyword. - self.classdefs.add(slineno) - elif toktype == token.OP and ttext == ':': - if not excluding and elineno in self.excluded: - # Start excluding a suite. We trigger off of the colon - # token so that the #pragma comment will be recognized on - # the same line as the colon. - exclude_indent = indent - excluding = True + elif toktype == token.NAME: + if ttext == 'class': + # Class definitions look like branches in the bytecode, so + # we need to exclude them. The simplest way is to note the + # lines with the 'class' keyword. + self.raw_classdefs.add(slineno) + elif toktype == token.OP: + if ttext == ':': + should_exclude = (elineno in self.raw_excluded) or excluding_decorators + if not excluding and should_exclude: + # Start excluding a suite. We trigger off of the colon + # token so that the #pragma comment will be recognized on + # the same line as the colon. + self.raw_excluded.add(elineno) + exclude_indent = indent + excluding = True + excluding_decorators = False + elif ttext == '@' and first_on_line: + # A decorator. + if elineno in self.raw_excluded: + excluding_decorators = True + if excluding_decorators: + self.raw_excluded.add(elineno) elif toktype == token.STRING and prev_toktype == token.INDENT: # Strings that are first on an indented line are docstrings. # (a trick from trace.py in the stdlib.) This works for # 99.9999% of cases. For the rest (!) see: # http://stackoverflow.com/questions/1769332/x/1769794#1769794 - self.docstrings.update(range(slineno, elineno+1)) + self.raw_docstrings.update(range(slineno, elineno+1)) elif toktype == token.NEWLINE: if first_line is not None and elineno != first_line: # We're at the end of a line, and we've ended on a # different line than the first line of the statement, # so record a multi-line range. for l in range(first_line, elineno+1): - self.multiline[l] = first_line + self._multiline[l] = first_line first_line = None + first_on_line = True if ttext.strip() and toktype != tokenize.COMMENT: # A non-whitespace token. @@ -156,21 +196,18 @@ if excluding and indent <= exclude_indent: excluding = False if excluding: - self.excluded.add(elineno) + self.raw_excluded.add(elineno) + first_on_line = False prev_toktype = toktype # Find the starts of the executable statements. if not empty: - self.statement_starts.update(self.byte_parser._find_statements()) + self.raw_statements.update(self.byte_parser._find_statements()) def first_line(self, line): """Return the first line number of the statement including `line`.""" - first_line = self.multiline.get(line) - if first_line: - return first_line - else: - return line + return self._multiline.get(line, line) def first_lines(self, lines): """Map the line numbers in `lines` to the correct first line of the @@ -187,20 +224,13 @@ def translate_arcs(self, arcs): """Implement `FileReporter.translate_arcs`.""" - return [ - (self.first_line(a), self.first_line(b)) - for (a, b) in arcs - ] + return [(self.first_line(a), self.first_line(b)) for (a, b) in arcs] - @expensive def parse_source(self): """Parse source text to find executable lines, excluded lines, etc. - Return values are 1) a set of executable line numbers, and 2) a set of - excluded line numbers. - - Reported line numbers are normalized to the first line of multi-line - statements. + Sets the .excluded and .statements attributes, normalized to the first + line of multi-line statements. """ try: @@ -211,20 +241,16 @@ else: lineno = err.args[1][0] # TokenError raise NotPython( - "Couldn't parse '%s' as Python source: '%s' at line %d" % ( + u"Couldn't parse '%s' as Python source: '%s' at line %d" % ( self.filename, err.args[0], lineno ) ) - excluded_lines = self.first_lines(self.excluded) - ignore = set() - ignore.update(excluded_lines) - ignore.update(self.docstrings) - starts = self.statement_starts - ignore - lines = self.first_lines(starts) - lines -= ignore + self.excluded = self.first_lines(self.raw_excluded) - return lines, excluded_lines + ignore = self.excluded | self.raw_docstrings + starts = self.raw_statements - ignore + self.statements = self.first_lines(starts) - ignore def arcs(self): """Get information about the arcs available in the code. @@ -234,99 +260,98 @@ """ if self._all_arcs is None: - self._all_arcs = set() - for l1, l2 in self.byte_parser._all_arcs(): - fl1 = self.first_line(l1) - fl2 = self.first_line(l2) - if fl1 != fl2: - self._all_arcs.add((fl1, fl2)) + self._analyze_ast() return self._all_arcs + def _analyze_ast(self): + """Run the AstArcAnalyzer and save its results. + + `_all_arcs` is the set of arcs in the code. + + """ + aaa = AstArcAnalyzer(self.text, self.raw_statements, self._multiline) + aaa.analyze() + + self._all_arcs = set() + for l1, l2 in aaa.arcs: + fl1 = self.first_line(l1) + fl2 = self.first_line(l2) + if fl1 != fl2: + self._all_arcs.add((fl1, fl2)) + + self._missing_arc_fragments = aaa.missing_arc_fragments + def exit_counts(self): """Get a count of exits from that each line. Excluded lines are excluded. """ - excluded_lines = self.first_lines(self.excluded) exit_counts = collections.defaultdict(int) for l1, l2 in self.arcs(): if l1 < 0: # Don't ever report -1 as a line number continue - if l1 in excluded_lines: + if l1 in self.excluded: # Don't report excluded lines as line numbers. continue - if l2 in excluded_lines: + if l2 in self.excluded: # Arcs to excluded lines shouldn't count. continue exit_counts[l1] += 1 # Class definitions have one extra exit, so remove one for each: - for l in self.classdefs: + for l in self.raw_classdefs: # Ensure key is there: class definitions can include excluded lines. if l in exit_counts: exit_counts[l] -= 1 return exit_counts - -## Opcodes that guide the ByteParser. + def missing_arc_description(self, start, end, executed_arcs=None): + """Provide an English sentence describing a missing arc.""" + if self._missing_arc_fragments is None: + self._analyze_ast() -def _opcode(name): - """Return the opcode by name from the dis module.""" - return dis.opmap[name] - + actual_start = start -def _opcode_set(*names): - """Return a set of opcodes by the names in `names`.""" - s = set() - for name in names: - try: - s.add(_opcode(name)) - except KeyError: - pass - return s + if ( + executed_arcs and + end < 0 and end == -start and + (end, start) not in executed_arcs and + (end, start) in self._missing_arc_fragments + ): + # It's a one-line callable, and we never even started it, + # and we have a message about not starting it. + start, end = end, start -# Opcodes that leave the code object. -OPS_CODE_END = _opcode_set('RETURN_VALUE') - -# Opcodes that unconditionally end the code chunk. -OPS_CHUNK_END = _opcode_set( - 'JUMP_ABSOLUTE', 'JUMP_FORWARD', 'RETURN_VALUE', 'RAISE_VARARGS', - 'BREAK_LOOP', 'CONTINUE_LOOP', -) + fragment_pairs = self._missing_arc_fragments.get((start, end), [(None, None)]) -# Opcodes that unconditionally begin a new code chunk. By starting new chunks -# with unconditional jump instructions, we neatly deal with jumps to jumps -# properly. -OPS_CHUNK_BEGIN = _opcode_set('JUMP_ABSOLUTE', 'JUMP_FORWARD') - -# Opcodes that push a block on the block stack. -OPS_PUSH_BLOCK = _opcode_set( - 'SETUP_LOOP', 'SETUP_EXCEPT', 'SETUP_FINALLY', 'SETUP_WITH' -) - -# Block types for exception handling. -OPS_EXCEPT_BLOCKS = _opcode_set('SETUP_EXCEPT', 'SETUP_FINALLY') + msgs = [] + for fragment_pair in fragment_pairs: + smsg, emsg = fragment_pair -# Opcodes that pop a block from the block stack. -OPS_POP_BLOCK = _opcode_set('POP_BLOCK') - -# Opcodes that have a jump destination, but aren't really a jump. -OPS_NO_JUMP = OPS_PUSH_BLOCK + if emsg is None: + if end < 0: + # Hmm, maybe we have a one-line callable, let's check. + if (-end, end) in self._missing_arc_fragments: + return self.missing_arc_description(-end, end) + emsg = "didn't jump to the function exit" + else: + emsg = "didn't jump to line {lineno}" + emsg = emsg.format(lineno=end) -# Individual opcodes we need below. -OP_BREAK_LOOP = _opcode('BREAK_LOOP') -OP_END_FINALLY = _opcode('END_FINALLY') -OP_COMPARE_OP = _opcode('COMPARE_OP') -COMPARE_EXCEPTION = 10 # just have to get this constant from the code. -OP_LOAD_CONST = _opcode('LOAD_CONST') -OP_RETURN_VALUE = _opcode('RETURN_VALUE') + msg = "line {start} {emsg}".format(start=actual_start, emsg=emsg) + if smsg is not None: + msg += ", because {smsg}".format(smsg=smsg.format(lineno=actual_start)) + + msgs.append(msg) + + return " or ".join(msgs) class ByteParser(object): - """Parse byte codes to understand the structure of code.""" + """Parse bytecode to understand the structure of code.""" @contract(text='unicode') def __init__(self, text, code=None, filename=None): @@ -338,14 +363,14 @@ self.code = compile_unicode(text, filename, "exec") except SyntaxError as synerr: raise NotPython( - "Couldn't parse '%s' as Python source: '%s' at line %d" % ( + u"Couldn't parse '%s' as Python source: '%s' at line %d" % ( filename, synerr.msg, synerr.lineno ) ) # Alternative Python implementations don't always provide all the # attributes on code objects that we need to do the analysis. - for attr in ['co_lnotab', 'co_firstlineno', 'co_consts', 'co_code']: + for attr in ['co_lnotab', 'co_firstlineno', 'co_consts']: if not hasattr(self.code, attr): raise CoverageException( "This implementation of Python doesn't support code analysis.\n" @@ -400,275 +425,610 @@ for _, l in bp._bytes_lines(): yield l - def _block_stack_repr(self, block_stack): # pragma: debugging - """Get a string version of `block_stack`, for debugging.""" - blocks = ", ".join( - "(%s, %r)" % (dis.opname[b[0]], b[1]) for b in block_stack - ) - return "[" + blocks + "]" + +# +# AST analysis +# + +class LoopBlock(object): + """A block on the block stack representing a `for` or `while` loop.""" + def __init__(self, start): + self.start = start + self.break_exits = set() + + +class FunctionBlock(object): + """A block on the block stack representing a function definition.""" + def __init__(self, start, name): + self.start = start + self.name = name + + +class TryBlock(object): + """A block on the block stack representing a `try` block.""" + def __init__(self, handler_start=None, final_start=None): + self.handler_start = handler_start + self.final_start = final_start + self.break_from = set() + self.continue_from = set() + self.return_from = set() + self.raise_from = set() + + +class ArcStart(collections.namedtuple("Arc", "lineno, cause")): + """The information needed to start an arc. + + `lineno` is the line number the arc starts from. `cause` is a fragment + used as the startmsg for AstArcAnalyzer.missing_arc_fragments. + + """ + def __new__(cls, lineno, cause=None): + return super(ArcStart, cls).__new__(cls, lineno, cause) + + +# Define contract words that PyContract doesn't have. +# ArcStarts is for a list or set of ArcStart's. +new_contract('ArcStarts', lambda seq: all(isinstance(x, ArcStart) for x in seq)) + + +class AstArcAnalyzer(object): + """Analyze source text with an AST to find executable code paths.""" + + @contract(text='unicode', statements=set) + def __init__(self, text, statements, multiline): + self.root_node = ast.parse(neuter_encoding_declaration(text)) + # TODO: I think this is happening in too many places. + self.statements = set(multiline.get(l, l) for l in statements) + self.multiline = multiline + + if int(os.environ.get("COVERAGE_ASTDUMP", 0)): # pragma: debugging + # Dump the AST so that failing tests have helpful output. + print("Statements: {}".format(self.statements)) + print("Multiline map: {}".format(self.multiline)) + ast_dump(self.root_node) + + self.arcs = set() + + # A map from arc pairs to a pair of sentence fragments: (startmsg, endmsg). + # For an arc from line 17, they should be usable like: + # "Line 17 {endmsg}, because {startmsg}" + self.missing_arc_fragments = collections.defaultdict(list) + self.block_stack = [] + + self.debug = bool(int(os.environ.get("COVERAGE_TRACK_ARCS", 0))) + + def analyze(self): + """Examine the AST tree from `root_node` to determine possible arcs. + + This sets the `arcs` attribute to be a set of (from, to) line number + pairs. + + """ + for node in ast.walk(self.root_node): + node_name = node.__class__.__name__ + code_object_handler = getattr(self, "_code_object__" + node_name, None) + if code_object_handler is not None: + code_object_handler(node) - def _split_into_chunks(self): - """Split the code object into a list of `Chunk` objects. + def add_arc(self, start, end, smsg=None, emsg=None): + """Add an arc, including message fragments to use if it is missing.""" + if self.debug: + print("\nAdding arc: ({}, {}): {!r}, {!r}".format(start, end, smsg, emsg)) + print(short_stack(limit=6)) + self.arcs.add((start, end)) + + if smsg is not None or emsg is not None: + self.missing_arc_fragments[(start, end)].append((smsg, emsg)) + + def nearest_blocks(self): + """Yield the blocks in nearest-to-farthest order.""" + return reversed(self.block_stack) + + @contract(returns=int) + def line_for_node(self, node): + """What is the right line number to use for this node? + + This dispatches to _line__Node functions where needed. + + """ + node_name = node.__class__.__name__ + handler = getattr(self, "_line__" + node_name, None) + if handler is not None: + return handler(node) + else: + return node.lineno + + def _line__Assign(self, node): + return self.line_for_node(node.value) + + def _line__Dict(self, node): + # Python 3.5 changed how dict literals are made. + if env.PYVERSION >= (3, 5) and node.keys: + if node.keys[0] is not None: + return node.keys[0].lineno + else: + # Unpacked dict literals `{**{'a':1}}` have None as the key, + # use the value in that case. + return node.values[0].lineno + else: + return node.lineno - Each chunk is only entered at its first instruction, though there can - be many exits from a chunk. + def _line__List(self, node): + if node.elts: + return self.line_for_node(node.elts[0]) + else: + return node.lineno + + def _line__Module(self, node): + if node.body: + return self.line_for_node(node.body[0]) + else: + # Modules have no line number, they always start at 1. + return 1 + + OK_TO_DEFAULT = set([ + "Assign", "Assert", "AugAssign", "Delete", "Exec", "Expr", "Global", + "Import", "ImportFrom", "Nonlocal", "Pass", "Print", + ]) + + @contract(returns='ArcStarts') + def add_arcs(self, node): + """Add the arcs for `node`. - Returns a list of `Chunk` objects. + Return a set of ArcStarts, exits from this node to the next. + + """ + node_name = node.__class__.__name__ + handler = getattr(self, "_handle__" + node_name, None) + if handler is not None: + return handler(node) + + if 0: + node_name = node.__class__.__name__ + if node_name not in self.OK_TO_DEFAULT: + print("*** Unhandled: {0}".format(node)) + return set([ArcStart(self.line_for_node(node), cause=None)]) + + @contract(returns='ArcStarts') + def add_body_arcs(self, body, from_start=None, prev_starts=None): + """Add arcs for the body of a compound statement. + + `body` is the body node. `from_start` is a single `ArcStart` that can + be the previous line in flow before this body. `prev_starts` is a set + of ArcStarts that can be the previous line. Only one of them should be + given. + + Returns a set of ArcStarts, the exits from this body. """ - # The list of chunks so far, and the one we're working on. - chunks = [] - chunk = None - - # A dict mapping byte offsets of line starts to the line numbers. - bytes_lines_map = dict(self._bytes_lines()) + if prev_starts is None: + prev_starts = set([from_start]) + for body_node in body: + lineno = self.line_for_node(body_node) + first_line = self.multiline.get(lineno, lineno) + if first_line not in self.statements: + continue + for prev_start in prev_starts: + self.add_arc(prev_start.lineno, lineno, prev_start.cause) + prev_starts = self.add_arcs(body_node) + return prev_starts - # The block stack: loops and try blocks get pushed here for the - # implicit jumps that can occur. - # Each entry is a tuple: (block type, destination) - block_stack = [] + def is_constant_expr(self, node): + """Is this a compile-time constant?""" + node_name = node.__class__.__name__ + if node_name in ["NameConstant", "Num"]: + return True + elif node_name == "Name": + if env.PY3 and node.id in ["True", "False", "None"]: + return True + return False - # Some op codes are followed by branches that should be ignored. This - # is a count of how many ignores are left. - ignore_branch = 0 + # tests to write: + # TODO: while EXPR: + # TODO: while False: + # TODO: listcomps hidden deep in other expressions + # TODO: listcomps hidden in lists: x = [[i for i in range(10)]] + # TODO: nested function definitions - # We have to handle the last two bytecodes specially. - ult = penult = None + @contract(exits='ArcStarts') + def process_break_exits(self, exits): + """Add arcs due to jumps from `exits` being breaks.""" + for block in self.nearest_blocks(): + if isinstance(block, LoopBlock): + block.break_exits.update(exits) + break + elif isinstance(block, TryBlock) and block.final_start is not None: + block.break_from.update(exits) + break - # Get a set of all of the jump-to points. - jump_to = set() - bytecodes = list(ByteCodes(self.code.co_code)) - for bc in bytecodes: - if bc.jump_to >= 0: - jump_to.add(bc.jump_to) - - chunk_lineno = 0 + @contract(exits='ArcStarts') + def process_continue_exits(self, exits): + """Add arcs due to jumps from `exits` being continues.""" + for block in self.nearest_blocks(): + if isinstance(block, LoopBlock): + for xit in exits: + self.add_arc(xit.lineno, block.start, xit.cause) + break + elif isinstance(block, TryBlock) and block.final_start is not None: + block.continue_from.update(exits) + break - # Walk the byte codes building chunks. - for bc in bytecodes: - # Maybe have to start a new chunk. - start_new_chunk = False - first_chunk = False - if bc.offset in bytes_lines_map: - # Start a new chunk for each source line number. - start_new_chunk = True - chunk_lineno = bytes_lines_map[bc.offset] - first_chunk = True - elif bc.offset in jump_to: - # To make chunks have a single entrance, we have to make a new - # chunk when we get to a place some bytecode jumps to. - start_new_chunk = True - elif bc.op in OPS_CHUNK_BEGIN: - # Jumps deserve their own unnumbered chunk. This fixes - # problems with jumps to jumps getting confused. - start_new_chunk = True + @contract(exits='ArcStarts') + def process_raise_exits(self, exits): + """Add arcs due to jumps from `exits` being raises.""" + for block in self.nearest_blocks(): + if isinstance(block, TryBlock): + if block.handler_start is not None: + for xit in exits: + self.add_arc(xit.lineno, block.handler_start, xit.cause) + break + elif block.final_start is not None: + block.raise_from.update(exits) + break + elif isinstance(block, FunctionBlock): + for xit in exits: + self.add_arc( + xit.lineno, -block.start, xit.cause, + "didn't except from function '{0}'".format(block.name), + ) + break + + @contract(exits='ArcStarts') + def process_return_exits(self, exits): + """Add arcs due to jumps from `exits` being returns.""" + for block in self.nearest_blocks(): + if isinstance(block, TryBlock) and block.final_start is not None: + block.return_from.update(exits) + break + elif isinstance(block, FunctionBlock): + for xit in exits: + self.add_arc( + xit.lineno, -block.start, xit.cause, + "didn't return from function '{0}'".format(block.name), + ) + break + + ## Handlers - if not chunk or start_new_chunk: - if chunk: - chunk.exits.add(bc.offset) - chunk = Chunk(bc.offset, chunk_lineno, first_chunk) - if not chunks: - # The very first chunk of a code object is always an - # entrance. - chunk.entrance = True - chunks.append(chunk) + @contract(returns='ArcStarts') + def _handle__Break(self, node): + here = self.line_for_node(node) + break_start = ArcStart(here, cause="the break on line {lineno} wasn't executed") + self.process_break_exits([break_start]) + return set() - # Look at the opcode. - if bc.jump_to >= 0 and bc.op not in OPS_NO_JUMP: - if ignore_branch: - # Someone earlier wanted us to ignore this branch. - ignore_branch -= 1 - else: - # The opcode has a jump, it's an exit for this chunk. - chunk.exits.add(bc.jump_to) + @contract(returns='ArcStarts') + def _handle_decorated(self, node): + """Add arcs for things that can be decorated (classes and functions).""" + last = self.line_for_node(node) + if node.decorator_list: + for dec_node in node.decorator_list: + dec_start = self.line_for_node(dec_node) + if dec_start != last: + self.add_arc(last, dec_start) + last = dec_start + # The definition line may have been missed, but we should have it + # in `self.statements`. For some constructs, `line_for_node` is + # not what we'd think of as the first line in the statement, so map + # it to the first one. + body_start = self.line_for_node(node.body[0]) + body_start = self.multiline.get(body_start, body_start) + for lineno in range(last+1, body_start): + if lineno in self.statements: + self.add_arc(last, lineno) + last = lineno + # The body is handled in collect_arcs. + return set([ArcStart(last, cause=None)]) + + _handle__ClassDef = _handle_decorated + + @contract(returns='ArcStarts') + def _handle__Continue(self, node): + here = self.line_for_node(node) + continue_start = ArcStart(here, cause="the continue on line {lineno} wasn't executed") + self.process_continue_exits([continue_start]) + return set() - if bc.op in OPS_CODE_END: - # The opcode can exit the code object. - chunk.exits.add(-self.code.co_firstlineno) - if bc.op in OPS_PUSH_BLOCK: - # The opcode adds a block to the block_stack. - block_stack.append((bc.op, bc.jump_to)) - if bc.op in OPS_POP_BLOCK: - # The opcode pops a block from the block stack. - block_stack.pop() - if bc.op in OPS_CHUNK_END: - # This opcode forces the end of the chunk. - if bc.op == OP_BREAK_LOOP: - # A break is implicit: jump where the top of the - # block_stack points. - chunk.exits.add(block_stack[-1][1]) - chunk = None - if bc.op == OP_END_FINALLY: - # For the finally clause we need to find the closest exception - # block, and use its jump target as an exit. - for block in reversed(block_stack): - if block[0] in OPS_EXCEPT_BLOCKS: - chunk.exits.add(block[1]) - break - if bc.op == OP_COMPARE_OP and bc.arg == COMPARE_EXCEPTION: - # This is an except clause. We want to overlook the next - # branch, so that except's don't count as branches. - ignore_branch += 1 + @contract(returns='ArcStarts') + def _handle__For(self, node): + start = self.line_for_node(node.iter) + self.block_stack.append(LoopBlock(start=start)) + from_start = ArcStart(start, cause="the loop on line {lineno} never started") + exits = self.add_body_arcs(node.body, from_start=from_start) + # Any exit from the body will go back to the top of the loop. + for xit in exits: + self.add_arc(xit.lineno, start, xit.cause) + my_block = self.block_stack.pop() + exits = my_block.break_exits + from_start = ArcStart(start, cause="the loop on line {lineno} didn't complete") + if node.orelse: + else_exits = self.add_body_arcs(node.orelse, from_start=from_start) + exits |= else_exits + else: + # no else clause: exit from the for line. + exits.add(from_start) + return exits + + _handle__AsyncFor = _handle__For + + _handle__FunctionDef = _handle_decorated + _handle__AsyncFunctionDef = _handle_decorated - penult = ult - ult = bc + @contract(returns='ArcStarts') + def _handle__If(self, node): + start = self.line_for_node(node.test) + from_start = ArcStart(start, cause="the condition on line {lineno} was never true") + exits = self.add_body_arcs(node.body, from_start=from_start) + from_start = ArcStart(start, cause="the condition on line {lineno} was never false") + exits |= self.add_body_arcs(node.orelse, from_start=from_start) + return exits + + @contract(returns='ArcStarts') + def _handle__Raise(self, node): + here = self.line_for_node(node) + raise_start = ArcStart(here, cause="the raise on line {lineno} wasn't executed") + self.process_raise_exits([raise_start]) + # `raise` statement jumps away, no exits from here. + return set() + + @contract(returns='ArcStarts') + def _handle__Return(self, node): + here = self.line_for_node(node) + return_start = ArcStart(here, cause="the return on line {lineno} wasn't executed") + self.process_return_exits([return_start]) + # `return` statement jumps away, no exits from here. + return set() - if chunks: - # The last two bytecodes could be a dummy "return None" that - # shouldn't be counted as real code. Every Python code object seems - # to end with a return, and a "return None" is inserted if there - # isn't an explicit return in the source. - if ult and penult: - if penult.op == OP_LOAD_CONST and ult.op == OP_RETURN_VALUE: - if self.code.co_consts[penult.arg] is None: - # This is "return None", but is it dummy? A real line - # would be a last chunk all by itself. - if chunks[-1].byte != penult.offset: - ex = -self.code.co_firstlineno - # Split the last chunk - last_chunk = chunks[-1] - last_chunk.exits.remove(ex) - last_chunk.exits.add(penult.offset) - chunk = Chunk( - penult.offset, last_chunk.line, False - ) - chunk.exits.add(ex) - chunks.append(chunk) + @contract(returns='ArcStarts') + def _handle__Try(self, node): + if node.handlers: + handler_start = self.line_for_node(node.handlers[0]) + else: + handler_start = None + + if node.finalbody: + final_start = self.line_for_node(node.finalbody[0]) + else: + final_start = None + + try_block = TryBlock(handler_start=handler_start, final_start=final_start) + self.block_stack.append(try_block) + + start = self.line_for_node(node) + exits = self.add_body_arcs(node.body, from_start=ArcStart(start, cause=None)) - # Give all the chunks a length. - chunks[-1].length = bc.next_offset - chunks[-1].byte - for i in range(len(chunks)-1): - chunks[i].length = chunks[i+1].byte - chunks[i].byte + # We're done with the `try` body, so this block no longer handles + # exceptions. We keep the block so the `finally` clause can pick up + # flows from the handlers and `else` clause. + if node.finalbody: + try_block.handler_start = None + if node.handlers: + # If there are `except` clauses, then raises in the try body + # will already jump to them. Start this set over for raises in + # `except` and `else`. + try_block.raise_from = set([]) + else: + self.block_stack.pop() + + handler_exits = set() + + if node.handlers: + last_handler_start = None + for handler_node in node.handlers: + handler_start = self.line_for_node(handler_node) + if last_handler_start is not None: + self.add_arc(last_handler_start, handler_start) + last_handler_start = handler_start + from_cause = "the exception caught by line {lineno} didn't happen" + from_start = ArcStart(handler_start, cause=from_cause) + handler_exits |= self.add_body_arcs(handler_node.body, from_start=from_start) - #self.validate_chunks(chunks) - return chunks + if node.orelse: + exits = self.add_body_arcs(node.orelse, prev_starts=exits) + + exits |= handler_exits - def validate_chunks(self, chunks): # pragma: debugging - """Validate the rule that chunks have a single entrance.""" - # starts is the entrances to the chunks - starts = set(ch.byte for ch in chunks) - for ch in chunks: - assert all((ex in starts or ex < 0) for ex in ch.exits) - - def _arcs(self): - """Find the executable arcs in the code. + if node.finalbody: + self.block_stack.pop() + final_from = ( # You can get to the `finally` clause from: + exits | # the exits of the body or `else` clause, + try_block.break_from | # or a `break`, + try_block.continue_from | # or a `continue`, + try_block.raise_from | # or a `raise`, + try_block.return_from # or a `return`. + ) - Yields pairs: (from,to). From and to are integer line numbers. If - from is < 0, then the arc is an entrance into the code object. If to - is < 0, the arc is an exit from the code object. + exits = self.add_body_arcs(node.finalbody, prev_starts=final_from) + if try_block.break_from: + break_exits = self._combine_finally_starts(try_block.break_from, exits) + self.process_break_exits(break_exits) + if try_block.continue_from: + continue_exits = self._combine_finally_starts(try_block.continue_from, exits) + self.process_continue_exits(continue_exits) + if try_block.raise_from: + raise_exits = self._combine_finally_starts(try_block.raise_from, exits) + self.process_raise_exits(raise_exits) + if try_block.return_from: + return_exits = self._combine_finally_starts(try_block.return_from, exits) + self.process_return_exits(return_exits) - """ - chunks = self._split_into_chunks() + return exits - # A map from byte offsets to the chunk starting at that offset. - byte_chunks = dict((c.byte, c) for c in chunks) + def _combine_finally_starts(self, starts, exits): + """Helper for building the cause of `finally` branches.""" + causes = [] + for lineno, cause in sorted(starts): + if cause is not None: + causes.append(cause.format(lineno=lineno)) + cause = " or ".join(causes) + exits = set(ArcStart(ex.lineno, cause) for ex in exits) + return exits - # Traverse from the first chunk in each line, and yield arcs where - # the trace function will be invoked. - for chunk in chunks: - if chunk.entrance: - yield (-1, chunk.line) + @contract(returns='ArcStarts') + def _handle__TryExcept(self, node): + # Python 2.7 uses separate TryExcept and TryFinally nodes. If we get + # TryExcept, it means there was no finally, so fake it, and treat as + # a general Try node. + node.finalbody = [] + return self._handle__Try(node) - if not chunk.first: - continue + @contract(returns='ArcStarts') + def _handle__TryFinally(self, node): + # Python 2.7 uses separate TryExcept and TryFinally nodes. If we get + # TryFinally, see if there's a TryExcept nested inside. If so, merge + # them. Otherwise, fake fields to complete a Try node. + node.handlers = [] + node.orelse = [] - chunks_considered = set() - chunks_to_consider = [chunk] - while chunks_to_consider: - # Get the chunk we're considering, and make sure we don't - # consider it again. - this_chunk = chunks_to_consider.pop() - chunks_considered.add(this_chunk) + first = node.body[0] + if first.__class__.__name__ == "TryExcept" and node.lineno == first.lineno: + assert len(node.body) == 1 + node.body = first.body + node.handlers = first.handlers + node.orelse = first.orelse + + return self._handle__Try(node) - # For each exit, add the line number if the trace function - # would be triggered, or add the chunk to those being - # considered if not. - for ex in this_chunk.exits: - if ex < 0: - yield (chunk.line, ex) - else: - next_chunk = byte_chunks[ex] - if next_chunk in chunks_considered: - continue + @contract(returns='ArcStarts') + def _handle__While(self, node): + constant_test = self.is_constant_expr(node.test) + start = to_top = self.line_for_node(node.test) + if constant_test: + to_top = self.line_for_node(node.body[0]) + self.block_stack.append(LoopBlock(start=start)) + from_start = ArcStart(start, cause="the condition on line {lineno} was never true") + exits = self.add_body_arcs(node.body, from_start=from_start) + for xit in exits: + self.add_arc(xit.lineno, to_top, xit.cause) + exits = set() + my_block = self.block_stack.pop() + exits.update(my_block.break_exits) + from_start = ArcStart(start, cause="the condition on line {lineno} was never false") + if node.orelse: + else_exits = self.add_body_arcs(node.orelse, from_start=from_start) + exits |= else_exits + else: + # No `else` clause: you can exit from the start. + if not constant_test: + exits.add(from_start) + return exits + + @contract(returns='ArcStarts') + def _handle__With(self, node): + start = self.line_for_node(node) + exits = self.add_body_arcs(node.body, from_start=ArcStart(start)) + return exits - # The trace function is invoked if visiting the first - # bytecode in a line, or if the transition is a - # backward jump. - backward_jump = next_chunk.byte < this_chunk.byte - if next_chunk.first or backward_jump: - if next_chunk.line != chunk.line: - yield (chunk.line, next_chunk.line) - else: - chunks_to_consider.append(next_chunk) + _handle__AsyncWith = _handle__With - def _all_chunks(self): - """Returns a list of `Chunk` objects for this code and its children. + def _code_object__Module(self, node): + start = self.line_for_node(node) + if node.body: + exits = self.add_body_arcs(node.body, from_start=ArcStart(-start)) + for xit in exits: + self.add_arc(xit.lineno, -start, xit.cause, "didn't exit the module") + else: + # Empty module. + self.add_arc(-start, start) + self.add_arc(start, -start) - See `_split_into_chunks` for details. + def _code_object__FunctionDef(self, node): + start = self.line_for_node(node) + self.block_stack.append(FunctionBlock(start=start, name=node.name)) + exits = self.add_body_arcs(node.body, from_start=ArcStart(-start)) + self.process_return_exits(exits) + self.block_stack.pop() + + _code_object__AsyncFunctionDef = _code_object__FunctionDef - """ - chunks = [] - for bp in self.child_parsers(): - chunks.extend(bp._split_into_chunks()) - - return chunks + def _code_object__ClassDef(self, node): + start = self.line_for_node(node) + self.add_arc(-start, start) + exits = self.add_body_arcs(node.body, from_start=ArcStart(start)) + for xit in exits: + self.add_arc( + xit.lineno, -start, xit.cause, + "didn't exit the body of class '{0}'".format(node.name), + ) - def _all_arcs(self): - """Get the set of all arcs in this code object and its children. - - See `_arcs` for details. + def _make_oneline_code_method(noun): # pylint: disable=no-self-argument + """A function to make methods for online callable _code_object__ methods.""" + def _code_object__oneline_callable(self, node): + start = self.line_for_node(node) + self.add_arc(-start, start, None, "didn't run the {0} on line {1}".format(noun, start)) + self.add_arc( + start, -start, None, + "didn't finish the {0} on line {1}".format(noun, start), + ) + return _code_object__oneline_callable - """ - arcs = set() - for bp in self.child_parsers(): - arcs.update(bp._arcs()) - - return arcs + _code_object__Lambda = _make_oneline_code_method("lambda") + _code_object__GeneratorExp = _make_oneline_code_method("generator expression") + _code_object__DictComp = _make_oneline_code_method("dictionary comprehension") + _code_object__SetComp = _make_oneline_code_method("set comprehension") + if env.PY3: + _code_object__ListComp = _make_oneline_code_method("list comprehension") -class Chunk(object): - """A sequence of byte codes with a single entrance. - - To analyze byte code, we have to divide it into chunks, sequences of byte - codes such that each chunk has only one entrance, the first instruction in - the block. - - This is almost the CS concept of `basic block`_, except that we're willing - to have many exits from a chunk, and "basic block" is a more cumbersome - term. +SKIP_DUMP_FIELDS = ["ctx"] - .. _basic block: http://en.wikipedia.org/wiki/Basic_block - - `byte` is the offset to the bytecode starting this chunk. - - `line` is the source line number containing this chunk. +def _is_simple_value(value): + """Is `value` simple enough to be displayed on a single line?""" + return ( + value in [None, [], (), {}, set()] or + isinstance(value, (string_class, int, float)) + ) - `first` is true if this is the first chunk in the source line. +# TODO: a test of ast_dump? +def ast_dump(node, depth=0): + """Dump the AST for `node`. - An exit < 0 means the chunk can leave the code (return). The exit is - the negative of the starting line number of the code block. - - The `entrance` attribute is a boolean indicating whether the code object - can be entered at this chunk. + This recursively walks the AST, printing a readable version. """ - def __init__(self, byte, line, first): - self.byte = byte - self.line = line - self.first = first - self.length = 0 - self.entrance = False - self.exits = set() + indent = " " * depth + if not isinstance(node, ast.AST): + print("{0}<{1} {2!r}>".format(indent, node.__class__.__name__, node)) + return + + lineno = getattr(node, "lineno", None) + if lineno is not None: + linemark = " @ {0}".format(node.lineno) + else: + linemark = "" + head = "{0}<{1}{2}".format(indent, node.__class__.__name__, linemark) - def __repr__(self): - return "<%d+%d @%d%s%s %r>" % ( - self.byte, - self.length, - self.line, - "!" if self.first else "", - "v" if self.entrance else "", - list(self.exits), - ) + named_fields = [ + (name, value) + for name, value in ast.iter_fields(node) + if name not in SKIP_DUMP_FIELDS + ] + if not named_fields: + print("{0}>".format(head)) + elif len(named_fields) == 1 and _is_simple_value(named_fields[0][1]): + field_name, value = named_fields[0] + print("{0} {1}: {2!r}>".format(head, field_name, value)) + else: + print(head) + if 0: + print("{0}# mro: {1}".format( + indent, ", ".join(c.__name__ for c in node.__class__.__mro__[1:]), + )) + next_indent = indent + " " + for field_name, value in named_fields: + prefix = "{0}{1}:".format(next_indent, field_name) + if _is_simple_value(value): + print("{0} {1!r}".format(prefix, value)) + elif isinstance(value, list): + print("{0} [".format(prefix)) + for n in value: + ast_dump(n, depth + 8) + print("{0}]".format(next_indent)) + else: + print(prefix) + ast_dump(value, depth + 8) + + print("{0}>".format(indent)) # # eflag: FileType = Python2
--- a/DebugClients/Python/coverage/phystokens.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/phystokens.py Sun Jul 24 12:01:01 2016 +0200 @@ -6,6 +6,7 @@ import codecs import keyword import re +import sys import token import tokenize @@ -91,8 +92,7 @@ line = [] col = 0 - # The \f is because of http://bugs.python.org/issue19035 - source = source.expandtabs(8).replace('\r\n', '\n').replace('\f', ' ') + source = source.expandtabs(8).replace('\r\n', '\n') tokgen = generate_tokens(source) for ttype, ttext, (_, scol), (_, ecol), _ in phys_tokens(tokgen): @@ -152,7 +152,7 @@ generate_tokens = CachedTokenizer().generate_tokens -COOKIE_RE = re.compile(r"^\s*#.*coding[:=]\s*([-\w.]+)", flags=re.MULTILINE) +COOKIE_RE = re.compile(r"^[ \t]*#.*coding[:=][ \t]*([-\w.]+)", flags=re.MULTILINE) @contract(source='bytes') def _source_encoding_py2(source): @@ -274,32 +274,23 @@ Python 2's compile() builtin has a stupid restriction: if the source string is Unicode, then it may not have a encoding declaration in it. Why not? - Who knows! + Who knows! It also decodes to utf8, and then tries to interpret those utf8 + bytes according to the encoding declaration. Why? Who knows! - This function catches that exception, neuters the coding declaration, and - compiles it anyway. + This function neuters the coding declaration, and compiles it. """ - try: - code = compile(source, filename, mode) - except SyntaxError as synerr: - if "coding declaration in unicode string" not in synerr.args[0].lower(): - raise - source = neuter_encoding_declaration(source) - code = compile(source, filename, mode) - + source = neuter_encoding_declaration(source) + if env.PY2 and isinstance(filename, unicode): + filename = filename.encode(sys.getfilesystemencoding(), "replace") + code = compile(source, filename, mode) return code @contract(source='unicode', returns='unicode') def neuter_encoding_declaration(source): - """Return `source`, with any encoding declaration neutered. - - This function will only ever be called on `source` that has an encoding - declaration, so some edge cases can be ignored. - - """ - source = COOKIE_RE.sub("# (deleted declaration)", source) + """Return `source`, with any encoding declaration neutered.""" + source = COOKIE_RE.sub("# (deleted declaration)", source, count=2) return source #
--- a/DebugClients/Python/coverage/pickle2json.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/pickle2json.py Sun Jul 24 12:01:01 2016 +0200 @@ -45,3 +45,6 @@ (options, args) = parser.parse_args() pickle2json(options.input_file, options.output_file) + +# +# eflag: FileType = Python2
--- a/DebugClients/Python/coverage/plugin.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/plugin.py Sun Jul 24 12:01:01 2016 +0200 @@ -324,11 +324,26 @@ executable line number to a count of how many exits it has. To be honest, this feels wrong, and should be refactored. Let me know - if you attempt to implement this... + if you attempt to implement this method in your plugin... """ return {} + def missing_arc_description(self, start, end, executed_arcs=None): # pylint: disable=unused-argument + """Provide an English sentence describing a missing arc. + + The `start` and `end` arguments are the line numbers of the missing + arc. Negative numbers indicate entering or exiting code objects. + + The `executed_arcs` argument is a set of line number pairs, the arcs + that were executed in this file. + + By default, this simply returns the string "Line {start} didn't jump + to {end}". + + """ + return "Line {start} didn't jump to line {end}".format(start=start, end=end) + def source_token_lines(self): """Generate a series of tokenized lines, one for each line in `source`. @@ -379,3 +394,6 @@ def __ge__(self, other): return self.filename >= other.filename + +# +# eflag: FileType = Python2
--- a/DebugClients/Python/coverage/plugin_support.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/plugin_support.py Sun Jul 24 12:01:01 2016 +0200 @@ -3,12 +3,15 @@ """Support for plugins.""" +import os import os.path import sys -from coverage.misc import CoverageException +from coverage.misc import CoverageException, isolate_module from coverage.plugin import CoveragePlugin, FileTracer, FileReporter +os = isolate_module(os) + class Plugins(object): """The currently loaded collection of coverage.py plugins.""" @@ -242,3 +245,6 @@ ret = list(self.reporter.source_token_lines()) self.debug.write("source_token_lines() --> %d tokens" % (len(ret),)) return ret + +# +# eflag: FileType = Python2
--- a/DebugClients/Python/coverage/python.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/python.py Sun Jul 24 12:01:01 2016 +0200 @@ -4,14 +4,19 @@ """Python source expertise for coverage.py""" import os.path +import types import zipimport from coverage import env, files -from coverage.misc import contract, expensive, NoSource, join_regex +from coverage.misc import ( + contract, CoverageException, expensive, NoSource, join_regex, isolate_module, +) from coverage.parser import PythonParser from coverage.phystokens import source_token_lines, source_encoding from coverage.plugin import FileReporter +os = isolate_module(os) + @contract(returns='bytes') def read_python_source(filename): @@ -48,6 +53,8 @@ # Couldn't find source. raise NoSource("No source for code: '%s'." % filename) + # Replace \f because of http://bugs.python.org/issue19035 + source = source.replace(b'\f', b' ') source = source.decode(source_encoding(source), "replace") # Python code should always end with a line with a newline. @@ -90,9 +97,15 @@ if hasattr(morf, '__file__'): filename = morf.__file__ + elif isinstance(morf, types.ModuleType): + # A module should have had .__file__, otherwise we can't use it. + # This could be a PEP-420 namespace package. + raise CoverageException("Module {0} has no file".format(morf)) else: filename = morf + filename = files.unicode_filename(filename) + # .pyc files should always refer to a .py instead. if filename.endswith(('.pyc', '.pyo')): filename = filename[:-1] @@ -104,6 +117,7 @@ if hasattr(morf, '__name__'): name = morf.__name__ name = name.replace(".", os.sep) + ".py" + name = files.unicode_filename(name) else: name = files.relative_filename(filename) self.relname = name @@ -113,6 +127,7 @@ self._statements = None self._excluded = None + @contract(returns='unicode') def relative_filename(self): return self.relname @@ -124,21 +139,16 @@ filename=self.filename, exclude=self.coverage._exclude_regex('exclude'), ) + self._parser.parse_source() return self._parser - @expensive def lines(self): """Return the line numbers of statements in the file.""" - if self._statements is None: - self._statements, self._excluded = self.parser.parse_source() - return self._statements + return self.parser.statements - @expensive def excluded_lines(self): """Return the line numbers of statements in the file.""" - if self._excluded is None: - self._statements, self._excluded = self.parser.parse_source() - return self._excluded + return self.parser.excluded def translate_lines(self, lines): return self.parser.translate_lines(lines) @@ -162,6 +172,9 @@ def exit_counts(self): return self.parser.exit_counts() + def missing_arc_description(self, start, end, executed_arcs=None): + return self.parser.missing_arc_description(start, end, executed_arcs) + @contract(returns='unicode') def source(self): if self._source is None: @@ -190,3 +203,6 @@ def source_token_lines(self): return source_token_lines(self.source()) + +# +# eflag: FileType = Python2
--- a/DebugClients/Python/coverage/pytracer.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/pytracer.py Sun Jul 24 12:01:01 2016 +0200 @@ -92,9 +92,12 @@ self.cur_file_dict = self.data[tracename] # The call event is really a "start frame" event, and happens for # function calls and re-entering generators. The f_lasti field is - # -1 for calls, and a real offset for generators. Use -1 as the + # -1 for calls, and a real offset for generators. Use <0 as the # line number for calls, and the real line number for generators. - self.last_line = -1 if (frame.f_lasti < 0) else frame.f_lineno + if frame.f_lasti < 0: + self.last_line = -frame.f_code.co_firstlineno + else: + self.last_line = frame.f_lineno elif event == 'line': # Record an executed line. if self.cur_file_dict is not None: @@ -134,7 +137,7 @@ def stop(self): """Stop this Tracer.""" self.stopped = True - if self.threading and self.thread != self.threading.currentThread(): + if self.threading and self.thread.ident != self.threading.currentThread().ident: # Called on a different thread than started us: we can't unhook # ourselves, but we've set the flag that we should stop, so we # won't do any more tracing. @@ -150,3 +153,6 @@ def get_stats(self): """Return a dictionary of statistics, or None.""" return None + +# +# eflag: FileType = Python2
--- a/DebugClients/Python/coverage/report.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/report.py Sun Jul 24 12:01:01 2016 +0200 @@ -4,9 +4,12 @@ """Reporter foundation for coverage.py.""" import os +import warnings from coverage.files import prep_patterns, FnmatchMatcher -from coverage.misc import CoverageException, NoSource, NotPython +from coverage.misc import CoverageException, NoSource, NotPython, isolate_module + +os = isolate_module(os) class Reporter(object): @@ -22,40 +25,45 @@ self.coverage = coverage self.config = config - # The FileReporters to report on. Set by find_file_reporters. - self.file_reporters = [] - # The directory into which to place the report, used by some derived # classes. self.directory = None + # Our method find_file_reporters used to set an attribute that other + # code could read. That's been refactored away, but some third parties + # were using that attribute. We'll continue to support it in a noisy + # way for now. + self._file_reporters = [] + + @property + def file_reporters(self): + """Keep .file_reporters working for private-grabbing tools.""" + warnings.warn( + "Report.file_reporters will no longer be available in Coverage.py 4.2", + DeprecationWarning, + ) + return self._file_reporters + def find_file_reporters(self, morfs): """Find the FileReporters we'll report on. `morfs` is a list of modules or file names. + Returns a list of FileReporters. + """ - self.file_reporters = self.coverage._get_file_reporters(morfs) + reporters = self.coverage._get_file_reporters(morfs) if self.config.include: - patterns = prep_patterns(self.config.include) - matcher = FnmatchMatcher(patterns) - filtered = [] - for fr in self.file_reporters: - if matcher.match(fr.filename): - filtered.append(fr) - self.file_reporters = filtered + matcher = FnmatchMatcher(prep_patterns(self.config.include)) + reporters = [fr for fr in reporters if matcher.match(fr.filename)] if self.config.omit: - patterns = prep_patterns(self.config.omit) - matcher = FnmatchMatcher(patterns) - filtered = [] - for fr in self.file_reporters: - if not matcher.match(fr.filename): - filtered.append(fr) - self.file_reporters = filtered + matcher = FnmatchMatcher(prep_patterns(self.config.omit)) + reporters = [fr for fr in reporters if not matcher.match(fr.filename)] - self.file_reporters.sort() + self._file_reporters = sorted(reporters) + return self._file_reporters def report_files(self, report_fn, morfs, directory=None): """Run a reporting function on a number of morfs. @@ -69,16 +77,16 @@ `analysis` is the `Analysis` for the morf. """ - self.find_file_reporters(morfs) + file_reporters = self.find_file_reporters(morfs) - if not self.file_reporters: + if not file_reporters: raise CoverageException("No data to report.") self.directory = directory if self.directory and not os.path.exists(self.directory): os.makedirs(self.directory) - for fr in self.file_reporters: + for fr in file_reporters: try: report_fn(fr, self.coverage._analyze(fr)) except NoSource:
--- a/DebugClients/Python/coverage/results.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/results.py Sun Jul 24 12:01:01 2016 +0200 @@ -30,9 +30,7 @@ self.no_branch = self.file_reporter.no_branch_lines() n_branches = self.total_branches() mba = self.missing_branch_arcs() - n_partial_branches = sum( - len(v) for k,v in iitems(mba) if k not in self.missing - ) + n_partial_branches = sum(len(v) for k,v in iitems(mba) if k not in self.missing) n_missing_branches = sum(len(v) for k,v in iitems(mba)) else: self._arc_possibilities = [] @@ -48,7 +46,7 @@ n_branches=n_branches, n_partial_branches=n_partial_branches, n_missing_branches=n_missing_branches, - ) + ) def missing_formatted(self): """The missing line numbers, formatted nicely. @@ -84,7 +82,7 @@ return sorted(missing) def arcs_missing_formatted(self): - """ The missing branch arcs, formatted nicely. + """The missing branch arcs, formatted nicely. Returns a string like "1->2, 1->3, 16->20". Omits any mention of branches from missing lines, so if line 17 is missing, then 17->18 @@ -98,7 +96,7 @@ for line, exits in line_exits: for ex in sorted(exits): if line not in missing: - pairs.append('%d->%d' % (line, ex)) + pairs.append("%d->%s" % (line, (ex if ex > 0 else "exit"))) return ', '.join(pairs) def arcs_unpredicted(self):
--- a/DebugClients/Python/coverage/summary.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/summary.py Sun Jul 24 12:01:01 2016 +0200 @@ -5,9 +5,10 @@ import sys +from coverage import env from coverage.report import Reporter from coverage.results import Numbers -from coverage.misc import NotPython, CoverageException +from coverage.misc import NotPython, CoverageException, output_encoding class SummaryReporter(Reporter): @@ -20,46 +21,53 @@ def report(self, morfs, outfile=None): """Writes a report summarizing coverage statistics per module. - `outfile` is a file object to write the summary to. + `outfile` is a file object to write the summary to. It must be opened + for native strings (bytes on Python 2, Unicode on Python 3). """ - self.find_file_reporters(morfs) + file_reporters = self.find_file_reporters(morfs) # Prepare the formatting strings - max_name = max([len(fr.relative_filename()) for fr in self.file_reporters] + [5]) - fmt_name = "%%- %ds " % max_name - fmt_err = "%s %s: %s\n" - fmt_skip_covered = "\n%s file%s skipped due to complete coverage.\n" + max_name = max([len(fr.relative_filename()) for fr in file_reporters] + [5]) + fmt_name = u"%%- %ds " % max_name + fmt_err = u"%s %s: %s" + fmt_skip_covered = u"\n%s file%s skipped due to complete coverage." - header = (fmt_name % "Name") + " Stmts Miss" - fmt_coverage = fmt_name + "%6d %6d" + header = (fmt_name % "Name") + u" Stmts Miss" + fmt_coverage = fmt_name + u"%6d %6d" if self.branches: - header += " Branch BrPart" - fmt_coverage += " %6d %6d" + header += u" Branch BrPart" + fmt_coverage += u" %6d %6d" width100 = Numbers.pc_str_width() - header += "%*s" % (width100+4, "Cover") - fmt_coverage += "%%%ds%%%%" % (width100+3,) + header += u"%*s" % (width100+4, "Cover") + fmt_coverage += u"%%%ds%%%%" % (width100+3,) if self.config.show_missing: - header += " Missing" - fmt_coverage += " %s" - rule = "-" * len(header) + "\n" - header += "\n" - fmt_coverage += "\n" + header += u" Missing" + fmt_coverage += u" %s" + rule = u"-" * len(header) - if not outfile: + if outfile is None: outfile = sys.stdout + def writeout(line): + """Write a line to the output, adding a newline.""" + if env.PY2: + line = line.encode(output_encoding()) + outfile.write(line.rstrip()) + outfile.write("\n") + # Write the header - outfile.write(header) - outfile.write(rule) + writeout(header) + writeout(rule) total = Numbers() skipped_count = 0 - for fr in self.file_reporters: + for fr in file_reporters: try: analysis = self.coverage._analyze(fr) nums = analysis.numbers + total += nums if self.config.skip_covered: # Don't report on 100% files. @@ -82,8 +90,7 @@ missing_fmtd += ", " missing_fmtd += branches_fmtd args += (missing_fmtd,) - outfile.write(fmt_coverage % args) - total += nums + writeout(fmt_coverage % args) except Exception: report_it = not self.config.ignore_errors if report_it: @@ -93,23 +100,23 @@ if typ is NotPython and not fr.should_be_python(): report_it = False if report_it: - outfile.write(fmt_err % (fr.relative_filename(), typ.__name__, msg)) + writeout(fmt_err % (fr.relative_filename(), typ.__name__, msg)) if total.n_files > 1: - outfile.write(rule) + writeout(rule) args = ("TOTAL", total.n_statements, total.n_missing) if self.branches: args += (total.n_branches, total.n_partial_branches) args += (total.pc_covered_str,) if self.config.show_missing: args += ("",) - outfile.write(fmt_coverage % args) + writeout(fmt_coverage % args) if not total.n_files and not skipped_count: raise CoverageException("No data to report.") if self.config.skip_covered and skipped_count: - outfile.write(fmt_skip_covered % (skipped_count, 's' if skipped_count > 1 else '')) + writeout(fmt_skip_covered % (skipped_count, 's' if skipped_count > 1 else '')) return total.n_statements and total.pc_covered
--- a/DebugClients/Python/coverage/templite.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/templite.py Sun Jul 24 12:01:01 2016 +0200 @@ -90,6 +90,9 @@ {# This will be ignored #} + Any of these constructs can have a hypen at the end (`-}}`, `-%}`, `-#}`), + which will collapse the whitespace following the tag. + Construct a Templite with the template text, then use `render` against a dictionary context to create a finished string:: @@ -151,53 +154,64 @@ # Split the text to form a list of tokens. tokens = re.split(r"(?s)({{.*?}}|{%.*?%}|{#.*?#})", text) + squash = False + for token in tokens: - if token.startswith('{#'): - # Comment: ignore it and move on. - continue - elif token.startswith('{{'): - # An expression to evaluate. - expr = self._expr_code(token[2:-2].strip()) - buffered.append("to_str(%s)" % expr) - elif token.startswith('{%'): - # Action tag: split into words and parse further. - flush_output() - words = token[2:-2].strip().split() - if words[0] == 'if': - # An if statement: evaluate the expression to determine if. - if len(words) != 2: - self._syntax_error("Don't understand if", token) - ops_stack.append('if') - code.add_line("if %s:" % self._expr_code(words[1])) - code.indent() - elif words[0] == 'for': - # A loop: iterate over expression result. - if len(words) != 4 or words[2] != 'in': - self._syntax_error("Don't understand for", token) - ops_stack.append('for') - self._variable(words[1], self.loop_vars) - code.add_line( - "for c_%s in %s:" % ( - words[1], - self._expr_code(words[3]) + if token.startswith('{'): + start, end = 2, -2 + squash = (token[-3] == '-') + if squash: + end = -3 + + if token.startswith('{#'): + # Comment: ignore it and move on. + continue + elif token.startswith('{{'): + # An expression to evaluate. + expr = self._expr_code(token[start:end].strip()) + buffered.append("to_str(%s)" % expr) + elif token.startswith('{%'): + # Action tag: split into words and parse further. + flush_output() + + words = token[start:end].strip().split() + if words[0] == 'if': + # An if statement: evaluate the expression to determine if. + if len(words) != 2: + self._syntax_error("Don't understand if", token) + ops_stack.append('if') + code.add_line("if %s:" % self._expr_code(words[1])) + code.indent() + elif words[0] == 'for': + # A loop: iterate over expression result. + if len(words) != 4 or words[2] != 'in': + self._syntax_error("Don't understand for", token) + ops_stack.append('for') + self._variable(words[1], self.loop_vars) + code.add_line( + "for c_%s in %s:" % ( + words[1], + self._expr_code(words[3]) + ) ) - ) - code.indent() - elif words[0].startswith('end'): - # Endsomething. Pop the ops stack. - if len(words) != 1: - self._syntax_error("Don't understand end", token) - end_what = words[0][3:] - if not ops_stack: - self._syntax_error("Too many ends", token) - start_what = ops_stack.pop() - if start_what != end_what: - self._syntax_error("Mismatched end tag", end_what) - code.dedent() - else: - self._syntax_error("Don't understand tag", words[0]) + code.indent() + elif words[0].startswith('end'): + # Endsomething. Pop the ops stack. + if len(words) != 1: + self._syntax_error("Don't understand end", token) + end_what = words[0][3:] + if not ops_stack: + self._syntax_error("Too many ends", token) + start_what = ops_stack.pop() + if start_what != end_what: + self._syntax_error("Mismatched end tag", end_what) + code.dedent() + else: + self._syntax_error("Don't understand tag", words[0]) else: # Literal content. If it isn't empty, output it. + if squash: + token = token.lstrip() if token: buffered.append(repr(token))
--- a/DebugClients/Python/coverage/test_helpers.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/test_helpers.py Sun Jul 24 12:01:01 2016 +0200 @@ -162,20 +162,20 @@ # nose keeps stdout from littering the screen, so we can safely Tee it, # but it doesn't capture stderr, so we don't want to Tee stderr to the # real stderr, since it will interfere with our nice field of dots. - self.old_stdout = sys.stdout + old_stdout = sys.stdout self.captured_stdout = StringIO() sys.stdout = Tee(sys.stdout, self.captured_stdout) - self.old_stderr = sys.stderr + old_stderr = sys.stderr self.captured_stderr = StringIO() sys.stderr = self.captured_stderr - self.addCleanup(self.cleanup_std_streams) + self.addCleanup(self.cleanup_std_streams, old_stdout, old_stderr) - def cleanup_std_streams(self): + def cleanup_std_streams(self, old_stdout, old_stderr): """Restore stdout and stderr.""" - sys.stdout = self.old_stdout - sys.stderr = self.old_stderr + sys.stdout = old_stdout + sys.stderr = old_stderr def stdout(self): """Return the data written to stdout during the test.""" @@ -186,6 +186,59 @@ return self.captured_stderr.getvalue() +class DelayedAssertionMixin(TestCase): + """A test case mixin that provides a `delayed_assertions` context manager. + + Use it like this:: + + with self.delayed_assertions(): + self.assertEqual(x, y) + self.assertEqual(z, w) + + All of the assertions will run. The failures will be displayed at the end + of the with-statement. + + NOTE: this only works with some assertions. These are known to work: + + - `assertEqual(str, str)` + + - `assertMultilineEqual(str, str)` + + """ + def __init__(self, *args, **kwargs): + super(DelayedAssertionMixin, self).__init__(*args, **kwargs) + # This mixin only works with assert methods that call `self.fail`. In + # Python 2.7, `assertEqual` didn't, but we can do what Python 3 does, + # and use `assertMultiLineEqual` for comparing strings. + self.addTypeEqualityFunc(str, 'assertMultiLineEqual') + self._delayed_assertions = None + + @contextlib.contextmanager + def delayed_assertions(self): + """The context manager: assert that we didn't collect any assertions.""" + self._delayed_assertions = [] + old_fail = self.fail + self.fail = self._delayed_fail + try: + yield + finally: + self.fail = old_fail + if self._delayed_assertions: + if len(self._delayed_assertions) == 1: + self.fail(self._delayed_assertions[0]) + else: + self.fail( + "{0} failed assertions:\n{1}".format( + len(self._delayed_assertions), + "\n".join(self._delayed_assertions), + ) + ) + + def _delayed_fail(self, msg=None): + """The stand-in for TestCase.fail during delayed_assertions.""" + self._delayed_assertions.append(msg) + + class TempDirMixin(SysPathAwareMixin, ModuleAwareMixin, TestCase): """A test case mixin that creates a temp directory and files in it. @@ -335,3 +388,6 @@ # When the process ends, find out about bad classes. atexit.register(TempDirMixin.report_on_class_behavior) + +# +# eflag: FileType = Python2
--- a/DebugClients/Python/coverage/version.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/version.py Sun Jul 24 12:01:01 2016 +0200 @@ -5,7 +5,7 @@ # This file is exec'ed in setup.py, don't import anything! # Same semantics as sys.version_info. -version_info = (4, 0, 0, 'final', 0) +version_info = (4, 1, 0, 'final', 0) def _make_version(major, minor, micro, releaselevel, serial): @@ -22,7 +22,7 @@ def _make_url(major, minor, micro, releaselevel, serial): """Make the URL people should start at for this version of coverage.py.""" - url = "https://coverage.readthedocs.org" + url = "https://coverage.readthedocs.io" if releaselevel != 'final': # For pre-releases, use a version-specific URL. url += "/en/coverage-" + _make_version(major, minor, micro, releaselevel, serial)
--- a/DebugClients/Python/coverage/xmlreport.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python/coverage/xmlreport.py Sun Jul 24 12:01:01 2016 +0200 @@ -4,13 +4,20 @@ """XML reporting for coverage.py""" import os +import os.path import sys import time import xml.dom.minidom +from coverage import env from coverage import __url__, __version__, files +from coverage.backward import iitems +from coverage.misc import isolate_module from coverage.report import Reporter +os = isolate_module(os) + + DTD_URL = ( 'https://raw.githubusercontent.com/cobertura/web/' 'f0366e5e2cf18f111cbd61fc34ef720a6584ba02' @@ -33,6 +40,10 @@ super(XmlReporter, self).__init__(coverage, config) self.source_paths = set() + if config.source: + for src in config.source: + if os.path.exists(src): + self.source_paths.add(files.canonical_filename(src)) self.packages = {} self.xml_out = None self.has_arcs = coverage.data.has_arcs() @@ -81,15 +92,14 @@ xcoverage.appendChild(xpackages) # Populate the XML DOM with the package info. - for pkg_name in sorted(self.packages.keys()): - pkg_data = self.packages[pkg_name] + for pkg_name, pkg_data in sorted(iitems(self.packages)): class_elts, lhits, lnum, bhits, bnum = pkg_data xpackage = self.xml_out.createElement("package") xpackages.appendChild(xpackage) xclasses = self.xml_out.createElement("classes") xpackage.appendChild(xclasses) - for class_name in sorted(class_elts.keys()): - xclasses.appendChild(class_elts[class_name]) + for _, class_elt in sorted(iitems(class_elts)): + xclasses.appendChild(class_elt) xpackage.setAttribute("name", pkg_name.replace(os.sep, '.')) xpackage.setAttribute("line-rate", rate(lhits, lnum)) if self.has_arcs: @@ -112,7 +122,10 @@ xcoverage.setAttribute("branch-rate", branch_rate) # Use the DOM to write the output file. - outfile.write(self.xml_out.toprettyxml()) + out = self.xml_out.toprettyxml() + if env.PY2: + out = out.encode("utf8") + outfile.write(out) # Return the total percentage. denom = lnum_tot + bnum_tot @@ -127,15 +140,20 @@ # Create the 'lines' and 'package' XML elements, which # are populated later. Note that a package == a directory. - filename = fr.relative_filename() - filename = filename.replace("\\", "/") - dirname = os.path.dirname(filename) or "." - parts = dirname.split("/") - dirname = "/".join(parts[:self.config.xml_package_depth]) + filename = fr.filename.replace("\\", "/") + for source_path in self.source_paths: + if filename.startswith(source_path.replace("\\", "/") + "/"): + rel_name = filename[len(source_path)+1:] + break + else: + rel_name = fr.relative_filename() + + dirname = os.path.dirname(rel_name) or "." + dirname = "/".join(dirname.split("/")[:self.config.xml_package_depth]) package_name = dirname.replace("/", ".") - className = fr.relative_filename() - self.source_paths.add(files.relative_directory().rstrip('/')) + if rel_name != fr.filename: + self.source_paths.add(fr.filename[:-len(rel_name)].rstrip(r"\/")) package = self.packages.setdefault(package_name, [{}, 0, 0, 0, 0]) xclass = self.xml_out.createElement("class") @@ -145,8 +163,8 @@ xlines = self.xml_out.createElement("lines") xclass.appendChild(xlines) - xclass.setAttribute("name", os.path.relpath(filename, dirname)) - xclass.setAttribute("filename", filename) + xclass.setAttribute("name", os.path.relpath(rel_name, dirname)) + xclass.setAttribute("filename", fr.relative_filename().replace("\\", "/")) xclass.setAttribute("complexity", "0") branch_stats = analysis.branch_stats() @@ -193,7 +211,7 @@ branch_rate = "0" xclass.setAttribute("branch-rate", branch_rate) - package[0][className] = xclass + package[0][rel_name] = xclass package[1] += class_hits package[2] += class_lines package[3] += class_br_hits
--- a/DebugClients/Python3/coverage/__init__.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python3/coverage/__init__.py Sun Jul 24 12:01:01 2016 +0200 @@ -14,6 +14,7 @@ from coverage.data import CoverageData from coverage.misc import CoverageException from coverage.plugin import CoveragePlugin, FileTracer, FileReporter +from coverage.pytracer import PyTracer # Backward compatibility. coverage = Coverage
--- a/DebugClients/Python3/coverage/annotate.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python3/coverage/annotate.py Sun Jul 24 12:01:01 2016 +0200 @@ -8,8 +8,12 @@ import re from coverage.files import flat_rootname +from coverage.misc import isolate_module from coverage.report import Reporter +os = isolate_module(os) + + class AnnotateReporter(Reporter): """Generate annotated source files showing line coverage.
--- a/DebugClients/Python3/coverage/backward.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python3/coverage/backward.py Sun Jul 24 12:01:01 2016 +0200 @@ -49,6 +49,15 @@ except NameError: range = range +# shlex.quote is new, but there's an undocumented implementation in "pipes", +# who knew!? +try: + from shlex import quote as shlex_quote +except ImportError: + # Useful function, available under a different (undocumented) name + # in Python versions earlier than 3.3. + from pipes import quote as shlex_quote + # A function to iterate listlessly over a dict's items. try: {}.iteritems @@ -84,10 +93,6 @@ """Produce a byte string with the ints from `byte_values`.""" return bytes(byte_values) - def byte_to_int(byte_value): - """Turn an element of a bytes object into an int.""" - return byte_value - def bytes_to_ints(bytes_value): """Turn a bytes object into a sequence of ints.""" # In Python 3, iterating bytes gives ints. @@ -102,10 +107,6 @@ """Produce a byte string with the ints from `byte_values`.""" return "".join(chr(b) for b in byte_values) - def byte_to_int(byte_value): - """Turn an element of a bytes object into an int.""" - return ord(byte_value) - def bytes_to_ints(bytes_value): """Turn a bytes object into a sequence of ints.""" for byte in bytes_value: @@ -142,11 +143,12 @@ PYC_MAGIC_NUMBER = imp.get_magic() -def import_local_file(modname): +def import_local_file(modname, modfile=None): """Import a local file as a module. Opens a file in the current directory named `modname`.py, imports it - as `modname`, and returns the module object. + as `modname`, and returns the module object. `modfile` is the file to + import if it isn't in the current directory. """ try: @@ -154,7 +156,8 @@ except ImportError: SourceFileLoader = None - modfile = modname + '.py' + if modfile is None: + modfile = modname + '.py' if SourceFileLoader: mod = SourceFileLoader(modname, modfile).load_module() else:
--- a/DebugClients/Python3/coverage/bytecode.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python3/coverage/bytecode.py Sun Jul 24 12:01:01 2016 +0200 @@ -3,73 +3,8 @@ """Bytecode manipulation for coverage.py""" -import opcode import types -from coverage.backward import byte_to_int - - -class ByteCode(object): - """A single bytecode.""" - def __init__(self): - # The offset of this bytecode in the code object. - self.offset = -1 - - # The opcode, defined in the `opcode` module. - self.op = -1 - - # The argument, a small integer, whose meaning depends on the opcode. - self.arg = -1 - - # The offset in the code object of the next bytecode. - self.next_offset = -1 - - # The offset to jump to. - self.jump_to = -1 - - -class ByteCodes(object): - """Iterator over byte codes in `code`. - - This handles the logic of EXTENDED_ARG byte codes internally. Those byte - codes are not returned by this iterator. - - Returns `ByteCode` objects. - - """ - def __init__(self, code): - self.code = code - - def __getitem__(self, i): - return byte_to_int(self.code[i]) - - def __iter__(self): - offset = 0 - ext_arg = 0 - while offset < len(self.code): - bc = ByteCode() - bc.op = self[offset] - bc.offset = offset - - next_offset = offset+1 - if bc.op >= opcode.HAVE_ARGUMENT: - bc.arg = ext_arg + self[offset+1] + 256*self[offset+2] - next_offset += 2 - - label = -1 - if bc.op in opcode.hasjrel: - label = next_offset + bc.arg - elif bc.op in opcode.hasjabs: - label = bc.arg - bc.jump_to = label - - bc.next_offset = offset = next_offset - if bc.op == opcode.EXTENDED_ARG: - ext_arg = bc.arg * 256*256 - else: - ext_arg = 0 - yield bc - class CodeObjects(object): """Iterate over all the code objects in `code`."""
--- a/DebugClients/Python3/coverage/cmdline.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python3/coverage/cmdline.py Sun Jul 24 12:01:01 2016 +0200 @@ -7,9 +7,11 @@ import optparse import os.path import sys +import textwrap import traceback from coverage import env +from coverage.collector import CTracer from coverage.execfile import run_python_file, run_python_module from coverage.misc import CoverageException, ExceptionDuringRun, NoSource from coverage.debug import info_formatter, info_header @@ -20,105 +22,117 @@ append = optparse.make_option( '-a', '--append', action='store_true', - help="Append coverage data to .coverage, otherwise it is started " - "clean with each run." - ) + help="Append coverage data to .coverage, otherwise it is started clean with each run.", + ) branch = optparse.make_option( '', '--branch', action='store_true', - help="Measure branch coverage in addition to statement coverage." - ) + help="Measure branch coverage in addition to statement coverage.", + ) CONCURRENCY_CHOICES = [ "thread", "gevent", "greenlet", "eventlet", "multiprocessing", ] concurrency = optparse.make_option( '', '--concurrency', action='store', metavar="LIB", choices=CONCURRENCY_CHOICES, - help="Properly measure code using a concurrency library. " - "Valid values are: %s." % ", ".join(CONCURRENCY_CHOICES) - ) + help=( + "Properly measure code using a concurrency library. " + "Valid values are: %s." + ) % ", ".join(CONCURRENCY_CHOICES), + ) debug = optparse.make_option( '', '--debug', action='store', metavar="OPTS", - help="Debug options, separated by commas" - ) + help="Debug options, separated by commas", + ) directory = optparse.make_option( '-d', '--directory', action='store', metavar="DIR", - help="Write the output files to DIR." - ) + help="Write the output files to DIR.", + ) 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="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." - ) + help="Get help on this command.", + ) ignore_errors = optparse.make_option( '-i', '--ignore-errors', action='store_true', - help="Ignore errors while reading source files." - ) + help="Ignore errors while reading source files.", + ) include = optparse.make_option( '', '--include', action='store', metavar="PAT1,PAT2,...", - help="Include only files whose paths match one of these patterns. " - "Accepts shell-style wildcards, which must be quoted." - ) + help=( + "Include only files whose paths match one of these patterns. " + "Accepts shell-style wildcards, which must be quoted." + ), + ) pylib = optparse.make_option( '-L', '--pylib', action='store_true', - help="Measure coverage even inside the Python installed library, " - "which isn't done by default." - ) + help=( + "Measure coverage even inside the Python installed library, " + "which isn't done by default." + ), + ) show_missing = optparse.make_option( '-m', '--show-missing', action='store_true', - help="Show line numbers of statements in each module that weren't " - "executed." - ) + help="Show line numbers of statements in each module that weren't executed.", + ) skip_covered = optparse.make_option( '--skip-covered', action='store_true', - help="Skip files with 100% coverage." - ) + help="Skip files with 100% coverage.", + ) omit = optparse.make_option( '', '--omit', action='store', metavar="PAT1,PAT2,...", - help="Omit files whose paths match one of these patterns. " - "Accepts shell-style wildcards, which must be quoted." - ) + help=( + "Omit files whose paths match one of these patterns. " + "Accepts shell-style wildcards, which must be quoted." + ), + ) output_xml = optparse.make_option( '-o', '', action='store', dest="outfile", metavar="OUTFILE", - help="Write the XML report to this file. Defaults to 'coverage.xml'" - ) + help="Write the XML report to this file. Defaults to 'coverage.xml'", + ) parallel_mode = optparse.make_option( '-p', '--parallel-mode', action='store_true', - help="Append the machine name, process id and random number to the " - ".coverage data file name to simplify collecting data from " - "many processes." - ) + 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." - ) + help=( + "<pyfile> is an importable Python module, not a script path, " + "to be run as 'python -m' would run it." + ), + ) rcfile = optparse.make_option( '', '--rcfile', action='store', - help="Specify configuration file. Defaults to '.coveragerc'" - ) + 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." - ) + 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!" - ) + help=( + "Use a simpler but slower trace method. Try this if you get " + "seemingly impossible results!" + ), + ) title = optparse.make_option( '', '--title', action='store', metavar="TITLE", - help="A text string to use as the title on the HTML." - ) + 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." - ) + help="Display version information and exit.", + ) class CoverageOptionParser(optparse.OptionParser, object): @@ -202,9 +216,7 @@ class CmdOptionParser(CoverageOptionParser): """Parse one of the new-style commands for coverage.py.""" - def __init__(self, action, options=None, defaults=None, usage=None, - description=None - ): + def __init__(self, action, options=None, defaults=None, usage=None, description=None): """Create an OptionParser for a coverage.py command. `action` is the slug to put into `options.action`. @@ -217,7 +229,6 @@ if usage: usage = "%prog " + usage super(CmdOptionParser, self).__init__( - prog="coverage %s" % action, usage=usage, description=description, ) @@ -231,6 +242,14 @@ # results, and they will compare equal to objects. return (other == "<CmdOptionParser:%s>" % self.cmd) + def get_prog_name(self): + """Override of an undocumented function in optparse.OptionParser.""" + program_name = super(CmdOptionParser, self).get_prog_name() + + # Include the sub-command for this parser as part of the command. + return "%(command)s %(subcommand)s" % {'command': program_name, 'subcommand': self.cmd} + + GLOBAL_ARGS = [ Opts.debug, Opts.help, @@ -238,48 +257,60 @@ ] CMDS = { - 'annotate': CmdOptionParser("annotate", + 'annotate': CmdOptionParser( + "annotate", [ Opts.directory, Opts.ignore_errors, Opts.include, Opts.omit, ] + 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 !." + usage="[options] [modules]", + description=( + "Make annotated copies of the given files, marking statements that are executed " + "with > and statements that are missed with !." ), + ), - 'combine': CmdOptionParser("combine", GLOBAL_ARGS, - usage = "<path1> <path2> ... <pathN>", - description = "Combine data from multiple coverage files collected " + 'combine': CmdOptionParser( + "combine", + GLOBAL_ARGS, + usage="<path1> <path2> ... <pathN>", + 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. The positional " "arguments are data files or directories containing data files. " "If no paths are provided, data files in the default data file's " "directory are combined." ), + ), - 'debug': CmdOptionParser("debug", GLOBAL_ARGS, - usage = "<topic>", - description = "Display information on the internals of coverage.py, " + '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." - ), + '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" - ), + 'help': CmdOptionParser( + "help", GLOBAL_ARGS, + usage="[command]", + description="Describe how to use coverage.py", + ), - 'html': CmdOptionParser("html", + 'html': CmdOptionParser( + "html", [ Opts.directory, Opts.fail_under, @@ -288,13 +319,16 @@ Opts.omit, Opts.title, ] + GLOBAL_ARGS, - usage = "[options] [modules]", - description = "Create an HTML report of the coverage of the files. " + 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." ), + ), - 'report': CmdOptionParser("report", + 'report': CmdOptionParser( + "report", [ Opts.fail_under, Opts.ignore_errors, @@ -303,11 +337,12 @@ Opts.show_missing, Opts.skip_covered, ] + GLOBAL_ARGS, - usage = "[options] [modules]", - description = "Report coverage statistics on modules." - ), + usage="[options] [modules]", + description="Report coverage statistics on modules." + ), - 'run': CmdOptionParser("run", + 'run': CmdOptionParser( + "run", [ Opts.append, Opts.branch, @@ -320,11 +355,12 @@ Opts.source, Opts.timid, ] + GLOBAL_ARGS, - usage = "[options] <pyfile> [program options]", - description = "Run a Python program, measuring code execution." - ), + usage="[options] <pyfile> [program options]", + description="Run a Python program, measuring code execution." + ), - 'xml': CmdOptionParser("xml", + 'xml': CmdOptionParser( + "xml", [ Opts.fail_under, Opts.ignore_errors, @@ -332,10 +368,10 @@ Opts.omit, Opts.output_xml, ] + GLOBAL_ARGS, - usage = "[options] [modules]", - description = "Generate an XML report of coverage results." - ), - } + usage="[options] [modules]", + description="Generate an XML report of coverage results." + ), +} OK, ERR, FAIL_UNDER = 0, 1, 2 @@ -362,6 +398,17 @@ self.coverage = None + self.program_name = os.path.basename(sys.argv[0]) + if env.WINDOWS: + # entry_points={'console_scripts':...} on Windows makes files + # called coverage.exe, coverage3.exe, and coverage-3.5.exe. These + # invoke coverage-script.py, coverage3-script.py, and + # coverage-3.5-script.py. argv[0] is the .py file, but we want to + # get back to the original form. + auto_suffix = "-script.py" + if self.program_name.endswith(auto_suffix): + self.program_name = self.program_name[:-len(auto_suffix)] + def command_line(self, argv): """The bulk of the command line interface to coverage.py. @@ -412,16 +459,16 @@ # 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, - concurrency = options.concurrency, + 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, + concurrency=options.concurrency, ) if options.action == "debug": @@ -443,10 +490,10 @@ # Remaining actions are reporting, with some common options. report_args = dict( - morfs = unglob_args(args), - ignore_errors = options.ignore_errors, - omit = omit, - include = include, + morfs=unglob_args(args), + ignore_errors=options.ignore_errors, + omit=omit, + include=include, ) self.coverage.load() @@ -495,13 +542,19 @@ assert error or topic or parser if error: print(error) - print("Use 'coverage help' for help.") + print("Use '%s help' for help." % (self.program_name,)) elif parser: print(parser.format_help().strip()) else: - help_msg = HELP_TOPICS.get(topic, '').strip() + help_params = dict(self.covpkg.__dict__) + help_params['program_name'] = self.program_name + if CTracer is not None: + help_params['extension_modifier'] = 'with C extension' + else: + help_params['extension_modifier'] = 'without C extension' + help_msg = textwrap.dedent(HELP_TOPICS.get(topic, '')).strip() if help_msg: - print(help_msg % self.covpkg.__dict__) + print(help_msg.format(**help_params)) else: print("Don't know topic %r" % topic) @@ -650,35 +703,34 @@ HELP_TOPICS = { -# ------------------------- -'help': """\ -Coverage.py, version %(__version__)s -Measure, collect, and report on code coverage in Python programs. + 'help': """\ + Coverage.py, version {__version__} {extension_modifier} + Measure, collect, and report on code coverage in Python programs. -usage: coverage <command> [options] [args] + usage: {program_name} <command> [options] [args] -Commands: - annotate Annotate source files with execution information. - combine Combine a number of data files. - erase Erase previously collected coverage data. - help Get help on using coverage.py. - html Create an HTML report. - report Report coverage stats on modules. - run Run a Python program and measure code execution. - xml Create an XML report of coverage results. + Commands: + annotate Annotate source files with execution information. + combine Combine a number of data files. + erase Erase previously collected coverage data. + help Get help on using coverage.py. + html Create an HTML report. + report Report coverage stats on modules. + run Run a Python program and measure code execution. + xml Create an XML report of coverage results. -Use "coverage help <command>" for detailed help on any command. -For full documentation, see %(__url__)s -""", -# ------------------------- -'minimum_help': """\ -Code coverage for Python. Use 'coverage help' for help. -""", -# ------------------------- -'version': """\ -Coverage.py, version %(__version__)s. -Documentation at %(__url__)s -""", + Use "{program_name} help <command>" for detailed help on any command. + For full documentation, see {__url__} + """, + + 'minimum_help': """\ + Code coverage for Python. Use '{program_name} help' for help. + """, + + 'version': """\ + Coverage.py, version {__version__} {extension_modifier} + Documentation at {__url__} + """, }
--- a/DebugClients/Python3/coverage/collector.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python3/coverage/collector.py Sun Jul 24 12:01:01 2016 +0200 @@ -3,14 +3,18 @@ """Raw data collector for coverage.py.""" -import os, sys +import os +import sys from coverage import env from coverage.backward import iitems from coverage.files import abs_file -from coverage.misc import CoverageException +from coverage.misc import CoverageException, isolate_module from coverage.pytracer import PyTracer +os = isolate_module(os) + + try: # Use the C extension code when we can, for speed. from coverage.tracer import CTracer, CFileDisposition # pylint: disable=no-name-in-module @@ -33,6 +37,13 @@ pass +def should_start_context(frame): + """Who-Tests-What hack: Determine whether this frame begins a new who-context.""" + fn_name = frame.f_code.co_name + if fn_name.startswith("test"): + return fn_name + + class Collector(object): """Collects trace data. @@ -112,6 +123,10 @@ "Couldn't trace with concurrency=%s, the module isn't installed." % concurrency ) + # Who-Tests-What is just a hack at the moment, so turn it on with an + # environment variable. + self.wtw = int(os.getenv('COVERAGE_WTW', 0)) + self.reset() if timid: @@ -143,6 +158,10 @@ # pairs as keys (if branch coverage). self.data = {} + # A dict mapping contexts to data dictionaries. + self.contexts = {} + self.contexts[None] = self.data + # A dictionary mapping file names to file tracer plugin names that will # handle them. self.file_tracers = {} @@ -202,6 +221,11 @@ tracer.threading = self.threading if hasattr(tracer, 'check_include'): tracer.check_include = self.check_include + if self.wtw: + if hasattr(tracer, 'should_start_context'): + tracer.should_start_context = should_start_context + if hasattr(tracer, 'switch_context'): + tracer.switch_context = self.switch_context fn = tracer.start() self.tracers.append(tracer) @@ -290,7 +314,7 @@ if stats: print("\nCoverage.py tracer stats:") for k in sorted(stats.keys()): - print("%16s: %s" % (k, stats[k])) + print("%20s: %s" % (k, stats[k])) if self.threading: self.threading.settrace(None) @@ -303,6 +327,14 @@ else: self._start_tracer() + def switch_context(self, new_context): + """Who-Tests-What hack: switch to a new who-context.""" + # Make a new data dict, or find the existing one, and switch all the + # tracers to use it. + data = self.contexts.setdefault(new_context, {}) + for tracer in self.tracers: + tracer.data = data + def save_data(self, covdata): """Save the collected data to a `CoverageData`. @@ -319,4 +351,11 @@ covdata.add_lines(abs_file_dict(self.data)) covdata.add_file_tracers(abs_file_dict(self.file_tracers)) + if self.wtw: + # Just a hack, so just hack it. + import pprint + out_file = "coverage_wtw_{:06}.py".format(os.getpid()) + with open(out_file, "w") as wtw_out: + pprint.pprint(self.contexts, wtw_out) + self.reset()
--- a/DebugClients/Python3/coverage/config.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python3/coverage/config.py Sun Jul 24 12:01:01 2016 +0200 @@ -9,7 +9,9 @@ import sys from coverage.backward import configparser, iitems, string_class -from coverage.misc import CoverageException +from coverage.misc import CoverageException, isolate_module + +os = isolate_module(os) class HandyConfigParser(configparser.RawConfigParser):
--- a/DebugClients/Python3/coverage/control.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python3/coverage/control.py Sun Jul 24 12:01:01 2016 +0200 @@ -7,6 +7,7 @@ import inspect import os import platform +import re import sys import traceback @@ -22,7 +23,7 @@ from coverage.files import ModuleMatcher, abs_file from coverage.html import HtmlReporter from coverage.misc import CoverageException, bool_or_none, join_regex -from coverage.misc import file_be_gone +from coverage.misc import file_be_gone, isolate_module from coverage.monkey import patch_multiprocessing from coverage.plugin import FileReporter from coverage.plugin_support import Plugins @@ -31,6 +32,7 @@ from coverage.summary import SummaryReporter from coverage.xmlreport import XmlReporter +os = isolate_module(os) # Pypy has some unusual stuff in the "stdlib". Consider those locations # when deciding where the stdlib is. @@ -108,8 +110,8 @@ `concurrency` is a string indicating the concurrency library being used in the measured code. Without this, coverage.py will get incorrect - results. Valid strings are "greenlet", "eventlet", "gevent", or - "thread" (the default). + results. Valid strings are "greenlet", "eventlet", "gevent", + "multiprocessing", or "thread" (the default). .. versionadded:: 4.0 The `concurrency` parameter. @@ -279,7 +281,7 @@ # data file will be written into the directory where the process # started rather than wherever the process eventually chdir'd to. self.data = CoverageData(debug=self.debug) - self.data_files = CoverageDataFiles(basename=self.config.data_file) + self.data_files = CoverageDataFiles(basename=self.config.data_file, warn=self._warn) # The directories for files considered "installed with the interpreter". self.pylib_dirs = set() @@ -289,7 +291,7 @@ # environments (virtualenv, for example), these modules may be # spread across a few locations. Look at all the candidate modules # we've imported, and take all the different ones. - for m in (atexit, inspect, os, platform, _structseq, traceback): + for m in (atexit, inspect, os, platform, re, _structseq, traceback): if m is not None and hasattr(m, "__file__"): self.pylib_dirs.add(self._canonical_dir(m)) if _structseq and not hasattr(_structseq, '__file__'): @@ -475,6 +477,11 @@ # can't do anything with the data later anyway. return nope(disp, "not a real file name") + # pyexpat does a dumb thing, calling the trace function explicitly from + # C code with a C file name. + if re.search(r"[/\\]Modules[/\\]pyexpat.c", filename): + return nope(disp, "pyexpat lies about itself") + # Jython reports the .class file to the tracer, use the source file. if filename.endswith("$py.class"): filename = filename[:-9] + ".py" @@ -798,7 +805,7 @@ """ self._init() if not self._measured: - return + return self.data self.collector.save_data(self.data) @@ -832,15 +839,6 @@ self.data.touch_file(py_file) - # Add run information. - self.data.add_run_info( - brief_sys=" ".join([ - platform.python_implementation(), - platform.python_version(), - platform.system(), - ]) - ) - if self.config.note: self.data.add_run_info(note=self.config.note) @@ -943,9 +941,9 @@ return file_reporters def report( - self, morfs=None, show_missing=True, ignore_errors=None, + self, morfs=None, show_missing=None, ignore_errors=None, file=None, # pylint: disable=redefined-builtin - omit=None, include=None, skip_covered=False, + omit=None, include=None, skip_covered=None, ): """Write a summary report to `file`. @@ -1049,7 +1047,10 @@ output_dir = os.path.dirname(self.config.xml_output) if output_dir and not os.path.isdir(output_dir): os.makedirs(output_dir) - outfile = open(self.config.xml_output, "w") + open_kwargs = {} + if env.PY3: + open_kwargs['encoding'] = 'utf8' + outfile = open(self.config.xml_output, "w", **open_kwargs) file_to_close = outfile try: reporter = XmlReporter(self, self.config) @@ -1165,11 +1166,14 @@ import coverage; coverage.process_startup() + Returns the :class:`Coverage` instance that was started, or None if it was + not started by this call. + """ cps = os.environ.get("COVERAGE_PROCESS_START") if not cps: # No request for coverage, nothing to do. - return + return None # This function can be called more than once in a process. This happens # because some virtualenv configurations make the same directory visible @@ -1184,10 +1188,12 @@ if hasattr(process_startup, "done"): # We've annotated this function before, so we must have already # started coverage.py in this process. Nothing to do. - return + return None process_startup.done = True cov = Coverage(config_file=cps, auto_data=True) cov.start() cov._warn_no_data = False cov._warn_unimported_source = False + + return cov
--- a/DebugClients/Python3/coverage/data.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python3/coverage/data.py Sun Jul 24 12:01:01 2016 +0200 @@ -4,6 +4,7 @@ """Coverage data for coverage.py.""" import glob +import itertools import json import optparse import os @@ -16,7 +17,9 @@ from coverage.backward import iitems, string_class from coverage.debug import _TEST_NAME_FILE from coverage.files import PathAliases -from coverage.misc import CoverageException, file_be_gone +from coverage.misc import CoverageException, file_be_gone, isolate_module + +os = isolate_module(os) class CoverageData(object): @@ -176,11 +179,12 @@ """ if self._arcs is not None: - if filename in self._arcs: - return [s for s, __ in self._arcs[filename] if s > 0] + arcs = self._arcs.get(filename) + if arcs is not None: + all_lines = itertools.chain.from_iterable(arcs) + return list(set(l for l in all_lines if l > 0)) elif self._lines is not None: - if filename in self._lines: - return self._lines[filename] + return self._lines.get(filename) return None def arcs(self, filename): @@ -574,7 +578,7 @@ def add_to_hash(self, filename, hasher): """Contribute `filename`'s data to the `hasher`. - `hasher` is a :class:`coverage.misc.Hasher` instance to be updated with + `hasher` is a `coverage.misc.Hasher` instance to be updated with the file's data. It should only get the results data, not the run data. @@ -601,12 +605,15 @@ class CoverageDataFiles(object): """Manage the use of coverage data files.""" - def __init__(self, basename=None): + def __init__(self, basename=None, warn=None): """Create a CoverageDataFiles to manage data files. + `warn` is the warning function to use. + `basename` is the name of the file to use for storing data. """ + self.warn = warn # Construct the file name that will be used for data storage. self.filename = os.path.abspath(basename or ".coverage") @@ -675,7 +682,9 @@ If `data_paths` is not provided, then the directory portion of `self.filename` is used as the directory to search for data files. - Every data file found and combined is then deleted from disk. + Every data file found and combined is then deleted from disk. If a file + cannot be read, a warning will be issued, and the file will not be + deleted. """ # Because of the os.path.abspath in the constructor, data_dir will @@ -696,9 +705,16 @@ for f in files_to_combine: new_data = CoverageData() - new_data.read_file(f) - data.update(new_data, aliases=aliases) - file_be_gone(f) + try: + new_data.read_file(f) + except CoverageException as exc: + if self.warn: + # The CoverageException has the file name in it, so just + # use the message as the warning. + self.warn(str(exc)) + else: + data.update(new_data, aliases=aliases) + file_be_gone(f) def canonicalize_json_data(data):
--- a/DebugClients/Python3/coverage/debug.py Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python3/coverage/debug.py Sun Jul 24 12:01:01 2016 +0200 @@ -7,6 +7,10 @@ import os import sys +from coverage.misc import isolate_module + +os = isolate_module(os) + # When debugging, it can be helpful to force some options, especially when # debugging the configuration mechanisms you usually use to control debugging! @@ -38,7 +42,7 @@ msg = "pid %5d: %s" % (os.getpid(), msg) self.output.write(msg+"\n") if self.should('callers'): - dump_stack_frames(self.output) + dump_stack_frames(out=self.output) self.output.flush() def write_formatted_info(self, header, info): @@ -76,7 +80,7 @@ yield "%*s: %s" % (label_len, label, data) -def short_stack(): # pragma: debugging +def short_stack(limit=None): # pragma: debugging """Return a string summarizing the call stack. The string is multi-line, with one line per stack frame. Each line shows @@ -88,13 +92,15 @@ import_local_file : /Users/ned/coverage/trunk/coverage/backward.py @159 ... + `limit` is the number of frames to include, defaulting to all of them. + """ - stack = inspect.stack()[:0:-1] + stack = inspect.stack()[limit:0:-1] return "\n".join("%30s : %s @%d" % (t[3], t[1], t[2]) for t in stack) -def dump_stack_frames(out=None): # pragma: debugging +def dump_stack_frames(limit=None, out=None): # pragma: debugging """Print a summary of the stack to stdout, or some place else.""" out = out or sys.stdout - out.write(short_stack()) + out.write(short_stack(limit=limit)) out.write("\n")
--- a/DebugClients/Python3/coverage/doc/AUTHORS.txt Sat Jul 23 13:33:54 2016 +0200 +++ b/DebugClients/Python3/coverage/doc/AUTHORS.txt Sun Jul 24 12:01:01 2016 +0200 @@ -7,6 +7,7 @@ Alex Gaynor Alexander Todorov Anthony Sottile +Arcadiy Ivanov Ben Finney Bill Hart Brandon Rhodes @@ -26,6 +27,7 @@ David Stanek Detlev Offenbach Devin Jeanpierre +Dmitry Shishov Dmitry Trofimov Eduardo Schettino Edward Loper @@ -34,6 +36,7 @@ George Song Greg Rogers Guillaume Chazarain +Ilia Meerovich Imri Goldberg Ionel Cristian Mărieș JT Olds @@ -49,11 +52,13 @@ Mark van der Wal Martin Fuzzey Matthew Desmarais +Max Linke Mickie Betz Noel O'Boyle Pablo Carballo Patrick Mezard Peter Portante +Rodrigue Cloutier Roger Hu Ross Lawley Sandra Martocchia
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DebugClients/Python3/coverage/doc/CHANGES.rst Sun Jul 24 12:01:01 2016 +0200 @@ -0,0 +1,1654 @@ +.. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +.. For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt + +============================== +Change history for Coverage.py +============================== + + +Version 4.1 --- 2016-05-21 +-------------------------- + +- The internal attribute `Reporter.file_reporters` was removed in 4.1b3. It + should have come has no surprise that there were third-party tools out there + using that attribute. It has been restored, but with a deprecation warning. + + +Version 4.1b3 --- 2016-05-10 +---------------------------- + +- When running your program, execution can jump from an ``except X:`` line to + some other line when an exception other than ``X`` happens. This jump is no + longer considered a branch when measuring branch coverage. + +- When measuring branch coverage, ``yield`` statements that were never resumed + were incorrectly marked as missing, as reported in `issue 440`_. This is now + fixed. + +- During branch coverage of single-line callables like lambdas and generator + expressions, coverage.py can now distinguish between them never being called, + or being called but not completed. Fixes `issue 90`_, `issue 460`_ and + `issue 475`_. + +- The HTML report now has a map of the file along the rightmost edge of the + page, giving an overview of where the missed lines are. Thanks, Dmitry + Shishov. + +- The HTML report now uses different monospaced fonts, favoring Consolas over + Courier. Along the way, `issue 472`_ about not properly handling one-space + indents was fixed. The index page also has slightly different styling, to + try to make the clickable detail pages more apparent. + +- Missing branches reported with ``coverage report -m`` will now say ``->exit`` + for missed branches to the exit of a function, rather than a negative number. + Fixes `issue 469`_. + +- ``coverage --help`` and ``coverage --version`` now mention which tracer is + installed, to help diagnose problems. The docs mention which features need + the C extension. (`issue 479`_) + +- Officially support PyPy 5.1, which required no changes, just updates to the + docs. + +- The `Coverage.report` function had two parameters with non-None defaults, + which have been changed. `show_missing` used to default to True, but now + defaults to None. If you had been calling `Coverage.report` without + specifying `show_missing`, you'll need to explicitly set it to True to keep + the same behavior. `skip_covered` used to default to False. It is now None, + which doesn't change the behavior. This fixes `issue 485`_. + +- It's never been possible to pass a namespace module to one of the analysis + functions, but now at least we raise a more specific error message, rather + than getting confused. (`issue 456`_) + +- The `coverage.process_startup` function now returns the `Coverage` instance + it creates, as suggested in `issue 481`_. + +- Make a small tweak to how we compare threads, to avoid buggy custom + comparison code in thread classes. (`issue 245`_) + +.. _issue 90: https://bitbucket.org/ned/coveragepy/issues/90/lambda-expression-confuses-branch +.. _issue 245: https://bitbucket.org/ned/coveragepy/issues/245/change-solution-for-issue-164 +.. _issue 440: https://bitbucket.org/ned/coveragepy/issues/440/yielded-twisted-failure-marked-as-missed +.. _issue 456: https://bitbucket.org/ned/coveragepy/issues/456/coverage-breaks-with-implicit-namespaces +.. _issue 460: https://bitbucket.org/ned/coveragepy/issues/460/confusing-html-report-for-certain-partial +.. _issue 469: https://bitbucket.org/ned/coveragepy/issues/469/strange-1-line-number-in-branch-coverage +.. _issue 472: https://bitbucket.org/ned/coveragepy/issues/472/html-report-indents-incorrectly-for-one +.. _issue 475: https://bitbucket.org/ned/coveragepy/issues/475/generator-expression-is-marked-as-not +.. _issue 479: https://bitbucket.org/ned/coveragepy/issues/479/clarify-the-need-for-the-c-extension +.. _issue 481: https://bitbucket.org/ned/coveragepy/issues/481/asyncioprocesspoolexecutor-tracing-not +.. _issue 485: https://bitbucket.org/ned/coveragepy/issues/485/coveragereport-ignores-show_missing-and + + +Version 4.1b2 --- 2016-01-23 +---------------------------- + +- Problems with the new branch measurement in 4.1 beta 1 were fixed: + + - Class docstrings were considered executable. Now they no longer are. + + - ``yield from`` and ``await`` were considered returns from functions, since + they could tranfer control to the caller. This produced unhelpful "missing + branch" reports in a number of circumstances. Now they no longer are + considered returns. + + - In unusual situations, a missing branch to a negative number was reported. + This has been fixed, closing `issue 466`_. + +- The XML report now produces correct package names for modules found in + directories specified with ``source=``. Fixes `issue 465`_. + +- ``coverage report`` won't produce trailing whitespace. + +.. _issue 465: https://bitbucket.org/ned/coveragepy/issues/465/coveragexml-produces-package-names-with-an +.. _issue 466: https://bitbucket.org/ned/coveragepy/issues/466/impossible-missed-branch-to-a-negative + + +Version 4.1b1 --- 2016-01-10 +---------------------------- + +- Branch analysis has been rewritten: it used to be based on bytecode, but now + uses AST analysis. This has changed a number of things: + + - More code paths are now considered runnable, especially in + ``try``/``except`` structures. This may mean that coverage.py will + identify more code paths as uncovered. This could either raise or lower + your overall coverage number. + + - Python 3.5's ``async`` and ``await`` keywords are properly supported, + fixing `issue 434`_. + + - Some long-standing branch coverage bugs were fixed: + + - `issue 129`_: functions with only a docstring for a body would + incorrectly report a missing branch on the ``def`` line. + + - `issue 212`_: code in an ``except`` block could be incorrectly marked as + a missing branch. + + - `issue 146`_: context managers (``with`` statements) in a loop or ``try`` + block could confuse the branch measurement, reporting incorrect partial + branches. + + - `issue 422`_: in Python 3.5, an actual partial branch could be marked as + complete. + +- Pragmas to disable coverage measurement can now be used on decorator lines, + and they will apply to the entire function or class being decorated. This + implements the feature requested in `issue 131`_. + +- Multiprocessing support is now available on Windows. Thanks, Rodrigue + Cloutier. + +- Files with two encoding declarations are properly supported, fixing + `issue 453`_. Thanks, Max Linke. + +- Non-ascii characters in regexes in the configuration file worked in 3.7, but + stopped working in 4.0. Now they work again, closing `issue 455`_. + +- Form-feed characters would prevent accurate determination of the beginning of + statements in the rest of the file. This is now fixed, closing `issue 461`_. + +.. _issue 129: https://bitbucket.org/ned/coveragepy/issues/129/misleading-branch-coverage-of-empty +.. _issue 131: https://bitbucket.org/ned/coveragepy/issues/131/pragma-on-a-decorator-line-should-affect +.. _issue 146: https://bitbucket.org/ned/coveragepy/issues/146/context-managers-confuse-branch-coverage +.. _issue 212: https://bitbucket.org/ned/coveragepy/issues/212/coverage-erroneously-reports-partial +.. _issue 422: https://bitbucket.org/ned/coveragepy/issues/422/python35-partial-branch-marked-as-fully +.. _issue 434: https://bitbucket.org/ned/coveragepy/issues/434/indexerror-in-python-35 +.. _issue 453: https://bitbucket.org/ned/coveragepy/issues/453/source-code-encoding-can-only-be-specified +.. _issue 455: https://bitbucket.org/ned/coveragepy/issues/455/unusual-exclusions-stopped-working-in +.. _issue 461: https://bitbucket.org/ned/coveragepy/issues/461/multiline-asserts-need-too-many-pragma + + +Version 4.0.3 --- 2015-11-24 +---------------------------- + +- Fixed a mysterious problem that manifested in different ways: sometimes + hanging the process (`issue 420`_), sometimes making database connections + fail (`issue 445`_). + +- The XML report now has correct ``<source>`` elements when using a + ``--source=`` option somewhere besides the current directory. This fixes + `issue 439`_. Thanks, Arcady Ivanov. + +- Fixed an unusual edge case of detecting source encodings, described in + `issue 443`_. + +- Help messages that mention the command to use now properly use the actual + command name, which might be different than "coverage". Thanks to Ben + Finney, this closes `issue 438`_. + +.. _issue 420: https://bitbucket.org/ned/coveragepy/issues/420/coverage-40-hangs-indefinitely-on-python27 +.. _issue 438: https://bitbucket.org/ned/coveragepy/issues/438/parameterise-coverage-command-name +.. _issue 439: https://bitbucket.org/ned/coveragepy/issues/439/incorrect-cobertura-file-sources-generated +.. _issue 443: https://bitbucket.org/ned/coveragepy/issues/443/coverage-gets-confused-when-encoding +.. _issue 445: https://bitbucket.org/ned/coveragepy/issues/445/django-app-cannot-connect-to-cassandra + + +Version 4.0.2 --- 2015-11-04 +---------------------------- + +- More work on supporting unusually encoded source. Fixed `issue 431`_. + +- Files or directories with non-ASCII characters are now handled properly, + fixing `issue 432`_. + +- Setting a trace function with sys.settrace was broken by a change in 4.0.1, + as reported in `issue 436`_. This is now fixed. + +- Officially support PyPy 4.0, which required no changes, just updates to the + docs. + +.. _issue 431: https://bitbucket.org/ned/coveragepy/issues/431/couldnt-parse-python-file-with-cp1252 +.. _issue 432: https://bitbucket.org/ned/coveragepy/issues/432/path-with-unicode-characters-various +.. _issue 436: https://bitbucket.org/ned/coveragepy/issues/436/disabled-coverage-ctracer-may-rise-from + + +Version 4.0.1 --- 2015-10-13 +---------------------------- + +- When combining data files, unreadable files will now generate a warning + instead of failing the command. This is more in line with the older + coverage.py v3.7.1 behavior, which silently ignored unreadable files. + Prompted by `issue 418`_. + +- The --skip-covered option would skip reporting on 100% covered files, but + also skipped them when calculating total coverage. This was wrong, it should + only remove lines from the report, not change the final answer. This is now + fixed, closing `issue 423`_. + +- In 4.0, the data file recorded a summary of the system on which it was run. + Combined data files would keep all of those summaries. This could lead to + enormous data files consisting of mostly repetitive useless information. That + summary is now gone, fixing `issue 415`_. If you want summary information, + get in touch, and we'll figure out a better way to do it. + +- Test suites that mocked os.path.exists would experience strange failures, due + to coverage.py using their mock inadvertently. This is now fixed, closing + `issue 416`_. + +- Importing a ``__init__`` module explicitly would lead to an error: + ``AttributeError: 'module' object has no attribute '__path__'``, as reported + in `issue 410`_. This is now fixed. + +- Code that uses ``sys.settrace(sys.gettrace())`` used to incur a more than 2x + speed penalty. Now there's no penalty at all. Fixes `issue 397`_. + +- Pyexpat C code will no longer be recorded as a source file, fixing + `issue 419`_. + +- The source kit now contains all of the files needed to have a complete source + tree, re-fixing `issue 137`_ and closing `issue 281`_. + +.. _issue 281: https://bitbucket.org/ned/coveragepy/issues/281/supply-scripts-for-testing-in-the +.. _issue 397: https://bitbucket.org/ned/coveragepy/issues/397/stopping-and-resuming-coverage-with +.. _issue 410: https://bitbucket.org/ned/coveragepy/issues/410/attributeerror-module-object-has-no +.. _issue 415: https://bitbucket.org/ned/coveragepy/issues/415/repeated-coveragedataupdates-cause +.. _issue 416: https://bitbucket.org/ned/coveragepy/issues/416/mocking-ospathexists-causes-failures +.. _issue 418: https://bitbucket.org/ned/coveragepy/issues/418/json-parse-error +.. _issue 419: https://bitbucket.org/ned/coveragepy/issues/419/nosource-no-source-for-code-path-to-c +.. _issue 423: https://bitbucket.org/ned/coveragepy/issues/423/skip_covered-changes-reported-total + + +Version 4.0 --- 2015-09-20 +-------------------------- + +No changes from 4.0b3 + + +Version 4.0b3 --- 2015-09-07 +---------------------------- + +- Reporting on an unmeasured file would fail with a traceback. This is now + fixed, closing `issue 403`_. + +- The Jenkins ShiningPanda plugin looks for an obsolete file name to find the + HTML reports to publish, so it was failing under coverage.py 4.0. Now we + create that file if we are running under Jenkins, to keep things working + smoothly. `issue 404`_. + +- Kits used to include tests and docs, but didn't install them anywhere, or + provide all of the supporting tools to make them useful. Kits no longer + include tests and docs. If you were using them from the older packages, get + in touch and help me understand how. + +.. _issue 403: https://bitbucket.org/ned/coveragepy/issues/403/hasherupdate-fails-with-typeerror-nonetype +.. _issue 404: https://bitbucket.org/ned/coveragepy/issues/404/shiningpanda-jenkins-plugin-cant-find-html + + + +Version 4.0b2 --- 2015-08-22 +---------------------------- + +- 4.0b1 broke ``--append`` creating new data files. This is now fixed, closing + `issue 392`_. + +- ``py.test --cov`` can write empty data, then touch files due to ``--source``, + which made coverage.py mistakenly force the data file to record lines instead + of arcs. This would lead to a "Can't combine line data with arc data" error + message. This is now fixed, and changed some method names in the + CoverageData interface. Fixes `issue 399`_. + +- `CoverageData.read_fileobj` and `CoverageData.write_fileobj` replace the + `.read` and `.write` methods, and are now properly inverses of each other. + +- When using ``report --skip-covered``, a message will now be included in the + report output indicating how many files were skipped, and if all files are + skipped, coverage.py won't accidentally scold you for having no data to + report. Thanks, Krystian Kichewko. + +- A new conversion utility has been added: ``python -m coverage.pickle2json`` + will convert v3.x pickle data files to v4.x JSON data files. Thanks, + Alexander Todorov. Closes `issue 395`_. + +- A new version identifier is available, `coverage.version_info`, a plain tuple + of values similar to `sys.version_info`_. + +.. _issue 392: https://bitbucket.org/ned/coveragepy/issues/392/run-append-doesnt-create-coverage-file +.. _issue 395: https://bitbucket.org/ned/coveragepy/issues/395/rfe-read-pickled-files-as-well-for +.. _issue 399: https://bitbucket.org/ned/coveragepy/issues/399/coverageexception-cant-combine-line-data +.. _sys.version_info: https://docs.python.org/3/library/sys.html#sys.version_info + + +Version 4.0b1 --- 2015-08-02 +---------------------------- + +- Coverage.py is now licensed under the Apache 2.0 license. See NOTICE.txt for + details. Closes `issue 313`_. + +- The data storage has been completely revamped. The data file is now + JSON-based instead of a pickle, closing `issue 236`_. The `CoverageData` + class is now a public supported documented API to the data file. + +- A new configuration option, ``[run] note``, lets you set a note that will be + stored in the `runs` section of the data file. You can use this to annotate + the data file with any information you like. + +- Unrecognized configuration options will now print an error message and stop + coverage.py. This should help prevent configuration mistakes from passing + silently. Finishes `issue 386`_. + +- In parallel mode, ``coverage erase`` will now delete all of the data files, + fixing `issue 262`_. + +- Coverage.py now accepts a directory name for ``coverage run`` and will run a + ``__main__.py`` found there, just like Python will. Fixes `issue 252`_. + Thanks, Dmitry Trofimov. + +- The XML report now includes a ``missing-branches`` attribute. Thanks, Steve + Peak. This is not a part of the Cobertura DTD, so the XML report no longer + references the DTD. + +- Missing branches in the HTML report now have a bit more information in the + right-hand annotations. Hopefully this will make their meaning clearer. + +- All the reporting functions now behave the same if no data had been + collected, exiting with a status code of 1. Fixed ``fail_under`` to be + applied even when the report is empty. Thanks, Ionel Cristian Mărieș. + +- Plugins are now initialized differently. Instead of looking for a class + called ``Plugin``, coverage.py looks for a function called ``coverage_init``. + +- A file-tracing plugin can now ask to have built-in Python reporting by + returning `"python"` from its `file_reporter()` method. + +- Code that was executed with `exec` would be mis-attributed to the file that + called it. This is now fixed, closing `issue 380`_. + +- The ability to use item access on `Coverage.config` (introduced in 4.0a2) has + been changed to a more explicit `Coverage.get_option` and + `Coverage.set_option` API. + +- The ``Coverage.use_cache`` method is no longer supported. + +- The private method ``Coverage._harvest_data`` is now called + ``Coverage.get_data``, and returns the ``CoverageData`` containing the + collected data. + +- The project is consistently referred to as "coverage.py" throughout the code + and the documentation, closing `issue 275`_. + +- Combining data files with an explicit configuration file was broken in 4.0a6, + but now works again, closing `issue 385`_. + +- ``coverage combine`` now accepts files as well as directories. + +- The speed is back to 3.7.1 levels, after having slowed down due to plugin + support, finishing up `issue 387`_. + +.. _issue 236: https://bitbucket.org/ned/coveragepy/issues/236/pickles-are-bad-and-you-should-feel-bad +.. _issue 252: https://bitbucket.org/ned/coveragepy/issues/252/coverage-wont-run-a-program-with +.. _issue 262: https://bitbucket.org/ned/coveragepy/issues/262/when-parallel-true-erase-should-erase-all +.. _issue 275: https://bitbucket.org/ned/coveragepy/issues/275/refer-consistently-to-project-as-coverage +.. _issue 313: https://bitbucket.org/ned/coveragepy/issues/313/add-license-file-containing-2-3-or-4 +.. _issue 380: https://bitbucket.org/ned/coveragepy/issues/380/code-executed-by-exec-excluded-from +.. _issue 385: https://bitbucket.org/ned/coveragepy/issues/385/coverage-combine-doesnt-work-with-rcfile +.. _issue 386: https://bitbucket.org/ned/coveragepy/issues/386/error-on-unrecognised-configuration +.. _issue 387: https://bitbucket.org/ned/coveragepy/issues/387/performance-degradation-from-371-to-40 + +.. 40 issues closed in 4.0 below here + + +Version 4.0a6 --- 2015-06-21 +---------------------------- + +- Python 3.5b2 and PyPy 2.6.0 are supported. + +- The original module-level function interface to coverage.py is no longer + supported. You must now create a ``coverage.Coverage`` object, and use + methods on it. + +- The ``coverage combine`` command now accepts any number of directories as + arguments, and will combine all the data files from those directories. This + means you don't have to copy the files to one directory before combining. + Thanks, Christine Lytwynec. Finishes `issue 354`_. + +- Branch coverage couldn't properly handle certain extremely long files. This + is now fixed (`issue 359`_). + +- Branch coverage didn't understand yield statements properly. Mickie Betz + persisted in pursuing this despite Ned's pessimism. Fixes `issue 308`_ and + `issue 324`_. + +- The COVERAGE_DEBUG environment variable can be used to set the ``[run] debug`` + configuration option to control what internal operations are logged. + +- HTML reports were truncated at formfeed characters. This is now fixed + (`issue 360`_). It's always fun when the problem is due to a `bug in the + Python standard library <http://bugs.python.org/issue19035>`_. + +- Files with incorrect encoding declaration comments are no longer ignored by + the reporting commands, fixing `issue 351`_. + +- HTML reports now include a timestamp in the footer, closing `issue 299`_. + Thanks, Conrad Ho. + +- HTML reports now begrudgingly use double-quotes rather than single quotes, + because there are "software engineers" out there writing tools that read HTML + and somehow have no idea that single quotes exist. Capitulates to the absurd + `issue 361`_. Thanks, Jon Chappell. + +- The ``coverage annotate`` command now handles non-ASCII characters properly, + closing `issue 363`_. Thanks, Leonardo Pistone. + +- Drive letters on Windows were not normalized correctly, now they are. Thanks, + Ionel Cristian Mărieș. + +- Plugin support had some bugs fixed, closing `issue 374`_ and `issue 375`_. + Thanks, Stefan Behnel. + +.. _issue 299: https://bitbucket.org/ned/coveragepy/issue/299/inserted-created-on-yyyy-mm-dd-hh-mm-in +.. _issue 308: https://bitbucket.org/ned/coveragepy/issue/308/yield-lambda-branch-coverage +.. _issue 324: https://bitbucket.org/ned/coveragepy/issue/324/yield-in-loop-confuses-branch-coverage +.. _issue 351: https://bitbucket.org/ned/coveragepy/issue/351/files-with-incorrect-encoding-are-ignored +.. _issue 354: https://bitbucket.org/ned/coveragepy/issue/354/coverage-combine-should-take-a-list-of +.. _issue 359: https://bitbucket.org/ned/coveragepy/issue/359/xml-report-chunk-error +.. _issue 360: https://bitbucket.org/ned/coveragepy/issue/360/html-reports-get-confused-by-l-in-the-code +.. _issue 361: https://bitbucket.org/ned/coveragepy/issue/361/use-double-quotes-in-html-output-to +.. _issue 363: https://bitbucket.org/ned/coveragepy/issue/363/annotate-command-hits-unicode-happy-fun +.. _issue 374: https://bitbucket.org/ned/coveragepy/issue/374/c-tracer-lookups-fail-in +.. _issue 375: https://bitbucket.org/ned/coveragepy/issue/375/ctracer_handle_return-reads-byte-code + + +Version 4.0a5 --- 2015-02-16 +---------------------------- + +- Plugin support is now implemented in the C tracer instead of the Python + tracer. This greatly improves the speed of tracing projects using plugins. + +- Coverage.py now always adds the current directory to sys.path, so that + plugins can import files in the current directory (`issue 358`_). + +- If the `config_file` argument to the Coverage constructor is specified as + ".coveragerc", it is treated as if it were True. This means setup.cfg is + also examined, and a missing file is not considered an error (`issue 357`_). + +- Wildly experimental: support for measuring processes started by the + multiprocessing module. To use, set ``--concurrency=multiprocessing``, + either on the command line or in the .coveragerc file (`issue 117`_). Thanks, + Eduardo Schettino. Currently, this does not work on Windows. + +- A new warning is possible, if a desired file isn't measured because it was + imported before coverage.py was started (`issue 353`_). + +- The `coverage.process_startup` function now will start coverage measurement + only once, no matter how many times it is called. This fixes problems due + to unusual virtualenv configurations (`issue 340`_). + +- Added 3.5.0a1 to the list of supported CPython versions. + +.. _issue 117: https://bitbucket.org/ned/coveragepy/issue/117/enable-coverage-measurement-of-code-run-by +.. _issue 340: https://bitbucket.org/ned/coveragepy/issue/340/keyerror-subpy +.. _issue 353: https://bitbucket.org/ned/coveragepy/issue/353/40a3-introduces-an-unexpected-third-case +.. _issue 357: https://bitbucket.org/ned/coveragepy/issue/357/behavior-changed-when-coveragerc-is +.. _issue 358: https://bitbucket.org/ned/coveragepy/issue/358/all-coverage-commands-should-adjust + + +Version 4.0a4 --- 2015-01-25 +---------------------------- + +- Plugins can now provide sys_info for debugging output. + +- Started plugins documentation. + +- Prepared to move the docs to readthedocs.org. + + +Version 4.0a3 --- 2015-01-20 +---------------------------- + +- Reports now use file names with extensions. Previously, a report would + describe a/b/c.py as "a/b/c". Now it is shown as "a/b/c.py". This allows + for better support of non-Python files, and also fixed `issue 69`_. + +- The XML report now reports each directory as a package again. This was a bad + regression, I apologize. This was reported in `issue 235`_, which is now + fixed. + +- A new configuration option for the XML report: ``[xml] package_depth`` + controls which directories are identified as packages in the report. + Directories deeper than this depth are not reported as packages. + The default is that all directories are reported as packages. + Thanks, Lex Berezhny. + +- When looking for the source for a frame, check if the file exists. On + Windows, .pyw files are no longer recorded as .py files. Along the way, this + fixed `issue 290`_. + +- Empty files are now reported as 100% covered in the XML report, not 0% + covered (`issue 345`_). + +- Regexes in the configuration file are now compiled as soon as they are read, + to provide error messages earlier (`issue 349`_). + +.. _issue 69: https://bitbucket.org/ned/coveragepy/issue/69/coverage-html-overwrite-files-that-doesnt +.. _issue 235: https://bitbucket.org/ned/coveragepy/issue/235/package-name-is-missing-in-xml-report +.. _issue 290: https://bitbucket.org/ned/coveragepy/issue/290/running-programmatically-with-pyw-files +.. _issue 345: https://bitbucket.org/ned/coveragepy/issue/345/xml-reports-line-rate-0-for-empty-files +.. _issue 349: https://bitbucket.org/ned/coveragepy/issue/349/bad-regex-in-config-should-get-an-earlier + + +Version 4.0a2 --- 2015-01-14 +---------------------------- + +- Officially support PyPy 2.4, and PyPy3 2.4. Drop support for + CPython 3.2 and older versions of PyPy. The code won't work on CPython 3.2. + It will probably still work on older versions of PyPy, but I'm not testing + against them. + +- Plugins! + +- The original command line switches (`-x` to run a program, etc) are no + longer supported. + +- A new option: `coverage report --skip-covered` will reduce the number of + files reported by skipping files with 100% coverage. Thanks, Krystian + Kichewko. This means that empty `__init__.py` files will be skipped, since + they are 100% covered, closing `issue 315`_. + +- You can now specify the ``--fail-under`` option in the ``.coveragerc`` file + as the ``[report] fail_under`` option. This closes `issue 314`_. + +- The ``COVERAGE_OPTIONS`` environment variable is no longer supported. It was + a hack for ``--timid`` before configuration files were available. + +- The HTML report now has filtering. Type text into the Filter box on the + index page, and only modules with that text in the name will be shown. + Thanks, Danny Allen. + +- The textual report and the HTML report used to report partial branches + differently for no good reason. Now the text report's "missing branches" + column is a "partial branches" column so that both reports show the same + numbers. This closes `issue 342`_. + +- If you specify a ``--rcfile`` that cannot be read, you will get an error + message. Fixes `issue 343`_. + +- The ``--debug`` switch can now be used on any command. + +- You can now programmatically adjust the configuration of coverage.py by + setting items on `Coverage.config` after construction. + +- A module run with ``-m`` can be used as the argument to ``--source``, fixing + `issue 328`_. Thanks, Buck Evan. + +- The regex for matching exclusion pragmas has been fixed to allow more kinds + of whitespace, fixing `issue 334`_. + +- Made some PyPy-specific tweaks to improve speed under PyPy. Thanks, Alex + Gaynor. + +- In some cases, with a source file missing a final newline, coverage.py would + count statements incorrectly. This is now fixed, closing `issue 293`_. + +- The status.dat file that HTML reports use to avoid re-creating files that + haven't changed is now a JSON file instead of a pickle file. This obviates + `issue 287`_ and `issue 237`_. + +.. _issue 237: https://bitbucket.org/ned/coveragepy/issue/237/htmlcov-with-corrupt-statusdat +.. _issue 287: https://bitbucket.org/ned/coveragepy/issue/287/htmlpy-doesnt-specify-pickle-protocol +.. _issue 293: https://bitbucket.org/ned/coveragepy/issue/293/number-of-statement-detection-wrong-if-no +.. _issue 314: https://bitbucket.org/ned/coveragepy/issue/314/fail_under-param-not-working-in-coveragerc +.. _issue 315: https://bitbucket.org/ned/coveragepy/issue/315/option-to-omit-empty-files-eg-__init__py +.. _issue 328: https://bitbucket.org/ned/coveragepy/issue/328/misbehavior-in-run-source +.. _issue 334: https://bitbucket.org/ned/coveragepy/issue/334/pragma-not-recognized-if-tab-character +.. _issue 342: https://bitbucket.org/ned/coveragepy/issue/342/console-and-html-coverage-reports-differ +.. _issue 343: https://bitbucket.org/ned/coveragepy/issue/343/an-explicitly-named-non-existent-config + + +Version 4.0a1 --- 2014-09-27 +---------------------------- + +- Python versions supported are now CPython 2.6, 2.7, 3.2, 3.3, and 3.4, and + PyPy 2.2. + +- Gevent, eventlet, and greenlet are now supported, closing `issue 149`_. + The ``concurrency`` setting specifies the concurrency library in use. Huge + thanks to Peter Portante for initial implementation, and to Joe Jevnik for + the final insight that completed the work. + +- Options are now also read from a setup.cfg file, if any. Sections are + prefixed with "coverage:", so the ``[run]`` options will be read from the + ``[coverage:run]`` section of setup.cfg. Finishes `issue 304`_. + +- The ``report -m`` command can now show missing branches when reporting on + branch coverage. Thanks, Steve Leonard. Closes `issue 230`_. + +- The XML report now contains a <source> element, fixing `issue 94`_. Thanks + Stan Hu. + +- The class defined in the coverage module is now called ``Coverage`` instead + of ``coverage``, though the old name still works, for backward compatibility. + +- The ``fail-under`` value is now rounded the same as reported results, + preventing paradoxical results, fixing `issue 284`_. + +- The XML report will now create the output directory if need be, fixing + `issue 285`_. Thanks, Chris Rose. + +- HTML reports no longer raise UnicodeDecodeError if a Python file has + undecodable characters, fixing `issue 303`_ and `issue 331`_. + +- The annotate command will now annotate all files, not just ones relative to + the current directory, fixing `issue 57`_. + +- The coverage module no longer causes deprecation warnings on Python 3.4 by + importing the imp module, fixing `issue 305`_. + +- Encoding declarations in source files are only considered if they are truly + comments. Thanks, Anthony Sottile. + +.. _issue 57: https://bitbucket.org/ned/coveragepy/issue/57/annotate-command-fails-to-annotate-many +.. _issue 94: https://bitbucket.org/ned/coveragepy/issue/94/coverage-xml-doesnt-produce-sources +.. _issue 149: https://bitbucket.org/ned/coveragepy/issue/149/coverage-gevent-looks-broken +.. _issue 230: https://bitbucket.org/ned/coveragepy/issue/230/show-line-no-for-missing-branches-in +.. _issue 284: https://bitbucket.org/ned/coveragepy/issue/284/fail-under-should-show-more-precision +.. _issue 285: https://bitbucket.org/ned/coveragepy/issue/285/xml-report-fails-if-output-file-directory +.. _issue 303: https://bitbucket.org/ned/coveragepy/issue/303/unicodedecodeerror +.. _issue 304: https://bitbucket.org/ned/coveragepy/issue/304/attempt-to-get-configuration-from-setupcfg +.. _issue 305: https://bitbucket.org/ned/coveragepy/issue/305/pendingdeprecationwarning-the-imp-module +.. _issue 331: https://bitbucket.org/ned/coveragepy/issue/331/failure-of-encoding-detection-on-python2 + + +Version 3.7.1 --- 2013-12-13 +---------------------------- + +- Improved the speed of HTML report generation by about 20%. + +- Fixed the mechanism for finding OS-installed static files for the HTML report + so that it will actually find OS-installed static files. + + +Version 3.7 --- 2013-10-06 +-------------------------- + +- Added the ``--debug`` switch to ``coverage run``. It accepts a list of + options indicating the type of internal activity to log to stderr. + +- Improved the branch coverage facility, fixing `issue 92`_ and `issue 175`_. + +- Running code with ``coverage run -m`` now behaves more like Python does, + setting sys.path properly, which fixes `issue 207`_ and `issue 242`_. + +- Coverage.py can now run .pyc files directly, closing `issue 264`_. + +- Coverage.py properly supports .pyw files, fixing `issue 261`_. + +- Omitting files within a tree specified with the ``source`` option would + cause them to be incorrectly marked as unexecuted, as described in + `issue 218`_. This is now fixed. + +- When specifying paths to alias together during data combining, you can now + specify relative paths, fixing `issue 267`_. + +- Most file paths can now be specified with username expansion (``~/src``, or + ``~build/src``, for example), and with environment variable expansion + (``build/$BUILDNUM/src``). + +- Trying to create an XML report with no files to report on, would cause a + ZeroDivideError, but no longer does, fixing `issue 250`_. + +- When running a threaded program under the Python tracer, coverage.py no + longer issues a spurious warning about the trace function changing: "Trace + function changed, measurement is likely wrong: None." This fixes `issue + 164`_. + +- Static files necessary for HTML reports are found in system-installed places, + to ease OS-level packaging of coverage.py. Closes `issue 259`_. + +- Source files with encoding declarations, but a blank first line, were not + decoded properly. Now they are. Thanks, Roger Hu. + +- The source kit now includes the ``__main__.py`` file in the root coverage + directory, fixing `issue 255`_. + +.. _issue 92: https://bitbucket.org/ned/coveragepy/issue/92/finally-clauses-arent-treated-properly-in +.. _issue 164: https://bitbucket.org/ned/coveragepy/issue/164/trace-function-changed-warning-when-using +.. _issue 175: https://bitbucket.org/ned/coveragepy/issue/175/branch-coverage-gets-confused-in-certain +.. _issue 207: https://bitbucket.org/ned/coveragepy/issue/207/run-m-cannot-find-module-or-package-in +.. _issue 242: https://bitbucket.org/ned/coveragepy/issue/242/running-a-two-level-package-doesnt-work +.. _issue 218: https://bitbucket.org/ned/coveragepy/issue/218/run-command-does-not-respect-the-omit-flag +.. _issue 250: https://bitbucket.org/ned/coveragepy/issue/250/uncaught-zerodivisionerror-when-generating +.. _issue 255: https://bitbucket.org/ned/coveragepy/issue/255/directory-level-__main__py-not-included-in +.. _issue 259: https://bitbucket.org/ned/coveragepy/issue/259/allow-use-of-system-installed-third-party +.. _issue 261: https://bitbucket.org/ned/coveragepy/issue/261/pyw-files-arent-reported-properly +.. _issue 264: https://bitbucket.org/ned/coveragepy/issue/264/coverage-wont-run-pyc-files +.. _issue 267: https://bitbucket.org/ned/coveragepy/issue/267/relative-path-aliases-dont-work + + +Version 3.6 --- 2013-01-05 +-------------------------- + +- Added a page to the docs about troublesome situations, closing `issue 226`_, + and added some info to the TODO file, closing `issue 227`_. + +.. _issue 226: https://bitbucket.org/ned/coveragepy/issue/226/make-readme-section-to-describe-when +.. _issue 227: https://bitbucket.org/ned/coveragepy/issue/227/update-todo + + +Version 3.6b3 --- 2012-12-29 +---------------------------- + +- Beta 2 broke the nose plugin. It's fixed again, closing `issue 224`_. + +.. _issue 224: https://bitbucket.org/ned/coveragepy/issue/224/36b2-breaks-nosexcover + + +Version 3.6b2 --- 2012-12-23 +---------------------------- + +- Coverage.py runs on Python 2.3 and 2.4 again. It was broken in 3.6b1. + +- The C extension is optionally compiled using a different more widely-used + technique, taking another stab at fixing `issue 80`_ once and for all. + +- Combining data files would create entries for phantom files if used with + ``source`` and path aliases. It no longer does. + +- ``debug sys`` now shows the configuration file path that was read. + +- If an oddly-behaved package claims that code came from an empty-string + file name, coverage.py no longer associates it with the directory name, + fixing `issue 221`_. + +.. _issue 221: https://bitbucket.org/ned/coveragepy/issue/221/coveragepy-incompatible-with-pyratemp + + +Version 3.6b1 --- 2012-11-28 +---------------------------- + +- Wildcards in ``include=`` and ``omit=`` arguments were not handled properly + in reporting functions, though they were when running. Now they are handled + uniformly, closing `issue 143`_ and `issue 163`_. **NOTE**: it is possible + that your configurations may now be incorrect. If you use ``include`` or + ``omit`` during reporting, whether on the command line, through the API, or + in a configuration file, please check carefully that you were not relying on + the old broken behavior. + +- The **report**, **html**, and **xml** commands now accept a ``--fail-under`` + switch that indicates in the exit status whether the coverage percentage was + less than a particular value. Closes `issue 139`_. + +- The reporting functions coverage.report(), coverage.html_report(), and + coverage.xml_report() now all return a float, the total percentage covered + measurement. + +- The HTML report's title can now be set in the configuration file, with the + ``--title`` switch on the command line, or via the API. + +- Configuration files now support substitution of environment variables, using + syntax like ``${WORD}``. Closes `issue 97`_. + +- Embarrassingly, the ``[xml] output=`` setting in the .coveragerc file simply + didn't work. Now it does. + +- The XML report now consistently uses file names for the file name attribute, + rather than sometimes using module names. Fixes `issue 67`_. + Thanks, Marcus Cobden. + +- Coverage percentage metrics are now computed slightly differently under + branch coverage. This means that completely unexecuted files will now + correctly have 0% coverage, fixing `issue 156`_. This also means that your + total coverage numbers will generally now be lower if you are measuring + branch coverage. + +- When installing, now in addition to creating a "coverage" command, two new + aliases are also installed. A "coverage2" or "coverage3" command will be + created, depending on whether you are installing in Python 2.x or 3.x. + A "coverage-X.Y" command will also be created corresponding to your specific + version of Python. Closes `issue 111`_. + +- The coverage.py installer no longer tries to bootstrap setuptools or + Distribute. You must have one of them installed first, as `issue 202`_ + recommended. + +- The coverage.py kit now includes docs (closing `issue 137`_) and tests. + +- On Windows, files are now reported in their correct case, fixing `issue 89`_ + and `issue 203`_. + +- If a file is missing during reporting, the path shown in the error message + is now correct, rather than an incorrect path in the current directory. + Fixes `issue 60`_. + +- Running an HTML report in Python 3 in the same directory as an old Python 2 + HTML report would fail with a UnicodeDecodeError. This issue (`issue 193`_) + is now fixed. + +- Fixed yet another error trying to parse non-Python files as Python, this + time an IndentationError, closing `issue 82`_ for the fourth time... + +- If `coverage xml` fails because there is no data to report, it used to + create a zero-length XML file. Now it doesn't, fixing `issue 210`_. + +- Jython files now work with the ``--source`` option, fixing `issue 100`_. + +- Running coverage.py under a debugger is unlikely to work, but it shouldn't + fail with "TypeError: 'NoneType' object is not iterable". Fixes `issue + 201`_. + +- On some Linux distributions, when installed with the OS package manager, + coverage.py would report its own code as part of the results. Now it won't, + fixing `issue 214`_, though this will take some time to be repackaged by the + operating systems. + +- Docstrings for the legacy singleton methods are more helpful. Thanks Marius + Gedminas. Closes `issue 205`_. + +- The pydoc tool can now show documentation for the class `coverage.coverage`. + Closes `issue 206`_. + +- Added a page to the docs about contributing to coverage.py, closing + `issue 171`_. + +- When coverage.py ended unsuccessfully, it may have reported odd errors like + ``'NoneType' object has no attribute 'isabs'``. It no longer does, + so kiss `issue 153`_ goodbye. + +.. _issue 60: https://bitbucket.org/ned/coveragepy/issue/60/incorrect-path-to-orphaned-pyc-files +.. _issue 67: https://bitbucket.org/ned/coveragepy/issue/67/xml-report-filenames-may-be-generated +.. _issue 89: https://bitbucket.org/ned/coveragepy/issue/89/on-windows-all-packages-are-reported-in +.. _issue 97: https://bitbucket.org/ned/coveragepy/issue/97/allow-environment-variables-to-be +.. _issue 100: https://bitbucket.org/ned/coveragepy/issue/100/source-directive-doesnt-work-for-packages +.. _issue 111: https://bitbucket.org/ned/coveragepy/issue/111/when-installing-coverage-with-pip-not +.. _issue 137: https://bitbucket.org/ned/coveragepy/issue/137/provide-docs-with-source-distribution +.. _issue 139: https://bitbucket.org/ned/coveragepy/issue/139/easy-check-for-a-certain-coverage-in-tests +.. _issue 143: https://bitbucket.org/ned/coveragepy/issue/143/omit-doesnt-seem-to-work-in-coverage +.. _issue 153: https://bitbucket.org/ned/coveragepy/issue/153/non-existent-filename-triggers +.. _issue 156: https://bitbucket.org/ned/coveragepy/issue/156/a-completely-unexecuted-file-shows-14 +.. _issue 163: https://bitbucket.org/ned/coveragepy/issue/163/problem-with-include-and-omit-filename +.. _issue 171: https://bitbucket.org/ned/coveragepy/issue/171/how-to-contribute-and-run-tests +.. _issue 193: https://bitbucket.org/ned/coveragepy/issue/193/unicodedecodeerror-on-htmlpy +.. _issue 201: https://bitbucket.org/ned/coveragepy/issue/201/coverage-using-django-14-with-pydb-on +.. _issue 202: https://bitbucket.org/ned/coveragepy/issue/202/get-rid-of-ez_setuppy-and +.. _issue 203: https://bitbucket.org/ned/coveragepy/issue/203/duplicate-filenames-reported-when-filename +.. _issue 205: https://bitbucket.org/ned/coveragepy/issue/205/make-pydoc-coverage-more-friendly +.. _issue 206: https://bitbucket.org/ned/coveragepy/issue/206/pydoc-coveragecoverage-fails-with-an-error +.. _issue 210: https://bitbucket.org/ned/coveragepy/issue/210/if-theres-no-coverage-data-coverage-xml +.. _issue 214: https://bitbucket.org/ned/coveragepy/issue/214/coveragepy-measures-itself-on-precise + + +Version 3.5.3 --- 2012-09-29 +---------------------------- + +- Line numbers in the HTML report line up better with the source lines, fixing + `issue 197`_, thanks Marius Gedminas. + +- When specifying a directory as the source= option, the directory itself no + longer needs to have a ``__init__.py`` file, though its sub-directories do, + to be considered as source files. + +- Files encoded as UTF-8 with a BOM are now properly handled, fixing + `issue 179`_. Thanks, Pablo Carballo. + +- Fixed more cases of non-Python files being reported as Python source, and + then not being able to parse them as Python. Closes `issue 82`_ (again). + Thanks, Julian Berman. + +- Fixed memory leaks under Python 3, thanks, Brett Cannon. Closes `issue 147`_. + +- Optimized .pyo files may not have been handled correctly, `issue 195`_. + Thanks, Marius Gedminas. + +- Certain unusually named file paths could have been mangled during reporting, + `issue 194`_. Thanks, Marius Gedminas. + +- Try to do a better job of the impossible task of detecting when we can't + build the C extension, fixing `issue 183`_. + +- Testing is now done with `tox`_, thanks, Marc Abramowitz. + +.. _issue 147: https://bitbucket.org/ned/coveragepy/issue/147/massive-memory-usage-by-ctracer +.. _issue 179: https://bitbucket.org/ned/coveragepy/issue/179/htmlreporter-fails-when-source-file-is +.. _issue 183: https://bitbucket.org/ned/coveragepy/issue/183/install-fails-for-python-23 +.. _issue 194: https://bitbucket.org/ned/coveragepy/issue/194/filelocatorrelative_filename-could-mangle +.. _issue 195: https://bitbucket.org/ned/coveragepy/issue/195/pyo-file-handling-in-codeunit +.. _issue 197: https://bitbucket.org/ned/coveragepy/issue/197/line-numbers-in-html-report-do-not-align +.. _tox: http://tox.readthedocs.org/ + + +Version 3.5.2 --- 2012-05-04 +---------------------------- + +No changes since 3.5.2.b1 + + +Version 3.5.2b1 --- 2012-04-29 +------------------------------ + +- The HTML report has slightly tweaked controls: the buttons at the top of + the page are color-coded to the source lines they affect. + +- Custom CSS can be applied to the HTML report by specifying a CSS file as + the ``extra_css`` configuration value in the ``[html]`` section. + +- Source files with custom encodings declared in a comment at the top are now + properly handled during reporting on Python 2. Python 3 always handled them + properly. This fixes `issue 157`_. + +- Backup files left behind by editors are no longer collected by the source= + option, fixing `issue 168`_. + +- If a file doesn't parse properly as Python, we don't report it as an error + if the file name seems like maybe it wasn't meant to be Python. This is a + pragmatic fix for `issue 82`_. + +- The ``-m`` switch on ``coverage report``, which includes missing line numbers + in the summary report, can now be specified as ``show_missing`` in the + config file. Closes `issue 173`_. + +- When running a module with ``coverage run -m <modulename>``, certain details + of the execution environment weren't the same as for + ``python -m <modulename>``. This had the unfortunate side-effect of making + ``coverage run -m unittest discover`` not work if you had tests in a + directory named "test". This fixes `issue 155`_ and `issue 142`_. + +- Now the exit status of your product code is properly used as the process + status when running ``python -m coverage run ...``. Thanks, JT Olds. + +- When installing into pypy, we no longer attempt (and fail) to compile + the C tracer function, closing `issue 166`_. + +.. _issue 142: https://bitbucket.org/ned/coveragepy/issue/142/executing-python-file-syspath-is-replaced +.. _issue 155: https://bitbucket.org/ned/coveragepy/issue/155/cant-use-coverage-run-m-unittest-discover +.. _issue 157: https://bitbucket.org/ned/coveragepy/issue/157/chokes-on-source-files-with-non-utf-8 +.. _issue 166: https://bitbucket.org/ned/coveragepy/issue/166/dont-try-to-compile-c-extension-on-pypy +.. _issue 168: https://bitbucket.org/ned/coveragepy/issue/168/dont-be-alarmed-by-emacs-droppings +.. _issue 173: https://bitbucket.org/ned/coveragepy/issue/173/theres-no-way-to-specify-show-missing-in + + +Version 3.5.1 --- 2011-09-23 +---------------------------- + +- The ``[paths]`` feature unfortunately didn't work in real world situations + where you wanted to, you know, report on the combined data. Now all paths + stored in the combined file are canonicalized properly. + + +Version 3.5.1b1 --- 2011-08-28 +------------------------------ + +- When combining data files from parallel runs, you can now instruct + coverage.py about which directories are equivalent on different machines. A + ``[paths]`` section in the configuration file lists paths that are to be + considered equivalent. Finishes `issue 17`_. + +- for-else constructs are understood better, and don't cause erroneous partial + branch warnings. Fixes `issue 122`_. + +- Branch coverage for ``with`` statements is improved, fixing `issue 128`_. + +- The number of partial branches reported on the HTML summary page was + different than the number reported on the individual file pages. This is + now fixed. + +- An explicit include directive to measure files in the Python installation + wouldn't work because of the standard library exclusion. Now the include + directive takes precedence, and the files will be measured. Fixes + `issue 138`_. + +- The HTML report now handles Unicode characters in Python source files + properly. This fixes `issue 124`_ and `issue 144`_. Thanks, Devin + Jeanpierre. + +- In order to help the core developers measure the test coverage of the + standard library, Brandon Rhodes devised an aggressive hack to trick Python + into running some coverage.py code before anything else in the process. + See the coverage/fullcoverage directory if you are interested. + +.. _issue 17: http://bitbucket.org/ned/coveragepy/issue/17/support-combining-coverage-data-from +.. _issue 122: http://bitbucket.org/ned/coveragepy/issue/122/for-else-always-reports-missing-branch +.. _issue 124: http://bitbucket.org/ned/coveragepy/issue/124/no-arbitrary-unicode-in-html-reports-in +.. _issue 128: http://bitbucket.org/ned/coveragepy/issue/128/branch-coverage-of-with-statement-in-27 +.. _issue 138: http://bitbucket.org/ned/coveragepy/issue/138/include-should-take-precedence-over-is +.. _issue 144: http://bitbucket.org/ned/coveragepy/issue/144/failure-generating-html-output-for + + +Version 3.5 --- 2011-06-29 +-------------------------- + +- The HTML report hotkeys now behave slightly differently when the current + chunk isn't visible at all: a chunk on the screen will be selected, + instead of the old behavior of jumping to the literal next chunk. + The hotkeys now work in Google Chrome. Thanks, Guido van Rossum. + + +Version 3.5b1 --- 2011-06-05 +---------------------------- + +- The HTML report now has hotkeys. Try ``n``, ``s``, ``m``, ``x``, ``b``,