summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.hgtags1
-rw-r--r--CHANGES.txt30
-rw-r--r--MANIFEST.in1
-rw-r--r--Makefile16
-rw-r--r--README.txt8
-rw-r--r--TODO.txt8
-rw-r--r--allcoverage.cmd44
-rw-r--r--allkits.cmd13
-rw-r--r--alltests.cmd4
-rw-r--r--checkeol.py1
-rw-r--r--coverage/__init__.py4
-rw-r--r--coverage/annotate.py18
-rw-r--r--coverage/bytecode.py22
-rw-r--r--coverage/cmdline.py102
-rw-r--r--coverage/codeunit.py35
-rw-r--r--coverage/collector.py48
-rw-r--r--coverage/control.py81
-rw-r--r--coverage/data.py46
-rw-r--r--coverage/execfile.py6
-rw-r--r--coverage/files.py12
-rw-r--r--coverage/html.py36
-rw-r--r--coverage/htmlfiles/pyfile.html2
-rw-r--r--coverage/misc.py12
-rw-r--r--coverage/parser.py164
-rw-r--r--coverage/phystokens.py12
-rw-r--r--coverage/report.py16
-rw-r--r--coverage/results.py24
-rw-r--r--coverage/summary.py6
-rw-r--r--coverage/templite.py38
-rw-r--r--coverage/tracer.c42
-rw-r--r--coverage/xmlreport.py20
-rw-r--r--distribute_setup.py458
-rw-r--r--doc/_static/neds.css4
-rw-r--r--doc/api.rst8
-rw-r--r--doc/branch.rst14
-rw-r--r--doc/changes.rst12
-rw-r--r--doc/cmd.rst8
-rw-r--r--doc/excluding.rst4
-rw-r--r--doc/index.rst6
-rw-r--r--howto.txt (renamed from release.txt)21
-rw-r--r--scripts/coverage10
-rw-r--r--setup.py50
-rw-r--r--test/backtest.py12
-rw-r--r--test/backunittest.py10
-rw-r--r--test/coverage_coverage.py110
-rw-r--r--test/coveragetest.py74
-rw-r--r--test/farm/html/gold_b_branch/b.html52
-rw-r--r--test/farm/html/run_b_branch.py9
-rw-r--r--test/farm/html/src/b.py14
-rw-r--r--test/osinfo.py6
-rw-r--r--test/test_api.py35
-rw-r--r--test/test_arcs.py8
-rw-r--r--test/test_cmdline.py34
-rw-r--r--test/test_codeunit.py3
-rw-r--r--test/test_coverage.py158
-rw-r--r--test/test_data.py22
-rw-r--r--test/test_execfile.py6
-rw-r--r--test/test_farm.py78
-rw-r--r--test/test_oddball.py57
-rw-r--r--test/test_parser.py10
-rw-r--r--test/test_phystokens.py2
-rw-r--r--test/test_results.py4
-rw-r--r--test/test_summary.py4
-rw-r--r--test/test_templite.py14
64 files changed, 1406 insertions, 783 deletions
diff --git a/.hgtags b/.hgtags
index 48dbcb30..76a31785 100644
--- a/.hgtags
+++ b/.hgtags
@@ -14,3 +14,4 @@ ad991769ad4f685089bacc594e689bab50a6db90 coverage-3.0b3
da052544181e335a18b4141b8b7a47e8246aa9e8 coverage-3.2b1
11f0fbec2bc9b22a5fd8687d8b94338a4a363e6c coverage-3.2b2
d42ad49a0aaca90867644876948a7bdab0aa9970 coverage-3.2b3
+3e21d5dc7e9d9c64db56d84b245618ad5c1a4743 coverage-3.2b4
diff --git a/CHANGES.txt b/CHANGES.txt
index 8f5e895a..02974ada 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -2,8 +2,8 @@
Change history for Coverage.py
------------------------------
-Version 3.2b4
--------------
+Version 3.2b4, 1 December 2009
+------------------------------
- Branch coverage improvements:
@@ -13,6 +13,10 @@ Version 3.2b4
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
-------------------------------
@@ -64,9 +68,9 @@ Version 3.2b1, 10 November 2009
- 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
---------------------------
@@ -140,10 +144,10 @@ 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
@@ -154,7 +158,7 @@ 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.
@@ -212,16 +216,16 @@ Major overhaul.
- 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.
-
+
- The minimum supported Python version is 2.3.
-
+
Version 2.85, 14 September 2008
-------------------------------
@@ -229,7 +233,7 @@ 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.
@@ -284,7 +288,7 @@ Version 2.7, 21 July 2007
- 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.
@@ -312,7 +316,7 @@ 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.
diff --git a/MANIFEST.in b/MANIFEST.in
index ce340f66..907d9d0d 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -2,6 +2,7 @@
include coverage.egg-info/*.*
include coverage/*.py
include coverage/htmlfiles/*.*
+include distribute_setup.py
include ez_setup.py
include setup.py
include README.txt
diff --git a/Makefile b/Makefile
index b493483d..7ae4bf0d 100644
--- a/Makefile
+++ b/Makefile
@@ -9,7 +9,7 @@ TEST_EGG = test/eggsrc/dist/covtestegg1-0.0.0-py*.egg
clean:
python test/test_farm.py clean
-rm -rf build coverage.egg-info dist htmlcov
- -rm -f *.pyd */*.pyd
+ -rm -f *.pyd */*.pyd
-rm -f *.pyc */*.pyc */*/*.pyc */*/*/*.pyc */*/*/*/*.pyc */*/*/*/*/*.pyc
-rm -f *.pyo */*.pyo */*/*.pyo */*/*/*.pyo */*/*/*/*.pyo */*/*/*/*/*.pyo
-rm -f *.bak */*.bak */*/*.bak */*/*/*.bak */*/*/*/*.bak */*/*/*/*/*.bak
@@ -21,15 +21,11 @@ clean:
-rm -f setuptools-*.egg
-rm -rf doc/_build/*
-LINTABLE = \
- coverage \
- scripts/coverage \
- setup.py \
- test
-
-lint:
+LINTABLE = coverage setup.py test
+
+lint:
-python -x /Python25/Scripts/pylint.bat --rcfile=.pylintrc $(LINTABLE)
- python /Python25/Lib/tabnanny.py coverage scripts test setup.py
+ python /Python25/Lib/tabnanny.py $(LINTABLE)
python checkeol.py
testready: testdata devinst
@@ -56,7 +52,7 @@ install:
DEVINST_FILE = coverage.egg-info/PKG-INFO
devinst: $(DEVINST_FILE)
-$(DEVINST_FILE): coverage/tracer.c
+$(DEVINST_FILE): coverage/tracer.c
-rm coverage/tracer.pyd
python setup.py develop
diff --git a/README.txt b/README.txt
index 0724a6e0..1dbbf5a8 100644
--- a/README.txt
+++ b/README.txt
@@ -1,8 +1,8 @@
-Coverage: code coverage testing for Python
+Coverage.py: code coverage testing for Python
-Coverage measures code coverage, typically during test execution. It uses 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 measures code coverage, typically during test execution. It uses
+the code analysis tools and tracing hooks provided in the Python standard
+library to determine which lines are executable, and which have been executed.
For more information, see http://nedbatchelder.com/code/coverage
diff --git a/TODO.txt b/TODO.txt
index 6c5dc46f..be8f8010 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -15,12 +15,14 @@ Coverage TODO
+ A missing branch to leave the function shows an annotation of -1. Now "exit".
+ XML report needs to get branch information.
+ Add branch info to "coverage debug data"
++ Polish up the help, and double-check the docs.
* 3.3
- Config file
- Better omit handling that ignores files during measurement.
+ - Deal with ~ in specified paths correctly.
* Speed
@@ -64,7 +66,9 @@ x Tricky swapping of collector like figleaf, pycov, et al. (Don't need to do
- Track callers of functions (ala std module trace)
- Method/Class/Module coverage reporting.
- .coverage files that can be kept separate, rather than accumulated.
-
+- test/coverage map: http://rbtcollins.wordpress.com/2009/09/16/back-from-hiatus/
+ - Similar to figleaf's sections.
+
* Convenience
@@ -97,6 +101,7 @@ x Why can't you specify execute (-x) and report (-r) in the same invocation?
- Some way to focus in on red and yellow
- Show only lines near highlights?
- Jump to next highlight?
+ - Cookie for changes to pyfile.html state.
+ Clickable column headers on the index page.
+ Syntax coloring in HTML report.
+ Dynamic effects in HTML report.
@@ -123,6 +128,7 @@ x Why can't you specify execute (-x) and report (-r) in the same invocation?
- Double function decorators: all decorator lines count as executable code.
- Document the .coverage file format.
+ HTML reporting.
+ - Much more detail about what's in the report.
- References between pages are off:
- They have <em> tags around them.
- They use #anchors that don't survive the px->html conversion.
diff --git a/allcoverage.cmd b/allcoverage.cmd
new file mode 100644
index 00000000..2545427b
--- /dev/null
+++ b/allcoverage.cmd
@@ -0,0 +1,44 @@
+@echo off
+make --quiet testdata
+
+call \ned\bin\switchpy 23
+python setup.py -q develop
+set COVERAGE_TEST_TRACER=c
+python test\coverage_coverage.py run %1 %2 %3 %4 %5 %6 %7 %8 %9
+del coverage\tracer.pyd
+set COVERAGE_TEST_TRACER=py
+python test\coverage_coverage.py run %1 %2 %3 %4 %5 %6 %7 %8 %9
+
+call \ned\bin\switchpy 24
+python setup.py -q develop
+set COVERAGE_TEST_TRACER=c
+python test\coverage_coverage.py run %1 %2 %3 %4 %5 %6 %7 %8 %9
+del coverage\tracer.pyd
+set COVERAGE_TEST_TRACER=py
+python test\coverage_coverage.py run %1 %2 %3 %4 %5 %6 %7 %8 %9
+
+call \ned\bin\switchpy 25
+python setup.py -q develop
+set COVERAGE_TEST_TRACER=c
+python test\coverage_coverage.py run %1 %2 %3 %4 %5 %6 %7 %8 %9
+del coverage\tracer.pyd
+set COVERAGE_TEST_TRACER=py
+python test\coverage_coverage.py run %1 %2 %3 %4 %5 %6 %7 %8 %9
+
+call \ned\bin\switchpy 26
+python setup.py -q develop
+set COVERAGE_TEST_TRACER=c
+python test\coverage_coverage.py run %1 %2 %3 %4 %5 %6 %7 %8 %9
+del coverage\tracer.pyd
+set COVERAGE_TEST_TRACER=py
+python test\coverage_coverage.py run %1 %2 %3 %4 %5 %6 %7 %8 %9
+
+call \ned\bin\switchpy 31
+python setup.py -q develop
+set COVERAGE_TEST_TRACER=c
+python test\coverage_coverage.py run %1 %2 %3 %4 %5 %6 %7 %8 %9
+del coverage\tracer.pyd
+set COVERAGE_TEST_TRACER=py
+python test\coverage_coverage.py run %1 %2 %3 %4 %5 %6 %7 %8 %9
+
+python test\coverage_coverage.py report
diff --git a/allkits.cmd b/allkits.cmd
index fa75988e..12c46d2e 100644
--- a/allkits.cmd
+++ b/allkits.cmd
@@ -1,17 +1,18 @@
@REM Build all the kits for coverage.py
+@REM Add "upload" onto the command line to also upload.
call \ned\bin\switchpy 23
-python setup.py bdist_wininst
+python setup.py bdist_wininst %1
call \ned\bin\switchpy 24
-python setup.py bdist_wininst
+python setup.py bdist_wininst %1
call \ned\bin\switchpy 25
-python setup.py bdist_wininst
+python setup.py bdist_wininst %1
call \ned\bin\switchpy 26
-python setup.py bdist_wininst
+python setup.py bdist_wininst %1
set TAR_OPTIONS=--group=100
-python setup.py sdist --formats=gztar
+python setup.py sdist --formats=gztar %1
set TAR_OPTIONS=
@REM Py3k
call \ned\bin\switchpy 31
-python setup.py bdist_wininst
+python setup.py bdist_wininst %1
diff --git a/alltests.cmd b/alltests.cmd
index 7b26ab81..5aee1e42 100644
--- a/alltests.cmd
+++ b/alltests.cmd
@@ -34,10 +34,10 @@ set COVERAGE_TEST_TRACER=py
nosetests %1 %2 %3 %4 %5 %6 %7 %8 %9
call \ned\bin\switchpy 31
-python setup.py -q install
+python setup.py -q develop
set COVERAGE_TEST_TRACER=c
python \python31\Scripts\nosetests3 %1 %2 %3 %4 %5 %6 %7 %8 %9
-del \python31\lib\site-packages\coverage\tracer.pyd
+del coverage\tracer.pyd
set COVERAGE_TEST_TRACER=py
python \python31\Scripts\nosetests3 %1 %2 %3 %4 %5 %6 %7 %8 %9
diff --git a/checkeol.py b/checkeol.py
index 8b6f60c0..68efc2fa 100644
--- a/checkeol.py
+++ b/checkeol.py
@@ -20,6 +20,5 @@ def check_files(root, patterns):
dirs.remove('.svn')
check_files("coverage", ["*.py"])
-check_files("scripts", ["*"])
check_files("test", ["*.py", "*,cover"])
check_file("setup.py")
diff --git a/coverage/__init__.py b/coverage/__init__.py
index 9dea8004..9b355adb 100644
--- a/coverage/__init__.py
+++ b/coverage/__init__.py
@@ -26,10 +26,10 @@ _the_coverage = None
def _singleton_method(name):
"""Return a function to the `name` method on a singleton `coverage` object.
-
+
The singleton object is created the first time one of these functions is
called.
-
+
"""
def wrapper(*args, **kwargs):
"""Singleton wrapper around a coverage method."""
diff --git a/coverage/annotate.py b/coverage/annotate.py
index 2fa9d5cf..2117b657 100644
--- a/coverage/annotate.py
+++ b/coverage/annotate.py
@@ -6,11 +6,11 @@ from coverage.report import Reporter
class AnnotateReporter(Reporter):
"""Generate annotated source files showing line coverage.
-
+
This reporter creates annotated copies of the measured source files. Each
.py file is copied as a .py,cover file, with a left-hand margin annotating
each line::
-
+
> def h(x):
- if 0: #pragma: no cover
- pass
@@ -18,30 +18,30 @@ class AnnotateReporter(Reporter):
! a = 1
> else:
> a = 2
-
+
> h(2)
Executed lines use '>', lines not executed use '!', lines excluded from
consideration use '-'.
-
+
"""
def __init__(self, coverage, ignore_errors=False):
super(AnnotateReporter, self).__init__(coverage, ignore_errors)
self.directory = None
-
+
blank_re = re.compile(r"\s*(#|$)")
else_re = re.compile(r"\s*else\s*:\s*(#|$)")
def report(self, morfs, directory=None, omit_prefixes=None):
"""Run the report."""
self.report_files(self.annotate_file, morfs, directory, omit_prefixes)
-
+
def annotate_file(self, cu, analysis):
"""Annotate a single file.
-
+
`cu` is the CodeUnit for the file to annotate.
-
+
"""
if not cu.relative:
return
@@ -77,7 +77,7 @@ class AnnotateReporter(Reporter):
if self.blank_re.match(line):
dest.write(' ')
elif self.else_re.match(line):
- # Special logic for lines containing only 'else:'.
+ # Special logic for lines containing only 'else:'.
if i >= len(statements) and j >= len(missing):
dest.write('! ')
elif i >= len(statements) or j >= len(missing):
diff --git a/coverage/bytecode.py b/coverage/bytecode.py
index 62a19bae..ac280342 100644
--- a/coverage/bytecode.py
+++ b/coverage/bytecode.py
@@ -14,14 +14,14 @@ class ByteCode(object):
class ByteCodes(object):
"""Iterator over byte codes in `code`.
-
+
Returns `ByteCode` objects.
-
+
"""
def __init__(self, code):
self.code = code
self.offset = 0
-
+
if sys.hexversion > 0x03000000:
def __getitem__(self, i):
return self.code[i]
@@ -31,30 +31,30 @@ class ByteCodes(object):
def __iter__(self):
return self
-
+
def __next__(self):
if self.offset >= len(self.code):
raise StopIteration
-
+
bc = ByteCode()
bc.op = self[self.offset]
bc.offset = self.offset
-
+
next_offset = self.offset+1
if bc.op >= opcode.HAVE_ARGUMENT:
bc.arg = self[self.offset+1] + 256*self[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 = self.offset = next_offset
return bc
-
+
next = __next__ # Py2k uses an old-style non-dunder name.
@@ -62,10 +62,10 @@ class CodeObjects(object):
"""Iterate over all the code objects in `code`."""
def __init__(self, code):
self.stack = [code]
-
+
def __iter__(self):
return self
-
+
def __next__(self):
if self.stack:
# We're going to return the code object on the stack, but first
diff --git a/coverage/cmdline.py b/coverage/cmdline.py
index 938099e2..fe8da549 100644
--- a/coverage/cmdline.py
+++ b/coverage/cmdline.py
@@ -1,6 +1,6 @@
"""Command-line support for Coverage."""
-import optparse, sys
+import optparse, re, sys
from coverage.execfile import run_python_file
from coverage.misc import CoverageException
@@ -8,10 +8,15 @@ from coverage.misc import CoverageException
class Opts(object):
"""A namespace class for individual options we'll build parsers from."""
-
+
+ append = optparse.Option(
+ '-a', '--append', action='store_false', dest="erase_first",
+ help="Append coverage data to .coverage, otherwise it is started "
+ "clean with each run."
+ )
branch = optparse.Option(
'', '--branch', action='store_true',
- help="Measure branch execution. HIGHLY EXPERIMENTAL!"
+ help="Measure branch coverage in addition to statement coverage."
)
directory = optparse.Option(
'-d', '--directory', action='store',
@@ -63,18 +68,18 @@ class Opts(object):
help="Use a simpler but slower trace method. Try this if you get "
"seemingly impossible results!"
)
- append = optparse.Option(
- '-a', '--append', action='store_false', dest="erase_first",
- help="Append coverage data to .coverage, otherwise it is started "
- "clean with each run."
+ version = optparse.Option(
+ '', '--version', action='store_true',
+ help="Display version information and exit."
)
-
+
+
class CoverageOptionParser(optparse.OptionParser, object):
"""Base OptionParser for coverage.
-
+
Problems don't exit the program.
Defaults are initialized for all options.
-
+
"""
def __init__(self, *args, **kwargs):
@@ -93,6 +98,7 @@ class CoverageOptionParser(optparse.OptionParser, object):
show_missing=None,
timid=None,
erase_first=None,
+ version=None,
)
self.disable_interspersed_args()
@@ -101,12 +107,12 @@ class CoverageOptionParser(optparse.OptionParser, object):
class OptionParserError(Exception):
"""Used to stop the optparse error handler ending the process."""
pass
-
+
def parse_args(self, args=None, options=None):
"""Call optparse.parse_args, but return a triple:
-
+
(ok, options, args)
-
+
"""
try:
options, args = \
@@ -114,7 +120,7 @@ class CoverageOptionParser(optparse.OptionParser, object):
except self.OptionParserError:
return False, None, None
return True, options, args
-
+
def error(self, msg):
"""Override optparse.error so sys.exit doesn't get called."""
self.help_fn(msg)
@@ -126,7 +132,7 @@ class ClassicOptionParser(CoverageOptionParser):
def __init__(self):
super(ClassicOptionParser, self).__init__()
-
+
self.add_action('-a', '--annotate', 'annotate')
self.add_action('-b', '--html', 'html')
self.add_action('-c', '--combine', 'combine')
@@ -143,6 +149,7 @@ class ClassicOptionParser(CoverageOptionParser):
Opts.old_omit,
Opts.parallel_mode,
Opts.timid,
+ Opts.version,
])
def add_action(self, dash, dashdash, action_code):
@@ -151,7 +158,7 @@ class ClassicOptionParser(CoverageOptionParser):
callback=self._append_action
)
option.action_code = action_code
-
+
def _append_action(self, option, opt_unused, value_unused, parser):
"""Callback for an option that adds to the `actions` list."""
parser.values.actions.append(option.action_code)
@@ -159,19 +166,19 @@ class ClassicOptionParser(CoverageOptionParser):
class CmdOptionParser(CoverageOptionParser):
"""Parse one of the new-style commands for coverage.py."""
-
+
def __init__(self, action, options=None, defaults=None, usage=None,
cmd=None, description=None
):
"""Create an OptionParser for a coverage command.
-
+
`action` is the slug to put into `options.actions`.
`options` is a list of Option's for the command.
`defaults` is a dict of default value for options.
`usage` is the usage string to display in help.
`cmd` is the command name, if different than `action`.
`description` is the description of the command, for the help text.
-
+
"""
if usage:
usage = "%prog " + usage
@@ -222,20 +229,20 @@ CMDS = {
"Each file gets its own page, with the source decorated to show "
"executed, excluded, and missed lines."
),
-
+
'combine': CmdOptionParser("combine", [Opts.help],
usage = " ",
description = "Combine data from multiple coverage files collected "
- "with 'run -p'. The combined results are stored into a single "
- "file representing the union of the coverage."
+ "with 'run -p'. The combined results are written to a single "
+ "file representing the union of the data."
),
'debug': CmdOptionParser("debug", [Opts.help],
usage = "<topic>",
- description = "Display information the internals of coverage.py, "
+ description = "Display information on the internals of coverage.py, "
"for diagnosing problems. "
- "Topics are 'data' to show a summary of the data collected in "
- ".coverage, or 'sys' to show installation information."
+ "Topics are 'data' to show a summary of the collected data, "
+ "or 'sys' to show installation information."
),
'erase': CmdOptionParser("erase", [Opts.help],
@@ -251,7 +258,7 @@ CMDS = {
Opts.help,
],
usage = "[options] [modules]",
- description = "Report coverage stats on modules."
+ description = "Report coverage statistics on modules."
),
'run': CmdOptionParser("execute",
@@ -263,12 +270,12 @@ CMDS = {
Opts.timid,
Opts.help,
],
- defaults = {'erase_first':True},
+ defaults = {'erase_first': True},
cmd = "run",
usage = "[options] <pyfile> [program options]",
- description = "Run a python program, measuring code execution."
+ description = "Run a Python program, measuring code execution."
),
-
+
'xml': CmdOptionParser("xml",
[
Opts.ignore_errors,
@@ -286,9 +293,10 @@ CMDS = {
OK, ERR = 0, 1
+
class CoverageScript(object):
"""The command-line interface to Coverage."""
-
+
def __init__(self, _covpkg=None, _run_python_file=None, _help_fn=None):
# _covpkg is for dependency injection, so we can test this code.
if _covpkg:
@@ -296,13 +304,13 @@ class CoverageScript(object):
else:
import coverage
self.covpkg = coverage
-
+
# _run_python_file is for dependency injection also.
self.run_python_file = _run_python_file or run_python_file
-
+
# _help_fn is for dependency injection.
self.help_fn = _help_fn or self.help
-
+
self.coverage = None
def help(self, error=None, topic=None, parser=None):
@@ -315,7 +323,6 @@ class CoverageScript(object):
print(parser.format_help().strip())
else:
# Parse out the topic we want from HELP_TOPICS
- import re
topic_list = re.split("(?m)^=+ (\w+) =+$", HELP_TOPICS)
topics = dict(zip(topic_list[1::2], topic_list[2::2]))
help_msg = topics.get(topic, '').strip()
@@ -326,14 +333,14 @@ class CoverageScript(object):
def command_line(self, argv):
"""The bulk of the command line interface to Coverage.
-
+
`argv` is the argument list to process.
Returns 0 if all is well, 1 if something went wrong.
"""
# Collect the command-line options.
-
+
if not argv:
self.help_fn(topic='minimum_help')
return OK
@@ -363,6 +370,10 @@ class CoverageScript(object):
self.help_fn(parser=parser)
return OK
+ if options.version:
+ self.help_fn(topic='version')
+ return OK
+
if "help" in options.actions:
if args:
for a in args:
@@ -399,11 +410,11 @@ class CoverageScript(object):
if not args_allowed and args:
self.help_fn("Unexpected arguments: %s" % " ".join(args))
return ERR
-
+
if 'execute' in options.actions and not args:
self.help_fn("Nothing to do.")
return ERR
-
+
# Do something.
self.coverage = self.covpkg.coverage(
data_suffix = bool(options.parallel_mode),
@@ -472,7 +483,7 @@ class CoverageScript(object):
if options.omit:
omit = options.omit.split(',')
report_args['omit_prefixes'] = omit
-
+
if 'report' in options.actions:
self.coverage.report(
show_missing=options.show_missing, **report_args)
@@ -494,7 +505,7 @@ class CoverageScript(object):
HELP_TOPICS = r"""
== classic ====================================================================
-Coverage version %(__version__)s
+Coverage.py version %(__version__)s
Measure, collect, and report on code coverage in Python programs.
Usage:
@@ -540,14 +551,14 @@ Coverage data is saved in the file .coverage by default. Set the
COVERAGE_FILE environment variable to save it somewhere else.
== help =======================================================================
-Coverage version %(__version__)s
+Coverage.py, version %(__version__)s
Measure, collect, and report on code coverage in Python programs.
usage: coverage <command> [options] [args]
Commands:
annotate Annotate source files with execution information.
- combine Combine a number of data files.
+ 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.
@@ -562,14 +573,17 @@ For more information, see %(__url__)s
== minimum_help ===============================================================
Code coverage for Python. Use 'coverage help' for help.
+== version ====================================================================
+Coverage.py, version %(__version__)s. %(__url__)s
+
"""
def main():
"""The main entrypoint to Coverage.
-
+
This is installed as the script entrypoint.
-
+
"""
try:
status = CoverageScript().command_line(sys.argv[1:])
diff --git a/coverage/codeunit.py b/coverage/codeunit.py
index e3107052..73baaa06 100644
--- a/coverage/codeunit.py
+++ b/coverage/codeunit.py
@@ -8,20 +8,20 @@ from coverage.misc import CoverageException
def code_unit_factory(morfs, file_locator, omit_prefixes=None):
"""Construct a list of CodeUnits from polymorphic inputs.
-
+
`morfs` is a module or a filename, or a list of same.
`file_locator` is a FileLocator that can help resolve filenames.
`omit_prefixes` is a list of prefixes. CodeUnits that match those prefixes
will be omitted from the list.
-
+
Returns a list of CodeUnit objects.
-
+
"""
# Be sure we have a list.
if not isinstance(morfs, (list, tuple)):
morfs = [morfs]
-
+
# On Windows, the shell doesn't expand wildcards. Do it here.
globbed = []
for morf in morfs:
@@ -32,8 +32,9 @@ def code_unit_factory(morfs, file_locator, omit_prefixes=None):
morfs = globbed
code_units = [CodeUnit(morf, file_locator) for morf in morfs]
-
+
if omit_prefixes:
+ assert not isinstance(omit_prefixes, string_class) # common mistake
prefixes = [file_locator.abs_file(p) for p in omit_prefixes]
filtered = []
for cu in code_units:
@@ -42,7 +43,7 @@ def code_unit_factory(morfs, file_locator, omit_prefixes=None):
break
else:
filtered.append(cu)
-
+
code_units = filtered
return code_units
@@ -50,13 +51,13 @@ def code_unit_factory(morfs, file_locator, omit_prefixes=None):
class CodeUnit(object):
"""Code unit: a filename or module.
-
+
Instance attributes:
-
+
`name` is a human-readable name for this code unit.
`filename` is the os path from which we can read the source.
`relative` is a boolean.
-
+
"""
def __init__(self, morf, file_locator):
@@ -91,34 +92,34 @@ class CodeUnit(object):
# Annoying comparison operators. Py3k wants __lt__ etc, and Py2k needs all
# of them defined.
-
+
def __lt__(self, other):
return self.name < other.name
-
+
def __le__(self, other):
return self.name <= other.name
def __eq__(self, other):
return self.name == other.name
-
+
def __ne__(self, other):
return self.name != other.name
def __gt__(self, other):
return self.name > other.name
-
+
def __ge__(self, other):
return self.name >= other.name
def flat_rootname(self):
"""A base for a flat filename to correspond to this code unit.
-
+
Useful for writing files about the code where you want all the files in
the same directory, but need to differentiate same-named files from
different directories.
-
+
For example, the file a/b/c.py might return 'a_b_c'
-
+
"""
if self.modname:
return self.modname.replace('.', '_')
@@ -136,7 +137,7 @@ class CodeUnit(object):
source = self.file_locator.get_zip_data(self.filename)
if source is not None:
return StringIO(source)
-
+
# Couldn't find source.
raise CoverageException(
"No source for code %r." % self.filename
diff --git a/coverage/collector.py b/coverage/collector.py
index 5bbd02e0..29dddf6b 100644
--- a/coverage/collector.py
+++ b/coverage/collector.py
@@ -12,7 +12,7 @@ except ImportError:
class PyTracer(object):
"""Python implementation of the raw data tracer."""
-
+
# Because of poor implementations of trace-function-manipulating tools,
# the Python trace function must be kept very simple. In particular, there
# must be only one function ever set as the trace function, both through
@@ -41,10 +41,10 @@ class PyTracer(object):
def _trace(self, frame, event, arg_unused):
"""The trace function passed to sys.settrace."""
-
+
#print "trace event: %s %r @%d" % (
# event, frame.f_code.co_filename, frame.f_lineno)
-
+
if self.last_exc_back:
if frame == self.last_exc_back:
# Someone forgot a return event.
@@ -52,7 +52,7 @@ class PyTracer(object):
self.cur_file_data[(self.last_line, -1)] = None
self.cur_file_data, self.last_line = self.data_stack.pop()
self.last_exc_back = None
-
+
if event == 'call':
# Entering a new function context. Decide if we should trace
# in this file.
@@ -85,7 +85,7 @@ class PyTracer(object):
#print "exc", self.last_line, frame.f_lineno
self.last_exc_back = frame.f_back
return self._trace
-
+
def start(self):
"""Start this Tracer."""
sys.settrace(self._trace)
@@ -102,18 +102,19 @@ class PyTracer(object):
class Collector(object):
"""Collects trace data.
- Creates a Tracer object for each thread, since they track stack information.
- Each Tracer points to the same shared data, contributing traced data points.
-
+ Creates a Tracer object for each thread, since they track stack
+ information. Each Tracer points to the same shared data, contributing
+ traced data points.
+
When the Collector is started, it creates a Tracer for the current thread,
and installs a function to create Tracers for each new thread started.
When the Collector is stopped, all active Tracers are stopped.
-
+
Threads started while the Collector is stopped will never have Tracers
associated with them.
-
+
"""
-
+
# The stack of active Collectors. Collectors are added here when started,
# and popped when stopped. Collectors on the stack are paused when not
# the top, and resumed when they become the top again.
@@ -121,20 +122,20 @@ class Collector(object):
def __init__(self, should_trace, timid, branch):
"""Create a collector.
-
+
`should_trace` is a function, taking a filename, and returning a
canonicalized filename, or False depending on whether the file should
be traced or not.
-
+
If `timid` is true, then a slower simpler trace function will be
used. This is important for some environments where manipulation of
tracing functions make the faster more sophisticated trace function not
operate properly.
-
+
If `branch` is true, then branches will be measured. This involves
collecting data on which statements followed each other (arcs). Use
`get_arc_data` to get the arc data.
-
+
"""
self.should_trace = should_trace
self.branch = branch
@@ -148,6 +149,9 @@ class Collector(object):
# trace function.
self._trace_class = Tracer or PyTracer
+ def __repr__(self):
+ return "<Collector at 0x%x>" % id(self)
+
def tracer_name(self):
"""Return the class name of the tracer we're using."""
return self._trace_class.__name__
@@ -157,7 +161,7 @@ class Collector(object):
# A dictionary mapping filenames to dicts with linenumber keys,
# or mapping filenames to dicts with linenumber pairs as keys.
self.data = {}
-
+
# 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).
@@ -196,6 +200,7 @@ class Collector(object):
if self._collectors:
self._collectors[-1].pause()
self._collectors.append(self)
+ #print >>sys.stderr, "Started: %r" % self._collectors
# Install the tracer on this thread.
self._start_tracer()
# Install our installation tracer in threading, to jump start other
@@ -204,12 +209,13 @@ class Collector(object):
def stop(self):
"""Stop collecting trace information."""
+ #print >>sys.stderr, "Stopping: %r" % self._collectors
assert self._collectors
assert self._collectors[-1] is self
self.pause()
self.tracers = []
-
+
# Remove this Collector from the stack, and resume the one underneath
# (if any).
self._collectors.pop()
@@ -226,7 +232,7 @@ class Collector(object):
for k in sorted(stats.keys()):
print("%16s: %s" % (k, stats[k]))
threading.settrace(None)
-
+
def resume(self):
"""Resume tracing after a `pause`."""
for tracer in self.tracers:
@@ -235,9 +241,9 @@ class Collector(object):
def get_line_data(self):
"""Return the line data collected.
-
+
Data is { filename: { lineno: None, ...}, ...}
-
+
"""
if self.branch:
# If we were measuring branches, then we have to re-build the dict
@@ -254,7 +260,7 @@ class Collector(object):
def get_arc_data(self):
"""Return the arc data collected.
-
+
Data is { filename: { (l1, l2): None, ...}, ...}
Note that no data is collected or returned if the Collector wasn't
diff --git a/coverage/control.py b/coverage/control.py
index eb134450..39e3dfbb 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -1,6 +1,6 @@
"""Core control stuff for Coverage."""
-import os, socket
+import atexit, os, socket
from coverage.annotate import AnnotateReporter
from coverage.backward import string_class
@@ -18,9 +18,9 @@ class coverage(object):
"""Programmatic access to Coverage.
To use::
-
+
from coverage import coverage
-
+
cov = coverage()
cov.start()
#.. blah blah (run your code) blah blah ..
@@ -31,24 +31,24 @@ class coverage(object):
def __init__(self, data_file=None, data_suffix=False, cover_pylib=None,
auto_data=False, timid=None, branch=None, config_file=True):
- """
+ """
`data_file` is the base name of the data file to use, defaulting to
".coverage". `data_suffix` is appended to `data_file` to create the
final file name. If `data_suffix` is simply True, then a suffix is
created with the machine and process identity included.
-
+
`cover_pylib` is a boolean determining whether Python code installed
with the Python interpreter is measured. This includes the Python
standard library and any packages installed with the interpreter.
-
+
If `auto_data` is true, then any existing data file will be read when
coverage measurement starts, and data will be saved automatically when
measurement stops.
-
+
If `timid` is true, then a slower and simpler trace function will be
used. This is important for some environments where manipulation of
tracing functions breaks the faster trace function.
-
+
If `branch` is true, then branch coverage will be measured in addition
to the usual statement coverage.
@@ -63,7 +63,7 @@ class coverage(object):
# Build our configuration from a number of sources:
# 1: defaults:
self.config = CoverageConfig()
-
+
# 2: from the coveragerc file:
if config_file:
if config_file is True:
@@ -83,12 +83,13 @@ class coverage(object):
)
self.auto_data = auto_data
-
+ self.atexit_registered = False
+
self.exclude_re = ""
self._compile_exclude()
self.file_locator = FileLocator()
-
+
self.collector = Collector(
self._should_trace, timid=self.config.timid,
branch=self.config.branch
@@ -121,13 +122,13 @@ class coverage(object):
def _should_trace(self, filename, frame):
"""Decide whether to trace execution in `filename`
-
+
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.
-
+
"""
if filename == '<string>':
# There's no point in ever tracing string executions, we can't do
@@ -173,9 +174,9 @@ class coverage(object):
def use_cache(self, usecache):
"""Control the use of a data file (incorrectly called a cache).
-
+
`usecache` is true or false, whether to read and write data on disk.
-
+
"""
self.data.usefile(usecache)
@@ -183,16 +184,17 @@ class coverage(object):
"""Load previously-collected coverage data from the data file."""
self.collector.reset()
self.data.read()
-
+
def start(self):
"""Start measuring code coverage."""
if self.auto_data:
self.load()
# Save coverage data when Python exits.
- import atexit
- atexit.register(self.save)
+ if not self.atexit_registered:
+ atexit.register(self.save)
+ self.atexit_registered = True
self.collector.start()
-
+
def stop(self):
"""Stop measuring code coverage."""
self.collector.stop()
@@ -200,10 +202,10 @@ class coverage(object):
def erase(self):
"""Erase previously-collected coverage data.
-
+
This removes the in-memory data collected in this session as well as
discarding the data file.
-
+
"""
self.collector.reset()
self.data.erase()
@@ -215,12 +217,12 @@ class coverage(object):
def exclude(self, regex):
"""Exclude source lines from execution consideration.
-
+
`regex` is a regular expression. Lines matching this expression are
not considered executable when reporting code coverage. A list of
regexes is maintained; this function adds a new regex to the list.
Matching any of the regexes excludes a source line.
-
+
"""
self.config.exclude_list.append(regex)
self._compile_exclude()
@@ -240,11 +242,11 @@ class coverage(object):
def combine(self):
"""Combine together a number of similarly-named coverage data files.
-
+
All coverage data files whose name starts with `data_file` (from the
coverage() constructor) will be read, and combined together into the
current measurements.
-
+
"""
self.data.combine_parallel_data()
@@ -262,14 +264,15 @@ class coverage(object):
def analysis2(self, morf):
"""Analyze a module.
-
+
`morf` is a module or a filename. It will be analyzed to determine
its coverage statistics. The return value is a 5-tuple:
-
+
* The filename for the module.
* A list of line numbers of executable statements.
* A list of line numbers of excluded statements.
- * A list of line numbers of statements not run (missing from execution).
+ * A list of line numbers of statements not run (missing from
+ execution).
* A readable formatted string of the missing line numbers.
The analysis uses the source file itself and the current measured
@@ -284,22 +287,22 @@ class coverage(object):
def _analyze(self, it):
"""Analyze a single morf or code unit.
-
+
Returns an `Analysis` object.
"""
if not isinstance(it, CodeUnit):
it = code_unit_factory(it, self.file_locator)[0]
-
+
return Analysis(self, it)
def report(self, morfs=None, show_missing=True, ignore_errors=False,
file=None, omit_prefixes=None): # pylint: disable-msg=W0622
"""Write a summary report to `file`.
-
+
Each module in `morfs` is listed, with counts of statements, executed
statements, missing statements, and a list of lines missed.
-
+
"""
reporter = SummaryReporter(self, show_missing, ignore_errors)
reporter.report(morfs, outfile=file, omit_prefixes=omit_prefixes)
@@ -307,12 +310,12 @@ class coverage(object):
def annotate(self, morfs=None, directory=None, ignore_errors=False,
omit_prefixes=None):
"""Annotate a list of modules.
-
+
Each module in `morfs` is annotated. The source is written to a new
file, named with a ",cover" suffix, with each line prefixed with a
marker to indicate the coverage of the line. Covered lines have ">",
excluded lines have "-", and missing lines have "!".
-
+
"""
reporter = AnnotateReporter(self, ignore_errors)
reporter.report(
@@ -321,7 +324,7 @@ class coverage(object):
def html_report(self, morfs=None, directory=None, ignore_errors=False,
omit_prefixes=None):
"""Generate an HTML report.
-
+
"""
reporter = HtmlReporter(self, ignore_errors)
reporter.report(
@@ -330,9 +333,9 @@ class coverage(object):
def xml_report(self, morfs=None, outfile=None, ignore_errors=False,
omit_prefixes=None):
"""Generate an XML report of coverage results.
-
+
The report is compatible with Cobertura reports.
-
+
"""
if outfile:
outfile = open(outfile, "w")
@@ -345,7 +348,7 @@ class coverage(object):
def sysinfo(self):
"""Return a list of key,value pairs showing internal information."""
-
+
import coverage as covmod
import platform, re, sys
diff --git a/coverage/data.py b/coverage/data.py
index 55ed7a3a..bd147756 100644
--- a/coverage/data.py
+++ b/coverage/data.py
@@ -7,20 +7,20 @@ from coverage.backward import pickle, sorted # pylint: disable-msg=W0622
class CoverageData(object):
"""Manages collected coverage data, including file storage.
-
+
The data file format is a pickled dict, with these keys:
-
+
* collector: a string identifying the collecting software
* lines: a dict mapping filenames to sorted lists of line numbers
executed:
{ 'file1': [17,23,45], 'file2': [1,2,3], ... }
-
+
* arcs: a dict mapping filenames to sorted lists of line number pairs:
{ 'file1': [(17,23), (17,25), (25,26)], ... }
"""
-
+
# Name of the data file (unless environment variable is set).
filename_default = ".coverage"
@@ -29,9 +29,9 @@ class CoverageData(object):
def __init__(self, basename=None, suffix=None, collector=None):
"""Create a CoverageData.
-
+
`basename` is the name of the file to use for storing data.
-
+
`suffix` is a suffix to append to the base file name. This can be used
for multiple or parallel execution, so that many coverage data files
can exist simultaneously.
@@ -40,7 +40,7 @@ class CoverageData(object):
"""
self.collector = collector or 'unknown'
-
+
self.use_file = True
# Construct the filename that will be used for data file storage, if we
@@ -60,14 +60,14 @@ class CoverageData(object):
# }
#
self.lines = {}
-
+
# A map from canonical Python source file name to a dictionary with an
# entry for each pair of line numbers forming an arc:
#
# { filename: { (l1,l2): None, ... }, ...}
#
self.arcs = {}
-
+
def usefile(self, use_file=True):
"""Set whether or not to use a disk file for data."""
self.use_file = use_file
@@ -91,7 +91,7 @@ class CoverageData(object):
os.remove(self.filename)
self.lines = {}
self.arcs = {}
-
+
def line_data(self):
"""Return the map from filenames to lists of line numbers executed."""
return dict(
@@ -103,11 +103,11 @@ class CoverageData(object):
return dict(
[(f, sorted(amap.keys())) for f, amap in self.arcs.items()]
)
-
+
def write_file(self, filename):
"""Write the coverage data to `filename`."""
- # Create the file data.
+ # Create the file data.
data = {}
data['lines'] = self.line_data()
@@ -140,10 +140,10 @@ class CoverageData(object):
def _read_file(self, filename):
"""Return the stored coverage data from the given file.
-
+
Returns two values, suitable for assigning to `self.lines` and
`self.arcs`.
-
+
"""
lines = {}
arcs = {}
@@ -166,10 +166,10 @@ class CoverageData(object):
def combine_parallel_data(self):
"""Combine a number of data files together.
-
+
Treat `self.filename` as a file prefix, and combine the data from all
of the data files starting with that prefix.
-
+
"""
data_dir, local = os.path.split(self.filename)
for f in os.listdir(data_dir or '.'):
@@ -183,18 +183,18 @@ class CoverageData(object):
def add_line_data(self, line_data):
"""Add executed line data.
-
+
`line_data` is { filename: { lineno: None, ... }, ...}
-
+
"""
for filename, linenos in line_data.items():
self.lines.setdefault(filename, {}).update(linenos)
def add_arc_data(self, arc_data):
"""Add measured arc data.
-
+
`arc_data` is { filename: { (l1,l2): None, ... }, ...}
-
+
"""
for filename, arcs in arc_data.items():
self.arcs.setdefault(filename, {}).update(arcs)
@@ -205,7 +205,7 @@ class CoverageData(object):
def executed_lines(self, filename):
"""A map containing all the line numbers executed in `filename`.
-
+
If `filename` hasn't been collected at all (because it wasn't executed)
then return an empty map.
@@ -218,11 +218,11 @@ class CoverageData(object):
def summary(self, fullpath=False):
"""Return a dict summarizing the coverage data.
-
+
Keys are based on the filenames, and values are the number of executed
lines. If `fullpath` is true, then the keys are the full pathnames of
the files, otherwise they are the basenames of the files.
-
+
"""
summ = {}
if fullpath:
diff --git a/coverage/execfile.py b/coverage/execfile.py
index ddcfa149..15f0a5f8 100644
--- a/coverage/execfile.py
+++ b/coverage/execfile.py
@@ -16,11 +16,11 @@ except KeyError:
def run_python_file(filename, args):
"""Run a python file as if it were the main program on the command line.
-
+
`filename` is the path to the file to execute, it need not be a .py file.
`args` is the argument array to present as sys.argv, including the first
element representing the file being executed.
-
+
"""
# Create a module to serve as __main__
old_main_mod = sys.modules['__main__']
@@ -44,7 +44,7 @@ def run_python_file(filename, args):
finally:
# Restore the old __main__
sys.modules['__main__'] = old_main_mod
-
+
# Restore the old argv and path
sys.argv = old_argv
sys.path[0] = old_path0
diff --git a/coverage/files.py b/coverage/files.py
index 400646ca..ba228c23 100644
--- a/coverage/files.py
+++ b/coverage/files.py
@@ -18,18 +18,18 @@ class FileLocator(object):
def relative_filename(self, filename):
"""Return the relative form of `filename`.
-
+
The filename will be relative to the current directory when the
FileLocator was constructed.
-
+
"""
return filename.replace(self.relative_dir, "")
def canonical_filename(self, filename):
"""Return a canonical filename for `filename`.
-
+
An absolute path with no redundant components and normalized case.
-
+
"""
if filename not in self.canonical_filename_cache:
f = filename
@@ -48,11 +48,11 @@ class FileLocator(object):
def get_zip_data(self, filename):
"""Get data from `filename` if it is a zip file path.
-
+
Returns the string data read from the zip file, or None if no zip file
could be found or `filename` isn't in it. The data returned will be
an empty string if the file is empty.
-
+
"""
import zipimport
markers = ['.zip'+os.sep, '.egg'+os.sep]
diff --git a/coverage/html.py b/coverage/html.py
index 6df84a57..4d51eb34 100644
--- a/coverage/html.py
+++ b/coverage/html.py
@@ -18,29 +18,29 @@ def data_filename(fname):
def data(fname):
"""Return the contents of a data file of ours."""
return open(data_filename(fname)).read()
-
+
class HtmlReporter(Reporter):
"""HTML reporting."""
-
+
def __init__(self, coverage, ignore_errors=False):
super(HtmlReporter, self).__init__(coverage, ignore_errors)
self.directory = None
self.source_tmpl = Templite(data("htmlfiles/pyfile.html"), globals())
-
+
self.files = []
self.arcs = coverage.data.has_arcs()
def report(self, morfs, directory, omit_prefixes=None):
"""Generate an HTML report for `morfs`.
-
+
`morfs` is a list of modules or filenames. `directory` is where to put
the HTML files. `omit_prefixes` is a list of strings, prefixes of
modules to omit from the report.
-
+
"""
assert directory, "must provide a directory for html reporting"
-
+
# Process all the files.
self.report_files(self.html_file, morfs, directory, omit_prefixes)
@@ -59,10 +59,10 @@ class HtmlReporter(Reporter):
def html_file(self, cu, analysis):
"""Generate an HTML file for one source file."""
-
+
source = cu.source_file().read()
- nums = analysis.numbers
+ nums = analysis.numbers
missing_branch_arcs = analysis.missing_branch_arcs()
n_par = 0 # accumulated below.
@@ -75,12 +75,13 @@ class HtmlReporter(Reporter):
c_par = " par" + c_run
lines = []
-
+
for lineno, line in enumerate(source_token_lines(source)):
lineno += 1 # 1-based line numbers.
# Figure out how to mark this line.
line_class = ""
- annotate = ""
+ annotate_html = ""
+ annotate_title = ""
if lineno in analysis.statements:
line_class += " stm"
if lineno in analysis.excluded:
@@ -96,10 +97,14 @@ class HtmlReporter(Reporter):
annlines.append("exit")
else:
annlines.append(str(b))
- annotate = " ".join(annlines)
+ annotate_html = "&nbsp;&nbsp; ".join(annlines)
+ if len(annlines) > 1:
+ annotate_title = "no jumps to these line numbers"
+ elif len(annlines) == 1:
+ annotate_title = "no jump to this line number"
elif lineno in analysis.statements:
line_class += c_run
-
+
# Build the HTML for the line
html = ""
for tok_type, tok_text in line:
@@ -113,7 +118,8 @@ class HtmlReporter(Reporter):
'html': html,
'number': lineno,
'class': line_class.strip() or "pln",
- 'annotate': annotate,
+ 'annotate': annotate_html,
+ 'annotate_title': annotate_title,
})
# Write the HTML page for this file.
@@ -167,10 +173,10 @@ def format_pct(p):
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.
-
+
"""
html = re.sub(">\s+<p ", ">\n<p ", html)
return html
diff --git a/coverage/htmlfiles/pyfile.html b/coverage/htmlfiles/pyfile.html
index 566244f2..62518ba0 100644
--- a/coverage/htmlfiles/pyfile.html
+++ b/coverage/htmlfiles/pyfile.html
@@ -48,7 +48,7 @@ function toggle_lines(btn, cls) {
</td>
<td class='text' valign='top'>
{% for line in lines %}
- <p class='{{line.class}}'>{% if line.annotate %}<span class='annotate'>{{line.annotate}}</span>{% endif %}{{line.html}}<span class='strut'>&nbsp;</span></p>
+ <p class='{{line.class}}'>{% if line.annotate %}<span class='annotate' title='{{line.annotate_title}}'>{{line.annotate}}</span>{% endif %}{{line.html}}<span class='strut'>&nbsp;</span></p>
{% endfor %}
</td>
</tr>
diff --git a/coverage/misc.py b/coverage/misc.py
index aa61fc98..0e6bcf99 100644
--- a/coverage/misc.py
+++ b/coverage/misc.py
@@ -2,10 +2,10 @@
def nice_pair(pair):
"""Make a nice string representation of a pair of numbers.
-
+
If the numbers are equal, just return the number, otherwise return the pair
with a dash between them, indicating the range.
-
+
"""
start, end = pair
if start == end:
@@ -20,10 +20,10 @@ def format_lines(statements, lines):
Format a list of line numbers for printing by coalescing groups of lines as
long as the lines represent consecutive statements. This will coalesce
even if there are gaps between statements.
-
+
For example, if `statements` is [1,2,3,4,5,10,11,12,13,14] and
`lines` is [1,2,5,10,11,13,14] then the result will be "1-2, 5-11, 13-14".
-
+
"""
pairs = []
i = 0
@@ -47,9 +47,9 @@ def format_lines(statements, lines):
def expensive(fn):
"""A decorator to cache the result of an expensive operation.
-
+
Only applies to methods with no arguments.
-
+
"""
attr = "_cache_" + fn.__name__
def _wrapped(self):
diff --git a/coverage/parser.py b/coverage/parser.py
index 505ce1cf..43f691f5 100644
--- a/coverage/parser.py
+++ b/coverage/parser.py
@@ -9,13 +9,13 @@ from coverage.misc import nice_pair, CoverageException, NoSource, expensive
class CodeParser(object):
"""Parse code to find executable lines, excluded lines, etc."""
-
+
def __init__(self, text=None, filename=None, exclude=None):
"""
Source can be provided as `text`, the text itself, or `filename`, from
- which text will be read. Excluded lines are those that match `exclude`,
- a regex.
-
+ which text will be read. Excluded lines are those that match
+ `exclude`, a regex.
+
"""
assert text or filename, "CodeParser needs either text or filename"
self.filename = filename or "<code>"
@@ -33,7 +33,7 @@ class CodeParser(object):
self.text = self.text.replace('\r\n', '\n')
self.exclude = exclude
-
+
self.show_tokens = False
# The text lines of the parsed code.
@@ -41,22 +41,22 @@ class CodeParser(object):
# The line numbers of excluded lines of code.
self.excluded = set()
-
+
# The line numbers of docstring lines.
self.docstrings = set()
-
+
# The line numbers of class definitions.
self.classdefs = set()
# A dict mapping line numbers to (lo,hi) for multi-line statements.
self.multiline = {}
-
+
# The line numbers that start statements.
self.statement_starts = set()
# Lazily-created ByteParser
self._byte_parser = None
-
+
def _get_byte_parser(self):
"""Create a ByteParser on demand."""
if not self._byte_parser:
@@ -67,9 +67,9 @@ class CodeParser(object):
def _raw_parse(self):
"""Parse the source to find the interesting facts about its lines.
-
+
A handful of member fields are updated.
-
+
"""
# Find lines which match an exclusion pattern.
if self.exclude:
@@ -77,7 +77,7 @@ class CodeParser(object):
for i, ltext in enumerate(self.lines):
if re_exclude.search(ltext):
self.excluded.add(i+1)
-
+
# Tokenize, to find excluded suites, to find docstrings, and to find
# multi-line statements.
indent = 0
@@ -88,7 +88,7 @@ class CodeParser(object):
tokgen = tokenize.generate_tokens(StringIO(self.text).readline)
for toktype, ttext, (slineno, _), (elineno, _), ltext in tokgen:
- if self.show_tokens:
+ if self.show_tokens: # pragma: no cover
print("%10s %5s %-20r %r" % (
tokenize.tok_name.get(toktype, toktype),
nice_pair((slineno, elineno)), ttext, ltext
@@ -125,7 +125,7 @@ class CodeParser(object):
for l in range(first_line, elineno+1):
self.multiline[l] = rng
first_line = None
-
+
if ttext.strip() and toktype != tokenize.COMMENT:
# A non-whitespace token.
if first_line is None:
@@ -137,7 +137,7 @@ class CodeParser(object):
excluding = False
if excluding:
self.excluded.add(elineno)
-
+
prev_toktype = toktype
# Find the starts of the executable statements.
@@ -155,11 +155,11 @@ class CodeParser(object):
def first_lines(self, lines, ignore=None):
"""Map the line numbers in `lines` to the correct first line of the
statement.
-
+
Skip any line mentioned in `ignore`.
-
+
Returns a sorted list of the first lines.
-
+
"""
ignore = ignore or []
lset = set()
@@ -170,31 +170,31 @@ class CodeParser(object):
if new_l not in ignore:
lset.add(new_l)
return sorted(lset)
-
+
def parse_source(self):
"""Parse source text to find executable lines, excluded lines, etc.
Return values are 1) a sorted list of executable line numbers, and
2) a sorted list of excluded line numbers.
-
+
Reported line numbers are normalized to the first line of multi-line
statements.
-
+
"""
self._raw_parse()
-
+
excluded_lines = self.first_lines(self.excluded)
ignore = excluded_lines + list(self.docstrings)
lines = self.first_lines(self.statement_starts, ignore)
-
+
return lines, excluded_lines
def arcs(self):
"""Get information about the arcs available in the code.
-
+
Returns a sorted list of line number pairs. Line numbers have been
normalized to the first line of multiline statements.
-
+
"""
all_arcs = []
for l1, l2 in self.byte_parser._all_arcs():
@@ -207,9 +207,9 @@ class CodeParser(object):
def exit_counts(self):
"""Get a mapping from line numbers to count of exits from that line.
-
+
Excluded lines are excluded.
-
+
"""
excluded_lines = self.first_lines(self.excluded)
exit_counts = {}
@@ -232,7 +232,7 @@ class CodeParser(object):
# Ensure key is there: classdefs can include excluded lines.
if l in exit_counts:
exit_counts[l] -= 1
-
+
return exit_counts
exit_counts = expensive(exit_counts)
@@ -303,13 +303,13 @@ class ByteParser(object):
def child_parsers(self):
"""Iterate over all the code objects nested within this one.
-
+
The iteration includes `self` as its first value.
-
+
"""
return map(lambda c: ByteParser(code=c), CodeObjects(self.code))
- # Getting numbers from the lnotab value changed in Py3.0.
+ # Getting numbers from the lnotab value changed in Py3.0.
if sys.hexversion >= 0x03000000:
def _lnotab_increments(self, lnotab):
"""Return a list of ints from the lnotab bytes in 3.x"""
@@ -321,15 +321,15 @@ class ByteParser(object):
def _bytes_lines(self):
"""Map byte offsets to line numbers in `code`.
-
+
Uses co_lnotab described in Python/compile.c to map byte offsets to
line numbers. Returns a list: [(b0, l0), (b1, l1), ...]
-
+
"""
# Adapted from dis.py in the standard library.
byte_increments = self._lnotab_increments(self.code.co_lnotab[0::2])
line_increments = self._lnotab_increments(self.code.co_lnotab[1::2])
-
+
bytes_lines = []
last_line_num = None
line_num = self.code.co_firstlineno
@@ -344,13 +344,13 @@ class ByteParser(object):
if line_num != last_line_num:
bytes_lines.append((byte_num, line_num))
return bytes_lines
-
+
def _find_statements(self):
"""Find the statements in `self.code`.
-
+
Return a set of line numbers that start statements. Recurses into all
code objects reachable from `self.code`.
-
+
"""
stmts = set()
for bp in self.child_parsers():
@@ -358,12 +358,12 @@ class ByteParser(object):
for _, l in bp._bytes_lines():
stmts.add(l)
return stmts
-
- def _disassemble(self):
+
+ def _disassemble(self): # pragma: no cover
"""Disassemble code, for ad-hoc experimenting."""
-
+
import dis
-
+
for bp in self.child_parsers():
print("\n%s: " % bp.code)
dis.dis(bp.code)
@@ -373,24 +373,24 @@ class ByteParser(object):
def _split_into_chunks(self):
"""Split the code object into a list of `Chunk` objects.
-
+
Each chunk is only entered at its first instruction, though there can
be many exits from a chunk.
-
+
Returns a list of `Chunk` objects.
-
+
"""
# The list of chunks so far, and the one we're working on.
chunks = []
chunk = None
bytes_lines_map = dict(self._bytes_lines())
-
+
# 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 = []
-
+
# Some op codes are followed by branches that should be ignored. This
# is a count of how many ignores are left.
ignore_branch = 0
@@ -405,12 +405,12 @@ class ByteParser(object):
chunk.exits.add(bc.offset)
chunk = Chunk(bc.offset, bytes_lines_map[bc.offset])
chunks.append(chunk)
-
+
if not chunk:
chunk = Chunk(bc.offset)
chunks.append(chunk)
- # Look at the opcode
+ # 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.
@@ -418,7 +418,7 @@ class ByteParser(object):
else:
# The opcode has a jump, it's an exit for this chunk.
chunk.exits.add(bc.jump_to)
-
+
if bc.op in OPS_CODE_END:
# The opcode can exit the code object.
chunk.exits.add(-1)
@@ -454,7 +454,7 @@ class ByteParser(object):
penult = ult
ult = bc
-
+
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
@@ -483,35 +483,35 @@ class ByteParser(object):
def _arcs(self):
"""Find the executable arcs in the code.
-
+
Returns a set of pairs, (from,to). From and to are integer line
numbers. If from is -1, then the arc is an entrance into the code
object. If to is -1, the arc is an exit from the code object.
-
+
"""
chunks = self._split_into_chunks()
-
+
# A map from byte offsets to chunks jumped into.
byte_chunks = dict([(c.byte, c) for c in chunks])
# Build a map from byte offsets to actual lines reached.
byte_lines = {-1:[-1]}
bytes_to_add = set([c.byte for c in chunks])
-
+
while bytes_to_add:
byte_to_add = bytes_to_add.pop()
if byte_to_add in byte_lines or byte_to_add == -1:
continue
-
+
# Which lines does this chunk lead to?
bytes_considered = set()
bytes_to_consider = [byte_to_add]
lines = set()
-
+
while bytes_to_consider:
byte = bytes_to_consider.pop()
bytes_considered.add(byte)
-
+
# Find chunk for byte
try:
ch = byte_chunks[byte]
@@ -523,7 +523,7 @@ class ByteParser(object):
# No chunk for this byte!
raise Exception("Couldn't find chunk @ %d" % byte)
byte_chunks[byte] = ch
-
+
if ch.line:
lines.add(ch.line)
else:
@@ -536,7 +536,7 @@ class ByteParser(object):
bytes_to_add.update(ch.exits)
byte_lines[byte_to_add] = lines
-
+
# Figure out for each chunk where the exits go.
arcs = set()
for chunk in chunks:
@@ -547,65 +547,65 @@ class ByteParser(object):
arcs.add((chunk.line, exit_line))
for line in byte_lines[0]:
arcs.add((-1, line))
-
+
return arcs
-
+
def _all_chunks(self):
"""Returns a list of `Chunk` objects for this code and its children.
-
+
See `_split_into_chunks` for details.
-
+
"""
chunks = []
for bp in self.child_parsers():
chunks.extend(bp._split_into_chunks())
-
+
return chunks
def _all_arcs(self):
"""Get the set of all arcs in this code object and its children.
-
+
See `_arcs` for details.
-
+
"""
arcs = set()
for bp in self.child_parsers():
arcs.update(bp._arcs())
-
+
return arcs
class Chunk(object):
"""A sequence of bytecodes with a single entrance.
-
+
To analyze byte code, we have to divide it into chunks, sequences of byte
codes such that each basic block has only one entrance, the first
- instruction in the block.
-
+ 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.
-
+
.. _basic block: http://en.wikipedia.org/wiki/Basic_block
-
+
An exit of -1 means the chunk can leave the code (return).
-
+
"""
def __init__(self, byte, line=0):
self.byte = byte
self.line = line
self.length = 0
self.exits = set()
-
+
def __repr__(self):
return "<%d+%d @%d %r>" % (
self.byte, self.length, self.line, list(self.exits)
)
-class AdHocMain(object):
+class AdHocMain(object): # pragma: no cover
"""An ad-hoc main for code parsing experiments."""
-
+
def main(self, args):
"""A main function for trying the code from the command line."""
@@ -632,7 +632,7 @@ class AdHocMain(object):
"-t", action="store_true", dest="tokens",
help="Show tokens"
)
-
+
options, args = parser.parse_args()
if options.recursive:
if args:
@@ -647,12 +647,12 @@ class AdHocMain(object):
def adhoc_one_file(self, options, filename):
"""Process just one file."""
-
+
if options.dis or options.chunks:
try:
bp = ByteParser(filename=filename)
except CoverageException:
- _, err, _ = sys.exc_info()
+ _, err, _ = sys.exc_info()
print("%s" % (err,))
return
@@ -679,7 +679,7 @@ class AdHocMain(object):
arc_width, arc_chars = self.arc_ascii_art(arcs)
else:
arc_width, arc_chars = 0, {}
-
+
exit_counts = cp.exit_counts()
for i, ltext in enumerate(cp.lines):
@@ -703,10 +703,10 @@ class AdHocMain(object):
def arc_ascii_art(self, arcs):
"""Draw arcs as ascii art.
-
+
Returns a width of characters needed to draw all the arcs, and a
dictionary mapping line numbers to ascii strings to draw for that line.
-
+
"""
arc_chars = {}
for lfrom, lto in sorted(arcs):
diff --git a/coverage/phystokens.py b/coverage/phystokens.py
index 131b362a..5824b9b9 100644
--- a/coverage/phystokens.py
+++ b/coverage/phystokens.py
@@ -5,13 +5,13 @@ from coverage.backward import StringIO # pylint: disable-msg=W0622
def phys_tokens(toks):
"""Return all physical tokens, even line continuations.
-
+
tokenize.generate_tokens() doesn't return a token for the backslash that
continues lines. This wrapper provides those tokens so that we can
re-create a faithful representation of the original source.
-
+
Returns the same values as generate_tokens()
-
+
"""
last_line = None
last_lineno = -1
@@ -61,13 +61,13 @@ def phys_tokens(toks):
def source_token_lines(source):
"""Generate a series of lines, one for each line in `source`.
-
+
Each line is a list of pairs, each pair is a token::
-
+
[('key', 'def'), ('ws', ' '), ('nam', 'hello'), ('op', '('), ... ]
Each pair has a token class, and the token text.
-
+
If you concatenate all the token texts, and then join them with newlines,
you should have your original `source` back, with two differences:
trailing whitespace is not preserved, and a final line with no newline
diff --git a/coverage/report.py b/coverage/report.py
index c2215521..5b66f999 100644
--- a/coverage/report.py
+++ b/coverage/report.py
@@ -6,30 +6,30 @@ from coverage.misc import CoverageException, NoSource
class Reporter(object):
"""A base class for all reporters."""
-
+
def __init__(self, coverage, ignore_errors=False):
"""Create a reporter.
-
+
`coverage` is the coverage instance. `ignore_errors` controls how
skittish the reporter will be during file processing.
"""
self.coverage = coverage
self.ignore_errors = ignore_errors
-
+
# The code units to report on. Set by find_code_units.
self.code_units = []
-
+
# The directory into which to place the report, used by some derived
# classes.
self.directory = None
def find_code_units(self, morfs, omit_prefixes):
"""Find the code units we'll report on.
-
+
`morfs` is a list of modules or filenames. `omit_prefixes` is a list
of prefixes to leave out of the list.
-
+
"""
morfs = morfs or self.coverage.data.executed_files()
self.code_units = code_unit_factory(
@@ -39,9 +39,9 @@ class Reporter(object):
def report_files(self, report_fn, morfs, directory=None,
omit_prefixes=None):
"""Run a reporting function on a number of morfs.
-
+
`report_fn` is called for each relative morf in `morfs`.
-
+
"""
self.find_code_units(morfs, omit_prefixes)
diff --git a/coverage/results.py b/coverage/results.py
index 77c461ad..e80ec0a4 100644
--- a/coverage/results.py
+++ b/coverage/results.py
@@ -9,11 +9,11 @@ from coverage.parser import CodeParser
class Analysis(object):
"""The results of analyzing a code unit."""
-
+
def __init__(self, cov, code_unit):
self.coverage = cov
self.code_unit = code_unit
-
+
self.filename = self.code_unit.filename
ext = os.path.splitext(self.filename)[1]
source = None
@@ -40,10 +40,10 @@ class Analysis(object):
n_missing_branches = sum([len(v) for v in mba.values()])
else:
n_branches = n_missing_branches = 0
-
+
self.numbers = Numbers(
n_files=1,
- n_statements=len(self.statements),
+ n_statements=len(self.statements),
n_excluded=len(self.excluded),
n_missing=len(self.missing),
n_branches=n_branches,
@@ -52,9 +52,9 @@ class Analysis(object):
def missing_formatted(self):
"""The missing line numbers, formatted nicely.
-
+
Returns a string like "1-2, 5-11, 13-14".
-
+
"""
return format_lines(self.statements, self.missing)
@@ -102,12 +102,12 @@ class Analysis(object):
"""How many total branches are there?"""
exit_counts = self.parser.exit_counts()
return sum([count for count in exit_counts.values() if count > 1])
-
+
def missing_branch_arcs(self):
"""Return arcs that weren't executed from branch lines.
-
+
Returns {l1:[l2a,l2b,...], ...}
-
+
"""
missing = self.arcs_missing()
branch_lines = set(self.branch_lines())
@@ -122,7 +122,7 @@ class Analysis(object):
class Numbers(object):
"""The numerical results of measuring coverage.
-
+
This holds the basic statistics from `Analysis`, and is used to roll
up statistics across files.
@@ -141,12 +141,12 @@ class Numbers(object):
"""Returns the number of executed statements."""
return self.n_statements - self.n_missing
n_executed = property(_get_n_executed)
-
+
def _get_n_executed_branches(self):
"""Returns the number of executed branches."""
return self.n_branches - self.n_missing_branches
n_executed_branches = property(_get_n_executed_branches)
-
+
def _get_pc_covered(self):
"""Returns a single percentage value for coverage."""
if self.n_statements > 0:
diff --git a/coverage/summary.py b/coverage/summary.py
index e0e9eba7..f4d3c2c6 100644
--- a/coverage/summary.py
+++ b/coverage/summary.py
@@ -8,7 +8,7 @@ from coverage.results import Numbers
class SummaryReporter(Reporter):
"""A reporter for writing the summary report."""
-
+
def __init__(self, coverage, show_missing=True, ignore_errors=False):
super(SummaryReporter, self).__init__(coverage, ignore_errors)
self.show_missing = show_missing
@@ -16,7 +16,7 @@ class SummaryReporter(Reporter):
def report(self, morfs, omit_prefixes=None, outfile=None):
"""Writes a report summarizing coverage statistics per module."""
-
+
self.find_code_units(morfs, omit_prefixes)
# Prepare the formatting strings
@@ -45,7 +45,7 @@ class SummaryReporter(Reporter):
outfile.write(rule)
total = Numbers()
-
+
for cu in self.code_units:
try:
analysis = self.coverage._analyze(cu)
diff --git a/coverage/templite.py b/coverage/templite.py
index 0654f292..d3c673c6 100644
--- a/coverage/templite.py
+++ b/coverage/templite.py
@@ -8,40 +8,40 @@ class Templite(object):
"""A simple template renderer, for a nano-subset of Django syntax.
Supported constructs are extended variable access::
-
+
{{var.modifer.modifier|filter|filter}}
-
+
loops::
-
+
{% for var in list %}...{% endfor %}
-
+
and ifs::
-
+
{% if var %}...{% endif %}
Comments are within curly-hash markers::
-
+
{# This will be ignored #}
Construct a Templite with the template text, then use `render` against a
dictionary context to create a finished string.
-
+
"""
def __init__(self, text, *contexts):
"""Construct a Templite with the given `text`.
-
+
`contexts` are dictionaries of values to use for future renderings.
These are good for filters and global values.
-
+
"""
self.text = text
self.context = {}
for context in contexts:
self.context.update(context)
-
+
# Split the text to form a list of tokens.
toks = re.split(r"(?s)({{.*?}}|{%.*?%}|{#.*?#})", text)
-
+
# Parse the tokens into a nested list of operations. Each item in the
# list is a tuple with an opcode, and arguments. They'll be
# interpreted by TempliteEngine.
@@ -83,21 +83,21 @@ class Templite(object):
raise SyntaxError("Don't understand tag %r" % words)
else:
ops.append(('lit', tok))
-
+
assert not ops_stack, "Unmatched action tag: %r" % ops_stack[-1][0]
self.ops = ops
def render(self, context=None):
"""Render this template by applying it to `context`.
-
+
`context` is a dictionary of values to use in this rendering.
-
+
"""
# Make the complete context we'll use.
ctx = dict(self.context)
if context:
ctx.update(context)
-
+
# Run it through an engine, and return the result.
engine = _TempliteEngine(ctx)
engine.execute(self.ops)
@@ -112,9 +112,9 @@ class _TempliteEngine(object):
def execute(self, ops):
"""Execute `ops` in the engine.
-
+
Called recursively for the bodies of if's and loops.
-
+
"""
for op, args in ops:
if op == 'lit':
@@ -142,9 +142,9 @@ class _TempliteEngine(object):
def evaluate(self, expr):
"""Evaluate an expression.
-
+
`expr` can have pipes and dots to indicate data access and filtering.
-
+
"""
if "|" in expr:
pipes = expr.split("|")
diff --git a/coverage/tracer.c b/coverage/tracer.c
index 304190e0..8193c34d 100644
--- a/coverage/tracer.c
+++ b/coverage/tracer.c
@@ -60,7 +60,7 @@ typedef struct {
PyObject * data;
PyObject * should_trace_cache;
PyObject * arcs;
-
+
/* Has the tracer been started? */
int started;
/* Are we tracing arcs, or just lines? */
@@ -71,7 +71,7 @@ typedef struct {
data for a single source file. The data stack parallels the call stack:
each call pushes the new frame's file data onto the data stack, and each
return pops file data off.
-
+
The file data is a dictionary whose form depends on the tracing options.
If tracing arcs, the keys are line number pairs. If not tracing arcs,
the keys are line numbers. In both cases, the value is irrelevant
@@ -130,7 +130,7 @@ Tracer_init(Tracer *self, PyObject *args, PyObject *kwds)
self->data = NULL;
self->should_trace_cache = NULL;
self->arcs = NULL;
-
+
self->started = 0;
self->tracing_arcs = 0;
@@ -143,7 +143,7 @@ Tracer_init(Tracer *self, PyObject *args, PyObject *kwds)
}
self->data_stack_alloc = STACK_DELTA;
- self->cur_file_data = NULL;
+ self->cur_file_data = NULL;
self->last_line = -1;
self->last_exc_back = NULL;
@@ -171,7 +171,7 @@ Tracer_dealloc(Tracer *self)
static const char *
indent(int n)
{
- static const char * spaces =
+ static const char * spaces =
" "
" "
" "
@@ -220,7 +220,7 @@ static int
Tracer_record_pair(Tracer *self, int l1, int l2)
{
int ret = 0;
-
+
PyObject * t = PyTuple_New(2);
if (t != NULL) {
PyTuple_SET_ITEM(t, 0, MyInt_FromLong(l1));
@@ -248,11 +248,11 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg)
PyObject * filename = NULL;
PyObject * tracename = NULL;
- #if WHAT_LOG
+ #if WHAT_LOG
if (what <= sizeof(what_sym)/sizeof(const char *)) {
printf("trace: %s @ %s %d\n", what_sym[what], MyText_AS_STRING(frame->f_code->co_filename), frame->f_lineno);
}
- #endif
+ #endif
#if TRACE_LOG
if (strstr(MyText_AS_STRING(frame->f_code->co_filename), start_file) && frame->f_lineno == start_line) {
@@ -269,7 +269,7 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg)
that frame is gone. Our handling for RETURN doesn't need the
actual frame, but we do log it, so that will look a little off if
you're looking at the detailed log.
-
+
If someday we need to examine the frame when doing RETURN, then
we'll need to keep more of the missed frame's state.
*/
@@ -288,7 +288,7 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg)
}
self->last_exc_back = NULL;
}
-
+
switch (what) {
case PyTrace_CALL: /* 0 */
@@ -361,12 +361,12 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg)
self->cur_file_data = NULL;
SHOWLOG(self->depth, frame->f_lineno, filename, "skipped");
}
-
+
Py_DECREF(tracename);
self->last_line = -1;
break;
-
+
case PyTrace_RETURN: /* 3 */
STATS( self->stats.returns++; )
/* A near-copy of this code is above in the missing-return handler. */
@@ -383,7 +383,7 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg)
self->depth--;
}
break;
-
+
case PyTrace_LINE: /* 2 */
STATS( self->stats.lines++; )
if (self->depth >= 0) {
@@ -414,29 +414,29 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg)
self->last_line = frame->f_lineno;
}
break;
-
+
case PyTrace_EXCEPTION:
/* Some code (Python 2.3, and pyexpat anywhere) fires an exception event
without a return event. To detect that, we'll keep a copy of the
parent frame for an exception event. If the next event is in that
frame, then we must have returned without a return event. We can
synthesize the missing event then.
-
+
Python itself fixed this problem in 2.4. Pyexpat still has the bug.
I've reported the problem with pyexpat as http://bugs.python.org/issue6359 .
If it gets fixed, this code should still work properly. Maybe some day
the bug will be fixed everywhere coverage.py is supported, and we can
remove this missing-return detection.
-
+
More about this fix: http://nedbatchelder.com/blog/200907/a_nasty_little_bug.html
*/
STATS( self->stats.exceptions++; )
self->last_exc_back = frame->f_back;
break;
-
+
default:
STATS( self->stats.others++; )
- break;
+ break;
}
return 0;
@@ -586,7 +586,7 @@ PyInit_tracer(void)
if (mod == NULL) {
return NULL;
}
-
+
TracerType.tp_new = PyType_GenericNew;
if (PyType_Ready(&TracerType) < 0) {
Py_DECREF(mod);
@@ -595,8 +595,8 @@ PyInit_tracer(void)
Py_INCREF(&TracerType);
PyModule_AddObject(mod, "Tracer", (PyObject *)&TracerType);
-
- return mod;
+
+ return mod;
}
#else
diff --git a/coverage/xmlreport.py b/coverage/xmlreport.py
index 9cc6567a..ab44025d 100644
--- a/coverage/xmlreport.py
+++ b/coverage/xmlreport.py
@@ -14,20 +14,20 @@ def rate(hit, num):
class XmlReporter(Reporter):
"""A reporter for writing Cobertura-style XML coverage results."""
-
+
def __init__(self, coverage, ignore_errors=False):
super(XmlReporter, self).__init__(coverage, ignore_errors)
-
+
self.packages = None
self.xml_out = None
self.arcs = coverage.data.has_arcs()
def report(self, morfs, omit_prefixes=None, outfile=None):
"""Generate a Cobertura-compatible XML report for `morfs`.
-
+
`morfs` is a list of modules or filenames. `omit_prefixes` is a list
of strings, prefixes of modules to omit from the report.
-
+
"""
# Initial setup.
outfile = outfile or sys.stdout
@@ -39,7 +39,7 @@ class XmlReporter(Reporter):
"http://cobertura.sourceforge.net/xml/coverage-03.dtd"
)
self.xml_out = impl.createDocument(None, "coverage", docType)
-
+
# Write header stuff.
xcoverage = self.xml_out.documentElement
xcoverage.setAttribute("version", __version__)
@@ -56,7 +56,7 @@ class XmlReporter(Reporter):
lnum_tot, lhits_tot = 0, 0
bnum_tot, bhits_tot = 0, 0
-
+
# Populate the XML DOM with the package info.
for pkg_name, pkg_data in self.packages.items():
class_elts, lhits, lnum, bhits, bnum = pkg_data
@@ -75,16 +75,16 @@ class XmlReporter(Reporter):
lhits_tot += lhits
bnum_tot += bnum
bhits_tot += bhits
-
+
xcoverage.setAttribute("line-rate", str(rate(lhits_tot, lnum_tot)))
xcoverage.setAttribute("branch-rate", str(rate(bhits_tot, bnum_tot)))
-
+
# Use the DOM to write the output file.
outfile.write(self.xml_out.toprettyxml())
def xml_file(self, cu, analysis):
"""Add to the XML report for a single file."""
-
+
# Create the 'lines' and 'package' XML elements, which
# are populated later. Note that a package == a directory.
dirname, fname = os.path.split(cu.name)
@@ -133,7 +133,7 @@ class XmlReporter(Reporter):
else:
class_branches = 0.0
class_branch_hits = 0.0
-
+
# Finalize the statistics that are collected in the XML DOM.
line_rate = rate(class_hits, class_lines)
branch_rate = rate(class_branch_hits, class_branches)
diff --git a/distribute_setup.py b/distribute_setup.py
new file mode 100644
index 00000000..cfb3bbef
--- /dev/null
+++ b/distribute_setup.py
@@ -0,0 +1,458 @@
+#!python
+"""Bootstrap distribute installation
+
+If you want to use setuptools in your package's setup.py, just include this
+file in the same directory with it, and add this to the top of your setup.py::
+
+ from distribute_setup import use_setuptools
+ use_setuptools()
+
+If you want to require a specific version of setuptools, set a download
+mirror, or use an alternate download directory, you can do so by supplying
+the appropriate options to ``use_setuptools()``.
+
+This file can also be run as a script to install or upgrade setuptools.
+"""
+import os
+import sys
+import time
+import fnmatch
+import tempfile
+import tarfile
+from distutils import log
+
+try:
+ from site import USER_SITE
+except ImportError:
+ USER_SITE = None
+
+try:
+ import subprocess
+
+ def _python_cmd(*args):
+ args = (sys.executable,) + args
+ return subprocess.call(args) == 0
+
+except ImportError:
+ # will be used for python 2.3
+ def _python_cmd(*args):
+ args = (sys.executable,) + args
+ # quoting arguments if windows
+ if sys.platform == 'win32':
+ def quote(arg):
+ if ' ' in arg:
+ return '"%s"' % arg
+ return arg
+ args = [quote(arg) for arg in args]
+ return os.spawnl(os.P_WAIT, sys.executable, *args) == 0
+
+DEFAULT_VERSION = "0.6.8"
+DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/"
+SETUPTOOLS_PKG_INFO = """\
+Metadata-Version: 1.0
+Name: setuptools
+Version: 0.6c9
+Summary: xxxx
+Home-page: xxx
+Author: xxx
+Author-email: xxx
+License: xxx
+Description: xxx
+"""
+
+
+def _install(tarball):
+ # extracting the tarball
+ tmpdir = tempfile.mkdtemp()
+ log.warn('Extracting in %s', tmpdir)
+ old_wd = os.getcwd()
+ try:
+ os.chdir(tmpdir)
+ tar = tarfile.open(tarball)
+ _extractall(tar)
+ tar.close()
+
+ # going in the directory
+ subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
+ os.chdir(subdir)
+ log.warn('Now working in %s', subdir)
+
+ # installing
+ log.warn('Installing Distribute')
+ assert _python_cmd('setup.py', 'install')
+ finally:
+ os.chdir(old_wd)
+
+
+def _build_egg(egg, tarball, to_dir):
+ # extracting the tarball
+ tmpdir = tempfile.mkdtemp()
+ log.warn('Extracting in %s', tmpdir)
+ old_wd = os.getcwd()
+ try:
+ os.chdir(tmpdir)
+ tar = tarfile.open(tarball)
+ _extractall(tar)
+ tar.close()
+
+ # going in the directory
+ subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
+ os.chdir(subdir)
+ log.warn('Now working in %s', subdir)
+
+ # building an egg
+ log.warn('Building a Distribute egg in %s', to_dir)
+ _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
+
+ finally:
+ os.chdir(old_wd)
+ # returning the result
+ log.warn(egg)
+ if not os.path.exists(egg):
+ raise IOError('Could not build the egg.')
+
+
+def _do_download(version, download_base, to_dir, download_delay):
+ egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg'
+ % (version, sys.version_info[0], sys.version_info[1]))
+ if not os.path.exists(egg):
+ tarball = download_setuptools(version, download_base,
+ to_dir, download_delay)
+ _build_egg(egg, tarball, to_dir)
+ sys.path.insert(0, egg)
+ import setuptools
+ setuptools.bootstrap_install_from = egg
+
+
+def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
+ to_dir=os.curdir, download_delay=15, no_fake=True):
+ # making sure we use the absolute path
+ to_dir = os.path.abspath(to_dir)
+ was_imported = 'pkg_resources' in sys.modules or \
+ 'setuptools' in sys.modules
+ try:
+ try:
+ import pkg_resources
+ if not hasattr(pkg_resources, '_distribute'):
+ if not no_fake:
+ _fake_setuptools()
+ raise ImportError
+ except ImportError:
+ return _do_download(version, download_base, to_dir, download_delay)
+ try:
+ pkg_resources.require("distribute>="+version)
+ return
+ except pkg_resources.VersionConflict:
+ e = sys.exc_info()[1]
+ if was_imported:
+ sys.stderr.write(
+ "The required version of distribute (>=%s) is not available,\n"
+ "and can't be installed while this script is running. Please\n"
+ "install a more recent version first, using\n"
+ "'easy_install -U distribute'."
+ "\n\n(Currently using %r)\n" % (version, e.args[0]))
+ sys.exit(2)
+ else:
+ del pkg_resources, sys.modules['pkg_resources'] # reload ok
+ return _do_download(version, download_base, to_dir,
+ download_delay)
+ except pkg_resources.DistributionNotFound:
+ return _do_download(version, download_base, to_dir,
+ download_delay)
+ finally:
+ if not no_fake:
+ _create_fake_setuptools_pkg_info(to_dir)
+
+def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
+ to_dir=os.curdir, delay=15):
+ """Download distribute from a specified location and return its filename
+
+ `version` should be a valid distribute version number that is available
+ as an egg for download under the `download_base` URL (which should end
+ with a '/'). `to_dir` is the directory where the egg will be downloaded.
+ `delay` is the number of seconds to pause before an actual download
+ attempt.
+ """
+ # making sure we use the absolute path
+ to_dir = os.path.abspath(to_dir)
+ try:
+ from urllib.request import urlopen
+ except ImportError:
+ from urllib2 import urlopen
+ tgz_name = "distribute-%s.tar.gz" % version
+ url = download_base + tgz_name
+ saveto = os.path.join(to_dir, tgz_name)
+ src = dst = None
+ if not os.path.exists(saveto): # Avoid repeated downloads
+ try:
+ log.warn("Downloading %s", url)
+ src = urlopen(url)
+ # Read/write all in one block, so we don't create a corrupt file
+ # if the download is interrupted.
+ data = src.read()
+ dst = open(saveto, "wb")
+ dst.write(data)
+ finally:
+ if src:
+ src.close()
+ if dst:
+ dst.close()
+ return os.path.realpath(saveto)
+
+
+def _patch_file(path, content):
+ """Will backup the file then patch it"""
+ existing_content = open(path).read()
+ if existing_content == content:
+ # already patched
+ log.warn('Already patched.')
+ return False
+ log.warn('Patching...')
+ _rename_path(path)
+ f = open(path, 'w')
+ try:
+ f.write(content)
+ finally:
+ f.close()
+ return True
+
+
+def _same_content(path, content):
+ return open(path).read() == content
+
+
+def _rename_path(path):
+ new_name = path + '.OLD.%s' % time.time()
+ log.warn('Renaming %s into %s', path, new_name)
+ try:
+ from setuptools.sandbox import DirectorySandbox
+ def _violation(*args):
+ pass
+ DirectorySandbox._violation = _violation
+ except ImportError:
+ pass
+
+ os.rename(path, new_name)
+ return new_name
+
+
+def _remove_flat_installation(placeholder):
+ if not os.path.isdir(placeholder):
+ log.warn('Unkown installation at %s', placeholder)
+ return False
+ found = False
+ for file in os.listdir(placeholder):
+ if fnmatch.fnmatch(file, 'setuptools*.egg-info'):
+ found = True
+ break
+ if not found:
+ log.warn('Could not locate setuptools*.egg-info')
+ return
+
+ log.warn('Removing elements out of the way...')
+ pkg_info = os.path.join(placeholder, file)
+ if os.path.isdir(pkg_info):
+ patched = _patch_egg_dir(pkg_info)
+ else:
+ patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO)
+
+ if not patched:
+ log.warn('%s already patched.', pkg_info)
+ return False
+ # now let's move the files out of the way
+ for element in ('setuptools', 'pkg_resources.py', 'site.py'):
+ element = os.path.join(placeholder, element)
+ if os.path.exists(element):
+ _rename_path(element)
+ else:
+ log.warn('Could not find the %s element of the '
+ 'Setuptools distribution', element)
+ return True
+
+
+def _after_install(dist):
+ log.warn('After install bootstrap.')
+ placeholder = dist.get_command_obj('install').install_purelib
+ _create_fake_setuptools_pkg_info(placeholder)
+
+def _create_fake_setuptools_pkg_info(placeholder):
+ if not placeholder or not os.path.exists(placeholder):
+ log.warn('Could not find the install location')
+ return
+ pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1])
+ setuptools_file = 'setuptools-0.6c9-py%s.egg-info' % pyver
+ pkg_info = os.path.join(placeholder, setuptools_file)
+ if os.path.exists(pkg_info):
+ log.warn('%s already exists', pkg_info)
+ return
+ log.warn('Creating %s', pkg_info)
+ f = open(pkg_info, 'w')
+ try:
+ f.write(SETUPTOOLS_PKG_INFO)
+ finally:
+ f.close()
+ pth_file = os.path.join(placeholder, 'setuptools.pth')
+ log.warn('Creating %s', pth_file)
+ f = open(pth_file, 'w')
+ try:
+ f.write(os.path.join(os.curdir, setuptools_file))
+ finally:
+ f.close()
+
+
+def _patch_egg_dir(path):
+ # let's check if it's already patched
+ pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
+ if os.path.exists(pkg_info):
+ if _same_content(pkg_info, SETUPTOOLS_PKG_INFO):
+ log.warn('%s already patched.', pkg_info)
+ return False
+ _rename_path(path)
+ os.mkdir(path)
+ os.mkdir(os.path.join(path, 'EGG-INFO'))
+ pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
+ f = open(pkg_info, 'w')
+ try:
+ f.write(SETUPTOOLS_PKG_INFO)
+ finally:
+ f.close()
+ return True
+
+
+def _before_install():
+ log.warn('Before install bootstrap.')
+ _fake_setuptools()
+
+
+def _under_prefix(location):
+ if 'install' not in sys.argv:
+ return True
+ args = sys.argv[sys.argv.index('install')+1:]
+ for index, arg in enumerate(args):
+ for option in ('--root', '--prefix'):
+ if arg.startswith('%s=' % option):
+ top_dir = arg.split('root=')[-1]
+ return location.startswith(top_dir)
+ elif arg == option:
+ if len(args) > index:
+ top_dir = args[index+1]
+ return location.startswith(top_dir)
+ elif option == '--user' and USER_SITE is not None:
+ return location.startswith(USER_SITE)
+ return True
+
+
+def _fake_setuptools():
+ log.warn('Scanning installed packages')
+ try:
+ import pkg_resources
+ except ImportError:
+ # we're cool
+ log.warn('Setuptools or Distribute does not seem to be installed.')
+ return
+ ws = pkg_resources.working_set
+ try:
+ setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools',
+ replacement=False))
+ except TypeError:
+ # old distribute API
+ setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools'))
+
+ if setuptools_dist is None:
+ log.warn('No setuptools distribution found')
+ return
+ # detecting if it was already faked
+ setuptools_location = setuptools_dist.location
+ log.warn('Setuptools installation detected at %s', setuptools_location)
+
+ # if --root or --preix was provided, and if
+ # setuptools is not located in them, we don't patch it
+ if not _under_prefix(setuptools_location):
+ log.warn('Not patching, --root or --prefix is installing Distribute'
+ ' in another location')
+ return
+
+ # let's see if its an egg
+ if not setuptools_location.endswith('.egg'):
+ log.warn('Non-egg installation')
+ res = _remove_flat_installation(setuptools_location)
+ if not res:
+ return
+ else:
+ log.warn('Egg installation')
+ pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO')
+ if (os.path.exists(pkg_info) and
+ _same_content(pkg_info, SETUPTOOLS_PKG_INFO)):
+ log.warn('Already patched.')
+ return
+ log.warn('Patching...')
+ # let's create a fake egg replacing setuptools one
+ res = _patch_egg_dir(setuptools_location)
+ if not res:
+ return
+ log.warn('Patched done.')
+ _relaunch()
+
+
+def _relaunch():
+ log.warn('Relaunching...')
+ # we have to relaunch the process
+ args = [sys.executable] + sys.argv
+ sys.exit(subprocess.call(args))
+
+
+def _extractall(self, path=".", members=None):
+ """Extract all members from the archive to the current working
+ directory and set owner, modification time and permissions on
+ directories afterwards. `path' specifies a different directory
+ to extract to. `members' is optional and must be a subset of the
+ list returned by getmembers().
+ """
+ import copy
+ import operator
+ from tarfile import ExtractError
+ directories = []
+
+ if members is None:
+ members = self
+
+ for tarinfo in members:
+ if tarinfo.isdir():
+ # Extract directories with a safe mode.
+ directories.append(tarinfo)
+ tarinfo = copy.copy(tarinfo)
+ tarinfo.mode = 448 # decimal for oct 0700
+ self.extract(tarinfo, path)
+
+ # Reverse sort directories.
+ if sys.version_info < (2, 4):
+ def sorter(dir1, dir2):
+ return cmp(dir1.name, dir2.name)
+ directories.sort(sorter)
+ directories.reverse()
+ else:
+ directories.sort(key=operator.attrgetter('name'), reverse=True)
+
+ # Set correct owner, mtime and filemode on directories.
+ for tarinfo in directories:
+ dirpath = os.path.join(path, tarinfo.name)
+ try:
+ self.chown(tarinfo, dirpath)
+ self.utime(tarinfo, dirpath)
+ self.chmod(tarinfo, dirpath)
+ except ExtractError:
+ e = sys.exc_info()[1]
+ if self.errorlevel > 1:
+ raise
+ else:
+ self._dbg(1, "tarfile: %s" % e)
+
+
+def main(argv, version=DEFAULT_VERSION):
+ """Install or upgrade setuptools and EasyInstall"""
+ tarball = download_setuptools()
+ _install(tarball)
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/doc/_static/neds.css b/doc/_static/neds.css
index 4d24a68e..d1b84a82 100644
--- a/doc/_static/neds.css
+++ b/doc/_static/neds.css
@@ -221,3 +221,7 @@ li {
ul {
list-style-type: square;
}
+
+.external.reference em {
+ font-style: normal;
+} \ No newline at end of file
diff --git a/doc/api.rst b/doc/api.rst
index 17f74b64..98187a8f 100644
--- a/doc/api.rst
+++ b/doc/api.rst
@@ -13,12 +13,12 @@ coverage object correspond to operations available in the command line
interface. For example, a simple use would be::
import coverage
-
+
cov = coverage.coverage()
cov.start()
-
+
# .. run your code ..
-
+
cov.stop()
cov.save()
@@ -29,4 +29,4 @@ The coverage module
.. module:: coverage
.. autoclass:: coverage
- :members:
+ :members:
diff --git a/doc/branch.rst b/doc/branch.rst
index 262ce1a5..7e156034 100644
--- a/doc/branch.rst
+++ b/doc/branch.rst
@@ -18,9 +18,9 @@ For example::
if x: # 2
y = 10 # 3
return y # 4
-
+
my_partial_fn(1)
-
+
In this code, line 2 is an ``if`` statement which can go next to either line 3
or line 4. Statement coverage would show all lines of the function as executed.
But the if was never evaluated as false, so line 2 never jumps to line 4.
@@ -35,7 +35,7 @@ How to measure branch coverage
To measure branch coverage, run coverage.py with the ``--branch`` flag::
coverage run --branch myprog.py
-
+
When you report on the results with ``coverage report`` or ``coverage html``,
the percentage of branch possibilities taken will be included in the percentage
covered total for each file. The coverage percentage for a file is the actual
@@ -48,7 +48,7 @@ far right showing branch destination line numbers that were not exercised.
The XML report produced by ``coverage xml`` also includes branch information,
including separate statement and branch coverage percentages. Each line is
-annotated with
+annotated with
How it works
@@ -91,10 +91,10 @@ unconditional loop will be marked as partially executed::
while True: # line 1
if some_condition(): # 2
- break
+ break
body_of_loop() # 4
-
+
keep_working() # 6
Because the loop never terminates naturally (jumping from line 1 to 6),
-coverage.py considers the branch partially executed.
+coverage.py considers the branch partially executed.
diff --git a/doc/changes.rst b/doc/changes.rst
index 97d9a8e6..bdf0ac71 100644
--- a/doc/changes.rst
+++ b/doc/changes.rst
@@ -38,7 +38,7 @@ Version 3.2
- Some exceptions reported by the command line interface have been cleaned up
so that tracebacks inside coverage.py aren't shown. Fixes `issue 23`_.
-
+
- Fixed some problems syntax coloring sources with line continuations and
source with tabs: `issue 30`_ and `issue 31`_.
@@ -116,7 +116,7 @@ Version 3.0, 13 June 2009
other encouragement.
- The minimum supported Python version is 2.3.
-
+
- 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.
@@ -130,12 +130,12 @@ Version 3.0, 13 June 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.
-
+
- coverage.annotate_file is no longer available.
-
+
- Removed the undocumented cache_file argument to coverage.usecache().
diff --git a/doc/cmd.rst b/doc/cmd.rst
index 41de00c9..ab93845e 100644
--- a/doc/cmd.rst
+++ b/doc/cmd.rst
@@ -71,7 +71,7 @@ can set a new file name with the COVERAGE_FILE environment variable. By default
each run of your program starts with an empty data set. If you need to run your
program multiple times to get complete data (for example, because you need to
supply disjoint options), you can accumulate data across runs with the ``-a``
-flag on the **run** command.
+flag on the **run** command.
To erase the collected data, use the **erase** command::
@@ -113,7 +113,7 @@ as a percentage.
The ``-m`` flag also shows the line numbers of missing statements::
- $ coverage report -m
+ $ coverage report -m
Name Stmts Exec Cover Missing
-------------------------------------------------------
my_program 20 16 80% 33-35, 39
@@ -178,7 +178,7 @@ Coverage status for each line of source is indicated with a character prefix::
For example::
# A simple function, never called with x==1
-
+
> def h(x):
"""Silly function."""
- if 0: #pragma: no cover
@@ -187,7 +187,7 @@ For example::
! a = 1
> else:
> a = 2
-
+
XML reporting
-------------
diff --git a/doc/excluding.rst b/doc/excluding.rst
index 1333f5c2..1b91fc9a 100644
--- a/doc/excluding.rst
+++ b/doc/excluding.rst
@@ -30,7 +30,7 @@ function is not reported as missing::
def __init__(self):
blah1()
blah2()
-
+
def __repr__(self): # pragma: no cover
return "<MyObject>"
@@ -71,7 +71,7 @@ in debugging code, and are uninteresting to test themselves. You could exclude
all of them by adding a regex to the exclusion list::
coverage.exclude('def __repr__')
-
+
Here's a list of exclusions I've used::
coverage.exclude('def __repr__')
diff --git a/doc/index.rst b/doc/index.rst
index eff3106e..5acbc9e9 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -43,7 +43,7 @@ Getting started is easy:
.. code-block:: console
- $ coverage report -m
+ $ coverage report -m
Name Stmts Exec Cover Missing
-------------------------------------------------------
my_program 20 16 80% 33-35, 39
@@ -53,7 +53,7 @@ Getting started is easy:
#. For a nicer presentation, use ``coverage html`` to get annotated HTML
listings detailing missed lines:
-
+
.. code-block:: console
$ coverage html -d htmlcov
@@ -99,7 +99,7 @@ More information
.. toctree::
:maxdepth: 1
-
+
cmd
api
excluding
diff --git a/release.txt b/howto.txt
index 36beef1e..ed582213 100644
--- a/release.txt
+++ b/howto.txt
@@ -16,7 +16,7 @@
- Generate new sample_html to get the latest, incl footer version number:
cd C:\ned\cog\trunk
rmdir/s/q htmlcov
- coverage run cogapp\test_cogapp.py CogTestsInMemory
+ coverage run --branch cogapp\test_cogapp.py CogTestsInMemory
coverage html -i -d htmlcov
copy htmlcov\*.* C:\ned\coverage\trunk\doc\sample_html
- Build and publish docs:
@@ -26,9 +26,9 @@
- $ allkits.cmd
- Update PyPi:
- $ make pypi
+ - upload the kits:
+ - $ allkits.cmd upload
- Visit http://pypi.python.org/pypi?%3Aaction=pkg_edit&name=coverage :
- - Upload kits
- - Label the source kit with "Use this for either Python 2.x or 3.x"
- show/hide the proper versions.
- Tag the tree
- hg tag -m "Coverage 3.0.1" coverage-3.0.1
@@ -37,3 +37,18 @@
- Update bitbucket:
- Issue tracker should get new version number in picker.
- Announce on TIP.
+
+
+
+* Testing
+
+(automate these someday)
+
+- On Py 2.x: need setuptools installed
+- On Py 3.x: need Distribute installed
+- In each Python installation to be used, create a "coverage_test_egg.pth"
+ containing::
+
+ c:\ned\coverage\trunk\test\eggsrc\dist\covtestegg1-0.0.0-py2.5.egg
+
+ (or equivalent).
diff --git a/scripts/coverage b/scripts/coverage
deleted file mode 100644
index e546cb7e..00000000
--- a/scripts/coverage
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/usr/bin/python
-""" Coverage.py code coverage measurement.
- http://nedbatchelder.com/code/coverage
-
- Copyright 2004-2009, Ned Batchelder.
-"""
-
-import sys
-from coverage import main # pylint: disable-msg=W0406
-sys.exit(main())
diff --git a/setup.py b/setup.py
index 4a5aba1c..6bd472ac 100644
--- a/setup.py
+++ b/setup.py
@@ -21,7 +21,8 @@ Environment :: Console
Intended Audience :: Developers
License :: OSI Approved :: BSD License
Operating System :: OS Independent
-Programming Language :: Python
+Programming Language :: Python :: 2
+Programming Language :: Python :: 3
Topic :: Software Development :: Quality Assurance
Topic :: Software Development :: Testing
"""
@@ -29,34 +30,18 @@ Topic :: Software Development :: Testing
# Pull in the tools we need.
import sys
-if sys.hexversion < 0x03000000:
- # In Py 2.x, use setuptools.
- from ez_setup import use_setuptools
- use_setuptools()
-
- from setuptools import setup
- from distutils.core import Extension
-
- more_setup_args = dict(
- entry_points = {
- 'console_scripts': [
- 'coverage = coverage:main',
- ]
- },
-
- # We need to get HTML assets from our htmlfiles dir.
- zip_safe = False,
- )
+# Distribute is a new fork of setuptools. It's supported on Py3.x, so we use
+# it there, but stick with classic setuptools on Py2.x until Distribute becomes
+# more accepted.
+if sys.hexversion > 0x03000000:
+ from distribute_setup import use_setuptools
else:
- # No setuptools yet for Py 3.x, so do without.
- from distutils.core import setup, Extension
+ from ez_setup import use_setuptools
- more_setup_args = dict(
- scripts = [
- 'scripts/coverage',
- ],
- )
+use_setuptools()
+from setuptools import setup
+from distutils.core import Extension
# Get or massage our metadata.
@@ -93,7 +78,16 @@ setup(
ext_modules = [
Extension("coverage.tracer", sources=["coverage/tracer.c"])
],
-
+
+ entry_points = {
+ 'console_scripts': [
+ 'coverage = coverage:main',
+ ]
+ },
+
+ # We need to get HTML assets from our htmlfiles dir.
+ zip_safe = False,
+
author = 'Ned Batchelder',
author_email = 'ned@nedbatchelder.com',
description = doclines[0],
@@ -102,6 +96,4 @@ setup(
license = 'BSD',
classifiers = classifier_list,
url = __url__,
-
- **more_setup_args
)
diff --git a/test/backtest.py b/test/backtest.py
index 21a14d6d..12bdbc97 100644
--- a/test/backtest.py
+++ b/test/backtest.py
@@ -12,18 +12,18 @@ try:
except ImportError:
def run_command(cmd):
"""Run a command in a subprocess.
-
+
Returns the exit code and the combined stdout and stderr.
-
+
"""
_, stdouterr = os.popen4(cmd)
return 0, stdouterr.read()
else:
def run_command(cmd):
"""Run a command in a subprocess.
-
+
Returns the exit code and the combined stdout and stderr.
-
+
"""
if sys.hexversion > 0x03000000 and cmd.startswith("coverage "):
@@ -36,13 +36,13 @@ else:
stderr=subprocess.STDOUT
)
retcode = proc.wait()
-
+
# Get the output, and canonicalize it to strings with newlines.
output = proc.stdout.read()
if not isinstance(output, str):
output = output.decode('utf-8')
output = output.replace('\r', '')
-
+
return retcode, output
# No more execfile in Py3k
diff --git a/test/backunittest.py b/test/backunittest.py
index 2088f2f7..28978c78 100644
--- a/test/backunittest.py
+++ b/test/backunittest.py
@@ -12,10 +12,10 @@ def _need(method):
class TestCase(unittest.TestCase):
"""Just like unittest.TestCase, but with assert methods added.
-
+
Designed to be compatible with 3.1 unittest. Methods are only defined if
the builtin `unittest` doesn't have them.
-
+
"""
if _need('assertFalse'):
def assertFalse(self, exp):
@@ -69,16 +69,16 @@ class TestCase(unittest.TestCase):
if _need('assertMultiLineEqual'):
def assertMultiLineEqual(self, first, second):
"""Assert that two multi-line strings are equal.
-
+
If they aren't, show a nice diff.
-
+
"""
# Adapted from Py3.1 unittest.
self.assert_(isinstance(first, str), (
'First argument is not a string'))
self.assert_(isinstance(second, str), (
'Second argument is not a string'))
-
+
if first != second:
msg = ''.join(difflib.ndiff(first.splitlines(True),
second.splitlines(True)))
diff --git a/test/coverage_coverage.py b/test/coverage_coverage.py
index e1e7674f..b7f903bf 100644
--- a/test/coverage_coverage.py
+++ b/test/coverage_coverage.py
@@ -1,45 +1,81 @@
-"""Coverage-test Coverage itself."""
+"""Coverage-test Coverage.py itself."""
-import coverage
import os, shutil, sys
+import nose
HTML_DIR = "htmlcov"
-if os.path.exists(HTML_DIR):
- shutil.rmtree(HTML_DIR)
-
-cov = coverage.coverage(branch=True)
-# Cheap trick: the coverage code itself is excluded from measurement, but if
-# we clobber the cover_prefix in the coverage object, we can defeat the
-# self-detection.
-cov.cover_prefix = "Please measure coverage.py!"
-cov.erase()
-cov.start()
-
-# Re-import coverage to get it coverage tested! I don't understand all the
-# mechanics here, but if I don't carry over the imported modules (in covmods),
-# then things go haywire (os == None eventually).
-covmods = {}
-covdir = os.path.split(coverage.__file__)
-for name, mod in sys.modules.items():
- if name.startswith('coverage'):
- if hasattr(mod, '__file__') and mod.__file__.startswith(covdir):
- covmods[name] = mod
- del sys.modules[name]
-import coverage # don't warn about re-import: pylint: disable-msg=W0404
-sys.modules.update(covmods)
-
-# Run nosetests, with the arguments from our command line.
-import nose
-nose.run(sys.argv[1:])
+def run_tests_with_coverage():
+ """Run the test suite with coverage measuring itself."""
+ import coverage
+
+ tracer = os.environ.get('COVERAGE_TEST_TRACER', 'c')
+ version = "%s%s" % sys.version_info[:2]
+ suffix = ".%s_%s" % (version, tracer)
+
+ cov = coverage.coverage(branch=True, data_suffix=suffix)
+ # Cheap trick: the coverage code itself is excluded from measurement, but
+ # if we clobber the cover_prefix in the coverage object, we can defeat the
+ # self-detection.
+ cov.cover_prefix = "Please measure coverage.py!"
+ cov.erase()
+ cov.start()
+
+ # Re-import coverage to get it coverage tested! I don't understand all the
+ # mechanics here, but if I don't carry over the imported modules (in
+ # covmods), then things go haywire (os == None, eventually).
+ covmods = {}
+ covdir = os.path.split(coverage.__file__)[0]
+ # We have to make a list since we'll be deleting in the loop.
+ modules = list(sys.modules.items())
+ for name, mod in modules:
+ if name.startswith('coverage'):
+ if hasattr(mod, '__file__') and mod.__file__.startswith(covdir):
+ covmods[name] = mod
+ del sys.modules[name]
+ import coverage # don't warn about re-import: pylint: disable-msg=W0404
+ sys.modules.update(covmods)
+
+ # Run nosetests, with the arguments from our command line.
+ print(":: Running nosetests %s" % " ".join(sys.argv[1:]))
+ nose.run()
+
+ cov.stop()
+ print(":: Saving .coverage%s" % suffix)
+ cov.save()
+
+def report_on_combined_files():
+ """Combine all the .coverage files and make an HTML report."""
+ if os.path.exists(HTML_DIR):
+ shutil.rmtree(HTML_DIR)
+
+ print(":: Writing HTML report to %s/index.html" % HTML_DIR)
+ import coverage
+ cov = coverage.coverage()
+ cov.combine()
+ cov.save()
+ cov.clear_exclude()
+ cov.exclude("#pragma: no cover")
+ cov.exclude("def __repr__")
+ cov.exclude("if __name__ == .__main__.:")
+ cov.exclude("raise AssertionError")
+
+ cov.html_report(
+ directory=HTML_DIR, ignore_errors=True, omit_prefixes=["mock"]
+ )
-cov.stop()
-cov.save()
-cov.clear_exclude()
-cov.exclude("#pragma: no cover")
-cov.exclude("def __repr__")
-cov.exclude("if __name__ == .__main__.:")
-cov.exclude("raise AssertionError")
+try:
+ cmd = sys.argv[1]
+except IndexError:
+ cmd = ''
-cov.html_report(directory=HTML_DIR, ignore_errors=True)
+if cmd == 'run':
+ # Ugly hack: nose.run reads sys.argv directly, so here I delete my command
+ # argument so that sys.argv is left as just nose arguments.
+ del sys.argv[1]
+ run_tests_with_coverage()
+elif cmd == 'report':
+ report_on_combined_files()
+else:
+ print("Need 'run' or 'report'")
diff --git a/test/coveragetest.py b/test/coveragetest.py
index e0b42457..fb6a5bcc 100644
--- a/test/coveragetest.py
+++ b/test/coveragetest.py
@@ -13,7 +13,7 @@ class Tee(object):
def __init__(self, *files):
"""Make a Tee that writes to all the files in `files.`"""
self.files = files
-
+
def write(self, data):
"""Write `data` to all the files."""
for f in self.files:
@@ -27,7 +27,7 @@ class CoverageTest(TestCase):
"""A base class for Coverage test cases."""
run_in_temp_dir = True
-
+
def setUp(self):
if self.run_in_temp_dir:
# Create a temporary directory.
@@ -37,11 +37,12 @@ class CoverageTest(TestCase):
os.makedirs(self.temp_dir)
self.old_dir = os.getcwd()
os.chdir(self.temp_dir)
+
# Modules should be importable from this temp directory.
self.old_syspath = sys.path[:]
sys.path.insert(0, '')
-
+
# Keep a counter to make every call to check_coverage unique.
self.n = 0
@@ -52,12 +53,12 @@ class CoverageTest(TestCase):
self.old_stdout = sys.stdout
self.captured_stdout = StringIO()
sys.stdout = Tee(sys.stdout, self.captured_stdout)
-
+
def tearDown(self):
if self.run_in_temp_dir:
# Restore the original sys.path.
sys.path = self.old_syspath
-
+
# Get rid of the temporary directory.
os.chdir(self.old_dir)
shutil.rmtree(self.temp_root)
@@ -100,14 +101,14 @@ class CoverageTest(TestCase):
def make_file(self, filename, text):
"""Create a temp file.
-
+
`filename` is the file name, and `text` is the content.
-
+
"""
# Tests that call `make_file` should be run in a temp environment.
assert self.run_in_temp_dir
text = textwrap.dedent(text)
-
+
# Create the file.
f = open(filename, 'w')
f.write(text)
@@ -117,7 +118,7 @@ class CoverageTest(TestCase):
"""Import the module named modname, and return the module object."""
modfile = modname + '.py'
f = open(modfile, 'r')
-
+
for suff in imp.get_suffixes():
if suff[0] == '.py':
break
@@ -138,23 +139,23 @@ class CoverageTest(TestCase):
modname = 'coverage_test_' + self.noise + str(self.n)
self.n += 1
return modname
-
+
# Map chars to numbers for arcz_to_arcs
_arcz_map = {'.': -1}
_arcz_map.update(dict([(c, ord(c)-ord('0')) for c in '123456789']))
_arcz_map.update(dict(
[(c, 10+ord(c)-ord('A')) for c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ']
))
-
+
def arcz_to_arcs(self, arcz):
"""Convert a compact textual representation of arcs to a list of pairs.
-
+
The text has space-separated pairs of letters. Period is -1, 1-9 are
1-9, A-Z are 10 through 36. The resulting list is sorted regardless of
the order of the input pairs.
-
+
".1 12 2." --> [(-1,1), (1,2), (2,-1)]
-
+
"""
arcs = []
for a,b in arcz.split():
@@ -164,23 +165,23 @@ class CoverageTest(TestCase):
def check_coverage(self, text, lines=None, missing="", excludes=None,
report="", arcz=None, arcz_missing="", arcz_unpredicted=""):
"""Check the coverage measurement of `text`.
-
+
The source `text` is run and measured. `lines` are the line numbers
that are executable, `missing` are the lines not executed, `excludes`
are regexes to match against for excluding lines, and `report` is
the text of the measurement report.
-
+
For arc measurement, `arcz` is a string that can be decoded into arcs
in the code (see `arcz_to_arcs` for the encoding scheme),
`arcz_missing` are the arcs that are not executed, and
`arcs_unpredicted` are the arcs executed in the code, but not deducible
from the code.
-
+
"""
# We write the code into a file so that we can import it.
# Coverage wants to deal with things as modules with file names.
modname = self.get_module_name()
-
+
self.make_file(modname+".py", text)
arcs = arcs_missing = arcs_unpredicted = None
@@ -188,7 +189,7 @@ class CoverageTest(TestCase):
arcs = self.arcz_to_arcs(arcz)
arcs_missing = self.arcz_to_arcs(arcz_missing or "")
arcs_unpredicted = self.arcz_to_arcs(arcz_unpredicted or "")
-
+
# Start up Coverage.
cov = coverage.coverage(branch=(arcs_missing is not None))
cov.erase()
@@ -196,11 +197,12 @@ class CoverageTest(TestCase):
cov.exclude(exc)
cov.start()
- # Import the python file, executing it.
- mod = self.import_module(modname)
-
- # Stop Coverage.
- cov.stop()
+ try:
+ # Import the python file, executing it.
+ mod = self.import_module(modname)
+ finally:
+ # Stop Coverage.
+ cov.stop()
# Clean up our side effects
del sys.modules[modname]
@@ -245,36 +247,36 @@ class CoverageTest(TestCase):
cov.report(mod, file=frep)
rep = " ".join(frep.getvalue().split("\n")[2].split()[1:])
self.assertEqual(report, rep)
-
+
def nice_file(self, *fparts):
"""Canonicalize the filename composed of the parts in `fparts`."""
fname = os.path.join(*fparts)
return os.path.normcase(os.path.abspath(os.path.realpath(fname)))
-
+
def command_line(self, args, ret=OK):
"""Run `args` through the command line.
-
+
Use this when you want to run the full coverage machinery, but in the
current process. Exceptions may be thrown from deep in the code.
Asserts that `ret` is returned by `CoverageScript.command_line`.
-
+
Compare with `run_command`.
-
+
Returns None.
-
+
"""
ret_actual = coverage.CoverageScript().command_line(shlex.split(args))
self.assertEqual(ret_actual, ret)
-
+
def run_command(self, cmd):
""" Run the command-line `cmd` in a subprocess, and print its output.
-
+
Use this when you need to test the process behavior of coverage.
-
+
Compare with `command_line`.
-
+
Returns the process' stdout text.
-
+
"""
# Add our test modules directory to PYTHONPATH. I'm sure there's too
# much path munging here, but...
@@ -286,7 +288,7 @@ class CoverageTest(TestCase):
pypath += os.pathsep
pypath += testmods + os.pathsep + zipfile
self.set_environ('PYTHONPATH', pypath)
-
+
_, output = run_command(cmd)
print(output)
return output
diff --git a/test/farm/html/gold_b_branch/b.html b/test/farm/html/gold_b_branch/b.html
index 270d596e..fed24838 100644
--- a/test/farm/html/gold_b_branch/b.html
+++ b/test/farm/html/gold_b_branch/b.html
@@ -24,15 +24,15 @@ function toggle_lines(btn, cls) {
<div id='header'>
<div class='content'>
<h1>Coverage for <b>b</b> :
- <span class='pc_cov'>77%</span>
+ <span class='pc_cov'>76%</span>
</h1>
<h2 class='stats'>
- 9 statements
- <span class='run hide_run' onclick='toggle_lines(this, "run")'>8 run</span>
+ 16 statements
+ <span class='run hide_run' onclick='toggle_lines(this, "run")'>14 run</span>
<span class='exc' onclick='toggle_lines(this, "exc")'>0 excluded</span>
- <span class='mis' onclick='toggle_lines(this, "mis")'>1 missing</span>
+ <span class='mis' onclick='toggle_lines(this, "mis")'>2 missing</span>
- <span class='par run hide_run' onclick='toggle_lines(this, "par")'>2 partial</span>
+ <span class='par run hide_run' onclick='toggle_lines(this, "par")'>3 partial</span>
</h2>
</div>
@@ -45,8 +45,8 @@ function toggle_lines(btn, cls) {
<p class='pln'>1</p>
<p class='pln'>2</p>
<p class='stm run hide_run'>3</p>
-<p class='stm par run hide_run'>4</p>
-<p class='pln'>5</p>
+<p class='pln'>4</p>
+<p class='stm par run hide_run'>5</p>
<p class='stm run hide_run'>6</p>
<p class='pln'>7</p>
<p class='stm mis'>8</p>
@@ -54,18 +54,30 @@ function toggle_lines(btn, cls) {
<p class='stm run hide_run'>10</p>
<p class='pln'>11</p>
<p class='stm run hide_run'>12</p>
-<p class='stm par run hide_run'>13</p>
-<p class='stm run hide_run'>14</p>
-<p class='pln'>15</p>
-<p class='stm run hide_run'>16</p>
+<p class='pln'>13</p>
+<p class='stm par run hide_run'>14</p>
+<p class='stm run hide_run'>15</p>
+<p class='pln'>16</p>
+<p class='stm run hide_run'>17</p>
+<p class='pln'>18</p>
+<p class='stm run hide_run'>19</p>
+<p class='pln'>20</p>
+<p class='stm par run hide_run'>21</p>
+<p class='stm run hide_run'>22</p>
+<p class='stm run hide_run'>23</p>
+<p class='pln'>24</p>
+<p class='stm mis'>25</p>
+<p class='stm run hide_run'>26</p>
+<p class='pln'>27</p>
+<p class='stm run hide_run'>28</p>
</td>
<td class='text' valign='top'>
<p class='pln'><span class='com'># A test file for HTML reporting by coverage.</span><span class='strut'>&nbsp;</span></p>
<p class='pln'><span class='strut'>&nbsp;</span></p>
<p class='stm run hide_run'><span class='key'>def</span> <span class='nam'>one</span><span class='op'>(</span><span class='nam'>x</span><span class='op'>)</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
-<p class='stm par run hide_run'><span class='annotate'>8</span>&nbsp; &nbsp; <span class='key'>if</span> <span class='nam'>x</span> <span class='op'>&lt;</span> <span class='num'>2</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
-<p class='pln'>&nbsp; &nbsp; &nbsp; &nbsp; <span class='com'># Needed a &lt; to look at HTML entities.</span><span class='strut'>&nbsp;</span></p>
+<p class='pln'>&nbsp; &nbsp; <span class='com'># This will be a branch that misses the else.</span><span class='strut'>&nbsp;</span></p>
+<p class='stm par run hide_run'><span class='annotate' title='no jump to this line number'>8</span>&nbsp; &nbsp; <span class='key'>if</span> <span class='nam'>x</span> <span class='op'>&lt;</span> <span class='num'>2</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
<p class='stm run hide_run'>&nbsp; &nbsp; &nbsp; &nbsp; <span class='nam'>a</span> <span class='op'>=</span> <span class='num'>3</span><span class='strut'>&nbsp;</span></p>
<p class='pln'>&nbsp; &nbsp; <span class='key'>else</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
<p class='stm mis'>&nbsp; &nbsp; &nbsp; &nbsp; <span class='nam'>a</span> <span class='op'>=</span> <span class='num'>4</span><span class='strut'>&nbsp;</span></p>
@@ -73,10 +85,22 @@ function toggle_lines(btn, cls) {
<p class='stm run hide_run'><span class='nam'>one</span><span class='op'>(</span><span class='num'>1</span><span class='op'>)</span><span class='strut'>&nbsp;</span></p>
<p class='pln'><span class='strut'>&nbsp;</span></p>
<p class='stm run hide_run'><span class='key'>def</span> <span class='nam'>two</span><span class='op'>(</span><span class='nam'>x</span><span class='op'>)</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
-<p class='stm par run hide_run'><span class='annotate'>exit</span>&nbsp; &nbsp; <span class='key'>if</span> <span class='nam'>x</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
+<p class='pln'>&nbsp; &nbsp; <span class='com'># A missed else that branches to &quot;exit&quot;</span><span class='strut'>&nbsp;</span></p>
+<p class='stm par run hide_run'><span class='annotate' title='no jump to this line number'>exit</span>&nbsp; &nbsp; <span class='key'>if</span> <span class='nam'>x</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
<p class='stm run hide_run'>&nbsp; &nbsp; &nbsp; &nbsp; <span class='nam'>a</span> <span class='op'>=</span> <span class='num'>5</span><span class='strut'>&nbsp;</span></p>
<p class='pln'><span class='strut'>&nbsp;</span></p>
<p class='stm run hide_run'><span class='nam'>two</span><span class='op'>(</span><span class='num'>1</span><span class='op'>)</span><span class='strut'>&nbsp;</span></p>
+<p class='pln'><span class='strut'>&nbsp;</span></p>
+<p class='stm run hide_run'><span class='key'>def</span> <span class='nam'>three_way</span><span class='op'>(</span><span class='op'>)</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
+<p class='pln'>&nbsp; &nbsp; <span class='com'># for-else can be a three-way branch.</span><span class='strut'>&nbsp;</span></p>
+<p class='stm par run hide_run'><span class='annotate' title='no jumps to these line numbers'>25&nbsp;&nbsp; 26</span>&nbsp; &nbsp; <span class='key'>for</span> <span class='nam'>i</span> <span class='key'>in</span> <span class='nam'>range</span><span class='op'>(</span><span class='num'>10</span><span class='op'>)</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
+<p class='stm run hide_run'>&nbsp; &nbsp; &nbsp; &nbsp; <span class='key'>if</span> <span class='nam'>i</span> <span class='op'>==</span> <span class='num'>3</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
+<p class='stm run hide_run'>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class='key'>break</span><span class='strut'>&nbsp;</span></p>
+<p class='pln'>&nbsp; &nbsp; <span class='key'>else</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
+<p class='stm mis'>&nbsp; &nbsp; &nbsp; &nbsp; <span class='key'>return</span> <span class='num'>23</span><span class='strut'>&nbsp;</span></p>
+<p class='stm run hide_run'>&nbsp; &nbsp; <span class='key'>return</span> <span class='num'>17</span><span class='strut'>&nbsp;</span></p>
+<p class='pln'><span class='strut'>&nbsp;</span></p>
+<p class='stm run hide_run'><span class='nam'>three_way</span><span class='op'>(</span><span class='op'>)</span><span class='strut'>&nbsp;</span></p>
</td>
</tr>
diff --git a/test/farm/html/run_b_branch.py b/test/farm/html/run_b_branch.py
index 8d24df2c..f129e436 100644
--- a/test/farm/html/run_b_branch.py
+++ b/test/farm/html/run_b_branch.py
@@ -15,13 +15,14 @@ compare("gold_b_branch", "html_b_branch", size_within=10, file_pattern="*.html")
contains("html_b_branch/b.html",
"<span class='key'>if</span> <span class='nam'>x</span> <span class='op'>&lt;</span> <span class='num'>2</span>",
"&nbsp; &nbsp; <span class='nam'>a</span> <span class='op'>=</span> <span class='num'>3</span>",
- "<span class='pc_cov'>77%</span>",
- "<span class='annotate'>8</span>",
- "<span class='annotate'>exit</span>"
+ "<span class='pc_cov'>76%</span>",
+ "<span class='annotate' title='no jump to this line number'>8</span>",
+ "<span class='annotate' title='no jump to this line number'>exit</span>",
+ "<span class='annotate' title='no jumps to these line numbers'>25&nbsp;&nbsp; 26</span>",
)
contains("html_b_branch/index.html",
"<a href='b.html'>b</a>",
- "<span class='pc_cov'>77%</span>"
+ "<span class='pc_cov'>76%</span>"
)
clean("html_b_branch")
diff --git a/test/farm/html/src/b.py b/test/farm/html/src/b.py
index e8752300..f5d051c6 100644
--- a/test/farm/html/src/b.py
+++ b/test/farm/html/src/b.py
@@ -1,8 +1,8 @@
# A test file for HTML reporting by coverage.
def one(x):
+ # This will be a branch that misses the else.
if x < 2:
- # Needed a < to look at HTML entities.
a = 3
else:
a = 4
@@ -10,7 +10,19 @@ def one(x):
one(1)
def two(x):
+ # A missed else that branches to "exit"
if x:
a = 5
two(1)
+
+def three_way():
+ # for-else can be a three-way branch.
+ for i in range(10):
+ if i == 3:
+ break
+ else:
+ return 23
+ return 17
+
+three_way()
diff --git a/test/osinfo.py b/test/osinfo.py
index 7a82610a..8bed2afd 100644
--- a/test/osinfo.py
+++ b/test/osinfo.py
@@ -23,7 +23,7 @@ if sys.hexversion >= 0x02050000 and sys.platform == 'win32':
('PeakPagefileUsage', ctypes.c_size_t),
('PrivateUsage', ctypes.c_size_t),
]
-
+
mem_struct = PROCESS_MEMORY_COUNTERS_EX()
ret = ctypes.windll.psapi.GetProcessMemoryInfo(
ctypes.windll.kernel32.GetCurrentProcess(),
@@ -37,9 +37,9 @@ if sys.hexversion >= 0x02050000 and sys.platform == 'win32':
elif sys.platform == 'linux2':
# Linux implementation
import os
-
+
_scale = {'kb': 1024, 'mb': 1024*1024}
-
+
def _VmB(key):
"""Read the /proc/PID/status file to find memory use."""
try:
diff --git a/test/test_api.py b/test/test_api.py
index 8470a661..0df2df4a 100644
--- a/test/test_api.py
+++ b/test/test_api.py
@@ -8,6 +8,9 @@ from coverage.backward import StringIO
sys.path.insert(0, os.path.split(__file__)[0]) # Force relative import for Py3k
from coveragetest import CoverageTest
+# This file uses the singleton module interface. Prevent it from writing
+# .coverage files at exit.
+coverage.use_cache(0)
class ApiTest(CoverageTest):
"""Api-oriented tests for Coverage."""
@@ -22,17 +25,17 @@ class ApiTest(CoverageTest):
c = 4
d = 5
""")
-
+
# Import the python file, executing it.
coverage.start()
self.import_module("mycode")
coverage.stop()
-
+
_, statements, missing, missingtext = coverage.analysis("mycode.py")
self.assertEqual(statements, [1,2,3,4,5])
self.assertEqual(missing, [4])
self.assertEqual(missingtext, "4")
-
+
def doReportWork(self, modname):
"""Create a module named `modname`, then measure it."""
coverage.erase()
@@ -46,12 +49,12 @@ class ApiTest(CoverageTest):
e = 6
f = 7
""")
-
+
# Import the python file, executing it.
coverage.start()
self.import_module(modname)
coverage.stop()
-
+
def testReport(self):
self.doReportWork("mycode2")
coverage.report(["mycode2.py"])
@@ -60,7 +63,7 @@ class ApiTest(CoverageTest):
---------------------------------------
mycode2 7 4 57% 4-6
"""))
-
+
def testReportFile(self):
# The file= argument of coverage.report makes the report go there.
self.doReportWork("mycode3")
@@ -90,16 +93,16 @@ class ApiTest(CoverageTest):
c = 4
d = 5
""")
-
+
self.make_file("not_run.py", """\
fooey = 17
""")
-
+
# Import the python file, executing it.
cov.start()
self.import_module("mycode")
cov.stop()
-
+
_, statements, missing, _ = cov.analysis("not_run.py")
self.assertEqual(statements, [1])
self.assertEqual(missing, [1])
@@ -110,22 +113,22 @@ class ApiTest(CoverageTest):
import mymod
a = 1
""")
-
+
self.make_file("mymod.py", """\
fooey = 17
""")
-
+
# Import the python file, executing it.
cov = coverage.coverage()
cov.start()
self.import_module("mymain")
cov.stop()
-
+
filename, _, _, _ = cov.analysis("mymain.py")
self.assertEqual(os.path.basename(filename), "mymain.py")
filename, _, _, _ = cov.analysis("mymod.py")
self.assertEqual(os.path.basename(filename), "mymod.py")
-
+
filename, _, _, _ = cov.analysis(sys.modules["mymain"])
self.assertEqual(os.path.basename(filename), "mymain.py")
filename, _, _, _ = cov.analysis(sys.modules["mymod"])
@@ -137,12 +140,12 @@ class ApiTest(CoverageTest):
cov.start()
self.import_module("mymain")
cov.stop()
-
+
filename, _, _, _ = cov.analysis("mymain.py")
self.assertEqual(os.path.basename(filename), "mymain.py")
filename, _, _, _ = cov.analysis("mymod.py")
self.assertEqual(os.path.basename(filename), "mymod.py")
-
+
filename, _, _, _ = cov.analysis(sys.modules["mymain"])
self.assertEqual(os.path.basename(filename), "mymain.py")
filename, _, _, _ = cov.analysis(sys.modules["mymod"])
@@ -154,7 +157,7 @@ class ApiTest(CoverageTest):
a = 1
hls = colorsys.rgb_to_hls(1.0, 0.5, 0.0)
""")
-
+
self.make_file("mymod.py", """\
fooey = 17
""")
diff --git a/test/test_arcs.py b/test/test_arcs.py
index 47a69987..45ab27e8 100644
--- a/test/test_arcs.py
+++ b/test/test_arcs.py
@@ -25,7 +25,7 @@ class SimpleArcTest(CoverageTest):
a = 2
b = 3
-
+
c = 5
""",
arcz=".2 23 35 5.")
@@ -34,7 +34,7 @@ class SimpleArcTest(CoverageTest):
self.check_coverage("""\
def foo():
a = 2
-
+
foo()
""",
arcz=".1 .2 14 2. 4.")
@@ -54,7 +54,7 @@ class SimpleArcTest(CoverageTest):
assert a == 1
""",
arcz=".1 12 23 24 34 4.", arcz_missing="23 34")
-
+
def test_if_else(self):
self.check_coverage("""\
if len([]) == 0:
@@ -139,7 +139,7 @@ class SimpleArcTest(CoverageTest):
class LoopArcTest(CoverageTest):
"""Arc-measuring tests involving loops."""
-
+
def test_loop(self):
self.check_coverage("""\
for i in range(10):
diff --git a/test/test_cmdline.py b/test/test_cmdline.py
index 20c9c5d4..258a08ac 100644
--- a/test/test_cmdline.py
+++ b/test/test_cmdline.py
@@ -10,7 +10,7 @@ from coveragetest import CoverageTest, OK, ERR
class CmdLineTest(CoverageTest):
"""Tests of execution paths through the command line interpreter."""
-
+
run_in_temp_dir = False
INIT_LOAD = """\
@@ -25,16 +25,16 @@ class CmdLineTest(CoverageTest):
def mock_command_line(self, args):
"""Run `args` through the command line, with a Mock.
-
+
Returns the Mock it used and the status code returned.
-
+
"""
m = self.model_object()
ret = coverage.CoverageScript(
_covpkg=m, _run_python_file=m.run_python_file, _help_fn=m.help_fn
).command_line(shlex.split(args))
return m, ret
-
+
def cmd_executes(self, args, code, ret=OK):
"""Assert that the `args` end up executing the sequence in `code`."""
m1, r1 = self.mock_command_line(args)
@@ -48,7 +48,7 @@ class CmdLineTest(CoverageTest):
code_obj = compile(code, "<code>", "exec")
eval(code_obj, globals(), { 'm2': m2 })
self.assertEqual(m1.method_calls, m2.method_calls)
-
+
def cmd_executes_same(self, args1, args2):
"""Assert that the `args1` executes the same as `args2`."""
m1, r1 = self.mock_command_line(args1)
@@ -58,10 +58,10 @@ class CmdLineTest(CoverageTest):
def cmd_help(self, args, help_msg=None, topic=None, ret=ERR):
"""Run a command line, and check that it prints the right help.
-
+
Only the last function call in the mock is checked, which should be the
help message that we want to see.
-
+
"""
m, r = self.mock_command_line(args)
self.assertEqual(r, ret,
@@ -75,7 +75,7 @@ class CmdLineTest(CoverageTest):
self.assertEqual(m.method_calls[-1],
('help_fn', (), {'topic':topic})
)
-
+
class ClassicCmdLineTest(CmdLineTest):
"""Tests of the classic coverage.py command line."""
@@ -90,7 +90,7 @@ class ClassicCmdLineTest(CmdLineTest):
def testExecute(self):
# coverage -x [-p] [-L] [--timid] MODULE.py [ARG1 ARG2 ...]
-
+
# -x calls coverage.load first.
self.cmd_executes("-x foo.py", """\
.coverage(cover_pylib=None, data_suffix=False, timid=None, branch=None)
@@ -270,8 +270,12 @@ class ClassicCmdLineTest(CmdLineTest):
self.cmd_help("-h", topic="help", ret=OK)
self.cmd_help("--help", topic="help", ret=OK)
+ def testVersion(self):
+ # coverage --version
+ self.cmd_help("--version", topic="version", ret=OK)
+
## Error cases
-
+
def testArglessActions(self):
self.cmd_help("-e foo bar", "Unexpected arguments: foo bar")
self.cmd_help("-c baz quux", "Unexpected arguments: baz quux")
@@ -345,7 +349,7 @@ class NewCmdLineTest(CmdLineTest):
out = self.stdout()
assert "version:" in out
assert "data_path:" in out
-
+
def testErase(self):
self.cmd_executes_same("erase", "-e")
@@ -383,7 +387,7 @@ class NewCmdLineTest(CmdLineTest):
self.cmd_executes_same("run -L f.py", "-e -x -L f.py")
self.cmd_executes_same("run --timid f.py", "-e -x --timid f.py")
self.cmd_executes_same("run", "-x")
-
+
def testXml(self):
# coverage xml [-i] [--omit DIR,...] [FILE1 FILE2 ...]
self.cmd_executes("xml", self.INIT_LOAD + """\
@@ -428,19 +432,19 @@ class NewCmdLineTest(CmdLineTest):
class CmdLineStdoutTest(CmdLineTest):
"""Test the command line with real stdout output."""
-
+
def testMinimumHelp(self):
self.command_line("")
out = self.stdout()
assert "Code coverage for Python." in out
assert out.count("\n") < 4
-
+
def testHelp(self):
self.command_line("help")
out = self.stdout()
assert "nedbatchelder.com" in out
assert out.count("\n") > 10
-
+
def testCmdHelp(self):
self.command_line("help run")
out = self.stdout()
diff --git a/test/test_codeunit.py b/test/test_codeunit.py
index 96df9633..ad073827 100644
--- a/test/test_codeunit.py
+++ b/test/test_codeunit.py
@@ -21,7 +21,7 @@ class CodeUnitTest(CoverageTest):
# Parent class saves and restores sys.path, we can just modify it.
testmods = self.nice_file(os.path.dirname(__file__), 'modules')
sys.path.append(testmods)
-
+
def test_filenames(self):
acu = code_unit_factory("aa/afile.py", FileLocator())
bcu = code_unit_factory("aa/bb/bfile.py", FileLocator())
@@ -81,4 +81,3 @@ class CodeUnitTest(CoverageTest):
self.assertEqual(cu[1].source_file().read().split("\n")[0],
"# My egg file!"
)
- \ No newline at end of file
diff --git a/test/test_coverage.py b/test/test_coverage.py
index 23990ccd..c1a72480 100644
--- a/test/test_coverage.py
+++ b/test/test_coverage.py
@@ -15,18 +15,18 @@ from coveragetest import CoverageTest
class BasicCoverageTest(CoverageTest):
"""The simplest tests, for quick smoke testing of fundamental changes."""
-
+
def testSimple(self):
self.check_coverage("""\
a = 1
b = 2
-
+
c = 4
# Nothing here
d = 6
""",
[1,2,4,6], report="4 4 100%")
-
+
def testIndentationWackiness(self):
# Partial final lines are OK.
self.check_coverage("""\
@@ -57,11 +57,11 @@ class BasicCoverageTest(CoverageTest):
assert l == [12, 14, 16, 18]
""",
[1,5], "")
-
+
class SimpleStatementTest(CoverageTest):
"""Testing simple single-line statements."""
-
+
def testExpression(self):
self.check_coverage("""\
1 + 2
@@ -73,7 +73,7 @@ class SimpleStatementTest(CoverageTest):
def testAssert(self):
self.check_coverage("""\
assert (1 + 2)
- assert (1 +
+ assert (1 +
2)
assert (1 + 2), 'the universe is broken'
assert (1 +
@@ -100,7 +100,7 @@ class SimpleStatementTest(CoverageTest):
assert a == 7 and b == 8 and c == 9
""",
[1,2,3], "")
-
+
def testAttributeAssignment(self):
# Attribute assignment
self.check_coverage("""\
@@ -113,7 +113,7 @@ class SimpleStatementTest(CoverageTest):
1
""",
[1,2,3,4,6], "")
-
+
def testListofAttributeAssignment(self):
self.check_coverage("""\
class obj: pass
@@ -127,7 +127,7 @@ class SimpleStatementTest(CoverageTest):
2
""",
[1,2,3,4,7], "")
-
+
def testAugmentedAssignment(self):
self.check_coverage("""\
a = 1
@@ -153,7 +153,7 @@ class SimpleStatementTest(CoverageTest):
'''
c = len('''
long expression
- ''' +
+ ''' +
'''
on many
lines.
@@ -197,7 +197,7 @@ class SimpleStatementTest(CoverageTest):
Foo().foo()
""",
([1,2,4,5], [1,2,5]), "")
-
+
def testDel(self):
self.check_coverage("""\
d = { 'a': 1, 'b': 1, 'c': 1, 'd': 1, 'e': 1 }
@@ -225,7 +225,7 @@ class SimpleStatementTest(CoverageTest):
print "goodbye",
""",
[1,2,4,5,6,8], "")
-
+
def testRaise(self):
self.check_coverage("""\
try:
@@ -253,7 +253,7 @@ class SimpleStatementTest(CoverageTest):
return (
a +
1)
-
+
x = fn()
assert(x == 2)
""",
@@ -264,7 +264,7 @@ class SimpleStatementTest(CoverageTest):
return (a,
a + 1,
a + 2)
-
+
x,y,z = fn()
assert x == 1 and y == 2 and z == 3
""",
@@ -284,7 +284,7 @@ class SimpleStatementTest(CoverageTest):
assert a == 1 and b == 9 and c == (1,2)
""",
[1,2,3,4,7,9,10], "")
-
+
def testBreak(self):
self.check_coverage("""\
for x in range(10):
@@ -294,7 +294,7 @@ class SimpleStatementTest(CoverageTest):
assert a == 2
""",
[1,2,3,4,5], "4")
-
+
def testContinue(self):
self.check_coverage("""\
for x in range(10):
@@ -304,7 +304,7 @@ class SimpleStatementTest(CoverageTest):
assert a == 11
""",
[1,2,3,4,5], "4")
-
+
if 0:
# Peephole optimization of jumps to jumps can mean that some statements
# never hit the line tracer. The behavior is different in different
@@ -321,7 +321,7 @@ class SimpleStatementTest(CoverageTest):
b += 1
c += 1
assert a == 50 and b == 50 and c == 50
-
+
a = b = c = 0
for n in range(100):
if n % 2:
@@ -334,7 +334,7 @@ class SimpleStatementTest(CoverageTest):
assert a == 33 and b == 50 and c == 50
""",
[1,2,3,4,5,6,8,9,10, 12,13,14,15,16,17,19,20,21], "")
-
+
def testImport(self):
self.check_coverage("""\
import string
@@ -381,7 +381,7 @@ class SimpleStatementTest(CoverageTest):
assert len(path) > 0
""",
[1,3], "")
-
+
def testGlobal(self):
self.check_coverage("""\
g = h = i = 1
@@ -488,24 +488,24 @@ class SimpleStatementTest(CoverageTest):
class CompoundStatementTest(CoverageTest):
"""Testing coverage of multi-line compound statements."""
-
+
def testStatementList(self):
self.check_coverage("""\
a = 1;
b = 2; c = 3
d = 4; e = 5;
-
+
assert (a,b,c,d,e) == (1,2,3,4,5)
""",
[1,2,3,5], "")
-
+
def testIf(self):
self.check_coverage("""\
a = 1
if a == 1:
x = 3
assert x == 3
- if (a ==
+ if (a ==
1):
x = 7
assert x == 7
@@ -541,7 +541,7 @@ class CompoundStatementTest(CoverageTest):
assert x == 4
""",
[1,2,3,4,6,8,9], "6-8")
-
+
def testElif(self):
self.check_coverage("""\
a = 1; b = 2; c = 3;
@@ -655,7 +655,7 @@ class CompoundStatementTest(CoverageTest):
assert z == 7
""",
[1,2,4,5,7,9,10], "4, 7")
-
+
def testPathologicalSplitIf(self):
self.check_coverage("""\
a = 1; b = 2; c = 3;
@@ -702,7 +702,7 @@ class CompoundStatementTest(CoverageTest):
assert z == 7
""",
[1,2,5,6,9,11,12], "5, 9")
-
+
def testAbsurdSplitIf(self):
self.check_coverage("""\
a = 1; b = 2; c = 3;
@@ -788,7 +788,7 @@ class CompoundStatementTest(CoverageTest):
assert a == 2 and b == 1
""",
[1,2,3,4,5,6,8,9], "6-8")
-
+
def testSplitWhile(self):
self.check_coverage("""\
a = 3; b = 0
@@ -836,7 +836,7 @@ class CompoundStatementTest(CoverageTest):
assert a == 1
""",
[1,2,3,4,5,6], "5")
-
+
def testForElse(self):
self.check_coverage("""\
a = 0
@@ -858,7 +858,7 @@ class CompoundStatementTest(CoverageTest):
assert a == 1
""",
[1,2,3,4,5,7,8], "5-7")
-
+
def testSplitFor(self):
self.check_coverage("""\
a = 0
@@ -878,7 +878,7 @@ class CompoundStatementTest(CoverageTest):
assert a == 15
""",
[1,2,6,7], "")
-
+
def testTryExcept(self):
self.check_coverage("""\
a = 0
@@ -948,7 +948,7 @@ class CompoundStatementTest(CoverageTest):
assert a == 99
""",
[1,2,3,4,5,6,8,9], "8")
-
+
def testTryFinally(self):
self.check_coverage("""\
a = 0
@@ -980,7 +980,7 @@ class CompoundStatementTest(CoverageTest):
''' docstring
'''
return 1
-
+
a = foo()
assert a == 1
""",
@@ -993,7 +993,7 @@ class CompoundStatementTest(CoverageTest):
''' docstring
'''
return a+b
-
+
x = foo(17, 23)
assert x == 40
""",
@@ -1009,7 +1009,7 @@ class CompoundStatementTest(CoverageTest):
''' docstring
'''
return a+b
-
+
x = foo()
assert x == 22
""",
@@ -1025,19 +1025,19 @@ class CompoundStatementTest(CoverageTest):
def __init__(self):
''' Another docstring. '''
self.a = 1
-
+
def foo(self):
return self.a
-
+
x = theClass().foo()
assert x == 1
""",
- [2,6,8,10,11,13,14], "")
+ [2,6,8,10,11,13,14], "")
class ExcludeTest(CoverageTest):
"""Tests of the exclusion feature to mark lines as not covered."""
-
+
def testSimple(self):
self.check_coverage("""\
a = 1; b = 2
@@ -1058,7 +1058,7 @@ class ExcludeTest(CoverageTest):
assert a == 1 and b == 2
""",
[1,3,5,7], "5", ['-cc', '-xx'])
-
+
def testExcludingIfSuite(self):
self.check_coverage("""\
a = 1; b = 2
@@ -1085,7 +1085,7 @@ class ExcludeTest(CoverageTest):
assert a == 8 and b == 9
""",
[1,8,9,10], "", ['if 0:'])
-
+
def testExcludingElseSuite(self):
self.check_coverage("""\
a = 1; b = 2
@@ -1107,14 +1107,14 @@ class ExcludeTest(CoverageTest):
a = 4
b = 5
c = 6
-
+
# Lots of comments to confuse the else handler.
# more.
-
+
else: #pragma: NO COVER
# Comments here too.
-
+
a = 8
b = 9
assert a == 4 and b == 5 and c == 6
@@ -1132,7 +1132,7 @@ class ExcludeTest(CoverageTest):
elif 1==0: #pragma: NO COVER
a = 8
b = 9
- else:
+ else:
a = 11
b = 12
assert a == 4 and b == 5 and c == 6
@@ -1145,7 +1145,7 @@ class ExcludeTest(CoverageTest):
a = 2
if 0: x = 3 # no cover
b = 4
-
+
foo()
""",
[1,2,4,6], "", ["no cover"])
@@ -1156,11 +1156,11 @@ class ExcludeTest(CoverageTest):
l = list(range(10))
a = l[:3] # no cover
b = 4
-
+
foo()
""",
[1,2,4,6], "", ["no cover"])
-
+
def testExcludingForSuite(self):
self.check_coverage("""\
a = 0
@@ -1188,7 +1188,7 @@ class ExcludeTest(CoverageTest):
assert a == 1
""",
[1,7], "", ['#pragma: NO COVER'])
-
+
def testExcludingForElse(self):
self.check_coverage("""\
a = 0
@@ -1201,7 +1201,7 @@ class ExcludeTest(CoverageTest):
assert a == 1
""",
[1,2,3,4,5,8], "5", ['#pragma: NO COVER'])
-
+
def testExcludingWhile(self):
self.check_coverage("""\
a = 3; b = 0
@@ -1292,7 +1292,7 @@ class ExcludeTest(CoverageTest):
assert a == 99
""",
[1,2,3,4,5,6,9], "", ['#pragma: NO COVER'])
-
+
def testExcludingTryExceptPass(self):
self.check_coverage("""\
a = 0
@@ -1338,7 +1338,7 @@ class ExcludeTest(CoverageTest):
assert a == 99
""",
[1,2,3,4,5,6,9], "", ['#pragma: NO COVER'])
-
+
def testExcludingIfPass(self):
# From a comment on the coverage page by Michael McNeil Forbes:
self.check_coverage("""\
@@ -1347,18 +1347,18 @@ class ExcludeTest(CoverageTest):
pass # This line still reported as missing
if False: # pragma: no cover
x = 1 # Now it is skipped.
-
+
f()
""",
[1,7], "", ["no cover"])
-
+
def testExcludingFunction(self):
self.check_coverage("""\
def fn(foo): #pragma: NO COVER
a = 1
b = 2
c = 3
-
+
x = 1
assert x == 1
""",
@@ -1369,24 +1369,24 @@ class ExcludeTest(CoverageTest):
class Fooey:
def __init__(self):
self.a = 1
-
+
def foo(self): #pragma: NO COVER
return self.a
-
+
x = Fooey()
assert x.a == 1
""",
[1,2,3,8,9], "", ['#pragma: NO COVER'])
-
+
def testExcludingClass(self):
self.check_coverage("""\
class Fooey: #pragma: NO COVER
def __init__(self):
self.a = 1
-
+
def foo(self):
return self.a
-
+
x = 1
assert x == 1
""",
@@ -1396,20 +1396,20 @@ class ExcludeTest(CoverageTest):
if sys.hexversion >= 0x020400f0:
class Py24Test(CoverageTest):
"""Tests of new syntax in Python 2.4."""
-
+
def testFunctionDecorators(self):
self.check_coverage("""\
def require_int(func):
def wrapper(arg):
assert isinstance(arg, int)
return func(arg)
-
+
return wrapper
-
+
@require_int
def p1(arg):
return arg*2
-
+
assert p1(10) == 20
""",
[1,2,3,4,6,8,10,12], "")
@@ -1422,11 +1422,11 @@ if sys.hexversion >= 0x020400f0:
return extra*func(arg)
return wrapper
return decorator
-
+
@boost_by(10)
def boosted(arg):
return arg*2
-
+
assert boosted(10) == 200
""",
[1,2,3,4,5,6,8,10,12], "")
@@ -1445,19 +1445,19 @@ if sys.hexversion >= 0x020400f0:
return extra*func(arg)
return wrapper
return decorator
-
+
@require_int
@boost_by(10)
def boosted1(arg):
return arg*2
-
+
assert boosted1(10) == 200
@boost_by(10)
@require_int
def boosted2(arg):
return arg*2
-
+
assert boosted2(10) == 200
""",
([1,2,3,4,5,7,8,9,10,11,12,14,15,17,19,21,22,24,26],
@@ -1471,19 +1471,19 @@ if sys.hexversion >= 0x020500f0:
def testWithStatement(self):
self.check_coverage("""\
from __future__ import with_statement
-
+
class Managed:
def __enter__(self):
desc = "enter"
-
+
def __exit__(self, type, value, tb):
desc = "exit"
-
+
m = Managed()
with m:
desc = "block1a"
desc = "block1b"
-
+
try:
with m:
desc = "block2"
@@ -1492,7 +1492,7 @@ if sys.hexversion >= 0x020500f0:
desc = "caught"
""",
[1,3,4,5,7,8,10,11,12,13,15,16,17,18,19,20], "")
-
+
def testTryExceptFinally(self):
self.check_coverage("""\
a = 0; b = 0
@@ -1574,7 +1574,7 @@ if sys.hexversion >= 0x020500f0:
assert a == 99 and b == 2
""",
[1,2,3,4,5,6,8,10,11], "8")
-
+
class ModuleTest(CoverageTest):
"""Tests for the module-level behavior of the `coverage` module."""
@@ -1593,7 +1593,7 @@ class ProcessTest(CoverageTest):
h = "Hello"
w = "world"
""")
-
+
self.assert_(not os.path.exists(".coverage"))
self.run_command("coverage -x mycode.py")
self.assert_(os.path.exists(".coverage"))
@@ -1611,7 +1611,7 @@ class ProcessTest(CoverageTest):
out = self.run_command("coverage -x mycode.py")
self.assert_(os.path.exists(".coverage"))
self.assertEqual(out, 'done\n')
-
+
def testCombineParallelData(self):
self.make_file("b_or_c.py", """\
import sys
@@ -1623,7 +1623,7 @@ class ProcessTest(CoverageTest):
d = 1
print ('done')
""")
-
+
out = self.run_command("coverage -x -p b_or_c.py b")
self.assertEqual(out, 'done\n')
self.assert_(not os.path.exists(".coverage"))
@@ -1631,7 +1631,7 @@ class ProcessTest(CoverageTest):
out = self.run_command("coverage -x -p b_or_c.py c")
self.assertEqual(out, 'done\n')
self.assert_(not os.path.exists(".coverage"))
-
+
# After two -p runs, there should be two .coverage.machine.123 files.
self.assertEqual(
len([f for f in os.listdir('.') if f.startswith('.coverage.')]),
diff --git a/test/test_data.py b/test/test_data.py
index 98acc2bb..4f784253 100644
--- a/test/test_data.py
+++ b/test/test_data.py
@@ -29,11 +29,11 @@ class DataTest(CoverageTest):
def assert_summary(self, covdata, summary):
"""Check that the summary of `covdata` is `summary`."""
self.assertEqual(covdata.summary(), summary)
-
+
def assert_executed_files(self, covdata, execed):
"""Check that `covdata`'s executed files are `execed`."""
self.assertSameElements(covdata.executed_files(), execed)
-
+
def test_reading_empty(self):
covdata = CoverageData()
covdata.read()
@@ -44,12 +44,12 @@ class DataTest(CoverageTest):
covdata.add_line_data(DATA_1)
self.assert_summary(covdata, SUMMARY_1)
self.assert_executed_files(covdata, EXECED_FILES_1)
-
+
def test_writing_and_reading(self):
covdata1 = CoverageData()
covdata1.add_line_data(DATA_1)
covdata1.write()
-
+
covdata2 = CoverageData()
covdata2.read()
self.assert_summary(covdata2, SUMMARY_1)
@@ -58,11 +58,11 @@ class DataTest(CoverageTest):
covdata1 = CoverageData(suffix='1')
covdata1.add_line_data(DATA_1)
covdata1.write()
-
+
covdata2 = CoverageData(suffix='2')
covdata2.add_line_data(DATA_2)
covdata2.write()
-
+
covdata3 = CoverageData()
covdata3.combine_parallel_data()
self.assert_summary(covdata3, SUMMARY_1_2)
@@ -74,7 +74,7 @@ class DataTest(CoverageTest):
covdata1.write()
covdata1.erase()
self.assert_summary(covdata1, {})
-
+
covdata2 = CoverageData()
covdata2.read()
self.assert_summary(covdata2, {})
@@ -84,13 +84,13 @@ class DataTest(CoverageTest):
covdata = CoverageData()
covdata.add_line_data(DATA_1)
covdata.write()
-
+
fdata = open(".coverage", 'rb')
try:
data = pickle.load(fdata)
finally:
fdata.close()
-
+
lines = data['lines']
self.assertSameElements(lines.keys(), EXECED_FILES_1)
self.assertSameElements(lines['a.py'], A_PY_LINES_1)
@@ -103,13 +103,13 @@ class DataTest(CoverageTest):
covdata = CoverageData()
covdata.add_arc_data(ARC_DATA_3)
covdata.write()
-
+
fdata = open(".coverage", 'rb')
try:
data = pickle.load(fdata)
finally:
fdata.close()
-
+
self.assertSameElements(data['lines'].keys(), [])
arcs = data['arcs']
self.assertSameElements(arcs['x.py'], X_PY_ARCS_3)
diff --git a/test/test_execfile.py b/test/test_execfile.py
index 5e9f4fd5..8c5e9a11 100644
--- a/test/test_execfile.py
+++ b/test/test_execfile.py
@@ -17,7 +17,7 @@ class RunTest(CoverageTest):
tryfile = os.path.join(here, "try_execfile.py")
run_python_file(tryfile, [tryfile, "arg1", "arg2"])
mod_globs = eval(self.stdout())
-
+
# The file should think it is __main__
self.assertEqual(mod_globs['__name__'], "__main__")
@@ -30,10 +30,10 @@ class RunTest(CoverageTest):
"Test file for run_python_file.")
self.assertEqual(mod_globs['DATA'], "xyzzy")
self.assertEqual(mod_globs['FN_VAL'], "my_fn('fooey')")
-
+
# It must be self-importable as __main__.
self.assertEqual(mod_globs['__main__.DATA'], "xyzzy")
-
+
# Argv should have the proper values.
self.assertEqual(mod_globs['argv'], [tryfile, "arg1", "arg2"])
diff --git a/test/test_farm.py b/test/test_farm.py
index a36ae122..8f7d5712 100644
--- a/test/test_farm.py
+++ b/test/test_farm.py
@@ -15,9 +15,9 @@ def test_farm(clean_only=False):
class FarmTestCase(object):
"""A test case from the farm tree.
-
+
Tests are short Python script files, often called run.py:
-
+
copy("src", "out")
run('''
coverage -x white.py
@@ -29,14 +29,14 @@ class FarmTestCase(object):
Verbs (copy, run, compare, clean) are methods in this class. FarmTestCase
has options to allow various uses of the test cases (normal execution,
cleaning-only, or run and leave the results for debugging).
-
+
"""
def __init__(self, runpy, clean_only=False, dont_clean=False):
"""Create a test case from a run.py file.
-
+
`clean_only` means that only the clean() action is executed.
`dont_clean` means that the clean() action is not executed.
-
+
"""
self.description = runpy
self.dir, self.runpy = os.path.split(runpy)
@@ -54,14 +54,14 @@ class FarmTestCase(object):
oldpath = sys.path[:]
sys.path.insert(0, directory)
return oldpath
-
+
def restorepath(self, path):
"""Restore the system path to `path`."""
sys.path = path
def __call__(self):
"""Execute the test from the run.py file.
-
+
"""
cwd = self.cd(self.dir)
@@ -74,13 +74,13 @@ class FarmTestCase(object):
glo = dict([(fn, getattr(self, fn)) for fn in fns])
if self.dont_clean:
glo['clean'] = self.noop
-
+
try:
execfile(self.runpy, glo)
finally:
self.cd(cwd)
- def run_fully(self):
+ def run_fully(self): # pragma: no cover
"""Run as a full test case, with setUp and tearDown."""
self.setUp()
try:
@@ -90,11 +90,11 @@ class FarmTestCase(object):
def fnmatch_list(self, files, file_pattern):
"""Filter the list of `files` to only those that match `file_pattern`.
-
+
If `file_pattern` is None, then return the entire list of files.
-
+
Returns a list of the filtered files.
-
+
"""
if file_pattern:
files = [f for f in files if fnmatch.fnmatch(f, file_pattern)]
@@ -106,7 +106,7 @@ class FarmTestCase(object):
# Modules should be importable from the current directory.
self.old_syspath = sys.path[:]
sys.path.insert(0, '')
-
+
def tearDown(self):
"""Test tear down, run by nose after __call__."""
# Make sure no matter what, the test is cleaned up.
@@ -118,11 +118,11 @@ class FarmTestCase(object):
sys.path = self.old_syspath
# Functions usable inside farm run.py files
-
+
def noop(self, *args, **kwargs):
"""A no-op function to stub out run, copy, etc, when only cleaning."""
pass
-
+
def copy(self, src, dst):
"""Copy a directory."""
@@ -132,11 +132,11 @@ class FarmTestCase(object):
def run(self, cmds, rundir="src", outfile=None):
"""Run a list of commands.
-
+
`cmds` is a string, commands separated by newlines.
`rundir` is the directory in which to run the commands.
`outfile` is a filename to redirect stdout to.
-
+
"""
cwd = self.cd(rundir)
try:
@@ -155,12 +155,12 @@ class FarmTestCase(object):
def runfunc(self, fn, rundir="src", addtopath=None):
"""Run a function.
-
+
`fn` is a callable.
`rundir` is the directory in which to run the function.
-
+
"""
-
+
cwd = self.cd(rundir)
oldpath = self.addtopath(addtopath)
try:
@@ -173,28 +173,28 @@ class FarmTestCase(object):
left_extra=False, right_extra=False, scrubs=None
):
"""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.
-
+
`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
often be different), but the file sizes must be within this amount.
For example, size_within=10 means that the two files' sizes must be
within 10 percent of each other to compare equal.
-
+
`left_extra` true means the left directory can have extra files in it
without triggering an assertion. `right_extra` means the right
directory can.
-
+
`scrubs` is a list of pairs, regex find and replace patterns to use to
scrub the files of unimportant differences.
-
+
An assertion will be raised if the directories fail one of their
matches.
-
+
"""
# Search for a dir2 with a version suffix.
version_suff = ''.join(map(str, sys.version_info[:3]))
@@ -207,12 +207,12 @@ class FarmTestCase(object):
assert os.path.exists(dir1), "Left directory missing: %s" % dir1
assert os.path.exists(dir2), "Right directory missing: %s" % dir2
-
+
dc = filecmp.dircmp(dir1, dir2)
diff_files = self.fnmatch_list(dc.diff_files, file_pattern)
left_only = self.fnmatch_list(dc.left_only, file_pattern)
right_only = self.fnmatch_list(dc.right_only, file_pattern)
-
+
if size_within:
# The files were already compared, use the diff_files list as a
# guide for size comparison.
@@ -253,10 +253,10 @@ class FarmTestCase(object):
def _scrub(self, strlist, scrubs):
"""Scrub uninteresting data from the strings in `strlist`.
-
+
`scrubs is a list of (find, replace) pairs of regexes that are used on
each string in `strlist`. A list of scrubbed strings is returned.
-
+
"""
scrubbed = []
for s in strlist:
@@ -267,10 +267,10 @@ class FarmTestCase(object):
def contains(self, filename, *strlist):
"""Check that the file contains all of a list of strings.
-
+
An assert will be raised if one of the arguments in `strlist` is
missing in `filename`.
-
+
"""
text = open(filename, "r").read()
for s in strlist:
@@ -278,10 +278,10 @@ class FarmTestCase(object):
def doesnt_contain(self, filename, *strlist):
"""Check that the file contains none of a list of strings.
-
+
An assert will be raised if any of the strings in strlist appears in
`filename`.
-
+
"""
text = open(filename, "r").read()
for s in strlist:
@@ -292,15 +292,15 @@ class FarmTestCase(object):
if os.path.exists(cleandir):
shutil.rmtree(cleandir)
-def main():
+def main(): # pragma: no cover
"""Command-line access to test_farm.
-
+
Commands:
-
+
run testcase - Run a single test case.
out testcase - Run a test case, but don't clean up, to see the output.
clean - Clean all the output for all tests.
-
+
"""
op = sys.argv[1]
if op == 'run':
@@ -317,7 +317,7 @@ def main():
test[0].run_fully()
else:
print("Need an operation: run, out, clean")
-
+
# So that we can run just one farm run.py at a time.
if __name__ == '__main__':
main()
diff --git a/test/test_oddball.py b/test/test_oddball.py
index 0376f65a..05252ef8 100644
--- a/test/test_oddball.py
+++ b/test/test_oddball.py
@@ -16,13 +16,13 @@ class ThreadingTest(CoverageTest):
def fromMainThread():
return "called from main thread"
-
+
def fromOtherThread():
return "called from other thread"
-
+
def neverCalled():
return "no one calls me"
-
+
other = threading.Thread(target=fromOtherThread)
other.start()
fromMainThread()
@@ -42,11 +42,11 @@ class RecursionTest(CoverageTest):
return 0
else:
return recur(n-1)+1
-
+
recur(495) # We can get at least this many stack frames.
""",
[1,2,3,5,7], "")
-
+
def testLongRecursion(self):
# We can't finish a very deep recursion, but we don't crash.
self.assertRaises(RuntimeError, self.check_coverage,
@@ -56,7 +56,7 @@ class RecursionTest(CoverageTest):
return 0
else:
return recur(n-1)+1
-
+
recur(100000) # This is definitely too many frames.
""",
[1,2,3,5,7], "")
@@ -68,9 +68,8 @@ class MemoryLeakTest(CoverageTest):
def test_for_leaks(self):
lines = list(range(301, 315))
lines.remove(306)
- baseline_ram = osinfo.process_ram()
# Ugly string mumbo jumbo to get 300 blank lines at the beginning..
- self.check_coverage("""\
+ code = """\
# blank line\n""" * 300 + """\
def once(x):
if x % 100 == 0:
@@ -80,15 +79,19 @@ class MemoryLeakTest(CoverageTest):
else:
return 11
i = 0 # Portable loop without alloc'ing memory.
- while i < 10000:
+ while i < ITERS:
try:
once(i)
except:
pass
i += 1
- """,
- lines, "")
- ram_growth = osinfo.process_ram() - baseline_ram
+ """
+ ram_0 = osinfo.process_ram()
+ self.check_coverage(code.replace("ITERS", "10"), lines, "")
+ ram_1 = osinfo.process_ram()
+ self.check_coverage(code.replace("ITERS", "10000"), lines, "")
+ ram_2 = osinfo.process_ram()
+ ram_growth = (ram_2 - ram_1) - (ram_1 - ram_0)
self.assert_(ram_growth < 100000, "RAM grew by %d" % (ram_growth))
@@ -132,7 +135,7 @@ class PyexpatTest(CoverageTest):
_, statements, missing, _ = cov.analysis("trydom.py")
self.assertEqual(statements, [1,3,8,9,10,11,13])
self.assertEqual(missing, [])
-
+
_, statements, missing, _ = cov.analysis("outer.py")
self.assertEqual(statements, [101,102])
self.assertEqual(missing, [])
@@ -142,7 +145,7 @@ class ExceptionTest(CoverageTest):
"""I suspect different versions of Python deal with exceptions differently
in the trace function.
"""
-
+
def testException(self):
# Python 2.3's trace function doesn't get called with "return" if the
# scope is exiting due to an exception. This confounds our trace
@@ -153,21 +156,21 @@ class ExceptionTest(CoverageTest):
# stack is in a different file, to try to trip up the tracer. Each
# file has active lines in a different range so we'll see if the lines
# get attributed to the wrong file.
-
+
self.make_file("oops.py", """\
def oops(args):
a = 2
raise Exception("oops")
a = 4
""")
-
+
self.make_file("fly.py", "\n"*100 + """\
def fly(calls):
a = 2
calls[0](calls[1:])
a = 4
""")
-
+
self.make_file("catch.py", "\n"*200 + """\
def catch(calls):
try:
@@ -177,7 +180,7 @@ class ExceptionTest(CoverageTest):
except:
a = 7
""")
-
+
self.make_file("doit.py", "\n"*300 + """\
def doit(calls):
try:
@@ -217,19 +220,19 @@ class ExceptionTest(CoverageTest):
'oops.py': [2,3],
}),
]
-
+
for callnames, lines_expected in runs:
-
+
# Make the list of functions we'll call for this test.
calls = [getattr(sys.modules[cn], cn) for cn in callnames.split()]
-
+
cov = coverage.coverage()
cov.start()
# Call our list of functions: invoke the first, with the rest as
# an argument.
calls[0](calls[1:])
cov.stop()
-
+
# Clean the line data and compare to expected results.
# The filenames are absolute, so keep just the base.
lines = cov.data.line_data()
@@ -245,10 +248,10 @@ class ExceptionTest(CoverageTest):
if sys.hexversion > 0x02050000:
class DoctestTest(CoverageTest):
"""Tests invoked with doctest should measure properly."""
-
+
def setUp(self):
super(DoctestTest, self).setUp()
-
+
# Oh, the irony! This test case exists because Python 2.4's
# doctest module doesn't play well with coverage. But nose fixes
# the problem by monkeypatching doctest. I want to undo the
@@ -257,12 +260,12 @@ if sys.hexversion > 0x02050000:
# enough: when the test imports doctest again, it will get a fresh
# copy without the monkeypatch.
del sys.modules['doctest']
-
+
def testDoctest(self):
self.check_coverage('''\
def return_arg_or_void(arg):
"""If <arg> is None, return "Void"; otherwise return <arg>
-
+
>>> return_arg_or_void(None)
'Void'
>>> return_arg_or_void("arg")
@@ -274,7 +277,7 @@ if sys.hexversion > 0x02050000:
return "Void"
else:
return arg
-
+
import doctest, sys
doctest.testmod(sys.modules[__name__]) # we're not __main__ :(
''',
diff --git a/test/test_parser.py b/test/test_parser.py
index 0dc7089c..b398044d 100644
--- a/test/test_parser.py
+++ b/test/test_parser.py
@@ -19,7 +19,7 @@ class ParserTest(CoverageTest):
cp = CodeParser(text, exclude="nocover")
cp.parse_source()
return cp
-
+
def test_exit_counts(self):
cp = self.parse_source("""\
# check some basic branch counting
@@ -29,7 +29,7 @@ class ParserTest(CoverageTest):
return 5
else:
return 7
-
+
class Bar:
pass
""")
@@ -52,13 +52,13 @@ class ParserTest(CoverageTest):
self.assertEqual(cp.exit_counts(), {
1: 1, 2:1, 3:1, 4:1, 5:1, 6:1, 7:1, 8:1, 9:1
})
-
+
def test_excluded_classes(self):
cp = self.parse_source("""\
class Foo:
def __init__(self):
pass
-
+
if 0: # nocover
class Bar:
pass
@@ -66,7 +66,7 @@ class ParserTest(CoverageTest):
self.assertEqual(cp.exit_counts(), {
1:0, 2:1, 3:1
})
-
+
def test_missing_branch_to_excluded_code(self):
cp = self.parse_source("""\
if fooey:
diff --git a/test/test_phystokens.py b/test/test_phystokens.py
index a01cc374..6b16d68e 100644
--- a/test/test_phystokens.py
+++ b/test/test_phystokens.py
@@ -77,5 +77,3 @@ class PhysTokensTest(CoverageTest):
# Check the tokenization of a stress-test file.
stress = os.path.join(HERE, "stress_phystoken.txt")
self.check_file_tokenization(stress)
-
- \ No newline at end of file
diff --git a/test/test_results.py b/test/test_results.py
index 33ebe4d7..ef15121c 100644
--- a/test/test_results.py
+++ b/test/test_results.py
@@ -10,7 +10,7 @@ from coveragetest import CoverageTest
class NumbersTest(CoverageTest):
"""Tests for Coverage.py's numeric measurement summaries."""
-
+
run_in_temp_dir = False
def test_basic(self):
@@ -19,7 +19,7 @@ class NumbersTest(CoverageTest):
self.assertEqual(n1.n_executed, 180)
self.assertEqual(n1.n_missing, 20)
self.assertEqual(n1.pc_covered, 90)
-
+
def test_addition(self):
n1 = Numbers(n_files=1, n_statements=200, n_missing=20)
n2 = Numbers(n_files=1, n_statements=10, n_missing=8)
diff --git a/test/test_summary.py b/test/test_summary.py
index 36db9969..0eec58ca 100644
--- a/test/test_summary.py
+++ b/test/test_summary.py
@@ -22,7 +22,7 @@ class SummaryTest(CoverageTest):
report = self.run_command(cmd).replace('\\', '/')
self.assert_("error" not in report.lower())
return report
-
+
def line_count(self, report):
"""How many lines are in `report`?"""
self.assertEqual(report.split('\n')[-1], "")
@@ -73,7 +73,7 @@ class SummaryTest(CoverageTest):
prefix = os.path.split(__file__)[0]
self.run_command("coverage -x mycode.py")
report = self.report_from_command("coverage -r -o %s" % prefix)
-
+
# Name Stmts Exec Cover
# ----------------------------
# mycode 4 4 100%
diff --git a/test/test_templite.py b/test/test_templite.py
index 35c1df55..57385cba 100644
--- a/test/test_templite.py
+++ b/test/test_templite.py
@@ -9,9 +9,9 @@ import unittest
class AnyOldObject(object):
"""Simple testing object.
-
+
Use keyword arguments in the constructor to set attributes on the object.
-
+
"""
def __init__(self, **attrs):
for n, v in attrs.items():
@@ -45,7 +45,7 @@ class TempliteTest(unittest.TestCase):
'second': lambda x: x[1],
}
self.try_render("Hello, {{name|upper}}!", data, "Hello, NED!")
-
+
# Pipes can be concatenated.
self.try_render("Hello, {{name|upper|second}}!", data, "Hello, E!")
@@ -55,7 +55,7 @@ class TempliteTest(unittest.TestCase):
'upper': lambda x: x.upper(),
'punct': '!',
}
-
+
template = Templite("This is {{name|upper}}{{punct}}", globs)
self.assertEqual(template.render({'name':'Ned'}), "This is NED!")
self.assertEqual(template.render({'name':'Ben'}), "This is BEN!")
@@ -67,7 +67,7 @@ class TempliteTest(unittest.TestCase):
obj2 = AnyOldObject(obj=obj, b="Bee")
self.try_render("{{obj2.obj.a}} {{obj2.b}}", locals(), "Ay Bee")
-
+
def test_member_function(self):
# Variables' member functions can be used, as long as they are nullary.
class WithMemberFns(AnyOldObject):
@@ -97,7 +97,7 @@ class TempliteTest(unittest.TestCase):
l = l[:]
l.reverse()
return l
-
+
self.try_render(
"Look: {% for n in nums|rev %}{{n}}, {% endfor %}done.",
locals(),
@@ -117,7 +117,7 @@ class TempliteTest(unittest.TestCase):
{'nums':[1,2,3]},
"Look: \n\n1, \n\n2, \n\n3, \ndone."
)
-
+
def test_multiple_loops(self):
self.try_render(
"{% for n in nums %}{{n}}{% endfor %} and "