diff options
author | Ned Batchelder <nedbat@gmail.com> | 2013-09-16 09:13:21 -0400 |
---|---|---|
committer | Ned Batchelder <nedbat@gmail.com> | 2013-09-16 09:13:21 -0400 |
commit | d39438e5121ebddaf8aa0f6b2abdf7ce07f7ea31 (patch) | |
tree | 1ee0cefdf520871cc54583410d8525e4dbf2eed8 | |
parent | dfe83e5fcdb5cc48880fda91d3d78353cb6ce4f7 (diff) | |
parent | f5675121528fe9d4729f0ba8052282de99ec9b79 (diff) | |
download | python-coveragepy-d39438e5121ebddaf8aa0f6b2abdf7ce07f7ea31.tar.gz |
Merged in rogerjhu/coverage.py (pull request #19)
Make UTF-8 detection more robust.
-rw-r--r-- | .hgignore | 64 | ||||
-rw-r--r-- | .travis.yml | 21 | ||||
-rw-r--r-- | CHANGES.txt | 16 | ||||
-rw-r--r-- | MANIFEST.in | 1 | ||||
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | coverage/__init__.py | 2 | ||||
-rw-r--r-- | coverage/cmdline.py | 21 | ||||
-rw-r--r-- | coverage/collector.py | 9 | ||||
-rw-r--r-- | coverage/config.py | 4 | ||||
-rw-r--r-- | coverage/control.py | 106 | ||||
-rw-r--r-- | coverage/data.py | 10 | ||||
-rw-r--r-- | coverage/debug.py | 54 | ||||
-rw-r--r-- | coverage/files.py | 8 | ||||
-rw-r--r-- | coverage/html.py | 29 | ||||
-rw-r--r-- | coverage/parser.py | 5 | ||||
-rw-r--r-- | coverage/version.py | 2 | ||||
-rw-r--r-- | igor.py | 8 | ||||
-rw-r--r-- | lab/hack_pyc.py | 164 | ||||
-rw-r--r-- | lab/sample.py | 10 | ||||
-rw-r--r-- | lab/show_pyc.py | 140 | ||||
-rw-r--r-- | lab/trace_sample.py | 114 | ||||
-rw-r--r-- | tests/coveragetest.py | 11 | ||||
-rw-r--r-- | tests/test_arcs.py | 63 | ||||
-rw-r--r-- | tests/test_cmdline.py | 80 | ||||
-rw-r--r-- | tests/test_collector.py | 57 | ||||
-rw-r--r-- | tests/test_debug.py | 113 | ||||
-rw-r--r-- | tests/test_farm.py | 6 | ||||
-rw-r--r-- | tests/test_files.py | 9 | ||||
-rw-r--r-- | tests/test_html.py | 3 | ||||
-rw-r--r-- | tests/test_misc.py | 1 | ||||
-rw-r--r-- | tests/test_process.py | 4 |
31 files changed, 767 insertions, 372 deletions
@@ -1,32 +1,32 @@ -syntax: glob
-
-# Files that can appear anywhere in the tree.
-*.pyc
-*.pyo
-*$py.class
-*.pyd
-*.so
-*.bak
-.coverage
-.coverage.*
-*.swp
-
-# Stuff generated by editors.
-.idea/
-
-# Stuff in the root.
-build
-*.egg-info
-dist
-htmlcov
-MANIFEST
-setuptools-*.egg
-.tox
-.tox_kits
-
-# Stuff in the test directory.
-zipmods.zip
-
-# Stuff in the doc directory.
-_build
-sample_html_beta
+syntax: glob + +# Files that can appear anywhere in the tree. +*.pyc +*.pyo +*$py.class +*.pyd +*.so +*.bak +.coverage +.coverage.* +*.swp + +# Stuff generated by editors. +.idea/ + +# Stuff in the root. +build +*.egg-info +dist +htmlcov +MANIFEST +setuptools-*.egg +.tox +.tox_kits + +# Stuff in the test directory. +zipmods.zip + +# Stuff in the doc directory. +_build +sample_html_beta diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..2dfd698 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,21 @@ +# Tell Travis what to do +# https://travis-ci.org/nedbat/coveragepy + +language: python + +python: + - 2.5 + - 2.6 + - 2.7 + - 3.2 + - 3.3 + - pypy + +install: + - python setup.py clean develop + +script: + - python igor.py zip_mods install_egg + - python igor.py test_with_tracer c + - python igor.py remove_extension + - python igor.py test_with_tracer py diff --git a/CHANGES.txt b/CHANGES.txt index 316df0c..47976b3 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -5,7 +5,10 @@ Change history for Coverage.py 3.6.1 ----- -- Improved the branch coverage mechanism, fixing `issue 175`_. +- 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`_. @@ -14,15 +17,20 @@ Change history for Coverage.py cause them to be incorrectly marked as unexecuted, as described in `issue 218`_. This is now fixed. -- When running a threaded program under the Python tracer, coverage would issue - a spurious warning about the trace function changing: "Trace function - changed, measurement is likely wrong: None." This fixes `issue 164`_. +- When running a threaded program under the Python tracer, coverage no longer + issues a spurious warning about the trace function changing: "Trace function + changed, measurement is likely wrong: None." This fixes `issue 164`_. + +- 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 255: https://bitbucket.org/ned/coveragepy/issue/255/directory-level-__main__py-not-included-in Version 3.6 --- 5 January 2013 diff --git a/MANIFEST.in b/MANIFEST.in index d2712d9..7224674 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,6 +4,7 @@ recursive-include coverage/fullcoverage * include coverage.egg-info/*.* include setup.py +include __main__.py include README.txt include CHANGES.txt include AUTHORS.txt @@ -48,7 +48,9 @@ metahtml: # Kitting -SDIST_CMD = python setup.py sdist --keep-temp --formats=gztar fixtar --owner=ned --group=coverage --clean +# For kitting on Windows: +# SDIST_CMD = python setup.py sdist --keep-temp --formats=gztar fixtar --owner=ned --group=coverage --clean +SDIST_CMD = python setup.py sdist --formats=gztar kit: $(SDIST_CMD) diff --git a/coverage/__init__.py b/coverage/__init__.py index 0ccc699..193b7a1 100644 --- a/coverage/__init__.py +++ b/coverage/__init__.py @@ -92,7 +92,7 @@ except KeyError: # COPYRIGHT AND LICENSE # # Copyright 2001 Gareth Rees. All rights reserved. -# Copyright 2004-2012 Ned Batchelder. All rights reserved. +# Copyright 2004-2013 Ned Batchelder. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are diff --git a/coverage/cmdline.py b/coverage/cmdline.py index ac80310..0881313 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -5,6 +5,7 @@ import optparse, os, sys, traceback from coverage.backward import sorted # pylint: disable=W0622 from coverage.execfile import run_python_file, run_python_module from coverage.misc import CoverageException, ExceptionDuringRun, NoSource +from coverage.debug import info_formatter class Opts(object): @@ -19,6 +20,10 @@ class Opts(object): '', '--branch', action='store_true', help="Measure branch coverage in addition to statement coverage." ) + debug = optparse.make_option( + '', '--debug', action='store', metavar="OPTS", + help="Debug options, separated by commas" + ) directory = optparse.make_option( '-d', '--directory', action='store', metavar="DIR", help="Write the output files to DIR." @@ -117,6 +122,7 @@ class CoverageOptionParser(optparse.OptionParser, object): self.set_defaults( actions=[], branch=None, + debug=None, directory=None, fail_under=None, help=None, @@ -310,6 +316,7 @@ CMDS = { [ Opts.append, Opts.branch, + Opts.debug, Opts.pylib, Opts.parallel_mode, Opts.module, @@ -404,6 +411,7 @@ class CoverageScript(object): source = unshell_list(options.source) omit = unshell_list(options.omit) include = unshell_list(options.include) + debug = unshell_list(options.debug) # Do something. self.coverage = self.covpkg.coverage( @@ -415,6 +423,7 @@ class CoverageScript(object): source = source, omit = omit, include = include, + debug = debug, ) if 'debug' in options.actions: @@ -584,16 +593,8 @@ class CoverageScript(object): for info in args: if info == 'sys': print("-- sys ----------------------------------------") - for label, info in self.coverage.sysinfo(): - if info == []: - info = "-none-" - if isinstance(info, list): - prefix = "%15s:" % label - for e in info: - print("%16s %s" % (prefix, e)) - prefix = "" - else: - print("%15s: %s" % (label, info)) + for line in info_formatter(self.coverage.sysinfo()): + print(" %s" % line) elif info == 'data': print("-- data ---------------------------------------") self.coverage.load() diff --git a/coverage/collector.py b/coverage/collector.py index 781a0fa..9a74700 100644 --- a/coverage/collector.py +++ b/coverage/collector.py @@ -75,10 +75,11 @@ class PyTracer(object): # in this file. self.data_stack.append((self.cur_file_data, self.last_line)) filename = frame.f_code.co_filename - tracename = self.should_trace_cache.get(filename) - if tracename is None: + if filename not in self.should_trace_cache: tracename = self.should_trace(filename, frame) self.should_trace_cache[filename] = tracename + else: + tracename = self.should_trace_cache[filename] #print("called, stack is %d deep, tracename is %r" % ( # len(self.data_stack), tracename)) if tracename: @@ -166,7 +167,7 @@ class Collector(object): """Create a collector. `should_trace` is a function, taking a filename, and returning a - canonicalized filename, or False depending on whether the file should + canonicalized filename, or None depending on whether the file should be traced or not. If `timid` is true, then a slower simpler trace function will be @@ -210,7 +211,7 @@ class Collector(object): # A cache of the results from should_trace, the decision about whether # to trace execution in a file. A dict of filename to (filename or - # False). + # None). self.should_trace_cache = {} # Our active Tracers. diff --git a/coverage/config.py b/coverage/config.py index c2ebecb..87318ff 100644 --- a/coverage/config.py +++ b/coverage/config.py @@ -111,6 +111,7 @@ class CoverageConfig(object): self.parallel = False self.timid = False self.source = None + self.debug = [] # Defaults for [report] self.exclude_list = DEFAULT_EXCLUDE[:] @@ -142,7 +143,7 @@ class CoverageConfig(object): if env: self.timid = ('--timid' in env) - MUST_BE_LIST = ["omit", "include"] + MUST_BE_LIST = ["omit", "include", "debug"] def from_args(self, **kwargs): """Read config values from `kwargs`.""" @@ -178,6 +179,7 @@ class CoverageConfig(object): ('branch', 'run:branch', 'boolean'), ('cover_pylib', 'run:cover_pylib', 'boolean'), ('data_file', 'run:data_file'), + ('debug', 'run:debug', 'list'), ('include', 'run:include', 'list'), ('omit', 'run:omit', 'list'), ('parallel', 'run:parallel', 'boolean'), diff --git a/coverage/control.py b/coverage/control.py index 8821bfb..4b76121 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -3,11 +3,12 @@ import atexit, os, random, socket, sys from coverage.annotate import AnnotateReporter -from coverage.backward import string_class, iitems +from coverage.backward import string_class, iitems, sorted # pylint: disable=W0622 from coverage.codeunit import code_unit_factory, CodeUnit from coverage.collector import Collector from coverage.config import CoverageConfig from coverage.data import CoverageData +from coverage.debug import DebugControl from coverage.files import FileLocator, TreeMatcher, FnmatchMatcher from coverage.files import PathAliases, find_python_files, prep_patterns from coverage.html import HtmlReporter @@ -17,6 +18,14 @@ from coverage.results import Analysis, Numbers from coverage.summary import SummaryReporter from coverage.xmlreport import XmlReporter +# Pypy has some unusual stuff in the "stdlib". Consider those locations +# when deciding where the stdlib is. +try: + import _structseq # pylint: disable=F0401 +except ImportError: + _structseq = None + + class coverage(object): """Programmatic access to coverage.py. @@ -33,7 +42,8 @@ class coverage(object): """ def __init__(self, data_file=None, data_suffix=None, cover_pylib=None, auto_data=False, timid=None, branch=None, config_file=True, - source=None, omit=None, include=None): + source=None, omit=None, include=None, debug=None, + debug_file=None): """ `data_file` is the base name of the data file to use, defaulting to ".coverage". `data_suffix` is appended (with a dot) to `data_file` to @@ -68,6 +78,10 @@ class coverage(object): `include` will be measured, files that match `omit` will not. Each will also accept a single string argument. + `debug` is a list of strings indicating what debugging information is + desired. `debug_file` is the file to write debug messages to, + defaulting to stderr. + """ from coverage import __version__ @@ -100,9 +114,12 @@ class coverage(object): self.config.from_args( data_file=data_file, cover_pylib=cover_pylib, timid=timid, branch=branch, parallel=bool_or_none(data_suffix), - source=source, omit=omit, include=include + source=source, omit=omit, include=include, debug=debug, ) + # Create and configure the debugging controller. + self.debug = DebugControl(self.config.debug, debug_file or sys.stderr) + self.auto_data = auto_data # _exclude_re is a dict mapping exclusion list names to compiled @@ -147,7 +164,8 @@ class coverage(object): # started rather than wherever the process eventually chdir'd to. self.data = CoverageData( basename=self.config.data_file, - collector="coverage v%s" % __version__ + collector="coverage v%s" % __version__, + debug=self.debug, ) # The dirs for files considered "installed with the interpreter". @@ -158,8 +176,8 @@ class coverage(object): # 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, os, random, socket): - if hasattr(m, "__file__"): + for m in (atexit, os, random, socket, _structseq): + if m is not None and hasattr(m, "__file__"): m_dir = self._canonical_dir(m) if m_dir not in self.pylib_dirs: self.pylib_dirs.append(m_dir) @@ -168,7 +186,7 @@ class coverage(object): # where we are. self.cover_dir = self._canonical_dir(__file__) - # The matchers for _should_trace, created when tracing starts. + # The matchers for _should_trace. self.source_match = None self.pylib_match = self.cover_match = None self.include_match = self.omit_match = None @@ -201,26 +219,28 @@ class coverage(object): filename = filename[:-9] + ".py" return filename - def _should_trace(self, filename, frame): - """Decide whether to trace execution in `filename` + def _should_trace_with_reason(self, filename, frame): + """Decide whether to trace execution in `filename`, with a reason. This function is called from the trace function. As each new file name is encountered, this function determines whether it is traced or not. - Returns a canonicalized filename if it should be traced, False if it - should not. + Returns a pair of values: the first indicates whether the file should + be traced: it's a canonicalized filename if it should be traced, None + if it should not. The second value is a string, the resason for the + decision. """ if not filename: # Empty string is pretty useless - return False + return None, "empty string isn't a filename" if filename.startswith('<'): # Lots of non-file execution is represented with artificial # filenames like "<string>", "<doctest readme.txt[0]>", or # "<exec_function>". Don't ever trace these executions, since we # can't do anything with the data later anyway. - return False + return None, "not a real filename" self._check_for_packages() @@ -246,35 +266,41 @@ class coverage(object): # stdlib and coverage.py directories. if self.source_match: if not self.source_match.match(canonical): - return False + return None, "falls outside the --source trees" elif self.include_match: if not self.include_match.match(canonical): - return False + return None, "falls outside the --include trees" else: # If we aren't supposed to trace installed code, then check if this # is near the Python standard library and skip it if so. if self.pylib_match and self.pylib_match.match(canonical): - return False + return None, "is in the stdlib" # We exclude the coverage code itself, since a little of it will be # measured otherwise. if self.cover_match and self.cover_match.match(canonical): - return False + return None, "is part of coverage.py" # Check the file against the omit pattern. if self.omit_match and self.omit_match.match(canonical): - return False + return None, "is inside an --omit pattern" - return canonical + return canonical, "because we love you" + + def _should_trace(self, filename, frame): + """Decide whether to trace execution in `filename`. - # To log what should_trace returns, change this to "if 1:" - if 0: - _real_should_trace = _should_trace - def _should_trace(self, filename, frame): # pylint: disable=E0102 - """A logging decorator around the real _should_trace function.""" - ret = self._real_should_trace(filename, frame) - print("should_trace: %r -> %r" % (filename, ret)) - return ret + Calls `_should_trace_with_reason`, and returns just the decision. + + """ + canonical, reason = self._should_trace_with_reason(filename, frame) + if self.debug.should('trace'): + if not canonical: + msg = "Not tracing %r: %s" % (filename, reason) + else: + msg = "Tracing %r" % (filename,) + self.debug.write(msg) + return canonical def _warn(self, msg): """Use `msg` as a warning.""" @@ -364,6 +390,16 @@ class coverage(object): if self.omit: self.omit_match = FnmatchMatcher(self.omit) + # The user may want to debug things, show info if desired. + if self.debug.should('config'): + self.debug.write("Configuration values:") + config_info = sorted(self.config.__dict__.items()) + self.debug.write_formatted_info(config_info) + + if self.debug.should('sys'): + self.debug.write("Debugging info:") + self.debug.write_formatted_info(self.sysinfo()) + self.collector.start() self._started = True self._measured = True @@ -688,11 +724,23 @@ class coverage(object): ('executable', sys.executable), ('cwd', os.getcwd()), ('path', sys.path), - ('environment', [ + ('environment', sorted([ ("%s = %s" % (k, v)) for k, v in iitems(os.environ) if re.search(r"^COV|^PY", k) - ]), + ])), + ('command_line', " ".join(getattr(sys, 'argv', ['???']))), ] + if self.source_match: + info.append(('source_match', self.source_match.info())) + if self.include_match: + info.append(('include_match', self.include_match.info())) + if self.omit_match: + info.append(('omit_match', self.omit_match.info())) + if self.cover_match: + info.append(('cover_match', self.cover_match.info())) + if self.pylib_match: + info.append(('pylib_match', self.pylib_match.info())) + return info diff --git a/coverage/data.py b/coverage/data.py index c86a77f..fb88c5b 100644 --- a/coverage/data.py +++ b/coverage/data.py @@ -23,15 +23,18 @@ class CoverageData(object): """ - def __init__(self, basename=None, collector=None): + def __init__(self, basename=None, collector=None, debug=None): """Create a CoverageData. `basename` is the name of the file to use for storing data. `collector` is a string describing the coverage measurement software. + `debug` is a `DebugControl` object for writing debug messages. + """ self.collector = collector or 'unknown' + self.debug = debug self.use_file = True @@ -121,6 +124,9 @@ class CoverageData(object): if self.collector: data['collector'] = self.collector + if self.debug and self.debug.should('dataio'): + self.debug.write("Writing data to %r" % (filename,)) + # Write the pickle to the file. fdata = open(filename, 'wb') try: @@ -134,6 +140,8 @@ class CoverageData(object): def raw_data(self, filename): """Return the raw pickled data from `filename`.""" + if self.debug and self.debug.should('dataio'): + self.debug.write("Reading data from %r" % (filename,)) fdata = open(filename, 'rb') try: data = pickle.load(fdata) diff --git a/coverage/debug.py b/coverage/debug.py new file mode 100644 index 0000000..104f3b1 --- /dev/null +++ b/coverage/debug.py @@ -0,0 +1,54 @@ +"""Control of and utilities for debugging.""" + +import os + + +# When debugging, it can be helpful to force some options, especially when +# debugging the configuration mechanisms you usually use to control debugging! +# This is a list of forced debugging options. +FORCED_DEBUG = [] + + +class DebugControl(object): + """Control and output for debugging.""" + + def __init__(self, options, output): + """Configure the options and output file for debugging.""" + self.options = options + self.output = output + + def should(self, option): + """Decide whether to output debug information in category `option`.""" + return (option in self.options or option in FORCED_DEBUG) + + def write(self, msg): + """Write a line of debug output.""" + if self.should('pid'): + msg = "pid %5d: %s" % (os.getpid(), msg) + self.output.write(msg+"\n") + self.output.flush() + + def write_formatted_info(self, info): + """Write a sequence of (label,data) pairs nicely.""" + for line in info_formatter(info): + self.write(" %s" % line) + + +def info_formatter(info): + """Produce a sequence of formatted lines from info. + + `info` is a sequence of pairs (label, data). The produced lines are + nicely formatted, ready to print. + + """ + label_len = max([len(l) for l, _d in info]) + for label, data in info: + if data == []: + data = "-none-" + if isinstance(data, (list, tuple)): + prefix = "%*s:" % (label_len, label) + for e in data: + yield "%*s %s" % (label_len+1, prefix, e) + prefix = "" + else: + yield "%*s: %s" % (label_len, label, data) diff --git a/coverage/files.py b/coverage/files.py index 4c55151..8d154c6 100644 --- a/coverage/files.py +++ b/coverage/files.py @@ -144,6 +144,10 @@ class TreeMatcher(object): def __repr__(self): return "<TreeMatcher %r>" % self.dirs + def info(self): + """A list of strings for displaying when dumping state.""" + return self.dirs + def add(self, directory): """Add another directory to the list we match for.""" self.dirs.append(directory) @@ -169,6 +173,10 @@ class FnmatchMatcher(object): def __repr__(self): return "<FnmatchMatcher %r>" % self.pats + def info(self): + """A list of strings for displaying when dumping state.""" + return self.pats + def match(self, fpath): """Does `fpath` match one of our filename patterns?""" for pat in self.pats: diff --git a/coverage/html.py b/coverage/html.py index ed8920f..aef43be 100644 --- a/coverage/html.py +++ b/coverage/html.py @@ -10,10 +10,6 @@ from coverage.report import Reporter from coverage.results import Numbers from coverage.templite import Templite -# Disable pylint msg W0612, because a bunch of variables look unused, but -# they're accessed in a Templite context via locals(). -# pylint: disable=W0612 - def data_filename(fname): """Return the path to a data file of ours.""" return os.path.join(os.path.split(__file__)[0], fname) @@ -162,7 +158,6 @@ class HtmlReporter(Reporter): nums = analysis.numbers missing_branch_arcs = analysis.missing_branch_arcs() - arcs = self.arcs # These classes determine which lines are highlighted by default. c_run = "run hide_run" @@ -220,13 +215,17 @@ class HtmlReporter(Reporter): }) # Write the HTML page for this file. - html_filename = flat_rootname + ".html" - html_path = os.path.join(self.directory, html_filename) - extra_css = self.extra_css + html = spaceless(self.source_tmpl.render({ + 'c_exc': c_exc, 'c_mis': c_mis, 'c_par': c_par, 'c_run': c_run, + 'arcs': self.arcs, 'extra_css': self.extra_css, + 'cu': cu, 'nums': nums, 'lines': lines, + })) - html = spaceless(self.source_tmpl.render(locals())) if sys.version_info < (3, 0): html = html.decode(encoding) + + html_filename = flat_rootname + ".html" + html_path = os.path.join(self.directory, html_filename) self.write_html(html_path, html) # Save this file's information for the index file. @@ -244,13 +243,15 @@ class HtmlReporter(Reporter): data("htmlfiles/index.html"), self.template_globals ) - files = self.files - arcs = self.arcs + self.totals = sum([f['nums'] for f in self.files]) - self.totals = totals = sum([f['nums'] for f in files]) - extra_css = self.extra_css + html = index_tmpl.render({ + 'arcs': self.arcs, + 'extra_css': self.extra_css, + 'files': self.files, + 'totals': self.totals, + }) - html = index_tmpl.render(locals()) if sys.version_info < (3, 0): html = html.decode("utf-8") self.write_html( diff --git a/coverage/parser.py b/coverage/parser.py index 8d6a077..2d777a5 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -512,11 +512,6 @@ class ByteParser(object): chunk.exits.add(block_stack[-1][1]) chunk = None if bc.op == OP_END_FINALLY: - if block_stack: - # A break that goes through a finally will jump to whatever - # block is on top of the stack. - # print self._block_stack_repr(block_stack) - chunk.exits.add(block_stack[-1][1]) # 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): diff --git a/coverage/version.py b/coverage/version.py index 422fbad..db4bca5 100644 --- a/coverage/version.py +++ b/coverage/version.py @@ -1,7 +1,7 @@ """The version and URL for coverage.py""" # This file is exec'ed in setup.py, don't import anything! -__version__ = "3.6.1a0" # see detailed history in CHANGES.txt +__version__ = "3.6.1a1" # see detailed history in CHANGES.txt __url__ = "http://nedbatchelder.com/code/coverage" if max(__version__).isalpha(): @@ -15,6 +15,10 @@ import sys import zipfile +# Functions named do_* are executable from the command line: do_blah is run +# by "python igor.py blah". + + def do_remove_extension(): """Remove the compiled C extension, no matter what its name.""" @@ -195,6 +199,8 @@ def do_check_eol(): check_file("setup.py") check_file("igor.py") check_file("Makefile") + check_file(".hgignore") + check_file(".travis.yml") check_files("doc", ["*.rst"]) check_files(".", ["*.txt"]) @@ -217,7 +223,7 @@ def print_banner(label): def do_help(): """List the available commands""" - items = globals().items() + items = list(globals().items()) items.sort() for name, value in items: if name.startswith('do_'): diff --git a/lab/hack_pyc.py b/lab/hack_pyc.py index e8992b9..1cdc476 100644 --- a/lab/hack_pyc.py +++ b/lab/hack_pyc.py @@ -1,82 +1,82 @@ -""" Wicked hack to get .pyc files to do bytecode tracing instead of
- line tracing.
-"""
-
-import marshal, new, opcode, sys, types
-
-from lnotab import lnotab_numbers, lnotab_string
-
-class PycFile:
- def read(self, f):
- if isinstance(f, basestring):
- f = open(f, "rb")
- self.magic = f.read(4)
- self.modtime = f.read(4)
- self.code = marshal.load(f)
-
- def write(self, f):
- if isinstance(f, basestring):
- f = open(f, "wb")
- f.write(self.magic)
- f.write(self.modtime)
- marshal.dump(self.code, f)
-
- def hack_line_numbers(self):
- self.code = hack_line_numbers(self.code)
-
-def hack_line_numbers(code):
- """ Replace a code object's line number information to claim that every
- byte of the bytecode is a new source line. Returns a new code
- object. Also recurses to hack the line numbers in nested code objects.
- """
-
- # Create a new lnotab table. Each opcode is claimed to be at
- # 1000*lineno + (opcode number within line), so for example, the opcodes on
- # source line 12 will be given new line numbers 12000, 12001, 12002, etc.
- old_num = list(lnotab_numbers(code.co_lnotab, code.co_firstlineno))
- n_bytes = len(code.co_code)
- new_num = []
- line = 0
- opnum_in_line = 0
- i_byte = 0
- while i_byte < n_bytes:
- if old_num and i_byte == old_num[0][0]:
- line = old_num.pop(0)[1]
- opnum_in_line = 0
- new_num.append((i_byte, 100000000 + 1000*line + opnum_in_line))
- if ord(code.co_code[i_byte]) >= opcode.HAVE_ARGUMENT:
- i_byte += 3
- else:
- i_byte += 1
- opnum_in_line += 1
-
- # new_num is a list of pairs, (byteoff, lineoff). Turn it into an lnotab.
- new_firstlineno = new_num[0][1]-1
- new_lnotab = lnotab_string(new_num, new_firstlineno)
-
- # Recurse into code constants in this code object.
- new_consts = []
- for const in code.co_consts:
- if type(const) == types.CodeType:
- new_consts.append(hack_line_numbers(const))
- else:
- new_consts.append(const)
-
- # Create a new code object, just like the old one, except with new
- # line numbers.
- new_code = new.code(
- code.co_argcount, code.co_nlocals, code.co_stacksize, code.co_flags,
- code.co_code, tuple(new_consts), code.co_names, code.co_varnames,
- code.co_filename, code.co_name, new_firstlineno, new_lnotab
- )
-
- return new_code
-
-def hack_file(f):
- pyc = PycFile()
- pyc.read(f)
- pyc.hack_line_numbers()
- pyc.write(f)
-
-if __name__ == '__main__':
- hack_file(sys.argv[1])
+""" Wicked hack to get .pyc files to do bytecode tracing instead of + line tracing. +""" + +import marshal, new, opcode, sys, types + +from lnotab import lnotab_numbers, lnotab_string + +class PycFile: + def read(self, f): + if isinstance(f, basestring): + f = open(f, "rb") + self.magic = f.read(4) + self.modtime = f.read(4) + self.code = marshal.load(f) + + def write(self, f): + if isinstance(f, basestring): + f = open(f, "wb") + f.write(self.magic) + f.write(self.modtime) + marshal.dump(self.code, f) + + def hack_line_numbers(self): + self.code = hack_line_numbers(self.code) + +def hack_line_numbers(code): + """ Replace a code object's line number information to claim that every + byte of the bytecode is a new source line. Returns a new code + object. Also recurses to hack the line numbers in nested code objects. + """ + + # Create a new lnotab table. Each opcode is claimed to be at + # 1000*lineno + (opcode number within line), so for example, the opcodes on + # source line 12 will be given new line numbers 12000, 12001, 12002, etc. + old_num = list(lnotab_numbers(code.co_lnotab, code.co_firstlineno)) + n_bytes = len(code.co_code) + new_num = [] + line = 0 + opnum_in_line = 0 + i_byte = 0 + while i_byte < n_bytes: + if old_num and i_byte == old_num[0][0]: + line = old_num.pop(0)[1] + opnum_in_line = 0 + new_num.append((i_byte, 100000000 + 1000*line + opnum_in_line)) + if ord(code.co_code[i_byte]) >= opcode.HAVE_ARGUMENT: + i_byte += 3 + else: + i_byte += 1 + opnum_in_line += 1 + + # new_num is a list of pairs, (byteoff, lineoff). Turn it into an lnotab. + new_firstlineno = new_num[0][1]-1 + new_lnotab = lnotab_string(new_num, new_firstlineno) + + # Recurse into code constants in this code object. + new_consts = [] + for const in code.co_consts: + if type(const) == types.CodeType: + new_consts.append(hack_line_numbers(const)) + else: + new_consts.append(const) + + # Create a new code object, just like the old one, except with new + # line numbers. + new_code = new.code( + code.co_argcount, code.co_nlocals, code.co_stacksize, code.co_flags, + code.co_code, tuple(new_consts), code.co_names, code.co_varnames, + code.co_filename, code.co_name, new_firstlineno, new_lnotab + ) + + return new_code + +def hack_file(f): + pyc = PycFile() + pyc.read(f) + pyc.hack_line_numbers() + pyc.write(f) + +if __name__ == '__main__': + hack_file(sys.argv[1]) diff --git a/lab/sample.py b/lab/sample.py index cf4f6dc..bb62848 100644 --- a/lab/sample.py +++ b/lab/sample.py @@ -1,5 +1,5 @@ -a, b = 1, 0
-if a or b or fn():
- # Hey
- a = 3
-d = 4
\ No newline at end of file +a, b = 1, 0 +if a or b or fn(): + # Hey + a = 3 +d = 4 diff --git a/lab/show_pyc.py b/lab/show_pyc.py index 7dacc2b..b2cbb34 100644 --- a/lab/show_pyc.py +++ b/lab/show_pyc.py @@ -1,70 +1,70 @@ -import dis, marshal, struct, sys, time, types
-
-def show_pyc_file(fname):
- f = open(fname, "rb")
- magic = f.read(4)
- moddate = f.read(4)
- modtime = time.asctime(time.localtime(struct.unpack('L', moddate)[0]))
- print "magic %s" % (magic.encode('hex'))
- print "moddate %s (%s)" % (moddate.encode('hex'), modtime)
- code = marshal.load(f)
- show_code(code)
-
-def show_py_file(fname):
- text = open(fname).read().replace('\r\n', '\n')
- show_py_text(text, fname=fname)
-
-def show_py_text(text, fname="<string>"):
- code = compile(text, fname, "exec")
- show_code(code)
-
-def show_code(code, indent=''):
- print "%scode" % indent
- indent += ' '
- print "%sargcount %d" % (indent, code.co_argcount)
- print "%snlocals %d" % (indent, code.co_nlocals)
- print "%sstacksize %d" % (indent, code.co_stacksize)
- print "%sflags %04x" % (indent, code.co_flags)
- show_hex("code", code.co_code, indent=indent)
- dis.disassemble(code)
- print "%sconsts" % indent
- for const in code.co_consts:
- if type(const) == types.CodeType:
- show_code(const, indent+' ')
- else:
- print " %s%r" % (indent, const)
- print "%snames %r" % (indent, code.co_names)
- print "%svarnames %r" % (indent, code.co_varnames)
- print "%sfreevars %r" % (indent, code.co_freevars)
- print "%scellvars %r" % (indent, code.co_cellvars)
- print "%sfilename %r" % (indent, code.co_filename)
- print "%sname %r" % (indent, code.co_name)
- print "%sfirstlineno %d" % (indent, code.co_firstlineno)
- show_hex("lnotab", code.co_lnotab, indent=indent)
-
-def show_hex(label, h, indent):
- h = h.encode('hex')
- if len(h) < 60:
- print "%s%s %s" % (indent, label, h)
- else:
- print "%s%s" % (indent, label)
- for i in range(0, len(h), 60):
- print "%s %s" % (indent, h[i:i+60])
-
-def show_file(fname):
- if fname.endswith('pyc'):
- show_pyc_file(fname)
- elif fname.endswith('py'):
- show_py_file(fname)
- else:
- print "Odd file:", fname
-
-def main(args):
- if args[0] == '-c':
- show_py_text(" ".join(args[1:]).replace(";", "\n"))
- else:
- for a in args:
- show_file(a)
-
-if __name__ == '__main__':
- main(sys.argv[1:])
+import dis, marshal, struct, sys, time, types + +def show_pyc_file(fname): + f = open(fname, "rb") + magic = f.read(4) + moddate = f.read(4) + modtime = time.asctime(time.localtime(struct.unpack('L', moddate)[0])) + print "magic %s" % (magic.encode('hex')) + print "moddate %s (%s)" % (moddate.encode('hex'), modtime) + code = marshal.load(f) + show_code(code) + +def show_py_file(fname): + text = open(fname).read().replace('\r\n', '\n') + show_py_text(text, fname=fname) + +def show_py_text(text, fname="<string>"): + code = compile(text, fname, "exec") + show_code(code) + +def show_code(code, indent=''): + print "%scode" % indent + indent += ' ' + print "%sargcount %d" % (indent, code.co_argcount) + print "%snlocals %d" % (indent, code.co_nlocals) + print "%sstacksize %d" % (indent, code.co_stacksize) + print "%sflags %04x" % (indent, code.co_flags) + show_hex("code", code.co_code, indent=indent) + dis.disassemble(code) + print "%sconsts" % indent + for const in code.co_consts: + if type(const) == types.CodeType: + show_code(const, indent+' ') + else: + print " %s%r" % (indent, const) + print "%snames %r" % (indent, code.co_names) + print "%svarnames %r" % (indent, code.co_varnames) + print "%sfreevars %r" % (indent, code.co_freevars) + print "%scellvars %r" % (indent, code.co_cellvars) + print "%sfilename %r" % (indent, code.co_filename) + print "%sname %r" % (indent, code.co_name) + print "%sfirstlineno %d" % (indent, code.co_firstlineno) + show_hex("lnotab", code.co_lnotab, indent=indent) + +def show_hex(label, h, indent): + h = h.encode('hex') + if len(h) < 60: + print "%s%s %s" % (indent, label, h) + else: + print "%s%s" % (indent, label) + for i in range(0, len(h), 60): + print "%s %s" % (indent, h[i:i+60]) + +def show_file(fname): + if fname.endswith('pyc'): + show_pyc_file(fname) + elif fname.endswith('py'): + show_py_file(fname) + else: + print "Odd file:", fname + +def main(args): + if args[0] == '-c': + show_py_text(" ".join(args[1:]).replace(";", "\n")) + else: + for a in args: + show_file(a) + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/lab/trace_sample.py b/lab/trace_sample.py index 9fa3724..3f81919 100644 --- a/lab/trace_sample.py +++ b/lab/trace_sample.py @@ -1,57 +1,57 @@ -import os, sys
-
-global nest
-nest = 0
-
-def trace(frame, event, arg):
- #if event == 'line':
- global nest
-
- print "%s%s %s %d" % (
- " " * nest,
- event,
- os.path.basename(frame.f_code.co_filename),
- frame.f_lineno,
- )
-
- if event == 'call':
- nest += 1
- if event == 'return':
- nest -= 1
-
- return trace
-
-def trace2(frame, event, arg):
- #if event == 'line':
- global nest
-
- print "2: %s%s %s %d" % (
- " " * nest,
- event,
- os.path.basename(frame.f_code.co_filename),
- frame.f_lineno,
- )
-
- if event == 'call':
- nest += 1
- if event == 'return':
- nest -= 1
-
- return trace2
-
-sys.settrace(trace)
-
-def bar():
- print "nar"
-
-a = 26
-def foo(n):
- a = 28
- sys.settrace(sys.gettrace())
- bar()
- a = 30
- return 2*n
-
-print foo(a)
-#import sample
-#import littleclass
+import os, sys + +global nest +nest = 0 + +def trace(frame, event, arg): + #if event == 'line': + global nest + + print "%s%s %s %d" % ( + " " * nest, + event, + os.path.basename(frame.f_code.co_filename), + frame.f_lineno, + ) + + if event == 'call': + nest += 1 + if event == 'return': + nest -= 1 + + return trace + +def trace2(frame, event, arg): + #if event == 'line': + global nest + + print "2: %s%s %s %d" % ( + " " * nest, + event, + os.path.basename(frame.f_code.co_filename), + frame.f_lineno, + ) + + if event == 'call': + nest += 1 + if event == 'return': + nest -= 1 + + return trace2 + +sys.settrace(trace) + +def bar(): + print "nar" + +a = 26 +def foo(n): + a = 28 + sys.settrace(sys.gettrace()) + bar() + a = 30 + return 2*n + +print foo(a) +#import sample +#import littleclass diff --git a/tests/coveragetest.py b/tests/coveragetest.py index 5613fa6..5060b53 100644 --- a/tests/coveragetest.py +++ b/tests/coveragetest.py @@ -36,8 +36,12 @@ OK, ERR = 0, 1 class CoverageTest(TestCase): """A base class for Coverage test cases.""" + # Our own setting: most CoverageTests run in their own temp directory. run_in_temp_dir = True + # Standard unittest setting: show me diffs even if they are very long. + maxDiff = None + def setUp(self): super(CoverageTest, self).setUp() @@ -61,8 +65,11 @@ class CoverageTest(TestCase): self.old_dir = os.getcwd() os.chdir(self.temp_dir) - # Modules should be importable from this temp directory. - sys.path.insert(0, '') + # Modules should be importable from this temp directory. We don't + # use '' because we make lots of different temp directories and + # nose's caching importer can get confused. The full path prevents + # problems. + sys.path.insert(0, os.getcwd()) # Keep a counter to make every call to check_coverage unique. self.n = 0 diff --git a/tests/test_arcs.py b/tests/test_arcs.py index f3c5fc3..6268e28 100644 --- a/tests/test_arcs.py +++ b/tests/test_arcs.py @@ -141,7 +141,7 @@ class SimpleArcTest(CoverageTest): ) if 0: # expected failure - def test_lambdas_are_confusing_bug_90(self): + def test_unused_lambdas_are_confusing_bug_90(self): self.check_coverage("""\ a = 1 fn = lambda x: x @@ -172,9 +172,9 @@ if sys.version_info >= (2, 6): self.check_coverage("""\ for i in range(2): with open("test", "w") as f: - print 3 - print 4 - print 5 + print(3) + print(4) + print(5) """, arcz=".1 12 23 34 41 15 5." ) @@ -456,8 +456,8 @@ class ExceptionArcTest(CoverageTest): d = 12 # C assert a == 5 and c == 10 and d == 12 # D """, - arcz=".1 12 23 34 3D 45 56 67 68 8A A3 AB AD BC CD D.", - arcz_missing="3D AD", arcz_unpredicted="7A") + arcz=".1 12 23 34 3D 45 56 67 68 8A A3 AB BC CD D.", + arcz_missing="3D", arcz_unpredicted="7A") self.check_coverage("""\ a, c, d, i = 1, 1, 1, 99 try: @@ -473,8 +473,8 @@ class ExceptionArcTest(CoverageTest): d = 12 # C assert a == 8 and c == 10 and d == 1 # D """, - arcz=".1 12 23 34 3D 45 56 67 68 8A A3 AB AD BC CD D.", - arcz_missing="67 AB AD BC CD", arcz_unpredicted="") + arcz=".1 12 23 34 3D 45 56 67 68 8A A3 AB BC CD D.", + arcz_missing="67 AB BC CD", arcz_unpredicted="") def test_break_in_finally(self): @@ -493,22 +493,45 @@ class ExceptionArcTest(CoverageTest): d = 12 # C assert a == 5 and c == 10 and d == 1 # D """, - arcz=".1 12 23 34 3D 45 56 67 68 7A 8A A3 AB AD BC CD D.", - arcz_missing="3D AB BC CD", arcz_unpredicted="") + arcz=".1 12 23 34 3D 45 56 67 68 7A 8A A3 AB BC CD D.", + arcz_missing="3D AB BC CD", arcz_unpredicted="AD") - if 0: # expected failure - def test_finally_in_loop_bug_92(self): + def test_finally_in_loop_bug_92(self): + self.check_coverage("""\ + for i in range(5): + try: + j = 3 + finally: + f = 5 + g = 6 + h = 7 + """, + arcz=".1 12 23 35 56 61 17 7.", + arcz_missing="", arcz_unpredicted="") + + # Run this test only on 2.6 and 2.7 for now. I hope to fix it on Py3 + # eventually... + if (2, 6) <= sys.version_info < (3,): + # "except Exception as e" is crucial here. + def test_bug_212(self): self.check_coverage("""\ - for i in range(5): + def b(exc): try: - j = 3 - finally: - f = 5 - g = 6 - h = 7 + while 1: + raise Exception(exc) # 4 + except Exception as e: + if exc != 'expected': + raise + q = 8 + + b('expected') + try: + b('unexpected') # C + except: + pass """, - arcz=".1 12 23 35 56 61 17 7.", - arcz_missing="", arcz_unpredicted="") + arcz=".1 .2 1A 23 34 56 67 68 8. AB BC C. DE E.", + arcz_missing="C.", arcz_unpredicted="45 7. CD") if sys.version_info >= (2, 5): # Try-except-finally was new in 2.5 diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py index 6b11b3e..493ce18 100644 --- a/tests/test_cmdline.py +++ b/tests/test_cmdline.py @@ -15,7 +15,7 @@ class CmdLineTest(CoverageTest): run_in_temp_dir = False INIT_LOAD = """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None) .load()\n""" def model_object(self): @@ -104,7 +104,7 @@ class ClassicCmdLineTest(CmdLineTest): def test_erase(self): # coverage -e self.cmd_executes("-e", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None) .erase() """) self.cmd_executes_same("-e", "--erase") @@ -114,7 +114,7 @@ class ClassicCmdLineTest(CmdLineTest): # -x calls coverage.load first. self.cmd_executes("-x foo.py", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None) .load() .start() .run_python_file('foo.py', ['foo.py']) @@ -123,7 +123,7 @@ class ClassicCmdLineTest(CmdLineTest): """) # -e -x calls coverage.erase first. self.cmd_executes("-e -x foo.py", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None) .erase() .start() .run_python_file('foo.py', ['foo.py']) @@ -132,7 +132,7 @@ class ClassicCmdLineTest(CmdLineTest): """) # --timid sets a flag, and program arguments get passed through. self.cmd_executes("-x --timid foo.py abc 123", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=True, branch=None, config_file=True, source=None, include=None, omit=None) + .coverage(cover_pylib=None, data_suffix=None, timid=True, branch=None, config_file=True, source=None, include=None, omit=None, debug=None) .load() .start() .run_python_file('foo.py', ['foo.py', 'abc', '123']) @@ -141,7 +141,7 @@ class ClassicCmdLineTest(CmdLineTest): """) # -L sets a flag, and flags for the program don't confuse us. self.cmd_executes("-x -p -L foo.py -a -b", """\ - .coverage(cover_pylib=True, data_suffix=True, timid=None, branch=None, config_file=True, source=None, include=None, omit=None) + .coverage(cover_pylib=True, data_suffix=True, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None) .load() .start() .run_python_file('foo.py', ['foo.py', '-a', '-b']) @@ -158,7 +158,7 @@ class ClassicCmdLineTest(CmdLineTest): def test_combine(self): # coverage -c self.cmd_executes("-c", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None) .load() .combine() .save() @@ -180,13 +180,13 @@ class ClassicCmdLineTest(CmdLineTest): show_missing=True) """) self.cmd_executes("-r -o fooey", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"]) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"], debug=None) .load() .report(ignore_errors=None, omit=["fooey"], include=None, morfs=[], show_missing=None) """) self.cmd_executes("-r -o fooey,booey", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"]) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"], debug=None) .load() .report(ignore_errors=None, omit=["fooey", "booey"], include=None, morfs=[], show_missing=None) @@ -225,13 +225,13 @@ class ClassicCmdLineTest(CmdLineTest): omit=None, include=None, morfs=[]) """) self.cmd_executes("-a -o fooey", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"]) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"], debug=None) .load() .annotate(directory=None, ignore_errors=None, omit=["fooey"], include=None, morfs=[]) """) self.cmd_executes("-a -o fooey,booey", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"]) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"], debug=None) .load() .annotate(directory=None, ignore_errors=None, omit=["fooey", "booey"], include=None, morfs=[]) @@ -270,13 +270,13 @@ class ClassicCmdLineTest(CmdLineTest): omit=None, include=None, morfs=[]) """) self.cmd_executes("-b -o fooey", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"]) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"], debug=None) .load() .html_report(directory=None, ignore_errors=None, title=None, omit=["fooey"], include=None, morfs=[]) """) self.cmd_executes("-b -o fooey,booey", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"]) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"], debug=None) .load() .html_report(directory=None, ignore_errors=None, title=None, omit=["fooey", "booey"], include=None, morfs=[]) @@ -481,7 +481,7 @@ class NewCmdLineTest(CmdLineTest): self.cmd_executes_same("run --timid f.py", "-e -x --timid f.py") self.cmd_executes_same("run", "-x") self.cmd_executes("run --branch foo.py", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=True, config_file=True, source=None, include=None, omit=None) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=True, config_file=True, source=None, include=None, omit=None, debug=None) .erase() .start() .run_python_file('foo.py', ['foo.py']) @@ -489,7 +489,7 @@ class NewCmdLineTest(CmdLineTest): .save() """) self.cmd_executes("run --rcfile=myrc.rc foo.py", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file="myrc.rc", source=None, include=None, omit=None) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file="myrc.rc", source=None, include=None, omit=None, debug=None) .erase() .start() .run_python_file('foo.py', ['foo.py']) @@ -497,7 +497,7 @@ class NewCmdLineTest(CmdLineTest): .save() """) self.cmd_executes("run --include=pre1,pre2 foo.py", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=["pre1", "pre2"], omit=None) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=["pre1", "pre2"], omit=None, debug=None) .erase() .start() .run_python_file('foo.py', ['foo.py']) @@ -505,7 +505,7 @@ class NewCmdLineTest(CmdLineTest): .save() """) self.cmd_executes("run --omit=opre1,opre2 foo.py", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["opre1", "opre2"]) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["opre1", "opre2"], debug=None) .erase() .start() .run_python_file('foo.py', ['foo.py']) @@ -517,7 +517,9 @@ class NewCmdLineTest(CmdLineTest): .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=["pre1", "pre2"], - omit=["opre1", "opre2"]) + omit=["opre1", "opre2"], + debug=None, + ) .erase() .start() .run_python_file('foo.py', ['foo.py']) @@ -529,7 +531,37 @@ class NewCmdLineTest(CmdLineTest): .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=["quux", "hi.there", "/home/bar"], include=None, - omit=None) + omit=None, + debug=None, + ) + .erase() + .start() + .run_python_file('foo.py', ['foo.py']) + .stop() + .save() + """) + + def test_run_debug(self): + self.cmd_executes("run --debug=opt1 foo.py", """\ + .coverage(cover_pylib=None, data_suffix=None, timid=None, + branch=None, config_file=True, + source=None, include=None, + omit=None, + debug=["opt1"], + ) + .erase() + .start() + .run_python_file('foo.py', ['foo.py']) + .stop() + .save() + """) + self.cmd_executes("run --debug=opt1,opt2 foo.py", """\ + .coverage(cover_pylib=None, data_suffix=None, timid=None, + branch=None, config_file=True, + source=None, include=None, + omit=None, + debug=["opt1","opt2"], + ) .erase() .start() .run_python_file('foo.py', ['foo.py']) @@ -539,7 +571,7 @@ class NewCmdLineTest(CmdLineTest): def test_run_module(self): self.cmd_executes("run -m mymodule", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None) .erase() .start() .run_python_module('mymodule', ['mymodule']) @@ -547,7 +579,7 @@ class NewCmdLineTest(CmdLineTest): .save() """) self.cmd_executes("run -m mymodule -qq arg1 arg2", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None) .erase() .start() .run_python_module('mymodule', ['mymodule', '-qq', 'arg1', 'arg2']) @@ -555,7 +587,7 @@ class NewCmdLineTest(CmdLineTest): .save() """) self.cmd_executes("run --branch -m mymodule", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=True, config_file=True, source=None, include=None, omit=None) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=True, config_file=True, source=None, include=None, omit=None, debug=None) .erase() .start() .run_python_module('mymodule', ['mymodule']) @@ -583,13 +615,13 @@ class NewCmdLineTest(CmdLineTest): outfile="-") """) self.cmd_executes("xml --omit fooey", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"]) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"], debug=None) .load() .xml_report(ignore_errors=None, omit=["fooey"], include=None, morfs=[], outfile=None) """) self.cmd_executes("xml --omit fooey,booey", """\ - .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"]) + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"], debug=None) .load() .xml_report(ignore_errors=None, omit=["fooey", "booey"], include=None, morfs=[], outfile=None) diff --git a/tests/test_collector.py b/tests/test_collector.py new file mode 100644 index 0000000..af3814f --- /dev/null +++ b/tests/test_collector.py @@ -0,0 +1,57 @@ +"""Tests of coverage/collector.py and other collectors.""" + +import re + +import coverage +from coverage.backward import StringIO + +from tests.coveragetest import CoverageTest + + +class CollectorTest(CoverageTest): + """Test specific aspects of the collection process.""" + + def test_should_trace_cache(self): + # The tracers should only invoke should_trace once for each file name. + # TODO: Might be better to do this with a mocked _should_trace, + # rather than by examining debug output. + + # Make some files that invoke each other. + self.make_file("f1.py", """\ + def f1(x, f): + return f(x) + """) + + self.make_file("f2.py", """\ + import f1 + + def func(x): + return f1.f1(x, otherfunc) + + def otherfunc(x): + return x*x + + for i in range(10): + func(i) + """) + + # Trace one file, but not the other, and get the debug output. + debug_out = StringIO() + cov = coverage.coverage( + include=["f1.py"], debug=['trace'], debug_file=debug_out + ) + + # Import the python file, executing it. + self.start_import_stop(cov, "f2") + + # Grab all the filenames mentioned in debug output, there should be no + # duplicates. + trace_lines = [ + l for l in debug_out.getvalue().splitlines() + if l.startswith("Tracing ") or l.startswith("Not tracing ") + ] + filenames = [re.search(r"'[^']+'", l).group() for l in trace_lines] + self.assertEqual(len(filenames), len(set(filenames))) + + # Double-check that the tracing messages are in there somewhere. + self.assertGreater(len(filenames), 5) diff --git a/tests/test_debug.py b/tests/test_debug.py new file mode 100644 index 0000000..0132c1c --- /dev/null +++ b/tests/test_debug.py @@ -0,0 +1,113 @@ +"""Tests of coverage/debug.py""" + +import os +import re + +import coverage +from coverage.backward import StringIO +from coverage.debug import info_formatter +from tests.coveragetest import CoverageTest + + +class InfoFormatterTest(CoverageTest): + """Tests of misc.info_formatter.""" + + def test_info_formatter(self): + lines = list(info_formatter([ + ('x', 'hello there'), + ('very long label', ['one element']), + ('regular', ['abc', 'def', 'ghi', 'jkl']), + ('nothing', []), + ])) + self.assertEqual(lines, [ + ' x: hello there', + 'very long label: one element', + ' regular: abc', + ' def', + ' ghi', + ' jkl', + ' nothing: -none-', + ]) + + +class DebugTraceTest(CoverageTest): + """Tests of debug output.""" + + def f1_debug_output(self, debug): + """Runs some code with `debug` option, returns the debug output.""" + # Make code to run. + self.make_file("f1.py", """\ + def f1(x): + return x+1 + + for i in range(5): + f1(i) + """) + + debug_out = StringIO() + cov = coverage.coverage(debug=debug, debug_file=debug_out) + self.start_import_stop(cov, "f1") + + out_lines = debug_out.getvalue().splitlines() + return out_lines + + def test_debug_no_trace(self): + out_lines = self.f1_debug_output([]) + + # We should have no output at all. + self.assertFalse(out_lines) + + def test_debug_trace(self): + out_lines = self.f1_debug_output(["trace"]) + + # We should have a line like "Tracing 'f1.py'" + self.assertIn("Tracing 'f1.py'", out_lines) + + # We should lines like "Not tracing 'collector.py'..." + coverage_lines = lines_matching( + out_lines, + r"^Not tracing .*: is part of coverage.py$" + ) + self.assertTrue(coverage_lines) + + def test_debug_trace_pid(self): + out_lines = self.f1_debug_output(["trace", "pid"]) + + # Now our lines are always prefixed with the process id. + pid_prefix = "^pid %5d: " % os.getpid() + pid_lines = lines_matching(out_lines, pid_prefix) + self.assertEqual(pid_lines, out_lines) + + # We still have some tracing, and some not tracing. + self.assertTrue(lines_matching(out_lines, pid_prefix + "Tracing ")) + self.assertTrue(lines_matching(out_lines, pid_prefix + "Not tracing ")) + + def test_debug_config(self): + out_lines = self.f1_debug_output(["config"]) + + labels = """ + attempted_config_files branch config_files cover_pylib data_file + debug exclude_list extra_css html_dir html_title ignore_errors + include omit parallel partial_always_list partial_list paths + precision show_missing source timid xml_output + """.split() + for label in labels: + label_pat = r"^\s*%s: " % label + self.assertEqual(len(lines_matching(out_lines, label_pat)), 1) + + def test_debug_sys(self): + out_lines = self.f1_debug_output(["sys"]) + + labels = """ + version coverage cover_dir pylib_dirs tracer config_files + configs_read data_path python platform implementation executable + cwd path environment command_line cover_match pylib_match + """.split() + for label in labels: + label_pat = r"^\s*%s: " % label + self.assertEqual(len(lines_matching(out_lines, label_pat)), 1) + + +def lines_matching(lines, pat): + """Gives the list of lines from `lines` that match `pat`.""" + return [l for l in lines if re.search(pat, l)] diff --git a/tests/test_farm.py b/tests/test_farm.py index e514e66..fee2806 100644 --- a/tests/test_farm.py +++ b/tests/test_farm.py @@ -195,9 +195,9 @@ class FarmTestCase(object): """Compare files matching `file_pattern` in `dir1` and `dir2`. `dir2` is interpreted as a prefix, with Python version numbers appended - to find the actual directory to compare with. "foo" will compare against - "foo_v241", "foo_v24", "foo_v2", or "foo", depending on which directory - is found first. + to find the actual directory to compare with. "foo" will compare + against "foo_v241", "foo_v24", "foo_v2", or "foo", depending on which + directory is found first. `size_within` is a percentage delta for the file sizes. If non-zero, then the file contents are not compared (since they are expected to diff --git a/tests/test_files.py b/tests/test_files.py index 509c23b..eeb2264 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -58,16 +58,19 @@ class MatcherTest(CoverageTest): file4 = self.make_file("sub3/file4.py") file5 = self.make_file("sub3/file5.c") fl = FileLocator() - tm = TreeMatcher([ + trees = [ fl.canonical_filename("sub"), fl.canonical_filename(file4), - ]) + ] + tm = TreeMatcher(trees) self.assertTrue(tm.match(fl.canonical_filename(file1))) self.assertTrue(tm.match(fl.canonical_filename(file2))) self.assertFalse(tm.match(fl.canonical_filename(file3))) self.assertTrue(tm.match(fl.canonical_filename(file4))) self.assertFalse(tm.match(fl.canonical_filename(file5))) + self.assertEqual(tm.info(), trees) + def test_fnmatch_matcher(self): file1 = self.make_file("sub/file1.py") file2 = self.make_file("sub/file2.c") @@ -82,6 +85,8 @@ class MatcherTest(CoverageTest): self.assertTrue(fnm.match(fl.canonical_filename(file4))) self.assertFalse(fnm.match(fl.canonical_filename(file5))) + self.assertEqual(fnm.info(), ["*.py", "*/sub2/*"]) + class PathAliasesTest(CoverageTest): """Tests for coverage/files.py:PathAliases""" diff --git a/tests/test_html.py b/tests/test_html.py index 40852ac..c004303 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -52,8 +52,6 @@ class HtmlDeltaTest(HtmlTestHelpers, CoverageTest): # so grab it here to restore it later. self.real_coverage_version = coverage.__version__ - self.maxDiff = None - def tearDown(self): coverage.__version__ = self.real_coverage_version super(HtmlDeltaTest, self).tearDown() @@ -291,6 +289,7 @@ class HtmlTest(CoverageTest): os.remove("sub/another.py") missing_file = os.path.join(self.temp_dir, "sub", "another.py") + missing_file = os.path.realpath(missing_file) self.assertRaisesRegexp(NoSource, "(?i)No source for code: '%s'" % re.escape(missing_file), cov.html_report diff --git a/tests/test_misc.py b/tests/test_misc.py index e7fa436..50ddb17 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -1,4 +1,5 @@ """Tests of miscellaneous stuff.""" + import sys from coverage.misc import Hasher, file_be_gone diff --git a/tests/test_process.py b/tests/test_process.py index 8554eb5..97e2f82 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -583,7 +583,7 @@ class ProcessStartupTest(CoverageTest): pass finally: pth.close() - else: # pragma: not covered + else: # pragma: not covered raise Exception("Couldn't find a place for the .pth file") def tearDown(self): @@ -615,7 +615,9 @@ class ProcessStartupTest(CoverageTest): import main # pylint: disable=F0401,W0612 self.assertEqual(open("out.txt").read(), "Hello, world!\n") + # Read the data from .coverage + self.assert_exists(".mycovdata") data = coverage.CoverageData() data.read_file(".mycovdata") self.assertEqual(data.summary()['sub.py'], 3) |