summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2013-02-02 11:15:11 -0500
committerNed Batchelder <ned@nedbatchelder.com>2013-02-02 11:15:11 -0500
commitd5f8295256d04ba8cb5b42a16ce741a34c9bb3c5 (patch)
treeff8c6d6310bb3865411d40198c07f26eb5709959 /tests
parentb5a466fc3d7a71fc811b2430f04e6fc270858935 (diff)
downloadpython-coveragepy-d5f8295256d04ba8cb5b42a16ce741a34c9bb3c5.tar.gz
Move the test directory to tests to avoid conflicts with the stdlib test package.
Diffstat (limited to 'tests')
-rw-r--r--tests/__init__.py1
-rw-r--r--tests/backtest.py49
-rw-r--r--tests/backunittest.py115
-rw-r--r--tests/coveragetest.py465
-rw-r--r--tests/covmodzip1.py3
-rw-r--r--tests/eggsrc/egg1/__init__.py0
-rw-r--r--tests/eggsrc/egg1/egg1.py4
-rw-r--r--tests/eggsrc/setup.py8
-rw-r--r--tests/farm/annotate/annotate_dir.py7
-rw-r--r--tests/farm/annotate/gold/white.py,cover33
-rw-r--r--tests/farm/annotate/gold_anno_dir/a___init__.py,cover0
-rw-r--r--tests/farm/annotate/gold_anno_dir/a_a.py,cover5
-rw-r--r--tests/farm/annotate/gold_anno_dir/b___init__.py,cover0
-rw-r--r--tests/farm/annotate/gold_anno_dir/b_b.py,cover3
-rw-r--r--tests/farm/annotate/gold_anno_dir/multi.py,cover5
-rw-r--r--tests/farm/annotate/gold_multi/a/__init__.py,cover0
-rw-r--r--tests/farm/annotate/gold_multi/a/a.py,cover5
-rw-r--r--tests/farm/annotate/gold_multi/b/__init__.py,cover0
-rw-r--r--tests/farm/annotate/gold_multi/b/b.py,cover2
-rw-r--r--tests/farm/annotate/gold_multi/multi.py,cover5
-rw-r--r--tests/farm/annotate/gold_v24/white.py,cover33
-rw-r--r--tests/farm/annotate/run.py7
-rw-r--r--tests/farm/annotate/run_multi.py7
-rw-r--r--tests/farm/annotate/src/a/__init__.py0
-rw-r--r--tests/farm/annotate/src/a/a.py5
-rw-r--r--tests/farm/annotate/src/b/__init__.py0
-rw-r--r--tests/farm/annotate/src/b/b.py3
-rw-r--r--tests/farm/annotate/src/multi.py5
-rw-r--r--tests/farm/annotate/src/white.py33
-rw-r--r--tests/farm/html/gold_a/a.html95
-rw-r--r--tests/farm/html/gold_a/index.html89
-rw-r--r--tests/farm/html/gold_b_branch/b.html139
-rw-r--r--tests/farm/html/gold_b_branch/index.html101
-rw-r--r--tests/farm/html/gold_bom/bom.html104
-rw-r--r--tests/farm/html/gold_bom/index.html90
-rw-r--r--tests/farm/html/gold_isolatin1/index.html89
-rw-r--r--tests/farm/html/gold_isolatin1/isolatin1.html91
-rw-r--r--tests/farm/html/gold_omit_1/index.html116
-rw-r--r--tests/farm/html/gold_omit_1/m1.html85
-rw-r--r--tests/farm/html/gold_omit_1/m2.html85
-rw-r--r--tests/farm/html/gold_omit_1/m3.html85
-rw-r--r--tests/farm/html/gold_omit_1/main.html101
-rw-r--r--tests/farm/html/gold_omit_2/index.html107
-rw-r--r--tests/farm/html/gold_omit_2/m2.html85
-rw-r--r--tests/farm/html/gold_omit_2/m3.html85
-rw-r--r--tests/farm/html/gold_omit_2/main.html101
-rw-r--r--tests/farm/html/gold_omit_3/index.html98
-rw-r--r--tests/farm/html/gold_omit_3/m3.html85
-rw-r--r--tests/farm/html/gold_omit_3/main.html101
-rw-r--r--tests/farm/html/gold_omit_4/index.html107
-rw-r--r--tests/farm/html/gold_omit_4/m1.html85
-rw-r--r--tests/farm/html/gold_omit_4/m3.html85
-rw-r--r--tests/farm/html/gold_omit_4/main.html101
-rw-r--r--tests/farm/html/gold_omit_5/index.html98
-rw-r--r--tests/farm/html/gold_omit_5/m1.html85
-rw-r--r--tests/farm/html/gold_omit_5/main.html101
-rw-r--r--tests/farm/html/gold_other/blah_blah_other.html89
-rw-r--r--tests/farm/html/gold_other/here.html97
-rw-r--r--tests/farm/html/gold_other/index.html98
-rw-r--r--tests/farm/html/gold_partial/index.html101
-rw-r--r--tests/farm/html/gold_partial/partial.html121
-rw-r--r--tests/farm/html/gold_styled/a.html95
-rw-r--r--tests/farm/html/gold_styled/extra.css1
-rw-r--r--tests/farm/html/gold_styled/index.html89
-rw-r--r--tests/farm/html/gold_styled/style.css275
-rw-r--r--tests/farm/html/gold_unicode/index.html89
-rw-r--r--tests/farm/html/gold_unicode/unicode.html91
-rw-r--r--tests/farm/html/gold_x_xml/coverage.xml20
-rw-r--r--tests/farm/html/gold_y_xml_branch/coverage.xml22
-rw-r--r--tests/farm/html/othersrc/other.py4
-rw-r--r--tests/farm/html/run_a.py25
-rw-r--r--tests/farm/html/run_a_xml_1.py21
-rw-r--r--tests/farm/html/run_a_xml_2.py21
-rw-r--r--tests/farm/html/run_b_branch.py28
-rw-r--r--tests/farm/html/run_bom.py21
-rw-r--r--tests/farm/html/run_isolatin1.py21
-rw-r--r--tests/farm/html/run_omit_1.py12
-rw-r--r--tests/farm/html/run_omit_2.py12
-rw-r--r--tests/farm/html/run_omit_3.py12
-rw-r--r--tests/farm/html/run_omit_4.py12
-rw-r--r--tests/farm/html/run_omit_5.py12
-rw-r--r--tests/farm/html/run_other.py26
-rw-r--r--tests/farm/html/run_partial.py32
-rw-r--r--tests/farm/html/run_styled.py28
-rw-r--r--tests/farm/html/run_tabbed.py24
-rw-r--r--tests/farm/html/run_unicode.py30
-rw-r--r--tests/farm/html/run_y_xml_branch.py21
-rw-r--r--tests/farm/html/src/a.py7
-rw-r--r--tests/farm/html/src/b.py29
-rw-r--r--tests/farm/html/src/bom.py11
-rw-r--r--tests/farm/html/src/coverage.xml20
-rw-r--r--tests/farm/html/src/extra.css1
-rw-r--r--tests/farm/html/src/here.py8
-rw-r--r--tests/farm/html/src/isolatin1.py5
-rw-r--r--tests/farm/html/src/m1.py2
-rw-r--r--tests/farm/html/src/m2.py2
-rw-r--r--tests/farm/html/src/m3.py2
-rw-r--r--tests/farm/html/src/main.py10
-rw-r--r--tests/farm/html/src/omit4.ini2
-rw-r--r--tests/farm/html/src/omit5.ini8
-rw-r--r--tests/farm/html/src/partial.py18
-rw-r--r--tests/farm/html/src/run_a_xml_2.ini3
-rw-r--r--tests/farm/html/src/tabbed.py7
-rw-r--r--tests/farm/html/src/unicode.py5
-rw-r--r--tests/farm/html/src/y.py9
-rw-r--r--tests/farm/run/run_chdir.py12
-rw-r--r--tests/farm/run/run_timid.py60
-rw-r--r--tests/farm/run/run_xxx.py12
-rw-r--r--tests/farm/run/src/chdir.py4
-rw-r--r--tests/farm/run/src/showtrace.py23
-rw-r--r--tests/farm/run/src/subdir/placeholder0
-rw-r--r--tests/farm/run/src/xxx8
-rw-r--r--tests/js/index.html52
-rw-r--r--tests/js/tests.js204
-rw-r--r--tests/modules/aa/__init__.py1
-rw-r--r--tests/modules/aa/afile.odd.py1
-rw-r--r--tests/modules/aa/afile.py1
-rw-r--r--tests/modules/aa/bb.odd/bfile.py1
-rw-r--r--tests/modules/aa/bb/__init__.py1
-rw-r--r--tests/modules/aa/bb/bfile.odd.py1
-rw-r--r--tests/modules/aa/bb/bfile.py1
-rw-r--r--tests/modules/aa/bb/cc/__init__.py0
-rw-r--r--tests/modules/aa/bb/cc/cfile.py1
-rw-r--r--tests/modules/aa/zfile.py1
-rw-r--r--tests/modules/covmod1.py3
-rw-r--r--tests/modules/pkg1/__init__.py3
-rw-r--r--tests/modules/pkg1/__main__.py3
-rw-r--r--tests/modules/pkg1/p1a.py5
-rw-r--r--tests/modules/pkg1/p1b.py3
-rw-r--r--tests/modules/pkg1/p1c.py3
-rw-r--r--tests/modules/pkg1/runmod2.py3
-rw-r--r--tests/modules/pkg1/sub/__init__.py0
-rw-r--r--tests/modules/pkg1/sub/__main__.py3
-rw-r--r--tests/modules/pkg1/sub/ps1a.py3
-rw-r--r--tests/modules/pkg1/sub/runmod3.py3
-rw-r--r--tests/modules/pkg2/__init__.py2
-rw-r--r--tests/modules/pkg2/p2a.py3
-rw-r--r--tests/modules/pkg2/p2b.py3
-rw-r--r--tests/modules/runmod1.py3
-rw-r--r--tests/modules/usepkgs.py4
-rw-r--r--tests/moremodules/othermods/__init__.py0
-rw-r--r--tests/moremodules/othermods/othera.py2
-rw-r--r--tests/moremodules/othermods/otherb.py2
-rw-r--r--tests/moremodules/othermods/sub/__init__.py0
-rw-r--r--tests/moremodules/othermods/sub/osa.py2
-rw-r--r--tests/moremodules/othermods/sub/osb.py2
-rw-r--r--tests/osinfo.py71
-rw-r--r--tests/qunit/jquery.tmpl.min.js10
-rw-r--r--tests/qunit/qunit.css225
-rw-r--r--tests/qunit/qunit.js1448
-rw-r--r--tests/stress_phystoken.tok52
-rw-r--r--tests/stress_phystoken_dos.tok52
-rw-r--r--tests/test_api.py571
-rw-r--r--tests/test_arcs.py578
-rw-r--r--tests/test_cmdline.py702
-rw-r--r--tests/test_codeunit.py103
-rw-r--r--tests/test_config.py225
-rw-r--r--tests/test_coverage.py1730
-rw-r--r--tests/test_data.py146
-rw-r--r--tests/test_execfile.py116
-rw-r--r--tests/test_farm.py366
-rw-r--r--tests/test_files.py169
-rw-r--r--tests/test_html.py297
-rw-r--r--tests/test_misc.py73
-rw-r--r--tests/test_oddball.py386
-rw-r--r--tests/test_parser.py131
-rw-r--r--tests/test_phystokens.py79
-rw-r--r--tests/test_process.py575
-rw-r--r--tests/test_results.py60
-rw-r--r--tests/test_summary.py298
-rw-r--r--tests/test_templite.py204
-rw-r--r--tests/test_testing.py192
-rw-r--r--tests/test_xml.py84
-rw-r--r--tests/try_execfile.py34
174 files changed, 14512 insertions, 0 deletions
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..5a0e30f
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1 @@
+"""Automated tests. Run with nosetests."""
diff --git a/tests/backtest.py b/tests/backtest.py
new file mode 100644
index 0000000..b17aa24
--- /dev/null
+++ b/tests/backtest.py
@@ -0,0 +1,49 @@
+"""Add things to old Pythons so I can pretend they are newer, for tests."""
+
+# pylint: disable=W0622
+# (Redefining built-in blah)
+# The whole point of this file is to redefine built-ins, so shut up about it.
+
+import os
+
+# Py2k and 3k don't agree on how to run commands in a subprocess.
+try:
+ import subprocess
+except ImportError:
+ def run_command(cmd, status=0):
+ """Run a command in a subprocess.
+
+ Returns the exit status code and the combined stdout and stderr.
+
+ """
+ _, stdouterr = os.popen4(cmd)
+ return status, stdouterr.read()
+
+else:
+ def run_command(cmd, status=0):
+ """Run a command in a subprocess.
+
+ Returns the exit status code and the combined stdout and stderr.
+
+ """
+ proc = subprocess.Popen(cmd, shell=True,
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT
+ )
+ output, _ = proc.communicate()
+ status = proc.returncode # pylint: disable=E1101
+
+ # Get the output, and canonicalize it to strings with newlines.
+ if not isinstance(output, str):
+ output = output.decode('utf-8')
+ output = output.replace('\r', '')
+
+ return status, output
+
+# No more execfile in Py3k
+try:
+ execfile = execfile
+except NameError:
+ def execfile(filename, globs):
+ """A Python 3 implementation of execfile."""
+ exec(compile(open(filename).read(), filename, 'exec'), globs)
diff --git a/tests/backunittest.py b/tests/backunittest.py
new file mode 100644
index 0000000..30da78e
--- /dev/null
+++ b/tests/backunittest.py
@@ -0,0 +1,115 @@
+"""Implementations of unittest features from the future."""
+
+import difflib, re, sys, unittest
+
+from coverage.backward import set # pylint: disable=W0622
+
+
+def _need(method):
+ """Do we need to define our own `method` method?"""
+ return not hasattr(unittest.TestCase, 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('assertTrue'):
+ def assertTrue(self, exp, msg=None):
+ """Assert that `exp` is true."""
+ if not exp:
+ self.fail(msg)
+
+ if _need('assertFalse'):
+ def assertFalse(self, exp, msg=None):
+ """Assert that `exp` is false."""
+ if exp:
+ self.fail(msg)
+
+ if _need('assertIn'):
+ def assertIn(self, member, container, msg=None):
+ """Assert that `member` is in `container`."""
+ if member not in container:
+ msg = msg or ('%r not found in %r' % (member, container))
+ self.fail(msg)
+
+ if _need('assertNotIn'):
+ def assertNotIn(self, member, container, msg=None):
+ """Assert that `member` is not in `container`."""
+ if member in container:
+ msg = msg or ('%r found in %r' % (member, container))
+ self.fail(msg)
+
+ if _need('assertGreater'):
+ def assertGreater(self, a, b, msg=None):
+ """Assert that `a` is greater than `b`."""
+ if not a > b:
+ msg = msg or ('%r not greater than %r' % (a, b))
+ self.fail(msg)
+
+ if _need('assertRaisesRegexp'):
+ def assertRaisesRegexp(self, excClass, regexp, callobj, *args, **kw):
+ """ Just like unittest.TestCase.assertRaises,
+ but checks that the message is right too.
+ """
+ try:
+ callobj(*args, **kw)
+ except excClass:
+ _, exc, _ = sys.exc_info()
+ excMsg = str(exc)
+ if re.search(regexp, excMsg):
+ # Message provided, and we got the right one: it passes.
+ return
+ else:
+ # Message provided, and it didn't match: fail!
+ raise self.failureException(
+ "Right exception, wrong message: "
+ "%r doesn't match %r" % (excMsg, regexp)
+ )
+ # No need to catch other exceptions: They'll fail the test all by
+ # themselves!
+ else:
+ if hasattr(excClass, '__name__'):
+ excName = excClass.__name__
+ else:
+ excName = str(excClass)
+ raise self.failureException(
+ "Expected to raise %s, didn't get an exception at all" %
+ excName
+ )
+
+ if _need('assertSameElements'):
+ def assertSameElements(self, s1, s2):
+ """Assert that the two arguments are equal as sets."""
+ self.assertEqual(set(s1), set(s2))
+
+ if _need('assertRegexpMatches'):
+ def assertRegexpMatches(self, text, regex, msg=None):
+ """Assert that `text` matches `regex`."""
+ m = re.search(regex, text)
+ if not m:
+ msg = msg or ("%r doesn't match %r" % (text, regex))
+ raise self.failureException(msg)
+
+ if _need('assertMultiLineEqual'):
+ def assertMultiLineEqual(self, first, second, msg=None):
+ """Assert that two multi-line strings are equal.
+
+ If they aren't, show a nice diff.
+
+ """
+ # Adapted from Py3.1 unittest.
+ self.assertTrue(isinstance(first, str),
+ 'First argument is not a string')
+ self.assertTrue(isinstance(second, str),
+ 'Second argument is not a string')
+
+ if first != second:
+ message = ''.join(difflib.ndiff(first.splitlines(True),
+ second.splitlines(True)))
+ if msg:
+ message += " : " + msg
+ self.fail("Multi-line strings are unequal:\n" + message)
diff --git a/tests/coveragetest.py b/tests/coveragetest.py
new file mode 100644
index 0000000..6f6217a
--- /dev/null
+++ b/tests/coveragetest.py
@@ -0,0 +1,465 @@
+"""Base test case class for coverage testing."""
+
+import glob, imp, os, random, shlex, shutil, sys, tempfile, textwrap
+
+import coverage
+from coverage.backward import sorted, StringIO # pylint: disable=W0622
+from coverage.backward import to_bytes
+from coverage.control import _TEST_NAME_FILE
+from test.backtest import run_command
+from test.backunittest import TestCase
+
+class Tee(object):
+ """A file-like that writes to all the file-likes it has."""
+
+ def __init__(self, *files):
+ """Make a Tee that writes to all the files in `files.`"""
+ self._files = files
+ if hasattr(files[0], "encoding"):
+ self.encoding = files[0].encoding
+
+ def write(self, data):
+ """Write `data` to all the files."""
+ for f in self._files:
+ f.write(data)
+
+ if 0:
+ # Use this if you need to use a debugger, though it makes some tests
+ # fail, I'm not sure why...
+ def __getattr__(self, name):
+ return getattr(self._files[0], name)
+
+
+# Status returns for the command line.
+OK, ERR = 0, 1
+
+class CoverageTest(TestCase):
+ """A base class for Coverage test cases."""
+
+ run_in_temp_dir = True
+
+ def setUp(self):
+ super(CoverageTest, self).setUp()
+
+ if _TEST_NAME_FILE:
+ f = open(_TEST_NAME_FILE, "w")
+ f.write("%s_%s" % (self.__class__.__name__, self._testMethodName))
+ f.close()
+
+ # Tell newer unittest implementations to print long helpful messages.
+ self.longMessage = True
+
+ # tearDown will restore the original sys.path
+ self.old_syspath = sys.path[:]
+
+ if self.run_in_temp_dir:
+ # Create a temporary directory.
+ self.noise = str(random.random())[2:]
+ self.temp_root = os.path.join(tempfile.gettempdir(), 'test_cover')
+ self.temp_dir = os.path.join(self.temp_root, self.noise)
+ os.makedirs(self.temp_dir)
+ self.old_dir = os.getcwd()
+ os.chdir(self.temp_dir)
+
+ # Modules should be importable from this temp directory.
+ sys.path.insert(0, '')
+
+ # Keep a counter to make every call to check_coverage unique.
+ self.n = 0
+
+ # Record environment variables that we changed with set_environ.
+ self.environ_undos = {}
+
+ # Capture stdout and stderr so we can examine them in tests.
+ # nose keeps stdout from littering the screen, so we can safely Tee it,
+ # but it doesn't capture stderr, so we don't want to Tee stderr to the
+ # real stderr, since it will interfere with our nice field of dots.
+ self.old_stdout = sys.stdout
+ self.captured_stdout = StringIO()
+ sys.stdout = Tee(sys.stdout, self.captured_stdout)
+ self.old_stderr = sys.stderr
+ self.captured_stderr = StringIO()
+ sys.stderr = self.captured_stderr
+
+ # Record sys.modules here so we can restore it in tearDown.
+ self.old_modules = dict(sys.modules)
+
+ def tearDown(self):
+ super(CoverageTest, self).tearDown()
+
+ # Restore the original sys.path.
+ sys.path = self.old_syspath
+
+ if self.run_in_temp_dir:
+ # Get rid of the temporary directory.
+ os.chdir(self.old_dir)
+ shutil.rmtree(self.temp_root)
+
+ # Restore the environment.
+ self.undo_environ()
+
+ # Restore stdout and stderr
+ sys.stdout = self.old_stdout
+ sys.stderr = self.old_stderr
+
+ self.clean_modules()
+
+ def clean_modules(self):
+ """Remove any new modules imported during the test run.
+
+ This lets us import the same source files for more than one test.
+
+ """
+ for m in [m for m in sys.modules if m not in self.old_modules]:
+ del sys.modules[m]
+
+ def set_environ(self, name, value):
+ """Set an environment variable `name` to be `value`.
+
+ The environment variable is set, and record is kept that it was set,
+ so that `tearDown` can restore its original value.
+
+ """
+ if name not in self.environ_undos:
+ self.environ_undos[name] = os.environ.get(name)
+ os.environ[name] = value
+
+ def original_environ(self, name, if_missing=None):
+ """The environment variable `name` from when the test started."""
+ if name in self.environ_undos:
+ ret = self.environ_undos[name]
+ else:
+ ret = os.environ.get(name)
+ if ret is None:
+ ret = if_missing
+ return ret
+
+ def undo_environ(self):
+ """Undo all the changes made by `set_environ`."""
+ for name, value in self.environ_undos.items():
+ if value is None:
+ del os.environ[name]
+ else:
+ os.environ[name] = value
+
+ def stdout(self):
+ """Return the data written to stdout during the test."""
+ return self.captured_stdout.getvalue()
+
+ def stderr(self):
+ """Return the data written to stderr during the test."""
+ return self.captured_stderr.getvalue()
+
+ def make_file(self, filename, text="", newline=None):
+ """Create a temp file.
+
+ `filename` is the path to the file, including directories if desired,
+ and `text` is the content. If `newline` is provided, it is a string
+ that will be used as the line endings in the created file.
+
+ Returns the path to the file.
+
+ """
+ # Tests that call `make_file` should be run in a temp environment.
+ assert self.run_in_temp_dir
+ text = textwrap.dedent(text)
+ if newline:
+ text = text.replace("\n", newline)
+
+ # Make sure the directories are available.
+ dirs, _ = os.path.split(filename)
+ if dirs and not os.path.exists(dirs):
+ os.makedirs(dirs)
+
+ # Create the file.
+ f = open(filename, 'wb')
+ try:
+ f.write(to_bytes(text))
+ finally:
+ f.close()
+
+ return filename
+
+ def clean_local_file_imports(self):
+ """Clean up the results of calls to `import_local_file`.
+
+ Use this if you need to `import_local_file` the same file twice in
+ one test.
+
+ """
+ # So that we can re-import files, clean them out first.
+ self.clean_modules()
+ # Also have to clean out the .pyc file, since the timestamp
+ # resolution is only one second, a changed file might not be
+ # picked up.
+ for pyc in glob.glob('*.pyc'):
+ os.remove(pyc)
+ if os.path.exists("__pycache__"):
+ shutil.rmtree("__pycache__")
+
+ def import_local_file(self, modname):
+ """Import a local file as a module.
+
+ Opens a file in the current directory named `modname`.py, imports it
+ as `modname`, and returns the module object.
+
+ """
+ modfile = modname + '.py'
+ f = open(modfile, 'r')
+
+ for suff in imp.get_suffixes():
+ if suff[0] == '.py':
+ break
+ try:
+ # pylint: disable=W0631
+ # (Using possibly undefined loop variable 'suff')
+ mod = imp.load_module(modname, f, modfile, suff)
+ finally:
+ f.close()
+ return mod
+
+ def start_import_stop(self, cov, modname):
+ """Start coverage, import a file, then stop coverage.
+
+ `cov` is started and stopped, with an `import_local_file` of
+ `modname` in the middle.
+
+ The imported module is returned.
+
+ """
+ cov.start()
+ try: # pragma: nested
+ # Import the python file, executing it.
+ mod = self.import_local_file(modname)
+ finally: # pragma: nested
+ # Stop Coverage.
+ cov.stop()
+ return mod
+
+ def get_module_name(self):
+ """Return the module name to use for this test run."""
+ # We append self.n because otherwise two calls in one test will use the
+ # same filename and whether the test works or not depends on the
+ # timestamps in the .pyc file, so it becomes random whether the second
+ # call will use the compiled version of the first call's code or not!
+ 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)]
+
+ Minus signs can be included in the pairs:
+
+ "-11, 12, 2-5" --> [(-1,1), (1,2), (2,-5)]
+
+ """
+ arcs = []
+ for pair in arcz.split():
+ asgn = bsgn = 1
+ if len(pair) == 2:
+ a,b = pair
+ else:
+ assert len(pair) == 3
+ if pair[0] == '-':
+ _,a,b = pair
+ asgn = -1
+ else:
+ assert pair[1] == '-'
+ a,_,b = pair
+ bsgn = -1
+ arcs.append((asgn*self._arcz_map[a], bsgn*self._arcz_map[b]))
+ return sorted(arcs)
+
+ def assertEqualArcs(self, a1, a2, msg=None):
+ """Assert that the arc lists `a1` and `a2` are equal."""
+ # Make them into multi-line strings so we can see what's going wrong.
+ s1 = "\n".join([repr(a) for a in a1]) + "\n"
+ s2 = "\n".join([repr(a) for a in a2]) + "\n"
+ self.assertMultiLineEqual(s1, s2, msg)
+
+ def check_coverage(self, text, lines=None, missing="", report="",
+ excludes=None, partials="",
+ 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, or a list of possible line numbers, any of which
+ could match. `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
+ if arcz is not None:
+ 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()
+ for exc in excludes or []:
+ cov.exclude(exc)
+ for par in partials or []:
+ cov.exclude(par, which='partial')
+
+ mod = self.start_import_stop(cov, modname)
+
+ # Clean up our side effects
+ del sys.modules[modname]
+
+ # Get the analysis results, and check that they are right.
+ analysis = cov._analyze(mod)
+ if lines is not None:
+ if type(lines[0]) == type(1):
+ # lines is just a list of numbers, it must match the statements
+ # found in the code.
+ self.assertEqual(analysis.statements, lines)
+ else:
+ # lines is a list of possible line number lists, one of them
+ # must match.
+ for line_list in lines:
+ if analysis.statements == line_list:
+ break
+ else:
+ self.fail("None of the lines choices matched %r" %
+ analysis.statements
+ )
+
+ if type(missing) == type(""):
+ self.assertEqual(analysis.missing_formatted(), missing)
+ else:
+ for missing_list in missing:
+ if analysis.missing_formatted() == missing_list:
+ break
+ else:
+ self.fail("None of the missing choices matched %r" %
+ analysis.missing_formatted()
+ )
+
+ if arcs is not None:
+ self.assertEqualArcs(
+ analysis.arc_possibilities(), arcs, "Possible arcs differ"
+ )
+
+ if arcs_missing is not None:
+ self.assertEqualArcs(
+ analysis.arcs_missing(), arcs_missing,
+ "Missing arcs differ"
+ )
+
+ if arcs_unpredicted is not None:
+ self.assertEqualArcs(
+ analysis.arcs_unpredicted(), arcs_unpredicted,
+ "Unpredicted arcs differ"
+ )
+
+ if report:
+ frep = StringIO()
+ 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 assert_same_files(self, flist1, flist2):
+ """Assert that `flist1` and `flist2` are the same set of file names."""
+ flist1_nice = [self.nice_file(f) for f in flist1]
+ flist2_nice = [self.nice_file(f) for f in flist2]
+ self.assertSameElements(flist1_nice, flist2_nice)
+
+ def assert_exists(self, fname):
+ """Assert that `fname` is a file that exists."""
+ msg = "File %r should exist" % fname
+ self.assert_(os.path.exists(fname), msg)
+
+ def assert_doesnt_exist(self, fname):
+ """Assert that `fname` is a file that doesn't exist."""
+ msg = "File %r shouldn't exist" % fname
+ self.assert_(not os.path.exists(fname), msg)
+
+ def command_line(self, args, ret=OK, _covpkg=None):
+ """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.
+
+ """
+ script = coverage.CoverageScript(_covpkg=_covpkg)
+ ret_actual = script.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.
+
+ """
+ _, output = self.run_command_status(cmd)
+ return output
+
+ def run_command_status(self, cmd, status=0):
+ """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 a pair: the process' exit status and stdout text.
+
+ The `status` argument is returned as the status on older Pythons where
+ we can't get the actual exit status of the process.
+
+ """
+ # Add our test modules directory to PYTHONPATH. I'm sure there's too
+ # much path munging here, but...
+ here = os.path.dirname(self.nice_file(coverage.__file__, ".."))
+ testmods = self.nice_file(here, 'test/modules')
+ zipfile = self.nice_file(here, 'test/zipmods.zip')
+ pypath = os.getenv('PYTHONPATH', '')
+ if pypath:
+ pypath += os.pathsep
+ pypath += testmods + os.pathsep + zipfile
+ self.set_environ('PYTHONPATH', pypath)
+
+ status, output = run_command(cmd, status=status)
+ print(output)
+ return status, output
diff --git a/tests/covmodzip1.py b/tests/covmodzip1.py
new file mode 100644
index 0000000..3ec4cdc
--- /dev/null
+++ b/tests/covmodzip1.py
@@ -0,0 +1,3 @@
+"""covmodzip.py: for putting into a zip file."""
+j = 1
+j += 1
diff --git a/tests/eggsrc/egg1/__init__.py b/tests/eggsrc/egg1/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/eggsrc/egg1/__init__.py
diff --git a/tests/eggsrc/egg1/egg1.py b/tests/eggsrc/egg1/egg1.py
new file mode 100644
index 0000000..3fadde3
--- /dev/null
+++ b/tests/eggsrc/egg1/egg1.py
@@ -0,0 +1,4 @@
+# My egg file!
+
+walrus = "Eggman"
+says = "coo-coo cachoo"
diff --git a/tests/eggsrc/setup.py b/tests/eggsrc/setup.py
new file mode 100644
index 0000000..f9b8b9d
--- /dev/null
+++ b/tests/eggsrc/setup.py
@@ -0,0 +1,8 @@
+from setuptools import setup
+
+setup(
+ name="covtestegg1",
+ packages=['egg1'],
+ zip_safe=True,
+ install_requires=[],
+ )
diff --git a/tests/farm/annotate/annotate_dir.py b/tests/farm/annotate/annotate_dir.py
new file mode 100644
index 0000000..3e37f9e
--- /dev/null
+++ b/tests/farm/annotate/annotate_dir.py
@@ -0,0 +1,7 @@
+copy("src", "run")
+run("""
+ coverage -e -x multi.py
+ coverage -a -d out_anno_dir
+ """, rundir="run")
+compare("run/out_anno_dir", "gold_anno_dir", "*,cover", left_extra=True)
+clean("run")
diff --git a/tests/farm/annotate/gold/white.py,cover b/tests/farm/annotate/gold/white.py,cover
new file mode 100644
index 0000000..36b0b99
--- /dev/null
+++ b/tests/farm/annotate/gold/white.py,cover
@@ -0,0 +1,33 @@
+ # A test case sent to me by Steve White
+
+> def f(self):
+! if self==1:
+! pass
+! elif self.m('fred'):
+! pass
+! elif (g==1) and (b==2):
+! pass
+! elif self.m('fred')==True:
+! pass
+! elif ((g==1) and (b==2))==True:
+! pass
+! else:
+! pass
+
+> def g(x):
+> if x == 1:
+> a = 1
+! else:
+! a = 2
+
+> g(1)
+
+> def h(x):
+- if 0: #pragma: no cover
+- pass
+> if x == 1:
+! a = 1
+> else:
+> a = 2
+
+> h(2)
diff --git a/tests/farm/annotate/gold_anno_dir/a___init__.py,cover b/tests/farm/annotate/gold_anno_dir/a___init__.py,cover
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/farm/annotate/gold_anno_dir/a___init__.py,cover
diff --git a/tests/farm/annotate/gold_anno_dir/a_a.py,cover b/tests/farm/annotate/gold_anno_dir/a_a.py,cover
new file mode 100644
index 0000000..d0ff3c0
--- /dev/null
+++ b/tests/farm/annotate/gold_anno_dir/a_a.py,cover
@@ -0,0 +1,5 @@
+> def a(x):
+> if x == 1:
+> print("x is 1")
+! else:
+! print("x is not 1")
diff --git a/tests/farm/annotate/gold_anno_dir/b___init__.py,cover b/tests/farm/annotate/gold_anno_dir/b___init__.py,cover
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/farm/annotate/gold_anno_dir/b___init__.py,cover
diff --git a/tests/farm/annotate/gold_anno_dir/b_b.py,cover b/tests/farm/annotate/gold_anno_dir/b_b.py,cover
new file mode 100644
index 0000000..90d076f
--- /dev/null
+++ b/tests/farm/annotate/gold_anno_dir/b_b.py,cover
@@ -0,0 +1,3 @@
+> def b(x):
+> msg = "x is %s" % x
+> print(msg)
diff --git a/tests/farm/annotate/gold_anno_dir/multi.py,cover b/tests/farm/annotate/gold_anno_dir/multi.py,cover
new file mode 100644
index 0000000..2a5c59c
--- /dev/null
+++ b/tests/farm/annotate/gold_anno_dir/multi.py,cover
@@ -0,0 +1,5 @@
+> import a.a
+> import b.b
+
+> a.a.a(1)
+> b.b.b(2)
diff --git a/tests/farm/annotate/gold_multi/a/__init__.py,cover b/tests/farm/annotate/gold_multi/a/__init__.py,cover
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/farm/annotate/gold_multi/a/__init__.py,cover
diff --git a/tests/farm/annotate/gold_multi/a/a.py,cover b/tests/farm/annotate/gold_multi/a/a.py,cover
new file mode 100644
index 0000000..fb3f543
--- /dev/null
+++ b/tests/farm/annotate/gold_multi/a/a.py,cover
@@ -0,0 +1,5 @@
+> def a(x):
+> if x == 1:
+> print "x is 1"
+! else:
+! print "x is not 1"
diff --git a/tests/farm/annotate/gold_multi/b/__init__.py,cover b/tests/farm/annotate/gold_multi/b/__init__.py,cover
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/farm/annotate/gold_multi/b/__init__.py,cover
diff --git a/tests/farm/annotate/gold_multi/b/b.py,cover b/tests/farm/annotate/gold_multi/b/b.py,cover
new file mode 100644
index 0000000..a3f5dae
--- /dev/null
+++ b/tests/farm/annotate/gold_multi/b/b.py,cover
@@ -0,0 +1,2 @@
+> def b(x):
+> print "x is %s" % x
diff --git a/tests/farm/annotate/gold_multi/multi.py,cover b/tests/farm/annotate/gold_multi/multi.py,cover
new file mode 100644
index 0000000..2a5c59c
--- /dev/null
+++ b/tests/farm/annotate/gold_multi/multi.py,cover
@@ -0,0 +1,5 @@
+> import a.a
+> import b.b
+
+> a.a.a(1)
+> b.b.b(2)
diff --git a/tests/farm/annotate/gold_v24/white.py,cover b/tests/farm/annotate/gold_v24/white.py,cover
new file mode 100644
index 0000000..bbd8d42
--- /dev/null
+++ b/tests/farm/annotate/gold_v24/white.py,cover
@@ -0,0 +1,33 @@
+ # A test case sent to me by Steve White
+
+> def f(self):
+! if self==1:
+! pass
+! elif self.m('fred'):
+! pass
+! elif (g==1) and (b==2):
+! pass
+! elif self.m('fred')==True:
+! pass
+! elif ((g==1) and (b==2))==True:
+! pass
+> else:
+! pass
+
+> def g(x):
+> if x == 1:
+> a = 1
+! else:
+! a = 2
+
+> g(1)
+
+> def h(x):
+- if 0: #pragma: no cover
+- pass
+> if x == 1:
+! a = 1
+> else:
+> a = 2
+
+> h(2)
diff --git a/tests/farm/annotate/run.py b/tests/farm/annotate/run.py
new file mode 100644
index 0000000..c645f21
--- /dev/null
+++ b/tests/farm/annotate/run.py
@@ -0,0 +1,7 @@
+copy("src", "out")
+run("""
+ coverage -e -x white.py
+ coverage -a white.py
+ """, rundir="out")
+compare("out", "gold", "*,cover")
+clean("out")
diff --git a/tests/farm/annotate/run_multi.py b/tests/farm/annotate/run_multi.py
new file mode 100644
index 0000000..4e8252e
--- /dev/null
+++ b/tests/farm/annotate/run_multi.py
@@ -0,0 +1,7 @@
+copy("src", "out_multi")
+run("""
+ coverage -e -x multi.py
+ coverage -a
+ """, rundir="out_multi")
+compare("out_multi", "gold_multi", "*,cover")
+clean("out_multi")
diff --git a/tests/farm/annotate/src/a/__init__.py b/tests/farm/annotate/src/a/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/farm/annotate/src/a/__init__.py
diff --git a/tests/farm/annotate/src/a/a.py b/tests/farm/annotate/src/a/a.py
new file mode 100644
index 0000000..c2583d1
--- /dev/null
+++ b/tests/farm/annotate/src/a/a.py
@@ -0,0 +1,5 @@
+def a(x):
+ if x == 1:
+ print("x is 1")
+ else:
+ print("x is not 1")
diff --git a/tests/farm/annotate/src/b/__init__.py b/tests/farm/annotate/src/b/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/farm/annotate/src/b/__init__.py
diff --git a/tests/farm/annotate/src/b/b.py b/tests/farm/annotate/src/b/b.py
new file mode 100644
index 0000000..625a549
--- /dev/null
+++ b/tests/farm/annotate/src/b/b.py
@@ -0,0 +1,3 @@
+def b(x):
+ msg = "x is %s" % x
+ print(msg)
diff --git a/tests/farm/annotate/src/multi.py b/tests/farm/annotate/src/multi.py
new file mode 100644
index 0000000..19a6200
--- /dev/null
+++ b/tests/farm/annotate/src/multi.py
@@ -0,0 +1,5 @@
+import a.a
+import b.b
+
+a.a.a(1)
+b.b.b(2)
diff --git a/tests/farm/annotate/src/white.py b/tests/farm/annotate/src/white.py
new file mode 100644
index 0000000..ecbbd25
--- /dev/null
+++ b/tests/farm/annotate/src/white.py
@@ -0,0 +1,33 @@
+# A test case sent to me by Steve White
+
+def f(self):
+ if self==1:
+ pass
+ elif self.m('fred'):
+ pass
+ elif (g==1) and (b==2):
+ pass
+ elif self.m('fred')==True:
+ pass
+ elif ((g==1) and (b==2))==True:
+ pass
+ else:
+ pass
+
+def g(x):
+ if x == 1:
+ a = 1
+ else:
+ a = 2
+
+g(1)
+
+def h(x):
+ if 0: #pragma: no cover
+ pass
+ if x == 1:
+ a = 1
+ else:
+ a = 2
+
+h(2)
diff --git a/tests/farm/html/gold_a/a.html b/tests/farm/html/gold_a/a.html
new file mode 100644
index 0000000..c794525
--- /dev/null
+++ b/tests/farm/html/gold_a/a.html
@@ -0,0 +1,95 @@
+<!doctype html PUBLIC "-//W3C//DTD html 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+
+
+ <meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
+ <title>Coverage for a: 67%</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='jquery.isonscreen.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.pyfile_ready);
+ </script>
+</head>
+<body id='pyfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage for <b>a</b> :
+ <span class='pc_cov'>67%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ <h2 class='stats'>
+ 3 statements
+ <span class='run hide_run shortkey_r' onclick='coverage.toggle_lines(this, "run")'>2 run</span>
+ <span class='mis shortkey_m' onclick='coverage.toggle_lines(this, "mis")'>1 missing</span>
+ <span class='exc shortkey_x' onclick='coverage.toggle_lines(this, "exc")'>0 excluded</span>
+
+ </h2>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+<p class='legend'>Hot-keys on this page</p>
+ <div>
+<p class='keyhelp'>
+ <span class='key'>r</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+ <span class='key'>p</span> &nbsp; toggle line displays
+ </p>
+<p class='keyhelp'>
+ <span class='key'>j</span>
+ <span class='key'>k</span> &nbsp; next/prev highlighted chunk
+ </p>
+<p class='keyhelp'>
+ <span class='key'>0</span> &nbsp; (zero) top of page
+ </p>
+<p class='keyhelp'>
+ <span class='key'>1</span> &nbsp; (one) first highlighted chunk
+ </p>
+ </div>
+</div>
+
+<div id='source'>
+ <table cellspacing='0' cellpadding='0'>
+ <tr>
+ <td class='linenos' valign='top'>
+<p id='n1' class='pln'><a href='#n1'>1</a></p>
+<p id='n2' class='pln'><a href='#n2'>2</a></p>
+<p id='n3' class='stm run hide_run'><a href='#n3'>3</a></p>
+<p id='n4' class='pln'><a href='#n4'>4</a></p>
+<p id='n5' class='stm run hide_run'><a href='#n5'>5</a></p>
+<p id='n6' class='pln'><a href='#n6'>6</a></p>
+<p id='n7' class='stm mis'><a href='#n7'>7</a></p>
+
+ </td>
+ <td class='text' valign='top'>
+<p id='t1' class='pln'><span class='com'># A test file for HTML reporting by coverage.</span><span class='strut'>&nbsp;</span></p>
+<p id='t2' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t3' class='stm run hide_run'><span class='key'>if</span> <span class='num'>1</span> <span class='op'>&lt;</span> <span class='num'>2</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
+<p id='t4' class='pln'>&nbsp; &nbsp; <span class='com'># Needed a &lt; to look at HTML entities.</span><span class='strut'>&nbsp;</span></p>
+<p id='t5' class='stm run hide_run'>&nbsp; &nbsp; <span class='nam'>a</span> <span class='op'>=</span> <span class='num'>3</span><span class='strut'>&nbsp;</span></p>
+<p id='t6' class='pln'><span class='key'>else</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
+<p id='t7' class='stm mis'>&nbsp; &nbsp; <span class='nam'>a</span> <span class='op'>=</span> <span class='num'>4</span><span class='strut'>&nbsp;</span></p>
+
+ </td>
+ </tr>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='index.html'>&#xab; index</a> &nbsp; &nbsp; <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_a/index.html b/tests/farm/html/gold_a/index.html
new file mode 100644
index 0000000..a821e9d
--- /dev/null
+++ b/tests/farm/html/gold_a/index.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+ <title>Coverage report</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.tablesorter.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.index_ready);
+ </script>
+</head>
+<body id='indexfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage report:
+ <span class='pc_cov'>67%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+ <p class='legend'>Hot-keys on this page</p>
+ <div>
+ <p class='keyhelp'>
+ <span class='key'>n</span>
+ <span class='key'>s</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+
+ <span class='key'>c</span> &nbsp; change column sorting
+ </p>
+ </div>
+</div>
+
+<div id='index'>
+ <table class='index'>
+ <thead>
+
+ <tr class='tablehead' title='Click to sort'>
+ <th class='name left headerSortDown shortkey_n'>Module</th>
+ <th class='shortkey_s'>statements</th>
+ <th class='shortkey_m'>missing</th>
+ <th class='shortkey_x'>excluded</th>
+
+ <th class='right shortkey_c'>coverage</th>
+ </tr>
+ </thead>
+
+ <tfoot>
+ <tr class='total'>
+ <td class='name left'>Total</td>
+ <td>3</td>
+ <td>1</td>
+ <td>0</td>
+
+ <td class='right'>67%</td>
+ </tr>
+ </tfoot>
+ <tbody>
+
+ <tr class='file'>
+ <td class='name left'><a href='a.html'>a</a></td>
+ <td>3</td>
+ <td>1</td>
+ <td>0</td>
+
+ <td class='right'>67%</td>
+ </tr>
+
+ </tbody>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_b_branch/b.html b/tests/farm/html/gold_b_branch/b.html
new file mode 100644
index 0000000..0258ad1
--- /dev/null
+++ b/tests/farm/html/gold_b_branch/b.html
@@ -0,0 +1,139 @@
+<!doctype html PUBLIC "-//W3C//DTD html 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+
+
+ <meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
+ <title>Coverage for b: 76%</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='jquery.isonscreen.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.pyfile_ready);
+ </script>
+</head>
+<body id='pyfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage for <b>b</b> :
+ <span class='pc_cov'>76%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ <h2 class='stats'>
+ 16 statements
+ <span class='run hide_run shortkey_r' onclick='coverage.toggle_lines(this, "run")'>14 run</span>
+ <span class='mis shortkey_m' onclick='coverage.toggle_lines(this, "mis")'>2 missing</span>
+ <span class='exc shortkey_x' onclick='coverage.toggle_lines(this, "exc")'>0 excluded</span>
+
+ <span class='par run hide_run shortkey_p' onclick='coverage.toggle_lines(this, "par")'>3 partial</span>
+
+ </h2>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+<p class='legend'>Hot-keys on this page</p>
+ <div>
+<p class='keyhelp'>
+ <span class='key'>r</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+ <span class='key'>p</span> &nbsp; toggle line displays
+ </p>
+<p class='keyhelp'>
+ <span class='key'>j</span>
+ <span class='key'>k</span> &nbsp; next/prev highlighted chunk
+ </p>
+<p class='keyhelp'>
+ <span class='key'>0</span> &nbsp; (zero) top of page
+ </p>
+<p class='keyhelp'>
+ <span class='key'>1</span> &nbsp; (one) first highlighted chunk
+ </p>
+ </div>
+</div>
+
+<div id='source'>
+ <table cellspacing='0' cellpadding='0'>
+ <tr>
+ <td class='linenos' valign='top'>
+<p id='n1' class='pln'><a href='#n1'>1</a></p>
+<p id='n2' class='pln'><a href='#n2'>2</a></p>
+<p id='n3' class='stm run hide_run'><a href='#n3'>3</a></p>
+<p id='n4' class='pln'><a href='#n4'>4</a></p>
+<p id='n5' class='stm par run hide_run'><a href='#n5'>5</a></p>
+<p id='n6' class='stm run hide_run'><a href='#n6'>6</a></p>
+<p id='n7' class='pln'><a href='#n7'>7</a></p>
+<p id='n8' class='stm mis'><a href='#n8'>8</a></p>
+<p id='n9' class='pln'><a href='#n9'>9</a></p>
+<p id='n10' class='stm run hide_run'><a href='#n10'>10</a></p>
+<p id='n11' class='pln'><a href='#n11'>11</a></p>
+<p id='n12' class='stm run hide_run'><a href='#n12'>12</a></p>
+<p id='n13' class='pln'><a href='#n13'>13</a></p>
+<p id='n14' class='stm par run hide_run'><a href='#n14'>14</a></p>
+<p id='n15' class='stm run hide_run'><a href='#n15'>15</a></p>
+<p id='n16' class='pln'><a href='#n16'>16</a></p>
+<p id='n17' class='stm run hide_run'><a href='#n17'>17</a></p>
+<p id='n18' class='pln'><a href='#n18'>18</a></p>
+<p id='n19' class='stm run hide_run'><a href='#n19'>19</a></p>
+<p id='n20' class='pln'><a href='#n20'>20</a></p>
+<p id='n21' class='stm par run hide_run'><a href='#n21'>21</a></p>
+<p id='n22' class='stm run hide_run'><a href='#n22'>22</a></p>
+<p id='n23' class='stm run hide_run'><a href='#n23'>23</a></p>
+<p id='n24' class='pln'><a href='#n24'>24</a></p>
+<p id='n25' class='stm mis'><a href='#n25'>25</a></p>
+<p id='n26' class='stm run hide_run'><a href='#n26'>26</a></p>
+<p id='n27' class='pln'><a href='#n27'>27</a></p>
+<p id='n28' class='stm run hide_run'><a href='#n28'>28</a></p>
+
+ </td>
+ <td class='text' valign='top'>
+<p id='t1' class='pln'><span class='com'># A test file for HTML reporting by coverage.</span><span class='strut'>&nbsp;</span></p>
+<p id='t2' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t3' 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 id='t4' class='pln'>&nbsp; &nbsp; <span class='com'># This will be a branch that misses the else.</span><span class='strut'>&nbsp;</span></p>
+<p id='t5' 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 id='t6' 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 id='t7' class='pln'>&nbsp; &nbsp; <span class='key'>else</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
+<p id='t8' 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>
+<p id='t9' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t10' 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 id='t11' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t12' 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 id='t13' class='pln'>&nbsp; &nbsp; <span class='com'># A missed else that branches to &quot;exit&quot;</span><span class='strut'>&nbsp;</span></p>
+<p id='t14' 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 id='t15' 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 id='t16' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t17' 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 id='t18' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t19' 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 id='t20' class='pln'>&nbsp; &nbsp; <span class='com'># for-else can be a three-way branch.</span><span class='strut'>&nbsp;</span></p>
+<p id='t21' 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 id='t22' 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 id='t23' class='stm run hide_run'>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class='key'>break</span><span class='strut'>&nbsp;</span></p>
+<p id='t24' class='pln'>&nbsp; &nbsp; <span class='key'>else</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
+<p id='t25' class='stm mis'>&nbsp; &nbsp; &nbsp; &nbsp; <span class='key'>return</span> <span class='num'>23</span><span class='strut'>&nbsp;</span></p>
+<p id='t26' class='stm run hide_run'>&nbsp; &nbsp; <span class='key'>return</span> <span class='num'>17</span><span class='strut'>&nbsp;</span></p>
+<p id='t27' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t28' 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>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='index.html'>&#xab; index</a> &nbsp; &nbsp; <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_b_branch/index.html b/tests/farm/html/gold_b_branch/index.html
new file mode 100644
index 0000000..cb6ffa1
--- /dev/null
+++ b/tests/farm/html/gold_b_branch/index.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+ <title>Coverage report</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.tablesorter.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.index_ready);
+ </script>
+</head>
+<body id='indexfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage report:
+ <span class='pc_cov'>76%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+ <p class='legend'>Hot-keys on this page</p>
+ <div>
+ <p class='keyhelp'>
+ <span class='key'>n</span>
+ <span class='key'>s</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+
+ <span class='key'>b</span>
+ <span class='key'>p</span>
+
+ <span class='key'>c</span> &nbsp; change column sorting
+ </p>
+ </div>
+</div>
+
+<div id='index'>
+ <table class='index'>
+ <thead>
+
+ <tr class='tablehead' title='Click to sort'>
+ <th class='name left headerSortDown shortkey_n'>Module</th>
+ <th class='shortkey_s'>statements</th>
+ <th class='shortkey_m'>missing</th>
+ <th class='shortkey_x'>excluded</th>
+
+ <th class='shortkey_b'>branches</th>
+ <th class='shortkey_p'>partial</th>
+
+ <th class='right shortkey_c'>coverage</th>
+ </tr>
+ </thead>
+
+ <tfoot>
+ <tr class='total'>
+ <td class='name left'>Total</td>
+ <td>16</td>
+ <td>2</td>
+ <td>0</td>
+
+ <td>9</td>
+ <td>4</td>
+
+ <td class='right'>76%</td>
+ </tr>
+ </tfoot>
+ <tbody>
+
+ <tr class='file'>
+ <td class='name left'><a href='b.html'>b</a></td>
+ <td>16</td>
+ <td>2</td>
+ <td>0</td>
+
+ <td>9</td>
+ <td>4</td>
+
+ <td class='right'>76%</td>
+ </tr>
+
+ </tbody>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_bom/bom.html b/tests/farm/html/gold_bom/bom.html
new file mode 100644
index 0000000..1d61a62
--- /dev/null
+++ b/tests/farm/html/gold_bom/bom.html
@@ -0,0 +1,104 @@
+<!doctype html PUBLIC "-//W3C//DTD html 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+
+
+ <meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
+ <title>Coverage for bom: 71%</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='jquery.isonscreen.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.pyfile_ready);
+ </script>
+</head>
+<body id='pyfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage for <b>bom</b> :
+ <span class='pc_cov'>71%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ <h2 class='stats'>
+ 7 statements &nbsp;
+ <span class='run hide_run shortkey_r button_toggle_run'>5 run</span>
+ <span class='mis shortkey_m button_toggle_mis'>2 missing</span>
+ <span class='exc shortkey_x button_toggle_exc'>0 excluded</span>
+
+ </h2>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+<p class='legend'>Hot-keys on this page</p>
+ <div>
+<p class='keyhelp'>
+ <span class='key'>r</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+ <span class='key'>p</span> &nbsp; toggle line displays
+ </p>
+<p class='keyhelp'>
+ <span class='key'>j</span>
+ <span class='key'>k</span> &nbsp; next/prev highlighted chunk
+ </p>
+<p class='keyhelp'>
+ <span class='key'>0</span> &nbsp; (zero) top of page
+ </p>
+<p class='keyhelp'>
+ <span class='key'>1</span> &nbsp; (one) first highlighted chunk
+ </p>
+ </div>
+</div>
+
+<div id='source'>
+ <table cellspacing='0' cellpadding='0'>
+ <tr>
+ <td class='linenos' valign='top'>
+<p id='n1' class='pln'><a href='#n1'>1</a></p>
+<p id='n2' class='stm run hide_run'><a href='#n2'>2</a></p>
+<p id='n3' class='pln'><a href='#n3'>3</a></p>
+<p id='n4' class='stm run hide_run'><a href='#n4'>4</a></p>
+<p id='n5' class='pln'><a href='#n5'>5</a></p>
+<p id='n6' class='stm run hide_run'><a href='#n6'>6</a></p>
+<p id='n7' class='stm mis'><a href='#n7'>7</a></p>
+<p id='n8' class='stm mis'><a href='#n8'>8</a></p>
+<p id='n9' class='pln'><a href='#n9'>9</a></p>
+<p id='n10' class='stm run hide_run'><a href='#n10'>10</a></p>
+<p id='n11' class='stm run hide_run'><a href='#n11'>11</a></p>
+
+ </td>
+ <td class='text' valign='top'>
+<p id='t1' class='pln'><span class='com'># A python source file in utf-8, with BOM</span><span class='strut'>&nbsp;</span></p>
+<p id='t2' class='stm run hide_run'><span class='nam'>math</span> <span class='op'>=</span> <span class='str'>&quot;3&#215;4 = 12, &#247;2 = 6&#177;0&quot;</span><span class='strut'>&nbsp;</span></p>
+<p id='t3' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t4' class='stm run hide_run'><span class='key'>import</span> <span class='nam'>sys</span><span class='strut'>&nbsp;</span></p>
+<p id='t5' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t6' class='stm run hide_run'><span class='key'>if</span> <span class='nam'>sys</span><span class='op'>.</span><span class='nam'>version_info</span> <span class='op'>&gt;=</span> <span class='op'>(</span><span class='num'>3</span><span class='op'>,</span> <span class='num'>0</span><span class='op'>)</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
+<p id='t7' class='stm mis'>&nbsp; &nbsp; <span class='key'>assert</span> <span class='nam'>len</span><span class='op'>(</span><span class='nam'>math</span><span class='op'>)</span> <span class='op'>==</span> <span class='num'>18</span><span class='strut'>&nbsp;</span></p>
+<p id='t8' class='stm mis'>&nbsp; &nbsp; <span class='key'>assert</span> <span class='nam'>len</span><span class='op'>(</span><span class='nam'>math</span><span class='op'>.</span><span class='nam'>encode</span><span class='op'>(</span><span class='str'>&#39;utf-8&#39;</span><span class='op'>)</span><span class='op'>)</span> <span class='op'>==</span> <span class='num'>21</span><span class='strut'>&nbsp;</span></p>
+<p id='t9' class='pln'><span class='key'>else</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
+<p id='t10' class='stm run hide_run'>&nbsp; &nbsp; <span class='key'>assert</span> <span class='nam'>len</span><span class='op'>(</span><span class='nam'>math</span><span class='op'>)</span> <span class='op'>==</span> <span class='num'>21</span><span class='strut'>&nbsp;</span></p>
+<p id='t11' class='stm run hide_run'>&nbsp; &nbsp; <span class='key'>assert</span> <span class='nam'>len</span><span class='op'>(</span><span class='nam'>math</span><span class='op'>.</span><span class='nam'>decode</span><span class='op'>(</span><span class='str'>&#39;utf-8&#39;</span><span class='op'>)</span><span class='op'>)</span> <span class='op'>==</span> <span class='num'>18</span><span class='strut'>&nbsp;</span></p>
+
+ </td>
+ </tr>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='index.html'>&#xab; index</a> &nbsp; &nbsp; <a class='nav' href='http://nedbatchelder.com/code/coverage'>coverage.py v3.5.2</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_bom/index.html b/tests/farm/html/gold_bom/index.html
new file mode 100644
index 0000000..8653b23
--- /dev/null
+++ b/tests/farm/html/gold_bom/index.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+ <title>Coverage report</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.tablesorter.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.index_ready);
+ </script>
+</head>
+<body id='indexfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage report:
+ <span class='pc_cov'>71%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+ <p class='legend'>Hot-keys on this page</p>
+ <div>
+ <p class='keyhelp'>
+ <span class='key'>n</span>
+ <span class='key'>s</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+
+ <span class='key'>c</span> &nbsp; change column sorting
+ </p>
+ </div>
+</div>
+
+<div id='index'>
+ <table class='index'>
+ <thead>
+
+ <tr class='tablehead' title='Click to sort'>
+ <th class='name left headerSortDown shortkey_n'>Module</th>
+ <th class='shortkey_s'>statements</th>
+ <th class='shortkey_m'>missing</th>
+ <th class='shortkey_x'>excluded</th>
+
+ <th class='right shortkey_c'>coverage</th>
+ </tr>
+ </thead>
+
+ <tfoot>
+ <tr class='total'>
+ <td class='name left'>Total</td>
+ <td>7</td>
+ <td>2</td>
+ <td>0</td>
+
+ <td class='right'>71%</td>
+ </tr>
+ </tfoot>
+ <tbody>
+
+ <tr class='file'>
+ <td class='name left'><a href='bom.html'>bom</a></td>
+ <td>7</td>
+ <td>2</td>
+ <td>0</td>
+
+ <td class='right'>71%</td>
+ </tr>
+
+ </tbody>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='http://nedbatchelder.com/code/coverage'>coverage.py v3.5.2</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_isolatin1/index.html b/tests/farm/html/gold_isolatin1/index.html
new file mode 100644
index 0000000..6e9f3ca
--- /dev/null
+++ b/tests/farm/html/gold_isolatin1/index.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+ <title>Coverage report</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.tablesorter.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.index_ready);
+ </script>
+</head>
+<body id='indexfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage report:
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+ <p class='legend'>Hot-keys on this page</p>
+ <div>
+ <p class='keyhelp'>
+ <span class='key'>n</span>
+ <span class='key'>s</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+
+ <span class='key'>c</span> &nbsp; change column sorting
+ </p>
+ </div>
+</div>
+
+<div id='index'>
+ <table class='index'>
+ <thead>
+
+ <tr class='tablehead' title='Click to sort'>
+ <th class='name left headerSortDown shortkey_n'>Module</th>
+ <th class='shortkey_s'>statements</th>
+ <th class='shortkey_m'>missing</th>
+ <th class='shortkey_x'>excluded</th>
+
+ <th class='right shortkey_c'>coverage</th>
+ </tr>
+ </thead>
+
+ <tfoot>
+ <tr class='total'>
+ <td class='name left'>Total</td>
+ <td>2</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+ </tfoot>
+ <tbody>
+
+ <tr class='file'>
+ <td class='name left'><a href='isolatin1.html'>isolatin1</a></td>
+ <td>2</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+
+ </tbody>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5.2b1'>coverage.py v3.5.2b1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_isolatin1/isolatin1.html b/tests/farm/html/gold_isolatin1/isolatin1.html
new file mode 100644
index 0000000..276a6c2
--- /dev/null
+++ b/tests/farm/html/gold_isolatin1/isolatin1.html
@@ -0,0 +1,91 @@
+<!doctype html PUBLIC "-//W3C//DTD html 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+
+
+ <meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
+ <title>Coverage for isolatin1: 100%</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='jquery.isonscreen.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.pyfile_ready);
+ </script>
+</head>
+<body id='pyfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage for <b>isolatin1</b> :
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ <h2 class='stats'>
+ 2 statements
+ <span class='run hide_run shortkey_r' onclick='coverage.toggle_lines(this, "run")'>2 run</span>
+ <span class='mis shortkey_m' onclick='coverage.toggle_lines(this, "mis")'>0 missing</span>
+ <span class='exc shortkey_x' onclick='coverage.toggle_lines(this, "exc")'>0 excluded</span>
+
+ </h2>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+<p class='legend'>Hot-keys on this page</p>
+ <div>
+<p class='keyhelp'>
+ <span class='key'>r</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+ <span class='key'>p</span> &nbsp; toggle line displays
+ </p>
+<p class='keyhelp'>
+ <span class='key'>j</span>
+ <span class='key'>k</span> &nbsp; next/prev highlighted chunk
+ </p>
+<p class='keyhelp'>
+ <span class='key'>0</span> &nbsp; (zero) top of page
+ </p>
+<p class='keyhelp'>
+ <span class='key'>1</span> &nbsp; (one) first highlighted chunk
+ </p>
+ </div>
+</div>
+
+<div id='source'>
+ <table cellspacing='0' cellpadding='0'>
+ <tr>
+ <td class='linenos' valign='top'>
+<p id='n1' class='pln'><a href='#n1'>1</a></p>
+<p id='n2' class='pln'><a href='#n2'>2</a></p>
+<p id='n3' class='pln'><a href='#n3'>3</a></p>
+<p id='n4' class='stm run hide_run'><a href='#n4'>4</a></p>
+<p id='n5' class='stm run hide_run'><a href='#n5'>5</a></p>
+
+ </td>
+ <td class='text' valign='top'>
+<p id='t1' class='pln'><span class='com'># A python source file in another encoding.</span><span class='strut'>&nbsp;</span></p>
+<p id='t2' class='pln'><span class='com'># -*- coding: iso8859-1 -*-</span><span class='strut'>&nbsp;</span></p>
+<p id='t3' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t4' class='stm run hide_run'><span class='nam'>math</span> <span class='op'>=</span> <span class='str'>&quot;3&#215;4 = 12, &#247;2 = 6&#177;0&quot;</span><span class='strut'>&nbsp;</span></p>
+<p id='t5' class='stm run hide_run'><span class='key'>assert</span> <span class='nam'>len</span><span class='op'>(</span><span class='nam'>math</span><span class='op'>)</span> <span class='op'>==</span> <span class='num'>18</span><span class='strut'>&nbsp;</span></p>
+
+ </td>
+ </tr>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='index.html'>&#xab; index</a> &nbsp; &nbsp; <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5.2b1'>coverage.py v3.5.2b1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_omit_1/index.html b/tests/farm/html/gold_omit_1/index.html
new file mode 100644
index 0000000..5616d01
--- /dev/null
+++ b/tests/farm/html/gold_omit_1/index.html
@@ -0,0 +1,116 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+ <title>Coverage report</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.tablesorter.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.index_ready);
+ </script>
+</head>
+<body id='indexfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage report:
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+ <p class='legend'>Hot-keys on this page</p>
+ <div>
+ <p class='keyhelp'>
+ <span class='key'>n</span>
+ <span class='key'>s</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+
+ <span class='key'>c</span> &nbsp; change column sorting
+ </p>
+ </div>
+</div>
+
+<div id='index'>
+ <table class='index'>
+ <thead>
+
+ <tr class='tablehead' title='Click to sort'>
+ <th class='name left headerSortDown shortkey_n'>Module</th>
+ <th class='shortkey_s'>statements</th>
+ <th class='shortkey_m'>missing</th>
+ <th class='shortkey_x'>excluded</th>
+
+ <th class='right shortkey_c'>coverage</th>
+ </tr>
+ </thead>
+
+ <tfoot>
+ <tr class='total'>
+ <td class='name left'>Total</td>
+ <td>14</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+ </tfoot>
+ <tbody>
+
+ <tr class='file'>
+ <td class='name left'><a href='m1.html'>m1</a></td>
+ <td>2</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+
+ <tr class='file'>
+ <td class='name left'><a href='m2.html'>m2</a></td>
+ <td>2</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+
+ <tr class='file'>
+ <td class='name left'><a href='m3.html'>m3</a></td>
+ <td>2</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+
+ <tr class='file'>
+ <td class='name left'><a href='main.html'>main</a></td>
+ <td>8</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+
+ </tbody>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_omit_1/m1.html b/tests/farm/html/gold_omit_1/m1.html
new file mode 100644
index 0000000..62ba1e0
--- /dev/null
+++ b/tests/farm/html/gold_omit_1/m1.html
@@ -0,0 +1,85 @@
+<!doctype html PUBLIC "-//W3C//DTD html 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+
+
+ <meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
+ <title>Coverage for m1: 100%</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='jquery.isonscreen.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.pyfile_ready);
+ </script>
+</head>
+<body id='pyfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage for <b>m1</b> :
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ <h2 class='stats'>
+ 2 statements
+ <span class='run hide_run shortkey_r' onclick='coverage.toggle_lines(this, "run")'>2 run</span>
+ <span class='mis shortkey_m' onclick='coverage.toggle_lines(this, "mis")'>0 missing</span>
+ <span class='exc shortkey_x' onclick='coverage.toggle_lines(this, "exc")'>0 excluded</span>
+
+ </h2>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+<p class='legend'>Hot-keys on this page</p>
+ <div>
+<p class='keyhelp'>
+ <span class='key'>r</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+ <span class='key'>p</span> &nbsp; toggle line displays
+ </p>
+<p class='keyhelp'>
+ <span class='key'>j</span>
+ <span class='key'>k</span> &nbsp; next/prev highlighted chunk
+ </p>
+<p class='keyhelp'>
+ <span class='key'>0</span> &nbsp; (zero) top of page
+ </p>
+<p class='keyhelp'>
+ <span class='key'>1</span> &nbsp; (one) first highlighted chunk
+ </p>
+ </div>
+</div>
+
+<div id='source'>
+ <table cellspacing='0' cellpadding='0'>
+ <tr>
+ <td class='linenos' valign='top'>
+<p id='n1' class='stm run hide_run'><a href='#n1'>1</a></p>
+<p id='n2' class='stm run hide_run'><a href='#n2'>2</a></p>
+
+ </td>
+ <td class='text' valign='top'>
+<p id='t1' class='stm run hide_run'><span class='nam'>m1a</span> <span class='op'>=</span> <span class='num'>1</span><span class='strut'>&nbsp;</span></p>
+<p id='t2' class='stm run hide_run'><span class='nam'>m1b</span> <span class='op'>=</span> <span class='num'>2</span><span class='strut'>&nbsp;</span></p>
+
+ </td>
+ </tr>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='index.html'>&#xab; index</a> &nbsp; &nbsp; <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_omit_1/m2.html b/tests/farm/html/gold_omit_1/m2.html
new file mode 100644
index 0000000..d75a5ba
--- /dev/null
+++ b/tests/farm/html/gold_omit_1/m2.html
@@ -0,0 +1,85 @@
+<!doctype html PUBLIC "-//W3C//DTD html 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+
+
+ <meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
+ <title>Coverage for m2: 100%</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='jquery.isonscreen.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.pyfile_ready);
+ </script>
+</head>
+<body id='pyfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage for <b>m2</b> :
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ <h2 class='stats'>
+ 2 statements
+ <span class='run hide_run shortkey_r' onclick='coverage.toggle_lines(this, "run")'>2 run</span>
+ <span class='mis shortkey_m' onclick='coverage.toggle_lines(this, "mis")'>0 missing</span>
+ <span class='exc shortkey_x' onclick='coverage.toggle_lines(this, "exc")'>0 excluded</span>
+
+ </h2>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+<p class='legend'>Hot-keys on this page</p>
+ <div>
+<p class='keyhelp'>
+ <span class='key'>r</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+ <span class='key'>p</span> &nbsp; toggle line displays
+ </p>
+<p class='keyhelp'>
+ <span class='key'>j</span>
+ <span class='key'>k</span> &nbsp; next/prev highlighted chunk
+ </p>
+<p class='keyhelp'>
+ <span class='key'>0</span> &nbsp; (zero) top of page
+ </p>
+<p class='keyhelp'>
+ <span class='key'>1</span> &nbsp; (one) first highlighted chunk
+ </p>
+ </div>
+</div>
+
+<div id='source'>
+ <table cellspacing='0' cellpadding='0'>
+ <tr>
+ <td class='linenos' valign='top'>
+<p id='n1' class='stm run hide_run'><a href='#n1'>1</a></p>
+<p id='n2' class='stm run hide_run'><a href='#n2'>2</a></p>
+
+ </td>
+ <td class='text' valign='top'>
+<p id='t1' class='stm run hide_run'><span class='nam'>m2a</span> <span class='op'>=</span> <span class='num'>1</span><span class='strut'>&nbsp;</span></p>
+<p id='t2' class='stm run hide_run'><span class='nam'>m2b</span> <span class='op'>=</span> <span class='num'>2</span><span class='strut'>&nbsp;</span></p>
+
+ </td>
+ </tr>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='index.html'>&#xab; index</a> &nbsp; &nbsp; <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_omit_1/m3.html b/tests/farm/html/gold_omit_1/m3.html
new file mode 100644
index 0000000..bd99138
--- /dev/null
+++ b/tests/farm/html/gold_omit_1/m3.html
@@ -0,0 +1,85 @@
+<!doctype html PUBLIC "-//W3C//DTD html 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+
+
+ <meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
+ <title>Coverage for m3: 100%</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='jquery.isonscreen.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.pyfile_ready);
+ </script>
+</head>
+<body id='pyfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage for <b>m3</b> :
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ <h2 class='stats'>
+ 2 statements
+ <span class='run hide_run shortkey_r' onclick='coverage.toggle_lines(this, "run")'>2 run</span>
+ <span class='mis shortkey_m' onclick='coverage.toggle_lines(this, "mis")'>0 missing</span>
+ <span class='exc shortkey_x' onclick='coverage.toggle_lines(this, "exc")'>0 excluded</span>
+
+ </h2>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+<p class='legend'>Hot-keys on this page</p>
+ <div>
+<p class='keyhelp'>
+ <span class='key'>r</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+ <span class='key'>p</span> &nbsp; toggle line displays
+ </p>
+<p class='keyhelp'>
+ <span class='key'>j</span>
+ <span class='key'>k</span> &nbsp; next/prev highlighted chunk
+ </p>
+<p class='keyhelp'>
+ <span class='key'>0</span> &nbsp; (zero) top of page
+ </p>
+<p class='keyhelp'>
+ <span class='key'>1</span> &nbsp; (one) first highlighted chunk
+ </p>
+ </div>
+</div>
+
+<div id='source'>
+ <table cellspacing='0' cellpadding='0'>
+ <tr>
+ <td class='linenos' valign='top'>
+<p id='n1' class='stm run hide_run'><a href='#n1'>1</a></p>
+<p id='n2' class='stm run hide_run'><a href='#n2'>2</a></p>
+
+ </td>
+ <td class='text' valign='top'>
+<p id='t1' class='stm run hide_run'><span class='nam'>m3a</span> <span class='op'>=</span> <span class='num'>1</span><span class='strut'>&nbsp;</span></p>
+<p id='t2' class='stm run hide_run'><span class='nam'>m3b</span> <span class='op'>=</span> <span class='num'>2</span><span class='strut'>&nbsp;</span></p>
+
+ </td>
+ </tr>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='index.html'>&#xab; index</a> &nbsp; &nbsp; <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_omit_1/main.html b/tests/farm/html/gold_omit_1/main.html
new file mode 100644
index 0000000..0394871
--- /dev/null
+++ b/tests/farm/html/gold_omit_1/main.html
@@ -0,0 +1,101 @@
+<!doctype html PUBLIC "-//W3C//DTD html 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+
+
+ <meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
+ <title>Coverage for main: 100%</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='jquery.isonscreen.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.pyfile_ready);
+ </script>
+</head>
+<body id='pyfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage for <b>main</b> :
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ <h2 class='stats'>
+ 8 statements
+ <span class='run hide_run shortkey_r' onclick='coverage.toggle_lines(this, "run")'>8 run</span>
+ <span class='mis shortkey_m' onclick='coverage.toggle_lines(this, "mis")'>0 missing</span>
+ <span class='exc shortkey_x' onclick='coverage.toggle_lines(this, "exc")'>0 excluded</span>
+
+ </h2>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+<p class='legend'>Hot-keys on this page</p>
+ <div>
+<p class='keyhelp'>
+ <span class='key'>r</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+ <span class='key'>p</span> &nbsp; toggle line displays
+ </p>
+<p class='keyhelp'>
+ <span class='key'>j</span>
+ <span class='key'>k</span> &nbsp; next/prev highlighted chunk
+ </p>
+<p class='keyhelp'>
+ <span class='key'>0</span> &nbsp; (zero) top of page
+ </p>
+<p class='keyhelp'>
+ <span class='key'>1</span> &nbsp; (one) first highlighted chunk
+ </p>
+ </div>
+</div>
+
+<div id='source'>
+ <table cellspacing='0' cellpadding='0'>
+ <tr>
+ <td class='linenos' valign='top'>
+<p id='n1' class='stm run hide_run'><a href='#n1'>1</a></p>
+<p id='n2' class='stm run hide_run'><a href='#n2'>2</a></p>
+<p id='n3' class='stm run hide_run'><a href='#n3'>3</a></p>
+<p id='n4' class='pln'><a href='#n4'>4</a></p>
+<p id='n5' class='stm run hide_run'><a href='#n5'>5</a></p>
+<p id='n6' class='stm run hide_run'><a href='#n6'>6</a></p>
+<p id='n7' class='pln'><a href='#n7'>7</a></p>
+<p id='n8' class='stm run hide_run'><a href='#n8'>8</a></p>
+<p id='n9' class='stm run hide_run'><a href='#n9'>9</a></p>
+<p id='n10' class='stm run hide_run'><a href='#n10'>10</a></p>
+
+ </td>
+ <td class='text' valign='top'>
+<p id='t1' class='stm run hide_run'><span class='key'>import</span> <span class='nam'>m1</span><span class='strut'>&nbsp;</span></p>
+<p id='t2' class='stm run hide_run'><span class='key'>import</span> <span class='nam'>m2</span><span class='strut'>&nbsp;</span></p>
+<p id='t3' class='stm run hide_run'><span class='key'>import</span> <span class='nam'>m3</span><span class='strut'>&nbsp;</span></p>
+<p id='t4' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t5' class='stm run hide_run'><span class='nam'>a</span> <span class='op'>=</span> <span class='num'>5</span><span class='strut'>&nbsp;</span></p>
+<p id='t6' class='stm run hide_run'><span class='nam'>b</span> <span class='op'>=</span> <span class='num'>6</span><span class='strut'>&nbsp;</span></p>
+<p id='t7' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t8' class='stm run hide_run'><span class='key'>assert</span> <span class='nam'>m1</span><span class='op'>.</span><span class='nam'>m1a</span> <span class='op'>==</span> <span class='num'>1</span><span class='strut'>&nbsp;</span></p>
+<p id='t9' class='stm run hide_run'><span class='key'>assert</span> <span class='nam'>m2</span><span class='op'>.</span><span class='nam'>m2a</span> <span class='op'>==</span> <span class='num'>1</span><span class='strut'>&nbsp;</span></p>
+<p id='t10' class='stm run hide_run'><span class='key'>assert</span> <span class='nam'>m3</span><span class='op'>.</span><span class='nam'>m3a</span> <span class='op'>==</span> <span class='num'>1</span><span class='strut'>&nbsp;</span></p>
+
+ </td>
+ </tr>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='index.html'>&#xab; index</a> &nbsp; &nbsp; <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_omit_2/index.html b/tests/farm/html/gold_omit_2/index.html
new file mode 100644
index 0000000..3ce5bad
--- /dev/null
+++ b/tests/farm/html/gold_omit_2/index.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+ <title>Coverage report</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.tablesorter.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.index_ready);
+ </script>
+</head>
+<body id='indexfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage report:
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+ <p class='legend'>Hot-keys on this page</p>
+ <div>
+ <p class='keyhelp'>
+ <span class='key'>n</span>
+ <span class='key'>s</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+
+ <span class='key'>c</span> &nbsp; change column sorting
+ </p>
+ </div>
+</div>
+
+<div id='index'>
+ <table class='index'>
+ <thead>
+
+ <tr class='tablehead' title='Click to sort'>
+ <th class='name left headerSortDown shortkey_n'>Module</th>
+ <th class='shortkey_s'>statements</th>
+ <th class='shortkey_m'>missing</th>
+ <th class='shortkey_x'>excluded</th>
+
+ <th class='right shortkey_c'>coverage</th>
+ </tr>
+ </thead>
+
+ <tfoot>
+ <tr class='total'>
+ <td class='name left'>Total</td>
+ <td>12</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+ </tfoot>
+ <tbody>
+
+ <tr class='file'>
+ <td class='name left'><a href='m2.html'>m2</a></td>
+ <td>2</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+
+ <tr class='file'>
+ <td class='name left'><a href='m3.html'>m3</a></td>
+ <td>2</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+
+ <tr class='file'>
+ <td class='name left'><a href='main.html'>main</a></td>
+ <td>8</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+
+ </tbody>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_omit_2/m2.html b/tests/farm/html/gold_omit_2/m2.html
new file mode 100644
index 0000000..d75a5ba
--- /dev/null
+++ b/tests/farm/html/gold_omit_2/m2.html
@@ -0,0 +1,85 @@
+<!doctype html PUBLIC "-//W3C//DTD html 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+
+
+ <meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
+ <title>Coverage for m2: 100%</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='jquery.isonscreen.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.pyfile_ready);
+ </script>
+</head>
+<body id='pyfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage for <b>m2</b> :
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ <h2 class='stats'>
+ 2 statements
+ <span class='run hide_run shortkey_r' onclick='coverage.toggle_lines(this, "run")'>2 run</span>
+ <span class='mis shortkey_m' onclick='coverage.toggle_lines(this, "mis")'>0 missing</span>
+ <span class='exc shortkey_x' onclick='coverage.toggle_lines(this, "exc")'>0 excluded</span>
+
+ </h2>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+<p class='legend'>Hot-keys on this page</p>
+ <div>
+<p class='keyhelp'>
+ <span class='key'>r</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+ <span class='key'>p</span> &nbsp; toggle line displays
+ </p>
+<p class='keyhelp'>
+ <span class='key'>j</span>
+ <span class='key'>k</span> &nbsp; next/prev highlighted chunk
+ </p>
+<p class='keyhelp'>
+ <span class='key'>0</span> &nbsp; (zero) top of page
+ </p>
+<p class='keyhelp'>
+ <span class='key'>1</span> &nbsp; (one) first highlighted chunk
+ </p>
+ </div>
+</div>
+
+<div id='source'>
+ <table cellspacing='0' cellpadding='0'>
+ <tr>
+ <td class='linenos' valign='top'>
+<p id='n1' class='stm run hide_run'><a href='#n1'>1</a></p>
+<p id='n2' class='stm run hide_run'><a href='#n2'>2</a></p>
+
+ </td>
+ <td class='text' valign='top'>
+<p id='t1' class='stm run hide_run'><span class='nam'>m2a</span> <span class='op'>=</span> <span class='num'>1</span><span class='strut'>&nbsp;</span></p>
+<p id='t2' class='stm run hide_run'><span class='nam'>m2b</span> <span class='op'>=</span> <span class='num'>2</span><span class='strut'>&nbsp;</span></p>
+
+ </td>
+ </tr>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='index.html'>&#xab; index</a> &nbsp; &nbsp; <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_omit_2/m3.html b/tests/farm/html/gold_omit_2/m3.html
new file mode 100644
index 0000000..bd99138
--- /dev/null
+++ b/tests/farm/html/gold_omit_2/m3.html
@@ -0,0 +1,85 @@
+<!doctype html PUBLIC "-//W3C//DTD html 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+
+
+ <meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
+ <title>Coverage for m3: 100%</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='jquery.isonscreen.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.pyfile_ready);
+ </script>
+</head>
+<body id='pyfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage for <b>m3</b> :
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ <h2 class='stats'>
+ 2 statements
+ <span class='run hide_run shortkey_r' onclick='coverage.toggle_lines(this, "run")'>2 run</span>
+ <span class='mis shortkey_m' onclick='coverage.toggle_lines(this, "mis")'>0 missing</span>
+ <span class='exc shortkey_x' onclick='coverage.toggle_lines(this, "exc")'>0 excluded</span>
+
+ </h2>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+<p class='legend'>Hot-keys on this page</p>
+ <div>
+<p class='keyhelp'>
+ <span class='key'>r</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+ <span class='key'>p</span> &nbsp; toggle line displays
+ </p>
+<p class='keyhelp'>
+ <span class='key'>j</span>
+ <span class='key'>k</span> &nbsp; next/prev highlighted chunk
+ </p>
+<p class='keyhelp'>
+ <span class='key'>0</span> &nbsp; (zero) top of page
+ </p>
+<p class='keyhelp'>
+ <span class='key'>1</span> &nbsp; (one) first highlighted chunk
+ </p>
+ </div>
+</div>
+
+<div id='source'>
+ <table cellspacing='0' cellpadding='0'>
+ <tr>
+ <td class='linenos' valign='top'>
+<p id='n1' class='stm run hide_run'><a href='#n1'>1</a></p>
+<p id='n2' class='stm run hide_run'><a href='#n2'>2</a></p>
+
+ </td>
+ <td class='text' valign='top'>
+<p id='t1' class='stm run hide_run'><span class='nam'>m3a</span> <span class='op'>=</span> <span class='num'>1</span><span class='strut'>&nbsp;</span></p>
+<p id='t2' class='stm run hide_run'><span class='nam'>m3b</span> <span class='op'>=</span> <span class='num'>2</span><span class='strut'>&nbsp;</span></p>
+
+ </td>
+ </tr>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='index.html'>&#xab; index</a> &nbsp; &nbsp; <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_omit_2/main.html b/tests/farm/html/gold_omit_2/main.html
new file mode 100644
index 0000000..0394871
--- /dev/null
+++ b/tests/farm/html/gold_omit_2/main.html
@@ -0,0 +1,101 @@
+<!doctype html PUBLIC "-//W3C//DTD html 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+
+
+ <meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
+ <title>Coverage for main: 100%</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='jquery.isonscreen.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.pyfile_ready);
+ </script>
+</head>
+<body id='pyfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage for <b>main</b> :
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ <h2 class='stats'>
+ 8 statements
+ <span class='run hide_run shortkey_r' onclick='coverage.toggle_lines(this, "run")'>8 run</span>
+ <span class='mis shortkey_m' onclick='coverage.toggle_lines(this, "mis")'>0 missing</span>
+ <span class='exc shortkey_x' onclick='coverage.toggle_lines(this, "exc")'>0 excluded</span>
+
+ </h2>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+<p class='legend'>Hot-keys on this page</p>
+ <div>
+<p class='keyhelp'>
+ <span class='key'>r</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+ <span class='key'>p</span> &nbsp; toggle line displays
+ </p>
+<p class='keyhelp'>
+ <span class='key'>j</span>
+ <span class='key'>k</span> &nbsp; next/prev highlighted chunk
+ </p>
+<p class='keyhelp'>
+ <span class='key'>0</span> &nbsp; (zero) top of page
+ </p>
+<p class='keyhelp'>
+ <span class='key'>1</span> &nbsp; (one) first highlighted chunk
+ </p>
+ </div>
+</div>
+
+<div id='source'>
+ <table cellspacing='0' cellpadding='0'>
+ <tr>
+ <td class='linenos' valign='top'>
+<p id='n1' class='stm run hide_run'><a href='#n1'>1</a></p>
+<p id='n2' class='stm run hide_run'><a href='#n2'>2</a></p>
+<p id='n3' class='stm run hide_run'><a href='#n3'>3</a></p>
+<p id='n4' class='pln'><a href='#n4'>4</a></p>
+<p id='n5' class='stm run hide_run'><a href='#n5'>5</a></p>
+<p id='n6' class='stm run hide_run'><a href='#n6'>6</a></p>
+<p id='n7' class='pln'><a href='#n7'>7</a></p>
+<p id='n8' class='stm run hide_run'><a href='#n8'>8</a></p>
+<p id='n9' class='stm run hide_run'><a href='#n9'>9</a></p>
+<p id='n10' class='stm run hide_run'><a href='#n10'>10</a></p>
+
+ </td>
+ <td class='text' valign='top'>
+<p id='t1' class='stm run hide_run'><span class='key'>import</span> <span class='nam'>m1</span><span class='strut'>&nbsp;</span></p>
+<p id='t2' class='stm run hide_run'><span class='key'>import</span> <span class='nam'>m2</span><span class='strut'>&nbsp;</span></p>
+<p id='t3' class='stm run hide_run'><span class='key'>import</span> <span class='nam'>m3</span><span class='strut'>&nbsp;</span></p>
+<p id='t4' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t5' class='stm run hide_run'><span class='nam'>a</span> <span class='op'>=</span> <span class='num'>5</span><span class='strut'>&nbsp;</span></p>
+<p id='t6' class='stm run hide_run'><span class='nam'>b</span> <span class='op'>=</span> <span class='num'>6</span><span class='strut'>&nbsp;</span></p>
+<p id='t7' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t8' class='stm run hide_run'><span class='key'>assert</span> <span class='nam'>m1</span><span class='op'>.</span><span class='nam'>m1a</span> <span class='op'>==</span> <span class='num'>1</span><span class='strut'>&nbsp;</span></p>
+<p id='t9' class='stm run hide_run'><span class='key'>assert</span> <span class='nam'>m2</span><span class='op'>.</span><span class='nam'>m2a</span> <span class='op'>==</span> <span class='num'>1</span><span class='strut'>&nbsp;</span></p>
+<p id='t10' class='stm run hide_run'><span class='key'>assert</span> <span class='nam'>m3</span><span class='op'>.</span><span class='nam'>m3a</span> <span class='op'>==</span> <span class='num'>1</span><span class='strut'>&nbsp;</span></p>
+
+ </td>
+ </tr>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='index.html'>&#xab; index</a> &nbsp; &nbsp; <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_omit_3/index.html b/tests/farm/html/gold_omit_3/index.html
new file mode 100644
index 0000000..fb826bf
--- /dev/null
+++ b/tests/farm/html/gold_omit_3/index.html
@@ -0,0 +1,98 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+ <title>Coverage report</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.tablesorter.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.index_ready);
+ </script>
+</head>
+<body id='indexfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage report:
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+ <p class='legend'>Hot-keys on this page</p>
+ <div>
+ <p class='keyhelp'>
+ <span class='key'>n</span>
+ <span class='key'>s</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+
+ <span class='key'>c</span> &nbsp; change column sorting
+ </p>
+ </div>
+</div>
+
+<div id='index'>
+ <table class='index'>
+ <thead>
+
+ <tr class='tablehead' title='Click to sort'>
+ <th class='name left headerSortDown shortkey_n'>Module</th>
+ <th class='shortkey_s'>statements</th>
+ <th class='shortkey_m'>missing</th>
+ <th class='shortkey_x'>excluded</th>
+
+ <th class='right shortkey_c'>coverage</th>
+ </tr>
+ </thead>
+
+ <tfoot>
+ <tr class='total'>
+ <td class='name left'>Total</td>
+ <td>10</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+ </tfoot>
+ <tbody>
+
+ <tr class='file'>
+ <td class='name left'><a href='m3.html'>m3</a></td>
+ <td>2</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+
+ <tr class='file'>
+ <td class='name left'><a href='main.html'>main</a></td>
+ <td>8</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+
+ </tbody>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_omit_3/m3.html b/tests/farm/html/gold_omit_3/m3.html
new file mode 100644
index 0000000..bd99138
--- /dev/null
+++ b/tests/farm/html/gold_omit_3/m3.html
@@ -0,0 +1,85 @@
+<!doctype html PUBLIC "-//W3C//DTD html 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+
+
+ <meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
+ <title>Coverage for m3: 100%</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='jquery.isonscreen.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.pyfile_ready);
+ </script>
+</head>
+<body id='pyfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage for <b>m3</b> :
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ <h2 class='stats'>
+ 2 statements
+ <span class='run hide_run shortkey_r' onclick='coverage.toggle_lines(this, "run")'>2 run</span>
+ <span class='mis shortkey_m' onclick='coverage.toggle_lines(this, "mis")'>0 missing</span>
+ <span class='exc shortkey_x' onclick='coverage.toggle_lines(this, "exc")'>0 excluded</span>
+
+ </h2>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+<p class='legend'>Hot-keys on this page</p>
+ <div>
+<p class='keyhelp'>
+ <span class='key'>r</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+ <span class='key'>p</span> &nbsp; toggle line displays
+ </p>
+<p class='keyhelp'>
+ <span class='key'>j</span>
+ <span class='key'>k</span> &nbsp; next/prev highlighted chunk
+ </p>
+<p class='keyhelp'>
+ <span class='key'>0</span> &nbsp; (zero) top of page
+ </p>
+<p class='keyhelp'>
+ <span class='key'>1</span> &nbsp; (one) first highlighted chunk
+ </p>
+ </div>
+</div>
+
+<div id='source'>
+ <table cellspacing='0' cellpadding='0'>
+ <tr>
+ <td class='linenos' valign='top'>
+<p id='n1' class='stm run hide_run'><a href='#n1'>1</a></p>
+<p id='n2' class='stm run hide_run'><a href='#n2'>2</a></p>
+
+ </td>
+ <td class='text' valign='top'>
+<p id='t1' class='stm run hide_run'><span class='nam'>m3a</span> <span class='op'>=</span> <span class='num'>1</span><span class='strut'>&nbsp;</span></p>
+<p id='t2' class='stm run hide_run'><span class='nam'>m3b</span> <span class='op'>=</span> <span class='num'>2</span><span class='strut'>&nbsp;</span></p>
+
+ </td>
+ </tr>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='index.html'>&#xab; index</a> &nbsp; &nbsp; <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_omit_3/main.html b/tests/farm/html/gold_omit_3/main.html
new file mode 100644
index 0000000..0394871
--- /dev/null
+++ b/tests/farm/html/gold_omit_3/main.html
@@ -0,0 +1,101 @@
+<!doctype html PUBLIC "-//W3C//DTD html 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+
+
+ <meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
+ <title>Coverage for main: 100%</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='jquery.isonscreen.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.pyfile_ready);
+ </script>
+</head>
+<body id='pyfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage for <b>main</b> :
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ <h2 class='stats'>
+ 8 statements
+ <span class='run hide_run shortkey_r' onclick='coverage.toggle_lines(this, "run")'>8 run</span>
+ <span class='mis shortkey_m' onclick='coverage.toggle_lines(this, "mis")'>0 missing</span>
+ <span class='exc shortkey_x' onclick='coverage.toggle_lines(this, "exc")'>0 excluded</span>
+
+ </h2>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+<p class='legend'>Hot-keys on this page</p>
+ <div>
+<p class='keyhelp'>
+ <span class='key'>r</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+ <span class='key'>p</span> &nbsp; toggle line displays
+ </p>
+<p class='keyhelp'>
+ <span class='key'>j</span>
+ <span class='key'>k</span> &nbsp; next/prev highlighted chunk
+ </p>
+<p class='keyhelp'>
+ <span class='key'>0</span> &nbsp; (zero) top of page
+ </p>
+<p class='keyhelp'>
+ <span class='key'>1</span> &nbsp; (one) first highlighted chunk
+ </p>
+ </div>
+</div>
+
+<div id='source'>
+ <table cellspacing='0' cellpadding='0'>
+ <tr>
+ <td class='linenos' valign='top'>
+<p id='n1' class='stm run hide_run'><a href='#n1'>1</a></p>
+<p id='n2' class='stm run hide_run'><a href='#n2'>2</a></p>
+<p id='n3' class='stm run hide_run'><a href='#n3'>3</a></p>
+<p id='n4' class='pln'><a href='#n4'>4</a></p>
+<p id='n5' class='stm run hide_run'><a href='#n5'>5</a></p>
+<p id='n6' class='stm run hide_run'><a href='#n6'>6</a></p>
+<p id='n7' class='pln'><a href='#n7'>7</a></p>
+<p id='n8' class='stm run hide_run'><a href='#n8'>8</a></p>
+<p id='n9' class='stm run hide_run'><a href='#n9'>9</a></p>
+<p id='n10' class='stm run hide_run'><a href='#n10'>10</a></p>
+
+ </td>
+ <td class='text' valign='top'>
+<p id='t1' class='stm run hide_run'><span class='key'>import</span> <span class='nam'>m1</span><span class='strut'>&nbsp;</span></p>
+<p id='t2' class='stm run hide_run'><span class='key'>import</span> <span class='nam'>m2</span><span class='strut'>&nbsp;</span></p>
+<p id='t3' class='stm run hide_run'><span class='key'>import</span> <span class='nam'>m3</span><span class='strut'>&nbsp;</span></p>
+<p id='t4' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t5' class='stm run hide_run'><span class='nam'>a</span> <span class='op'>=</span> <span class='num'>5</span><span class='strut'>&nbsp;</span></p>
+<p id='t6' class='stm run hide_run'><span class='nam'>b</span> <span class='op'>=</span> <span class='num'>6</span><span class='strut'>&nbsp;</span></p>
+<p id='t7' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t8' class='stm run hide_run'><span class='key'>assert</span> <span class='nam'>m1</span><span class='op'>.</span><span class='nam'>m1a</span> <span class='op'>==</span> <span class='num'>1</span><span class='strut'>&nbsp;</span></p>
+<p id='t9' class='stm run hide_run'><span class='key'>assert</span> <span class='nam'>m2</span><span class='op'>.</span><span class='nam'>m2a</span> <span class='op'>==</span> <span class='num'>1</span><span class='strut'>&nbsp;</span></p>
+<p id='t10' class='stm run hide_run'><span class='key'>assert</span> <span class='nam'>m3</span><span class='op'>.</span><span class='nam'>m3a</span> <span class='op'>==</span> <span class='num'>1</span><span class='strut'>&nbsp;</span></p>
+
+ </td>
+ </tr>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='index.html'>&#xab; index</a> &nbsp; &nbsp; <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_omit_4/index.html b/tests/farm/html/gold_omit_4/index.html
new file mode 100644
index 0000000..e437cf1
--- /dev/null
+++ b/tests/farm/html/gold_omit_4/index.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+ <title>Coverage report</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.tablesorter.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.index_ready);
+ </script>
+</head>
+<body id='indexfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage report:
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+ <p class='legend'>Hot-keys on this page</p>
+ <div>
+ <p class='keyhelp'>
+ <span class='key'>n</span>
+ <span class='key'>s</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+
+ <span class='key'>c</span> &nbsp; change column sorting
+ </p>
+ </div>
+</div>
+
+<div id='index'>
+ <table class='index'>
+ <thead>
+
+ <tr class='tablehead' title='Click to sort'>
+ <th class='name left headerSortDown shortkey_n'>Module</th>
+ <th class='shortkey_s'>statements</th>
+ <th class='shortkey_m'>missing</th>
+ <th class='shortkey_x'>excluded</th>
+
+ <th class='right shortkey_c'>coverage</th>
+ </tr>
+ </thead>
+
+ <tfoot>
+ <tr class='total'>
+ <td class='name left'>Total</td>
+ <td>12</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+ </tfoot>
+ <tbody>
+
+ <tr class='file'>
+ <td class='name left'><a href='m1.html'>m1</a></td>
+ <td>2</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+
+ <tr class='file'>
+ <td class='name left'><a href='m3.html'>m3</a></td>
+ <td>2</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+
+ <tr class='file'>
+ <td class='name left'><a href='main.html'>main</a></td>
+ <td>8</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+
+ </tbody>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_omit_4/m1.html b/tests/farm/html/gold_omit_4/m1.html
new file mode 100644
index 0000000..62ba1e0
--- /dev/null
+++ b/tests/farm/html/gold_omit_4/m1.html
@@ -0,0 +1,85 @@
+<!doctype html PUBLIC "-//W3C//DTD html 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+
+
+ <meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
+ <title>Coverage for m1: 100%</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='jquery.isonscreen.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.pyfile_ready);
+ </script>
+</head>
+<body id='pyfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage for <b>m1</b> :
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ <h2 class='stats'>
+ 2 statements
+ <span class='run hide_run shortkey_r' onclick='coverage.toggle_lines(this, "run")'>2 run</span>
+ <span class='mis shortkey_m' onclick='coverage.toggle_lines(this, "mis")'>0 missing</span>
+ <span class='exc shortkey_x' onclick='coverage.toggle_lines(this, "exc")'>0 excluded</span>
+
+ </h2>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+<p class='legend'>Hot-keys on this page</p>
+ <div>
+<p class='keyhelp'>
+ <span class='key'>r</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+ <span class='key'>p</span> &nbsp; toggle line displays
+ </p>
+<p class='keyhelp'>
+ <span class='key'>j</span>
+ <span class='key'>k</span> &nbsp; next/prev highlighted chunk
+ </p>
+<p class='keyhelp'>
+ <span class='key'>0</span> &nbsp; (zero) top of page
+ </p>
+<p class='keyhelp'>
+ <span class='key'>1</span> &nbsp; (one) first highlighted chunk
+ </p>
+ </div>
+</div>
+
+<div id='source'>
+ <table cellspacing='0' cellpadding='0'>
+ <tr>
+ <td class='linenos' valign='top'>
+<p id='n1' class='stm run hide_run'><a href='#n1'>1</a></p>
+<p id='n2' class='stm run hide_run'><a href='#n2'>2</a></p>
+
+ </td>
+ <td class='text' valign='top'>
+<p id='t1' class='stm run hide_run'><span class='nam'>m1a</span> <span class='op'>=</span> <span class='num'>1</span><span class='strut'>&nbsp;</span></p>
+<p id='t2' class='stm run hide_run'><span class='nam'>m1b</span> <span class='op'>=</span> <span class='num'>2</span><span class='strut'>&nbsp;</span></p>
+
+ </td>
+ </tr>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='index.html'>&#xab; index</a> &nbsp; &nbsp; <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_omit_4/m3.html b/tests/farm/html/gold_omit_4/m3.html
new file mode 100644
index 0000000..bd99138
--- /dev/null
+++ b/tests/farm/html/gold_omit_4/m3.html
@@ -0,0 +1,85 @@
+<!doctype html PUBLIC "-//W3C//DTD html 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+
+
+ <meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
+ <title>Coverage for m3: 100%</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='jquery.isonscreen.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.pyfile_ready);
+ </script>
+</head>
+<body id='pyfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage for <b>m3</b> :
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ <h2 class='stats'>
+ 2 statements
+ <span class='run hide_run shortkey_r' onclick='coverage.toggle_lines(this, "run")'>2 run</span>
+ <span class='mis shortkey_m' onclick='coverage.toggle_lines(this, "mis")'>0 missing</span>
+ <span class='exc shortkey_x' onclick='coverage.toggle_lines(this, "exc")'>0 excluded</span>
+
+ </h2>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+<p class='legend'>Hot-keys on this page</p>
+ <div>
+<p class='keyhelp'>
+ <span class='key'>r</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+ <span class='key'>p</span> &nbsp; toggle line displays
+ </p>
+<p class='keyhelp'>
+ <span class='key'>j</span>
+ <span class='key'>k</span> &nbsp; next/prev highlighted chunk
+ </p>
+<p class='keyhelp'>
+ <span class='key'>0</span> &nbsp; (zero) top of page
+ </p>
+<p class='keyhelp'>
+ <span class='key'>1</span> &nbsp; (one) first highlighted chunk
+ </p>
+ </div>
+</div>
+
+<div id='source'>
+ <table cellspacing='0' cellpadding='0'>
+ <tr>
+ <td class='linenos' valign='top'>
+<p id='n1' class='stm run hide_run'><a href='#n1'>1</a></p>
+<p id='n2' class='stm run hide_run'><a href='#n2'>2</a></p>
+
+ </td>
+ <td class='text' valign='top'>
+<p id='t1' class='stm run hide_run'><span class='nam'>m3a</span> <span class='op'>=</span> <span class='num'>1</span><span class='strut'>&nbsp;</span></p>
+<p id='t2' class='stm run hide_run'><span class='nam'>m3b</span> <span class='op'>=</span> <span class='num'>2</span><span class='strut'>&nbsp;</span></p>
+
+ </td>
+ </tr>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='index.html'>&#xab; index</a> &nbsp; &nbsp; <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_omit_4/main.html b/tests/farm/html/gold_omit_4/main.html
new file mode 100644
index 0000000..0394871
--- /dev/null
+++ b/tests/farm/html/gold_omit_4/main.html
@@ -0,0 +1,101 @@
+<!doctype html PUBLIC "-//W3C//DTD html 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+
+
+ <meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
+ <title>Coverage for main: 100%</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='jquery.isonscreen.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.pyfile_ready);
+ </script>
+</head>
+<body id='pyfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage for <b>main</b> :
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ <h2 class='stats'>
+ 8 statements
+ <span class='run hide_run shortkey_r' onclick='coverage.toggle_lines(this, "run")'>8 run</span>
+ <span class='mis shortkey_m' onclick='coverage.toggle_lines(this, "mis")'>0 missing</span>
+ <span class='exc shortkey_x' onclick='coverage.toggle_lines(this, "exc")'>0 excluded</span>
+
+ </h2>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+<p class='legend'>Hot-keys on this page</p>
+ <div>
+<p class='keyhelp'>
+ <span class='key'>r</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+ <span class='key'>p</span> &nbsp; toggle line displays
+ </p>
+<p class='keyhelp'>
+ <span class='key'>j</span>
+ <span class='key'>k</span> &nbsp; next/prev highlighted chunk
+ </p>
+<p class='keyhelp'>
+ <span class='key'>0</span> &nbsp; (zero) top of page
+ </p>
+<p class='keyhelp'>
+ <span class='key'>1</span> &nbsp; (one) first highlighted chunk
+ </p>
+ </div>
+</div>
+
+<div id='source'>
+ <table cellspacing='0' cellpadding='0'>
+ <tr>
+ <td class='linenos' valign='top'>
+<p id='n1' class='stm run hide_run'><a href='#n1'>1</a></p>
+<p id='n2' class='stm run hide_run'><a href='#n2'>2</a></p>
+<p id='n3' class='stm run hide_run'><a href='#n3'>3</a></p>
+<p id='n4' class='pln'><a href='#n4'>4</a></p>
+<p id='n5' class='stm run hide_run'><a href='#n5'>5</a></p>
+<p id='n6' class='stm run hide_run'><a href='#n6'>6</a></p>
+<p id='n7' class='pln'><a href='#n7'>7</a></p>
+<p id='n8' class='stm run hide_run'><a href='#n8'>8</a></p>
+<p id='n9' class='stm run hide_run'><a href='#n9'>9</a></p>
+<p id='n10' class='stm run hide_run'><a href='#n10'>10</a></p>
+
+ </td>
+ <td class='text' valign='top'>
+<p id='t1' class='stm run hide_run'><span class='key'>import</span> <span class='nam'>m1</span><span class='strut'>&nbsp;</span></p>
+<p id='t2' class='stm run hide_run'><span class='key'>import</span> <span class='nam'>m2</span><span class='strut'>&nbsp;</span></p>
+<p id='t3' class='stm run hide_run'><span class='key'>import</span> <span class='nam'>m3</span><span class='strut'>&nbsp;</span></p>
+<p id='t4' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t5' class='stm run hide_run'><span class='nam'>a</span> <span class='op'>=</span> <span class='num'>5</span><span class='strut'>&nbsp;</span></p>
+<p id='t6' class='stm run hide_run'><span class='nam'>b</span> <span class='op'>=</span> <span class='num'>6</span><span class='strut'>&nbsp;</span></p>
+<p id='t7' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t8' class='stm run hide_run'><span class='key'>assert</span> <span class='nam'>m1</span><span class='op'>.</span><span class='nam'>m1a</span> <span class='op'>==</span> <span class='num'>1</span><span class='strut'>&nbsp;</span></p>
+<p id='t9' class='stm run hide_run'><span class='key'>assert</span> <span class='nam'>m2</span><span class='op'>.</span><span class='nam'>m2a</span> <span class='op'>==</span> <span class='num'>1</span><span class='strut'>&nbsp;</span></p>
+<p id='t10' class='stm run hide_run'><span class='key'>assert</span> <span class='nam'>m3</span><span class='op'>.</span><span class='nam'>m3a</span> <span class='op'>==</span> <span class='num'>1</span><span class='strut'>&nbsp;</span></p>
+
+ </td>
+ </tr>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='index.html'>&#xab; index</a> &nbsp; &nbsp; <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_omit_5/index.html b/tests/farm/html/gold_omit_5/index.html
new file mode 100644
index 0000000..4bde6b7
--- /dev/null
+++ b/tests/farm/html/gold_omit_5/index.html
@@ -0,0 +1,98 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+ <title>Coverage report</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.tablesorter.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.index_ready);
+ </script>
+</head>
+<body id='indexfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage report:
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+ <p class='legend'>Hot-keys on this page</p>
+ <div>
+ <p class='keyhelp'>
+ <span class='key'>n</span>
+ <span class='key'>s</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+
+ <span class='key'>c</span> &nbsp; change column sorting
+ </p>
+ </div>
+</div>
+
+<div id='index'>
+ <table class='index'>
+ <thead>
+
+ <tr class='tablehead' title='Click to sort'>
+ <th class='name left headerSortDown shortkey_n'>Module</th>
+ <th class='shortkey_s'>statements</th>
+ <th class='shortkey_m'>missing</th>
+ <th class='shortkey_x'>excluded</th>
+
+ <th class='right shortkey_c'>coverage</th>
+ </tr>
+ </thead>
+
+ <tfoot>
+ <tr class='total'>
+ <td class='name left'>Total</td>
+ <td>10</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+ </tfoot>
+ <tbody>
+
+ <tr class='file'>
+ <td class='name left'><a href='m1.html'>m1</a></td>
+ <td>2</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+
+ <tr class='file'>
+ <td class='name left'><a href='main.html'>main</a></td>
+ <td>8</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+
+ </tbody>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_omit_5/m1.html b/tests/farm/html/gold_omit_5/m1.html
new file mode 100644
index 0000000..62ba1e0
--- /dev/null
+++ b/tests/farm/html/gold_omit_5/m1.html
@@ -0,0 +1,85 @@
+<!doctype html PUBLIC "-//W3C//DTD html 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+
+
+ <meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
+ <title>Coverage for m1: 100%</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='jquery.isonscreen.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.pyfile_ready);
+ </script>
+</head>
+<body id='pyfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage for <b>m1</b> :
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ <h2 class='stats'>
+ 2 statements
+ <span class='run hide_run shortkey_r' onclick='coverage.toggle_lines(this, "run")'>2 run</span>
+ <span class='mis shortkey_m' onclick='coverage.toggle_lines(this, "mis")'>0 missing</span>
+ <span class='exc shortkey_x' onclick='coverage.toggle_lines(this, "exc")'>0 excluded</span>
+
+ </h2>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+<p class='legend'>Hot-keys on this page</p>
+ <div>
+<p class='keyhelp'>
+ <span class='key'>r</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+ <span class='key'>p</span> &nbsp; toggle line displays
+ </p>
+<p class='keyhelp'>
+ <span class='key'>j</span>
+ <span class='key'>k</span> &nbsp; next/prev highlighted chunk
+ </p>
+<p class='keyhelp'>
+ <span class='key'>0</span> &nbsp; (zero) top of page
+ </p>
+<p class='keyhelp'>
+ <span class='key'>1</span> &nbsp; (one) first highlighted chunk
+ </p>
+ </div>
+</div>
+
+<div id='source'>
+ <table cellspacing='0' cellpadding='0'>
+ <tr>
+ <td class='linenos' valign='top'>
+<p id='n1' class='stm run hide_run'><a href='#n1'>1</a></p>
+<p id='n2' class='stm run hide_run'><a href='#n2'>2</a></p>
+
+ </td>
+ <td class='text' valign='top'>
+<p id='t1' class='stm run hide_run'><span class='nam'>m1a</span> <span class='op'>=</span> <span class='num'>1</span><span class='strut'>&nbsp;</span></p>
+<p id='t2' class='stm run hide_run'><span class='nam'>m1b</span> <span class='op'>=</span> <span class='num'>2</span><span class='strut'>&nbsp;</span></p>
+
+ </td>
+ </tr>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='index.html'>&#xab; index</a> &nbsp; &nbsp; <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_omit_5/main.html b/tests/farm/html/gold_omit_5/main.html
new file mode 100644
index 0000000..0394871
--- /dev/null
+++ b/tests/farm/html/gold_omit_5/main.html
@@ -0,0 +1,101 @@
+<!doctype html PUBLIC "-//W3C//DTD html 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+
+
+ <meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
+ <title>Coverage for main: 100%</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='jquery.isonscreen.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.pyfile_ready);
+ </script>
+</head>
+<body id='pyfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage for <b>main</b> :
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ <h2 class='stats'>
+ 8 statements
+ <span class='run hide_run shortkey_r' onclick='coverage.toggle_lines(this, "run")'>8 run</span>
+ <span class='mis shortkey_m' onclick='coverage.toggle_lines(this, "mis")'>0 missing</span>
+ <span class='exc shortkey_x' onclick='coverage.toggle_lines(this, "exc")'>0 excluded</span>
+
+ </h2>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+<p class='legend'>Hot-keys on this page</p>
+ <div>
+<p class='keyhelp'>
+ <span class='key'>r</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+ <span class='key'>p</span> &nbsp; toggle line displays
+ </p>
+<p class='keyhelp'>
+ <span class='key'>j</span>
+ <span class='key'>k</span> &nbsp; next/prev highlighted chunk
+ </p>
+<p class='keyhelp'>
+ <span class='key'>0</span> &nbsp; (zero) top of page
+ </p>
+<p class='keyhelp'>
+ <span class='key'>1</span> &nbsp; (one) first highlighted chunk
+ </p>
+ </div>
+</div>
+
+<div id='source'>
+ <table cellspacing='0' cellpadding='0'>
+ <tr>
+ <td class='linenos' valign='top'>
+<p id='n1' class='stm run hide_run'><a href='#n1'>1</a></p>
+<p id='n2' class='stm run hide_run'><a href='#n2'>2</a></p>
+<p id='n3' class='stm run hide_run'><a href='#n3'>3</a></p>
+<p id='n4' class='pln'><a href='#n4'>4</a></p>
+<p id='n5' class='stm run hide_run'><a href='#n5'>5</a></p>
+<p id='n6' class='stm run hide_run'><a href='#n6'>6</a></p>
+<p id='n7' class='pln'><a href='#n7'>7</a></p>
+<p id='n8' class='stm run hide_run'><a href='#n8'>8</a></p>
+<p id='n9' class='stm run hide_run'><a href='#n9'>9</a></p>
+<p id='n10' class='stm run hide_run'><a href='#n10'>10</a></p>
+
+ </td>
+ <td class='text' valign='top'>
+<p id='t1' class='stm run hide_run'><span class='key'>import</span> <span class='nam'>m1</span><span class='strut'>&nbsp;</span></p>
+<p id='t2' class='stm run hide_run'><span class='key'>import</span> <span class='nam'>m2</span><span class='strut'>&nbsp;</span></p>
+<p id='t3' class='stm run hide_run'><span class='key'>import</span> <span class='nam'>m3</span><span class='strut'>&nbsp;</span></p>
+<p id='t4' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t5' class='stm run hide_run'><span class='nam'>a</span> <span class='op'>=</span> <span class='num'>5</span><span class='strut'>&nbsp;</span></p>
+<p id='t6' class='stm run hide_run'><span class='nam'>b</span> <span class='op'>=</span> <span class='num'>6</span><span class='strut'>&nbsp;</span></p>
+<p id='t7' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t8' class='stm run hide_run'><span class='key'>assert</span> <span class='nam'>m1</span><span class='op'>.</span><span class='nam'>m1a</span> <span class='op'>==</span> <span class='num'>1</span><span class='strut'>&nbsp;</span></p>
+<p id='t9' class='stm run hide_run'><span class='key'>assert</span> <span class='nam'>m2</span><span class='op'>.</span><span class='nam'>m2a</span> <span class='op'>==</span> <span class='num'>1</span><span class='strut'>&nbsp;</span></p>
+<p id='t10' class='stm run hide_run'><span class='key'>assert</span> <span class='nam'>m3</span><span class='op'>.</span><span class='nam'>m3a</span> <span class='op'>==</span> <span class='num'>1</span><span class='strut'>&nbsp;</span></p>
+
+ </td>
+ </tr>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='index.html'>&#xab; index</a> &nbsp; &nbsp; <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_other/blah_blah_other.html b/tests/farm/html/gold_other/blah_blah_other.html
new file mode 100644
index 0000000..ab5ae37
--- /dev/null
+++ b/tests/farm/html/gold_other/blah_blah_other.html
@@ -0,0 +1,89 @@
+<!doctype html PUBLIC "-//W3C//DTD html 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+
+
+ <meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
+ <title>Coverage for /home/ned/coverage/trunk/test/farm/html/othersrc/other: 100%</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='jquery.isonscreen.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.pyfile_ready);
+ </script>
+</head>
+<body id='pyfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage for <b>/home/ned/coverage/trunk/test/farm/html/othersrc/other</b> :
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ <h2 class='stats'>
+ 1 statements
+ <span class='run hide_run shortkey_r' onclick='coverage.toggle_lines(this, "run")'>1 run</span>
+ <span class='mis shortkey_m' onclick='coverage.toggle_lines(this, "mis")'>0 missing</span>
+ <span class='exc shortkey_x' onclick='coverage.toggle_lines(this, "exc")'>0 excluded</span>
+
+ </h2>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+<p class='legend'>Hot-keys on this page</p>
+ <div>
+<p class='keyhelp'>
+ <span class='key'>r</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+ <span class='key'>p</span> &nbsp; toggle line displays
+ </p>
+<p class='keyhelp'>
+ <span class='key'>j</span>
+ <span class='key'>k</span> &nbsp; next/prev highlighted chunk
+ </p>
+<p class='keyhelp'>
+ <span class='key'>0</span> &nbsp; (zero) top of page
+ </p>
+<p class='keyhelp'>
+ <span class='key'>1</span> &nbsp; (one) first highlighted chunk
+ </p>
+ </div>
+</div>
+
+<div id='source'>
+ <table cellspacing='0' cellpadding='0'>
+ <tr>
+ <td class='linenos' valign='top'>
+<p id='n1' class='pln'><a href='#n1'>1</a></p>
+<p id='n2' class='pln'><a href='#n2'>2</a></p>
+<p id='n3' class='pln'><a href='#n3'>3</a></p>
+<p id='n4' class='stm run hide_run'><a href='#n4'>4</a></p>
+
+ </td>
+ <td class='text' valign='top'>
+<p id='t1' class='pln'><span class='com'># A file in another directory.&nbsp; We&#39;re checking that it ends up in the</span><span class='strut'>&nbsp;</span></p>
+<p id='t2' class='pln'><span class='com'># HTML report.</span><span class='strut'>&nbsp;</span></p>
+<p id='t3' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t4' class='stm run hide_run'><span class='key'>print</span><span class='op'>(</span><span class='str'>&quot;This is the other src!&quot;</span><span class='op'>)</span><span class='strut'>&nbsp;</span></p>
+
+ </td>
+ </tr>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='index.html'>&#xab; index</a> &nbsp; &nbsp; <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_other/here.html b/tests/farm/html/gold_other/here.html
new file mode 100644
index 0000000..1da5bcd
--- /dev/null
+++ b/tests/farm/html/gold_other/here.html
@@ -0,0 +1,97 @@
+<!doctype html PUBLIC "-//W3C//DTD html 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+
+
+ <meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
+ <title>Coverage for here: 75%</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='jquery.isonscreen.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.pyfile_ready);
+ </script>
+</head>
+<body id='pyfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage for <b>here</b> :
+ <span class='pc_cov'>75%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ <h2 class='stats'>
+ 4 statements
+ <span class='run hide_run shortkey_r' onclick='coverage.toggle_lines(this, "run")'>3 run</span>
+ <span class='mis shortkey_m' onclick='coverage.toggle_lines(this, "mis")'>1 missing</span>
+ <span class='exc shortkey_x' onclick='coverage.toggle_lines(this, "exc")'>0 excluded</span>
+
+ </h2>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+<p class='legend'>Hot-keys on this page</p>
+ <div>
+<p class='keyhelp'>
+ <span class='key'>r</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+ <span class='key'>p</span> &nbsp; toggle line displays
+ </p>
+<p class='keyhelp'>
+ <span class='key'>j</span>
+ <span class='key'>k</span> &nbsp; next/prev highlighted chunk
+ </p>
+<p class='keyhelp'>
+ <span class='key'>0</span> &nbsp; (zero) top of page
+ </p>
+<p class='keyhelp'>
+ <span class='key'>1</span> &nbsp; (one) first highlighted chunk
+ </p>
+ </div>
+</div>
+
+<div id='source'>
+ <table cellspacing='0' cellpadding='0'>
+ <tr>
+ <td class='linenos' valign='top'>
+<p id='n1' class='pln'><a href='#n1'>1</a></p>
+<p id='n2' class='pln'><a href='#n2'>2</a></p>
+<p id='n3' class='stm run hide_run'><a href='#n3'>3</a></p>
+<p id='n4' class='pln'><a href='#n4'>4</a></p>
+<p id='n5' class='stm run hide_run'><a href='#n5'>5</a></p>
+<p id='n6' class='stm run hide_run'><a href='#n6'>6</a></p>
+<p id='n7' class='pln'><a href='#n7'>7</a></p>
+<p id='n8' class='stm mis'><a href='#n8'>8</a></p>
+
+ </td>
+ <td class='text' valign='top'>
+<p id='t1' class='pln'><span class='com'># A test file for HTML reporting by coverage.</span><span class='strut'>&nbsp;</span></p>
+<p id='t2' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t3' class='stm run hide_run'><span class='key'>import</span> <span class='nam'>other</span><span class='strut'>&nbsp;</span></p>
+<p id='t4' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t5' class='stm run hide_run'><span class='key'>if</span> <span class='num'>1</span> <span class='op'>&lt;</span> <span class='num'>2</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
+<p id='t6' class='stm run hide_run'>&nbsp; &nbsp; <span class='nam'>h</span> <span class='op'>=</span> <span class='num'>3</span><span class='strut'>&nbsp;</span></p>
+<p id='t7' class='pln'><span class='key'>else</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
+<p id='t8' class='stm mis'>&nbsp; &nbsp; <span class='nam'>h</span> <span class='op'>=</span> <span class='num'>4</span><span class='strut'>&nbsp;</span></p>
+
+ </td>
+ </tr>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='index.html'>&#xab; index</a> &nbsp; &nbsp; <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_other/index.html b/tests/farm/html/gold_other/index.html
new file mode 100644
index 0000000..7665cfe
--- /dev/null
+++ b/tests/farm/html/gold_other/index.html
@@ -0,0 +1,98 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+ <title>Coverage report</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.tablesorter.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.index_ready);
+ </script>
+</head>
+<body id='indexfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage report:
+ <span class='pc_cov'>80%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+ <p class='legend'>Hot-keys on this page</p>
+ <div>
+ <p class='keyhelp'>
+ <span class='key'>n</span>
+ <span class='key'>s</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+
+ <span class='key'>c</span> &nbsp; change column sorting
+ </p>
+ </div>
+</div>
+
+<div id='index'>
+ <table class='index'>
+ <thead>
+
+ <tr class='tablehead' title='Click to sort'>
+ <th class='name left headerSortDown shortkey_n'>Module</th>
+ <th class='shortkey_s'>statements</th>
+ <th class='shortkey_m'>missing</th>
+ <th class='shortkey_x'>excluded</th>
+
+ <th class='right shortkey_c'>coverage</th>
+ </tr>
+ </thead>
+
+ <tfoot>
+ <tr class='total'>
+ <td class='name left'>Total</td>
+ <td>5</td>
+ <td>1</td>
+ <td>0</td>
+
+ <td class='right'>80%</td>
+ </tr>
+ </tfoot>
+ <tbody>
+
+ <tr class='file'>
+ <td class='name left'><a href='_home_ned_coverage_trunk_test_farm_html_othersrc_other.html'>/home/ned/coverage/trunk/test/farm/html/othersrc/other</a></td>
+ <td>1</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+
+ <tr class='file'>
+ <td class='name left'><a href='here.html'>here</a></td>
+ <td>4</td>
+ <td>1</td>
+ <td>0</td>
+
+ <td class='right'>75%</td>
+ </tr>
+
+ </tbody>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_partial/index.html b/tests/farm/html/gold_partial/index.html
new file mode 100644
index 0000000..5556150
--- /dev/null
+++ b/tests/farm/html/gold_partial/index.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+ <title>Coverage report</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.tablesorter.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.index_ready);
+ </script>
+</head>
+<body id='indexfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage report:
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+ <p class='legend'>Hot-keys on this page</p>
+ <div>
+ <p class='keyhelp'>
+ <span class='key'>n</span>
+ <span class='key'>s</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+
+ <span class='key'>b</span>
+ <span class='key'>p</span>
+
+ <span class='key'>c</span> &nbsp; change column sorting
+ </p>
+ </div>
+</div>
+
+<div id='index'>
+ <table class='index'>
+ <thead>
+
+ <tr class='tablehead' title='Click to sort'>
+ <th class='name left headerSortDown shortkey_n'>Module</th>
+ <th class='shortkey_s'>statements</th>
+ <th class='shortkey_m'>missing</th>
+ <th class='shortkey_x'>excluded</th>
+
+ <th class='shortkey_b'>branches</th>
+ <th class='shortkey_p'>partial</th>
+
+ <th class='right shortkey_c'>coverage</th>
+ </tr>
+ </thead>
+
+ <tfoot>
+ <tr class='total'>
+ <td class='name left'>Total</td>
+ <td>8</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td>6</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+ </tfoot>
+ <tbody>
+
+ <tr class='file'>
+ <td class='name left'><a href='partial.html'>partial</a></td>
+ <td>8</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td>6</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+
+ </tbody>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_partial/partial.html b/tests/farm/html/gold_partial/partial.html
new file mode 100644
index 0000000..b9640ce
--- /dev/null
+++ b/tests/farm/html/gold_partial/partial.html
@@ -0,0 +1,121 @@
+<!doctype html PUBLIC "-//W3C//DTD html 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+
+
+ <meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
+ <title>Coverage for partial: 100%</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='jquery.isonscreen.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.pyfile_ready);
+ </script>
+</head>
+<body id='pyfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage for <b>partial</b> :
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ <h2 class='stats'>
+ 8 statements
+ <span class='run hide_run shortkey_r' onclick='coverage.toggle_lines(this, "run")'>8 run</span>
+ <span class='mis shortkey_m' onclick='coverage.toggle_lines(this, "mis")'>0 missing</span>
+ <span class='exc shortkey_x' onclick='coverage.toggle_lines(this, "exc")'>0 excluded</span>
+
+ <span class='par run hide_run shortkey_p' onclick='coverage.toggle_lines(this, "par")'>0 partial</span>
+
+ </h2>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+<p class='legend'>Hot-keys on this page</p>
+ <div>
+<p class='keyhelp'>
+ <span class='key'>r</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+ <span class='key'>p</span> &nbsp; toggle line displays
+ </p>
+<p class='keyhelp'>
+ <span class='key'>j</span>
+ <span class='key'>k</span> &nbsp; next/prev highlighted chunk
+ </p>
+<p class='keyhelp'>
+ <span class='key'>0</span> &nbsp; (zero) top of page
+ </p>
+<p class='keyhelp'>
+ <span class='key'>1</span> &nbsp; (one) first highlighted chunk
+ </p>
+ </div>
+</div>
+
+<div id='source'>
+ <table cellspacing='0' cellpadding='0'>
+ <tr>
+ <td class='linenos' valign='top'>
+<p id='n1' class='pln'><a href='#n1'>1</a></p>
+<p id='n2' class='pln'><a href='#n2'>2</a></p>
+<p id='n3' class='stm run hide_run'><a href='#n3'>3</a></p>
+<p id='n4' class='pln'><a href='#n4'>4</a></p>
+<p id='n5' class='stm run hide_run'><a href='#n5'>5</a></p>
+<p id='n6' class='stm run hide_run'><a href='#n6'>6</a></p>
+<p id='n7' class='pln'><a href='#n7'>7</a></p>
+<p id='n8' class='stm run hide_run'><a href='#n8'>8</a></p>
+<p id='n9' class='stm run hide_run'><a href='#n9'>9</a></p>
+<p id='n10' class='pln'><a href='#n10'>10</a></p>
+<p id='n11' class='stm run hide_run'><a href='#n11'>11</a></p>
+<p id='n12' class='stm run hide_run'><a href='#n12'>12</a></p>
+<p id='n13' class='pln'><a href='#n13'>13</a></p>
+<p id='n14' class='pln'><a href='#n14'>14</a></p>
+<p id='n15' class='pln'><a href='#n15'>15</a></p>
+<p id='n16' class='pln'><a href='#n16'>16</a></p>
+<p id='n17' class='pln'><a href='#n17'>17</a></p>
+<p id='n18' class='stm run hide_run'><a href='#n18'>18</a></p>
+<p id='n19' class='pln'><a href='#n19'>19</a></p>
+
+ </td>
+ <td class='text' valign='top'>
+<p id='t1' class='pln'><span class='com'># partial branches</span><span class='strut'>&nbsp;</span></p>
+<p id='t2' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t3' class='stm run hide_run'><span class='nam'>a</span> <span class='op'>=</span> <span class='num'>3</span><span class='strut'>&nbsp;</span></p>
+<p id='t4' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t5' class='stm run hide_run'><span class='key'>while</span> <span class='nam'>True</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
+<p id='t6' class='stm run hide_run'>&nbsp; &nbsp; <span class='key'>break</span><span class='strut'>&nbsp;</span></p>
+<p id='t7' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t8' class='stm run hide_run'><span class='key'>while</span> <span class='num'>1</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
+<p id='t9' class='stm run hide_run'>&nbsp; &nbsp; <span class='key'>break</span><span class='strut'>&nbsp;</span></p>
+<p id='t10' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t11' class='stm run hide_run'><span class='key'>while</span> <span class='nam'>a</span><span class='op'>:</span>&nbsp; &nbsp; &nbsp; &nbsp; <span class='com'># pragma: no branch</span><span class='strut'>&nbsp;</span></p>
+<p id='t12' class='stm run hide_run'>&nbsp; &nbsp; <span class='key'>break</span><span class='strut'>&nbsp;</span></p>
+<p id='t13' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t14' class='pln'><span class='key'>if</span> <span class='num'>0</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
+<p id='t15' class='pln'>&nbsp; &nbsp; <span class='nam'>never_happen</span><span class='op'>(</span><span class='op'>)</span><span class='strut'>&nbsp;</span></p>
+<p id='t16' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t17' class='pln'><span class='key'>if</span> <span class='num'>1</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
+<p id='t18' class='stm run hide_run'>&nbsp; &nbsp; <span class='nam'>a</span> <span class='op'>=</span> <span class='num'>13</span><span class='strut'>&nbsp;</span></p>
+<p id='t19' class='pln'><span class='strut'>&nbsp;</span></p>
+
+ </td>
+ </tr>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='index.html'>&#xab; index</a> &nbsp; &nbsp; <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_styled/a.html b/tests/farm/html/gold_styled/a.html
new file mode 100644
index 0000000..c794525
--- /dev/null
+++ b/tests/farm/html/gold_styled/a.html
@@ -0,0 +1,95 @@
+<!doctype html PUBLIC "-//W3C//DTD html 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+
+
+ <meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
+ <title>Coverage for a: 67%</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='jquery.isonscreen.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.pyfile_ready);
+ </script>
+</head>
+<body id='pyfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage for <b>a</b> :
+ <span class='pc_cov'>67%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ <h2 class='stats'>
+ 3 statements
+ <span class='run hide_run shortkey_r' onclick='coverage.toggle_lines(this, "run")'>2 run</span>
+ <span class='mis shortkey_m' onclick='coverage.toggle_lines(this, "mis")'>1 missing</span>
+ <span class='exc shortkey_x' onclick='coverage.toggle_lines(this, "exc")'>0 excluded</span>
+
+ </h2>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+<p class='legend'>Hot-keys on this page</p>
+ <div>
+<p class='keyhelp'>
+ <span class='key'>r</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+ <span class='key'>p</span> &nbsp; toggle line displays
+ </p>
+<p class='keyhelp'>
+ <span class='key'>j</span>
+ <span class='key'>k</span> &nbsp; next/prev highlighted chunk
+ </p>
+<p class='keyhelp'>
+ <span class='key'>0</span> &nbsp; (zero) top of page
+ </p>
+<p class='keyhelp'>
+ <span class='key'>1</span> &nbsp; (one) first highlighted chunk
+ </p>
+ </div>
+</div>
+
+<div id='source'>
+ <table cellspacing='0' cellpadding='0'>
+ <tr>
+ <td class='linenos' valign='top'>
+<p id='n1' class='pln'><a href='#n1'>1</a></p>
+<p id='n2' class='pln'><a href='#n2'>2</a></p>
+<p id='n3' class='stm run hide_run'><a href='#n3'>3</a></p>
+<p id='n4' class='pln'><a href='#n4'>4</a></p>
+<p id='n5' class='stm run hide_run'><a href='#n5'>5</a></p>
+<p id='n6' class='pln'><a href='#n6'>6</a></p>
+<p id='n7' class='stm mis'><a href='#n7'>7</a></p>
+
+ </td>
+ <td class='text' valign='top'>
+<p id='t1' class='pln'><span class='com'># A test file for HTML reporting by coverage.</span><span class='strut'>&nbsp;</span></p>
+<p id='t2' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t3' class='stm run hide_run'><span class='key'>if</span> <span class='num'>1</span> <span class='op'>&lt;</span> <span class='num'>2</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
+<p id='t4' class='pln'>&nbsp; &nbsp; <span class='com'># Needed a &lt; to look at HTML entities.</span><span class='strut'>&nbsp;</span></p>
+<p id='t5' class='stm run hide_run'>&nbsp; &nbsp; <span class='nam'>a</span> <span class='op'>=</span> <span class='num'>3</span><span class='strut'>&nbsp;</span></p>
+<p id='t6' class='pln'><span class='key'>else</span><span class='op'>:</span><span class='strut'>&nbsp;</span></p>
+<p id='t7' class='stm mis'>&nbsp; &nbsp; <span class='nam'>a</span> <span class='op'>=</span> <span class='num'>4</span><span class='strut'>&nbsp;</span></p>
+
+ </td>
+ </tr>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='index.html'>&#xab; index</a> &nbsp; &nbsp; <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_styled/extra.css b/tests/farm/html/gold_styled/extra.css
new file mode 100644
index 0000000..46c41fc
--- /dev/null
+++ b/tests/farm/html/gold_styled/extra.css
@@ -0,0 +1 @@
+/* Doesn't matter what goes in here, it gets copied. */
diff --git a/tests/farm/html/gold_styled/index.html b/tests/farm/html/gold_styled/index.html
new file mode 100644
index 0000000..a821e9d
--- /dev/null
+++ b/tests/farm/html/gold_styled/index.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+ <title>Coverage report</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.tablesorter.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.index_ready);
+ </script>
+</head>
+<body id='indexfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage report:
+ <span class='pc_cov'>67%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+ <p class='legend'>Hot-keys on this page</p>
+ <div>
+ <p class='keyhelp'>
+ <span class='key'>n</span>
+ <span class='key'>s</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+
+ <span class='key'>c</span> &nbsp; change column sorting
+ </p>
+ </div>
+</div>
+
+<div id='index'>
+ <table class='index'>
+ <thead>
+
+ <tr class='tablehead' title='Click to sort'>
+ <th class='name left headerSortDown shortkey_n'>Module</th>
+ <th class='shortkey_s'>statements</th>
+ <th class='shortkey_m'>missing</th>
+ <th class='shortkey_x'>excluded</th>
+
+ <th class='right shortkey_c'>coverage</th>
+ </tr>
+ </thead>
+
+ <tfoot>
+ <tr class='total'>
+ <td class='name left'>Total</td>
+ <td>3</td>
+ <td>1</td>
+ <td>0</td>
+
+ <td class='right'>67%</td>
+ </tr>
+ </tfoot>
+ <tbody>
+
+ <tr class='file'>
+ <td class='name left'><a href='a.html'>a</a></td>
+ <td>3</td>
+ <td>1</td>
+ <td>0</td>
+
+ <td class='right'>67%</td>
+ </tr>
+
+ </tbody>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5a1'>coverage.py v3.5a1</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_styled/style.css b/tests/farm/html/gold_styled/style.css
new file mode 100644
index 0000000..c40357b
--- /dev/null
+++ b/tests/farm/html/gold_styled/style.css
@@ -0,0 +1,275 @@
+/* CSS styles for Coverage. */
+/* Page-wide styles */
+html, body, h1, h2, h3, p, td, th {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-weight: inherit;
+ font-style: inherit;
+ font-size: 100%;
+ font-family: inherit;
+ vertical-align: baseline;
+ }
+
+/* Set baseline grid to 16 pt. */
+body {
+ font-family: georgia, serif;
+ font-size: 1em;
+ }
+
+html>body {
+ font-size: 16px;
+ }
+
+/* Set base font size to 12/16 */
+p {
+ font-size: .75em; /* 12/16 */
+ line-height: 1.3333em; /* 16/12 */
+ }
+
+table {
+ border-collapse: collapse;
+ }
+
+a.nav {
+ text-decoration: none;
+ color: inherit;
+ }
+a.nav:hover {
+ text-decoration: underline;
+ color: inherit;
+ }
+
+/* Page structure */
+#header {
+ background: #f8f8f8;
+ width: 100%;
+ border-bottom: 1px solid #eee;
+ }
+
+#source {
+ padding: 1em;
+ font-family: "courier new", monospace;
+ }
+
+#indexfile #footer {
+ margin: 1em 3em;
+ }
+
+#pyfile #footer {
+ margin: 1em 1em;
+ }
+
+#footer .content {
+ padding: 0;
+ font-size: 85%;
+ font-family: verdana, sans-serif;
+ color: #666666;
+ font-style: italic;
+ }
+
+#index {
+ margin: 1em 0 0 3em;
+ }
+
+/* Header styles */
+#header .content {
+ padding: 1em 3em;
+ }
+
+h1 {
+ font-size: 1.25em;
+}
+
+h2.stats {
+ margin-top: .5em;
+ font-size: 1em;
+}
+.stats span {
+ border: 1px solid;
+ padding: .1em .25em;
+ margin: 0 .1em;
+ cursor: pointer;
+ border-color: #999 #ccc #ccc #999;
+}
+.stats span.hide_run, .stats span.hide_exc,
+.stats span.hide_mis, .stats span.hide_par,
+.stats span.par.hide_run.hide_par {
+ border-color: #ccc #999 #999 #ccc;
+}
+.stats span.par.hide_run {
+ border-color: #999 #ccc #ccc #999;
+}
+
+/* Help panel */
+#keyboard_icon {
+ float: right;
+ cursor: pointer;
+}
+
+.help_panel {
+ position: absolute;
+ background: #ffc;
+ padding: .5em;
+ border: 1px solid #883;
+ display: none;
+}
+
+#indexfile .help_panel {
+ width: 20em; height: 4em;
+}
+
+#pyfile .help_panel {
+ width: 16em; height: 8em;
+}
+
+.help_panel .legend {
+ font-style: italic;
+ margin-bottom: 1em;
+}
+
+#panel_icon {
+ float: right;
+ cursor: pointer;
+}
+
+.keyhelp {
+ margin: .75em;
+}
+
+.keyhelp .key {
+ border: 1px solid black;
+ border-color: #888 #333 #333 #888;
+ padding: .1em .35em;
+ font-family: monospace;
+ font-weight: bold;
+ background: #eee;
+}
+
+/* Source file styles */
+.linenos p {
+ text-align: right;
+ margin: 0;
+ padding: 0 .5em;
+ color: #999999;
+ font-family: verdana, sans-serif;
+ font-size: .625em; /* 10/16 */
+ line-height: 1.6em; /* 16/10 */
+ }
+.linenos p.highlight {
+ background: #ffdd00;
+ }
+.linenos p a {
+ text-decoration: none;
+ color: #999999;
+ }
+.linenos p a:hover {
+ text-decoration: underline;
+ color: #999999;
+ }
+
+td.text {
+ width: 100%;
+ }
+.text p {
+ margin: 0;
+ padding: 0 0 0 .5em;
+ border-left: 2px solid #ffffff;
+ white-space: nowrap;
+ }
+
+.text p.mis {
+ background: #ffdddd;
+ border-left: 2px solid #ff0000;
+ }
+.text p.run, .text p.run.hide_par {
+ background: #ddffdd;
+ border-left: 2px solid #00ff00;
+ }
+.text p.exc {
+ background: #eeeeee;
+ border-left: 2px solid #808080;
+ }
+.text p.par, .text p.par.hide_run {
+ background: #ffffaa;
+ border-left: 2px solid #eeee99;
+ }
+.text p.hide_run, .text p.hide_exc, .text p.hide_mis, .text p.hide_par,
+.text p.hide_run.hide_par {
+ background: inherit;
+ }
+
+.text span.annotate {
+ font-family: georgia;
+ font-style: italic;
+ color: #666;
+ float: right;
+ padding-right: .5em;
+ }
+.text p.hide_par span.annotate {
+ display: none;
+ }
+
+/* Syntax coloring */
+.text .com {
+ color: green;
+ font-style: italic;
+ line-height: 1px;
+ }
+.text .key {
+ font-weight: bold;
+ line-height: 1px;
+ }
+.text .str {
+ color: #000080;
+ }
+
+/* index styles */
+#index td, #index th {
+ text-align: right;
+ width: 5em;
+ padding: .25em .5em;
+ border-bottom: 1px solid #eee;
+ }
+#index th {
+ font-style: italic;
+ color: #333;
+ border-bottom: 1px solid #ccc;
+ cursor: pointer;
+ }
+#index th:hover {
+ background: #eee;
+ border-bottom: 1px solid #999;
+ }
+#index td.left, #index th.left {
+ padding-left: 0;
+ }
+#index td.right, #index th.right {
+ padding-right: 0;
+ }
+#index th.headerSortDown, #index th.headerSortUp {
+ border-bottom: 1px solid #000;
+ }
+#index td.name, #index th.name {
+ text-align: left;
+ width: auto;
+ }
+#index td.name a {
+ text-decoration: none;
+ color: #000;
+ }
+#index td.name a:hover {
+ text-decoration: underline;
+ color: #000;
+ }
+#index tr.total {
+ }
+#index tr.total td {
+ font-weight: bold;
+ border-top: 1px solid #ccc;
+ border-bottom: none;
+ }
+#index tr.file:hover {
+ background: #eeeeee;
+ }
diff --git a/tests/farm/html/gold_unicode/index.html b/tests/farm/html/gold_unicode/index.html
new file mode 100644
index 0000000..9ba1bb3
--- /dev/null
+++ b/tests/farm/html/gold_unicode/index.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+ <title>Coverage report</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.tablesorter.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.index_ready);
+ </script>
+</head>
+<body id='indexfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage report:
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+ <p class='legend'>Hot-keys on this page</p>
+ <div>
+ <p class='keyhelp'>
+ <span class='key'>n</span>
+ <span class='key'>s</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+
+ <span class='key'>c</span> &nbsp; change column sorting
+ </p>
+ </div>
+</div>
+
+<div id='index'>
+ <table class='index'>
+ <thead>
+
+ <tr class='tablehead' title='Click to sort'>
+ <th class='name left headerSortDown shortkey_n'>Module</th>
+ <th class='shortkey_s'>statements</th>
+ <th class='shortkey_m'>missing</th>
+ <th class='shortkey_x'>excluded</th>
+
+ <th class='right shortkey_c'>coverage</th>
+ </tr>
+ </thead>
+
+ <tfoot>
+ <tr class='total'>
+ <td class='name left'>Total</td>
+ <td>1</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+ </tfoot>
+ <tbody>
+
+ <tr class='file'>
+ <td class='name left'><a href='unicode.html'>unicode</a></td>
+ <td>1</td>
+ <td>0</td>
+ <td>0</td>
+
+ <td class='right'>100%</td>
+ </tr>
+
+ </tbody>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5.1a0'>coverage.py v3.5.1a0</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_unicode/unicode.html b/tests/farm/html/gold_unicode/unicode.html
new file mode 100644
index 0000000..518a59a
--- /dev/null
+++ b/tests/farm/html/gold_unicode/unicode.html
@@ -0,0 +1,91 @@
+<!doctype html PUBLIC "-//W3C//DTD html 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+
+
+ <meta http-equiv='X-UA-Compatible' content='IE=emulateIE7' />
+ <title>Coverage for unicode: 100%</title>
+ <link rel='stylesheet' href='style.css' type='text/css'>
+ <script type='text/javascript' src='jquery-1.4.3.min.js'></script>
+ <script type='text/javascript' src='jquery.hotkeys.js'></script>
+ <script type='text/javascript' src='jquery.isonscreen.js'></script>
+ <script type='text/javascript' src='coverage_html.js'></script>
+ <script type='text/javascript' charset='utf-8'>
+ jQuery(document).ready(coverage.pyfile_ready);
+ </script>
+</head>
+<body id='pyfile'>
+
+<div id='header'>
+ <div class='content'>
+ <h1>Coverage for <b>unicode</b> :
+ <span class='pc_cov'>100%</span>
+ </h1>
+ <img id='keyboard_icon' src='keybd_closed.png'>
+ <h2 class='stats'>
+ 1 statements
+ <span class='run hide_run shortkey_r' onclick='coverage.toggle_lines(this, "run")'>1 run</span>
+ <span class='mis shortkey_m' onclick='coverage.toggle_lines(this, "mis")'>0 missing</span>
+ <span class='exc shortkey_x' onclick='coverage.toggle_lines(this, "exc")'>0 excluded</span>
+
+ </h2>
+ </div>
+</div>
+
+<div class='help_panel'>
+ <img id='panel_icon' src='keybd_open.png'>
+<p class='legend'>Hot-keys on this page</p>
+ <div>
+<p class='keyhelp'>
+ <span class='key'>r</span>
+ <span class='key'>m</span>
+ <span class='key'>x</span>
+ <span class='key'>p</span> &nbsp; toggle line displays
+ </p>
+<p class='keyhelp'>
+ <span class='key'>j</span>
+ <span class='key'>k</span> &nbsp; next/prev highlighted chunk
+ </p>
+<p class='keyhelp'>
+ <span class='key'>0</span> &nbsp; (zero) top of page
+ </p>
+<p class='keyhelp'>
+ <span class='key'>1</span> &nbsp; (one) first highlighted chunk
+ </p>
+ </div>
+</div>
+
+<div id='source'>
+ <table cellspacing='0' cellpadding='0'>
+ <tr>
+ <td class='linenos' valign='top'>
+<p id='n1' class='pln'><a href='#n1'>1</a></p>
+<p id='n2' class='pln'><a href='#n2'>2</a></p>
+<p id='n3' class='pln'><a href='#n3'>3</a></p>
+<p id='n4' class='stm run hide_run'><a href='#n4'>4</a></p>
+<p id='n5' class='pln'><a href='#n5'>5</a></p>
+
+ </td>
+ <td class='text' valign='top'>
+<p id='t1' class='pln'><span class='com'># A python source file with exotic characters</span><span class='strut'>&nbsp;</span></p>
+<p id='t2' class='pln'><span class='com'># -*- coding: utf-8 -*-</span><span class='strut'>&nbsp;</span></p>
+<p id='t3' class='pln'><span class='strut'>&nbsp;</span></p>
+<p id='t4' class='stm run hide_run'><span class='nam'>upside_down</span> <span class='op'>=</span> <span class='str'>&quot;&#654;d&#729;&#477;b&#592;&#633;&#477;&#652;o&#596;&quot;</span><span class='strut'>&nbsp;</span></p>
+<p id='t5' class='stm run hide_run'><span class='nam'>surrogate</span> <span class='op'>=</span> <span class='str'>&quot;db40,dd00: x&#56128;&#56576; &#917760;&quot;</span><span class='strut'>&nbsp;</span></p>
+
+ </td>
+ </tr>
+ </table>
+</div>
+
+<div id='footer'>
+ <div class='content'>
+ <p>
+ <a class='nav' href='index.html'>&#xab; index</a> &nbsp; &nbsp; <a class='nav' href='http://nedbatchelder.com/code/coverage/3.5.1a0'>coverage.py v3.5.1a0</a>
+ </p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/tests/farm/html/gold_x_xml/coverage.xml b/tests/farm/html/gold_x_xml/coverage.xml
new file mode 100644
index 0000000..912112f
--- /dev/null
+++ b/tests/farm/html/gold_x_xml/coverage.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" ?>
+<!DOCTYPE coverage
+ SYSTEM 'http://cobertura.sourceforge.net/xml/coverage-03.dtd'>
+<coverage branch-rate="0" line-rate="0.6667" timestamp="1253972570431" version="3.1b1">
+ <!-- Generated by coverage.py: http://nedbatchelder.com/code/coverage/VER -->
+ <packages>
+ <package branch-rate="0" complexity="0" line-rate="0.6667" name="">
+ <classes>
+ <class branch-rate="0" complexity="0" filename="a.py" line-rate="0.6667" name="a">
+ <methods/>
+ <lines>
+ <line hits="1" number="3"/>
+ <line hits="1" number="5"/>
+ <line hits="0" number="7"/>
+ </lines>
+ </class>
+ </classes>
+ </package>
+ </packages>
+</coverage>
diff --git a/tests/farm/html/gold_y_xml_branch/coverage.xml b/tests/farm/html/gold_y_xml_branch/coverage.xml
new file mode 100644
index 0000000..ecbe007
--- /dev/null
+++ b/tests/farm/html/gold_y_xml_branch/coverage.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" ?>
+<!DOCTYPE coverage
+ SYSTEM 'http://cobertura.sourceforge.net/xml/coverage-03.dtd'>
+<coverage branch-rate="0.5" line-rate="0.8" timestamp="1259288252325" version="3.2b4">
+ <!-- Generated by coverage.py: http://nedbatchelder.com/code/coverage/VER -->
+ <packages>
+ <package branch-rate="0.5" complexity="0" line-rate="0.8" name="">
+ <classes>
+ <class branch-rate="0.5" complexity="0" filename="y.py" line-rate="0.8" name="y">
+ <methods/>
+ <lines>
+ <line hits="1" number="3"/>
+ <line branch="true" condition-coverage="50% (1/2)" hits="1" number="4"/>
+ <line hits="1" number="5"/>
+ <line hits="0" number="7"/>
+ <line hits="1" number="9"/>
+ </lines>
+ </class>
+ </classes>
+ </package>
+ </packages>
+</coverage>
diff --git a/tests/farm/html/othersrc/other.py b/tests/farm/html/othersrc/other.py
new file mode 100644
index 0000000..6d3f86e
--- /dev/null
+++ b/tests/farm/html/othersrc/other.py
@@ -0,0 +1,4 @@
+# A file in another directory. We're checking that it ends up in the
+# HTML report.
+
+print("This is the other src!")
diff --git a/tests/farm/html/run_a.py b/tests/farm/html/run_a.py
new file mode 100644
index 0000000..59cc170
--- /dev/null
+++ b/tests/farm/html/run_a.py
@@ -0,0 +1,25 @@
+def html_it():
+ """Run coverage and make an HTML report for a."""
+ import coverage
+ cov = coverage.coverage()
+ cov.start()
+ import a # pragma: nested
+ cov.stop() # pragma: nested
+ cov.html_report(a, directory="../html_a")
+
+runfunc(html_it, rundir="src")
+
+# HTML files will change often. Check that the sizes are reasonable,
+# and check that certain key strings are in the output.
+compare("gold_a", "html_a", size_within=10, file_pattern="*.html")
+contains("html_a/a.html",
+ "<span class='key'>if</span> <span class='num'>1</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'>67%</span>"
+ )
+contains("html_a/index.html",
+ "<a href='a.html'>a</a>",
+ "<span class='pc_cov'>67%</span>"
+ )
+
+clean("html_a")
diff --git a/tests/farm/html/run_a_xml_1.py b/tests/farm/html/run_a_xml_1.py
new file mode 100644
index 0000000..3d18702
--- /dev/null
+++ b/tests/farm/html/run_a_xml_1.py
@@ -0,0 +1,21 @@
+def html_it():
+ """Run coverage and make an XML report for a."""
+ import coverage
+ cov = coverage.coverage()
+ cov.start()
+ import a # pragma: nested
+ cov.stop() # pragma: nested
+ cov.xml_report(a, outfile="../xml_1/coverage.xml")
+
+import os
+if not os.path.exists("xml_1"):
+ os.makedirs("xml_1")
+
+runfunc(html_it, rundir="src")
+
+compare("gold_x_xml", "xml_1", scrubs=[
+ (r' timestamp="\d+"', ' timestamp="TIMESTAMP"'),
+ (r' version="[-.\w]+"', ' version="VERSION"'),
+ (r'/code/coverage/?[-.\w]*', '/code/coverage/VER'),
+ ])
+clean("xml_1")
diff --git a/tests/farm/html/run_a_xml_2.py b/tests/farm/html/run_a_xml_2.py
new file mode 100644
index 0000000..53691ea
--- /dev/null
+++ b/tests/farm/html/run_a_xml_2.py
@@ -0,0 +1,21 @@
+def html_it():
+ """Run coverage and make an XML report for a."""
+ import coverage
+ cov = coverage.coverage(config_file="run_a_xml_2.ini")
+ cov.start()
+ import a # pragma: nested
+ cov.stop() # pragma: nested
+ cov.xml_report(a)
+
+import os
+if not os.path.exists("xml_2"):
+ os.makedirs("xml_2")
+
+runfunc(html_it, rundir="src")
+
+compare("gold_x_xml", "xml_2", scrubs=[
+ (r' timestamp="\d+"', ' timestamp="TIMESTAMP"'),
+ (r' version="[-.\w]+"', ' version="VERSION"'),
+ (r'/code/coverage/?[-.\w]*', '/code/coverage/VER'),
+ ])
+clean("xml_2")
diff --git a/tests/farm/html/run_b_branch.py b/tests/farm/html/run_b_branch.py
new file mode 100644
index 0000000..d0955a2
--- /dev/null
+++ b/tests/farm/html/run_b_branch.py
@@ -0,0 +1,28 @@
+def html_it():
+ """Run coverage with branches and make an HTML report for b."""
+ import coverage
+ cov = coverage.coverage(branch=True)
+ cov.start()
+ import b # pragma: nested
+ cov.stop() # pragma: nested
+ cov.html_report(b, directory="../html_b_branch")
+
+runfunc(html_it, rundir="src")
+
+# HTML files will change often. Check that the sizes are reasonable,
+# and check that certain key strings are in the output.
+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'>70%</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'>23&nbsp;&nbsp; 25</span>",
+ )
+contains("html_b_branch/index.html",
+ "<a href='b.html'>b</a>",
+ "<span class='pc_cov'>70%</span>"
+ )
+
+clean("html_b_branch")
diff --git a/tests/farm/html/run_bom.py b/tests/farm/html/run_bom.py
new file mode 100644
index 0000000..c35079b
--- /dev/null
+++ b/tests/farm/html/run_bom.py
@@ -0,0 +1,21 @@
+import sys
+
+def html_it():
+ """Run coverage and make an HTML report for bom.py."""
+ import coverage
+ cov = coverage.coverage()
+ cov.start()
+ import bom # pragma: nested
+ cov.stop() # pragma: nested
+ cov.html_report(bom, directory="../html_bom")
+
+runfunc(html_it, rundir="src")
+
+# HTML files will change often. Check that the sizes are reasonable,
+# and check that certain key strings are in the output.
+compare("gold_bom", "html_bom", size_within=10, file_pattern="*.html")
+contains("html_bom/bom.html",
+ "<span class='str'>&quot;3&#215;4 = 12, &#247;2 = 6&#177;0&quot;</span>",
+ )
+
+clean("html_bom")
diff --git a/tests/farm/html/run_isolatin1.py b/tests/farm/html/run_isolatin1.py
new file mode 100644
index 0000000..063e6e0
--- /dev/null
+++ b/tests/farm/html/run_isolatin1.py
@@ -0,0 +1,21 @@
+import sys
+
+def html_it():
+ """Run coverage and make an HTML report for isolatin1.py."""
+ import coverage
+ cov = coverage.coverage()
+ cov.start()
+ import isolatin1 # pragma: nested
+ cov.stop() # pragma: nested
+ cov.html_report(isolatin1, directory="../html_isolatin1")
+
+runfunc(html_it, rundir="src")
+
+# HTML files will change often. Check that the sizes are reasonable,
+# and check that certain key strings are in the output.
+compare("gold_isolatin1", "html_isolatin1", size_within=10, file_pattern="*.html")
+contains("html_isolatin1/isolatin1.html",
+ "<span class='str'>&quot;3&#215;4 = 12, &#247;2 = 6&#177;0&quot;</span>",
+ )
+
+clean("html_isolatin1")
diff --git a/tests/farm/html/run_omit_1.py b/tests/farm/html/run_omit_1.py
new file mode 100644
index 0000000..a5556af
--- /dev/null
+++ b/tests/farm/html/run_omit_1.py
@@ -0,0 +1,12 @@
+def html_it():
+ """Run coverage and make an HTML report for main."""
+ import coverage
+ cov = coverage.coverage()
+ cov.start()
+ import main # pragma: nested
+ cov.stop() # pragma: nested
+ cov.html_report(directory="../html_omit_1")
+
+runfunc(html_it, rundir="src")
+compare("gold_omit_1", "html_omit_1", size_within=10, file_pattern="*.html")
+clean("html_omit_1")
diff --git a/tests/farm/html/run_omit_2.py b/tests/farm/html/run_omit_2.py
new file mode 100644
index 0000000..19f0ebd
--- /dev/null
+++ b/tests/farm/html/run_omit_2.py
@@ -0,0 +1,12 @@
+def html_it():
+ """Run coverage and make an HTML report for main."""
+ import coverage
+ cov = coverage.coverage()
+ cov.start()
+ import main # pragma: nested
+ cov.stop() # pragma: nested
+ cov.html_report(directory="../html_omit_2", omit=["m1.py"])
+
+runfunc(html_it, rundir="src")
+compare("gold_omit_2", "html_omit_2", size_within=10, file_pattern="*.html")
+clean("html_omit_2")
diff --git a/tests/farm/html/run_omit_3.py b/tests/farm/html/run_omit_3.py
new file mode 100644
index 0000000..87ab8c3
--- /dev/null
+++ b/tests/farm/html/run_omit_3.py
@@ -0,0 +1,12 @@
+def html_it():
+ """Run coverage and make an HTML report for main."""
+ import coverage
+ cov = coverage.coverage()
+ cov.start()
+ import main # pragma: nested
+ cov.stop() # pragma: nested
+ cov.html_report(directory="../html_omit_3", omit=["m1.py", "m2.py"])
+
+runfunc(html_it, rundir="src")
+compare("gold_omit_3", "html_omit_3", size_within=10, file_pattern="*.html")
+clean("html_omit_3")
diff --git a/tests/farm/html/run_omit_4.py b/tests/farm/html/run_omit_4.py
new file mode 100644
index 0000000..ede223f
--- /dev/null
+++ b/tests/farm/html/run_omit_4.py
@@ -0,0 +1,12 @@
+def html_it():
+ """Run coverage and make an HTML report for main."""
+ import coverage
+ cov = coverage.coverage(config_file="omit4.ini")
+ cov.start()
+ import main # pragma: nested
+ cov.stop() # pragma: nested
+ cov.html_report(directory="../html_omit_4")
+
+runfunc(html_it, rundir="src")
+compare("gold_omit_4", "html_omit_4", size_within=10, file_pattern="*.html")
+clean("html_omit_4")
diff --git a/tests/farm/html/run_omit_5.py b/tests/farm/html/run_omit_5.py
new file mode 100644
index 0000000..8da5199
--- /dev/null
+++ b/tests/farm/html/run_omit_5.py
@@ -0,0 +1,12 @@
+def html_it():
+ """Run coverage and make an HTML report for main."""
+ import coverage
+ cov = coverage.coverage(config_file="omit5.ini")
+ cov.start()
+ import main # pragma: nested
+ cov.stop() # pragma: nested
+ cov.html_report()
+
+runfunc(html_it, rundir="src")
+compare("gold_omit_5", "html_omit_5", size_within=10, file_pattern="*.html")
+clean("html_omit_5")
diff --git a/tests/farm/html/run_other.py b/tests/farm/html/run_other.py
new file mode 100644
index 0000000..72bb3ec
--- /dev/null
+++ b/tests/farm/html/run_other.py
@@ -0,0 +1,26 @@
+def html_it():
+ """Run coverage and make an HTML report for everything."""
+ import coverage
+ cov = coverage.coverage()
+ cov.start()
+ import here # pragma: nested
+ cov.stop() # pragma: nested
+ cov.html_report(directory="../html_other")
+
+runfunc(html_it, rundir="src", addtopath="../othersrc")
+
+# Different platforms will name the "other" file differently. Rename it
+import os, glob
+
+for p in glob.glob("html_other/*_other.html"):
+ os.rename(p, "html_other/blah_blah_other.html")
+
+# HTML files will change often. Check that the sizes are reasonable,
+# and check that certain key strings are in the output.
+compare("gold_other", "html_other", size_within=10, file_pattern="*.html")
+contains("html_other/index.html",
+ "<a href='here.html'>here</a>",
+ "other.html'>", "other</a>",
+ )
+
+clean("html_other")
diff --git a/tests/farm/html/run_partial.py b/tests/farm/html/run_partial.py
new file mode 100644
index 0000000..41e6ba9
--- /dev/null
+++ b/tests/farm/html/run_partial.py
@@ -0,0 +1,32 @@
+import sys
+
+def html_it():
+ """Run coverage and make an HTML report for partial."""
+ import coverage
+ cov = coverage.coverage(branch=True)
+ cov.start()
+ import partial # pragma: nested
+ cov.stop() # pragma: nested
+ cov.html_report(partial, directory="../html_partial")
+
+runfunc(html_it, rundir="src")
+
+# HTML files will change often. Check that the sizes are reasonable,
+# and check that certain key strings are in the output.
+compare("gold_partial", "html_partial", size_within=10, file_pattern="*.html")
+contains("html_partial/partial.html",
+ "<p id='t5' class='stm run hide_run'>",
+ "<p id='t8' class='stm run hide_run'>",
+ "<p id='t11' class='stm run hide_run'>",
+ # The "if 0" and "if 1" statements are optimized away.
+ "<p id='t14' class='pln'>",
+ )
+contains("html_partial/index.html",
+ "<a href='partial.html'>partial</a>",
+ )
+if sys.version_info >= (2, 4):
+ contains("html_partial/index.html",
+ "<span class='pc_cov'>100%</span>"
+ )
+
+clean("html_partial")
diff --git a/tests/farm/html/run_styled.py b/tests/farm/html/run_styled.py
new file mode 100644
index 0000000..ac538ff
--- /dev/null
+++ b/tests/farm/html/run_styled.py
@@ -0,0 +1,28 @@
+def html_it():
+ """Run coverage and make an HTML report for a."""
+ import coverage
+ cov = coverage.coverage()
+ cov.start()
+ import a # pragma: nested
+ cov.stop() # pragma: nested
+ cov.html_report(a, directory="../html_styled", extra_css="extra.css")
+
+runfunc(html_it, rundir="src")
+
+# HTML files will change often. Check that the sizes are reasonable,
+# and check that certain key strings are in the output.
+compare("gold_styled", "html_styled", size_within=10, file_pattern="*.html")
+compare("gold_styled", "html_styled", size_within=10, file_pattern="*.css")
+contains("html_styled/a.html",
+ "<link rel='stylesheet' href='extra.css' type='text/css'>",
+ "<span class='key'>if</span> <span class='num'>1</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'>67%</span>"
+ )
+contains("html_styled/index.html",
+ "<link rel='stylesheet' href='extra.css' type='text/css'>",
+ "<a href='a.html'>a</a>",
+ "<span class='pc_cov'>67%</span>"
+ )
+
+clean("html_styled")
diff --git a/tests/farm/html/run_tabbed.py b/tests/farm/html/run_tabbed.py
new file mode 100644
index 0000000..0e9b527
--- /dev/null
+++ b/tests/farm/html/run_tabbed.py
@@ -0,0 +1,24 @@
+def html_it():
+ """Run coverage and make an HTML report for tabbed."""
+ import coverage
+ cov = coverage.coverage()
+ cov.start()
+ import tabbed # pragma: nested
+ cov.stop() # pragma: nested
+ cov.html_report(tabbed, directory="../html_tabbed")
+
+runfunc(html_it, rundir="src")
+
+# Editors like to change things, make sure our source file still has tabs.
+contains("src/tabbed.py", "\tif x:\t\t\t\t\t# look nice")
+
+contains("html_tabbed/tabbed.html",
+ ">&nbsp; &nbsp; &nbsp; &nbsp; <span class='key'>if</span> "
+ "<span class='nam'>x</span><span class='op'>:</span>"
+ "&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "
+ "&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; "
+ "<span class='com'># look nice</span>"
+ )
+
+doesnt_contain("html_tabbed/tabbed.html", "\t")
+clean("html_tabbed")
diff --git a/tests/farm/html/run_unicode.py b/tests/farm/html/run_unicode.py
new file mode 100644
index 0000000..cef26ee
--- /dev/null
+++ b/tests/farm/html/run_unicode.py
@@ -0,0 +1,30 @@
+import sys
+
+def html_it():
+ """Run coverage and make an HTML report for unicode.py."""
+ import coverage
+ cov = coverage.coverage()
+ cov.start()
+ import unicode # pragma: nested
+ cov.stop() # pragma: nested
+ cov.html_report(unicode, directory="../html_unicode")
+
+runfunc(html_it, rundir="src")
+
+# HTML files will change often. Check that the sizes are reasonable,
+# and check that certain key strings are in the output.
+compare("gold_unicode", "html_unicode", size_within=10, file_pattern="*.html")
+contains("html_unicode/unicode.html",
+ "<span class='str'>&quot;&#654;d&#729;&#477;b&#592;&#633;&#477;&#652;o&#596;&quot;</span>",
+ )
+
+if sys.maxunicode == 65535:
+ contains("html_unicode/unicode.html",
+ "<span class='str'>&quot;db40,dd00: x&#56128;&#56576;&quot;</span>",
+ )
+else:
+ contains("html_unicode/unicode.html",
+ "<span class='str'>&quot;db40,dd00: x&#917760;&quot;</span>",
+ )
+
+clean("html_unicode")
diff --git a/tests/farm/html/run_y_xml_branch.py b/tests/farm/html/run_y_xml_branch.py
new file mode 100644
index 0000000..88a2e44
--- /dev/null
+++ b/tests/farm/html/run_y_xml_branch.py
@@ -0,0 +1,21 @@
+def xml_it():
+ """Run coverage and make an XML report for y."""
+ import coverage
+ cov = coverage.coverage(branch=True)
+ cov.start()
+ import y # pragma: nested
+ cov.stop() # pragma: nested
+ cov.xml_report(y, outfile="../xml_branch/coverage.xml")
+
+import os
+if not os.path.exists("xml_branch"):
+ os.makedirs("xml_branch")
+
+runfunc(xml_it, rundir="src")
+
+compare("gold_y_xml_branch", "xml_branch", scrubs=[
+ (r' timestamp="\d+"', ' timestamp="TIMESTAMP"'),
+ (r' version="[-.\w]+"', ' version="VERSION"'),
+ (r'/code/coverage/?[-.\w]*', '/code/coverage/VER'),
+ ])
+clean("xml_branch")
diff --git a/tests/farm/html/src/a.py b/tests/farm/html/src/a.py
new file mode 100644
index 0000000..9e71aeb
--- /dev/null
+++ b/tests/farm/html/src/a.py
@@ -0,0 +1,7 @@
+# A test file for HTML reporting by coverage.
+
+if 1 < 2:
+ # Needed a < to look at HTML entities.
+ a = 3
+else:
+ a = 4
diff --git a/tests/farm/html/src/b.py b/tests/farm/html/src/b.py
new file mode 100644
index 0000000..3bf73a9
--- /dev/null
+++ b/tests/farm/html/src/b.py
@@ -0,0 +1,29 @@
+# A test file for HTML reporting by coverage.
+
+def one(x):
+ # This will be a branch that misses the else.
+ if x < 2:
+ a = 3
+ else:
+ a = 4
+
+one(1)
+
+def two(x):
+ # A missed else that branches to "exit"
+ if x:
+ a = 5
+
+two(1)
+
+def three():
+ try:
+ # This if has two branches, *neither* one taken.
+ if name_error_this_variable_doesnt_exist:
+ a = 1
+ else:
+ a = 2
+ except:
+ pass
+
+three()
diff --git a/tests/farm/html/src/bom.py b/tests/farm/html/src/bom.py
new file mode 100644
index 0000000..2db8b71
--- /dev/null
+++ b/tests/farm/html/src/bom.py
@@ -0,0 +1,11 @@
+# A python source file in utf-8, with BOM
+math = "3×4 = 12, ÷2 = 6±0"
+
+import sys
+
+if sys.version_info >= (3, 0):
+ assert len(math) == 18
+ assert len(math.encode('utf-8')) == 21
+else:
+ assert len(math) == 21
+ assert len(math.decode('utf-8')) == 18
diff --git a/tests/farm/html/src/coverage.xml b/tests/farm/html/src/coverage.xml
new file mode 100644
index 0000000..128cf75
--- /dev/null
+++ b/tests/farm/html/src/coverage.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" ?>
+<!DOCTYPE coverage
+ SYSTEM 'http://cobertura.sourceforge.net/xml/coverage-03.dtd'>
+<coverage branch-rate="0.0" line-rate="0.666666666667" timestamp="1263087779313" version="3.3a1">
+ <!-- Generated by coverage.py: http://nedbatchelder.com/code/coverage -->
+ <packages>
+ <package branch-rate="0.0" complexity="0.0" line-rate="0.666666666667" name="">
+ <classes>
+ <class branch-rate="0.0" complexity="0.0" filename="a.py" line-rate="0.666666666667" name="a">
+ <methods/>
+ <lines>
+ <line hits="1" number="3"/>
+ <line hits="1" number="5"/>
+ <line hits="0" number="7"/>
+ </lines>
+ </class>
+ </classes>
+ </package>
+ </packages>
+</coverage>
diff --git a/tests/farm/html/src/extra.css b/tests/farm/html/src/extra.css
new file mode 100644
index 0000000..46c41fc
--- /dev/null
+++ b/tests/farm/html/src/extra.css
@@ -0,0 +1 @@
+/* Doesn't matter what goes in here, it gets copied. */
diff --git a/tests/farm/html/src/here.py b/tests/farm/html/src/here.py
new file mode 100644
index 0000000..d0d26ea
--- /dev/null
+++ b/tests/farm/html/src/here.py
@@ -0,0 +1,8 @@
+# A test file for HTML reporting by coverage.
+
+import other
+
+if 1 < 2:
+ h = 3
+else:
+ h = 4
diff --git a/tests/farm/html/src/isolatin1.py b/tests/farm/html/src/isolatin1.py
new file mode 100644
index 0000000..057c097
--- /dev/null
+++ b/tests/farm/html/src/isolatin1.py
@@ -0,0 +1,5 @@
+# A python source file in another encoding.
+# -*- coding: iso8859-1 -*-
+
+math = "34 = 12, 2 = 60"
+assert len(math) == 18
diff --git a/tests/farm/html/src/m1.py b/tests/farm/html/src/m1.py
new file mode 100644
index 0000000..927e1f6
--- /dev/null
+++ b/tests/farm/html/src/m1.py
@@ -0,0 +1,2 @@
+m1a = 1
+m1b = 2
diff --git a/tests/farm/html/src/m2.py b/tests/farm/html/src/m2.py
new file mode 100644
index 0000000..ffddf6c
--- /dev/null
+++ b/tests/farm/html/src/m2.py
@@ -0,0 +1,2 @@
+m2a = 1
+m2b = 2
diff --git a/tests/farm/html/src/m3.py b/tests/farm/html/src/m3.py
new file mode 100644
index 0000000..395d7d2
--- /dev/null
+++ b/tests/farm/html/src/m3.py
@@ -0,0 +1,2 @@
+m3a = 1
+m3b = 2
diff --git a/tests/farm/html/src/main.py b/tests/farm/html/src/main.py
new file mode 100644
index 0000000..ce89446
--- /dev/null
+++ b/tests/farm/html/src/main.py
@@ -0,0 +1,10 @@
+import m1
+import m2
+import m3
+
+a = 5
+b = 6
+
+assert m1.m1a == 1
+assert m2.m2a == 1
+assert m3.m3a == 1
diff --git a/tests/farm/html/src/omit4.ini b/tests/farm/html/src/omit4.ini
new file mode 100644
index 0000000..6821ecd
--- /dev/null
+++ b/tests/farm/html/src/omit4.ini
@@ -0,0 +1,2 @@
+[report]
+omit = m2.py
diff --git a/tests/farm/html/src/omit5.ini b/tests/farm/html/src/omit5.ini
new file mode 100644
index 0000000..7e32b41
--- /dev/null
+++ b/tests/farm/html/src/omit5.ini
@@ -0,0 +1,8 @@
+[report]
+omit =
+ fooey
+ gooey, m[23]*, kablooey
+ helloworld
+
+[html]
+directory = ../html_omit_5
diff --git a/tests/farm/html/src/partial.py b/tests/farm/html/src/partial.py
new file mode 100644
index 0000000..8d62f5c
--- /dev/null
+++ b/tests/farm/html/src/partial.py
@@ -0,0 +1,18 @@
+# partial branches
+
+a = 3
+
+while True:
+ break
+
+while 1:
+ break
+
+while a: # pragma: no branch
+ break
+
+if 0:
+ never_happen()
+
+if 1:
+ a = 13
diff --git a/tests/farm/html/src/run_a_xml_2.ini b/tests/farm/html/src/run_a_xml_2.ini
new file mode 100644
index 0000000..8d28f97
--- /dev/null
+++ b/tests/farm/html/src/run_a_xml_2.ini
@@ -0,0 +1,3 @@
+# Put all the XML output in xml_2
+[xml]
+output = ../xml_2/coverage.xml
diff --git a/tests/farm/html/src/tabbed.py b/tests/farm/html/src/tabbed.py
new file mode 100644
index 0000000..2035852
--- /dev/null
+++ b/tests/farm/html/src/tabbed.py
@@ -0,0 +1,7 @@
+# This file should have tabs.
+x = 1
+if x:
+ a = "Tabbed" # Aligned comments
+ if x: # look nice
+ b = "No spaces" # when they
+ c = "Done" # line up.
diff --git a/tests/farm/html/src/unicode.py b/tests/farm/html/src/unicode.py
new file mode 100644
index 0000000..f6a9a05
--- /dev/null
+++ b/tests/farm/html/src/unicode.py
@@ -0,0 +1,5 @@
+# A python source file with exotic characters
+# -*- coding: utf-8 -*-
+
+upside_down = "ʎd˙ǝbɐɹǝʌoɔ"
+surrogate = "db40,dd00: x󠄀"
diff --git a/tests/farm/html/src/y.py b/tests/farm/html/src/y.py
new file mode 100644
index 0000000..af7c968
--- /dev/null
+++ b/tests/farm/html/src/y.py
@@ -0,0 +1,9 @@
+# A test file for XML reporting by coverage.
+
+def choice(x):
+ if x < 2:
+ return 3
+ else:
+ return 4
+
+assert choice(1) == 3
diff --git a/tests/farm/run/run_chdir.py b/tests/farm/run/run_chdir.py
new file mode 100644
index 0000000..f459f50
--- /dev/null
+++ b/tests/farm/run/run_chdir.py
@@ -0,0 +1,12 @@
+copy("src", "out")
+run("""
+ coverage run chdir.py
+ coverage -r
+ """, rundir="out", outfile="stdout.txt")
+contains("out/stdout.txt",
+ "Line One",
+ "Line Two",
+ "chdir"
+ )
+doesnt_contain("out/stdout.txt", "No such file or directory")
+clean("out")
diff --git a/tests/farm/run/run_timid.py b/tests/farm/run/run_timid.py
new file mode 100644
index 0000000..ce78fff
--- /dev/null
+++ b/tests/farm/run/run_timid.py
@@ -0,0 +1,60 @@
+# Test that the --timid command line argument properly swaps the tracer
+# function for a simpler one.
+#
+# This is complicated by the fact that the tests are run twice for each
+# version: once with a compiled C-based trace function, and once without
+# it, to also test the Python trace function. So this test has to examine
+# an environment variable set in igor.py to know whether to expect to see
+# the C trace function or not.
+
+import os
+
+# When meta-coverage testing, this test doesn't work, because it finds
+# coverage.py's own trace function.
+if os.environ.get('COVERAGE_COVERAGE', ''):
+ skip("Can't test timid during coverage measurement.")
+
+copy("src", "out")
+run("""
+ python showtrace.py none
+ coverage -e -x showtrace.py regular
+ coverage -e -x --timid showtrace.py timid
+ """, rundir="out", outfile="showtraceout.txt")
+
+# When running without coverage, no trace function
+# When running timidly, the trace function is always Python.
+contains("out/showtraceout.txt",
+ "none None",
+ "timid PyTracer",
+ )
+
+if os.environ.get('COVERAGE_TEST_TRACER', 'c') == 'c':
+ # If the C trace function is being tested, then regular running should have
+ # the C function, which registers itself as f_trace.
+ contains("out/showtraceout.txt", "regular CTracer")
+else:
+ # If the Python trace function is being tested, then regular running will
+ # also show the Python function.
+ contains("out/showtraceout.txt", "regular PyTracer")
+
+# Try the environment variable.
+old_opts = os.environ.get('COVERAGE_OPTIONS')
+os.environ['COVERAGE_OPTIONS'] = '--timid'
+
+run("""
+ coverage -e -x showtrace.py regular
+ coverage -e -x --timid showtrace.py timid
+ """, rundir="out", outfile="showtraceout.txt")
+
+contains("out/showtraceout.txt",
+ "none None",
+ "timid PyTracer",
+ "regular PyTracer",
+ )
+
+if old_opts:
+ os.environ['COVERAGE_OPTIONS'] = old_opts
+else:
+ del os.environ['COVERAGE_OPTIONS']
+
+clean("out")
diff --git a/tests/farm/run/run_xxx.py b/tests/farm/run/run_xxx.py
new file mode 100644
index 0000000..19e94a4
--- /dev/null
+++ b/tests/farm/run/run_xxx.py
@@ -0,0 +1,12 @@
+copy("src", "out")
+run("""
+ coverage -e -x xxx
+ coverage -r
+ """, rundir="out", outfile="stdout.txt")
+contains("out/stdout.txt",
+ "xxx: 3 4 0 7",
+ "\nxxx ", # The reporting line for xxx
+ " 7 1 86%" # The reporting data for xxx
+ )
+doesnt_contain("out/stdout.txt", "No such file or directory")
+clean("out")
diff --git a/tests/farm/run/src/chdir.py b/tests/farm/run/src/chdir.py
new file mode 100644
index 0000000..6d83492
--- /dev/null
+++ b/tests/farm/run/src/chdir.py
@@ -0,0 +1,4 @@
+import os
+print("Line One")
+os.chdir("subdir")
+print("Line Two")
diff --git a/tests/farm/run/src/showtrace.py b/tests/farm/run/src/showtrace.py
new file mode 100644
index 0000000..e97412e
--- /dev/null
+++ b/tests/farm/run/src/showtrace.py
@@ -0,0 +1,23 @@
+# Show the current frame's trace function, so that we can test what the
+# command-line options do to the trace function used.
+
+import sys
+
+# Show what the trace function is. If a C-based function is used, then f_trace
+# may be None.
+trace_fn = sys._getframe(0).f_trace
+if trace_fn is None:
+ trace_name = "None"
+else:
+ # Get the name of the tracer class. Py3k has a different way to get it.
+ try:
+ trace_name = trace_fn.im_class.__name__
+ except AttributeError:
+ try:
+ trace_name = trace_fn.__self__.__class__.__name__
+ except AttributeError:
+ # A C-based function could also manifest as an f_trace value
+ # which doesn't have im_class or __self__.
+ trace_name = trace_fn.__class__.__name__
+
+print("%s %s" % (sys.argv[1], trace_name))
diff --git a/tests/farm/run/src/subdir/placeholder b/tests/farm/run/src/subdir/placeholder
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/farm/run/src/subdir/placeholder
diff --git a/tests/farm/run/src/xxx b/tests/farm/run/src/xxx
new file mode 100644
index 0000000..8f727f0
--- /dev/null
+++ b/tests/farm/run/src/xxx
@@ -0,0 +1,8 @@
+# This is a python file though it doesn't look like it, like a main script.
+a = b = c = d = 0
+a = 3
+b = 4
+if not b:
+ c = 6
+d = 7
+print("xxx: %r %r %r %r" % (a, b, c, d))
diff --git a/tests/js/index.html b/tests/js/index.html
new file mode 100644
index 0000000..60bdb30
--- /dev/null
+++ b/tests/js/index.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Coverage.py Javascript Test Suite</title>
+ <link rel="stylesheet" href="../qunit/qunit.css" type="text/css" media="screen">
+ <script type="text/javascript" src="../../coverage/htmlfiles/jquery-1.4.3.min.js"></script>
+ <script type='text/javascript' src="../../coverage/htmlfiles/jquery.isonscreen.js"></script>
+ <script type="text/javascript" src="../../coverage/htmlfiles/coverage_html.js"></script>
+ <script type="text/javascript" src="../qunit/qunit.js"></script>
+ <script type="text/javascript" src="../qunit/jquery.tmpl.min.js"></script>
+
+ <style>
+ .r { background-color: red; }
+ .w { }
+ .b { background-color: blue; }
+ </style>
+
+ <!-- Templates for the coverage report output -->
+ <script id="fixture-template" type="text/x-jquery-tmpl">
+ <table cellspacing='0' cellpadding='0'>
+ <tr>
+ <td class='linenos' valign='top'>
+ <!-- #lineno-template goes here -->
+ </td>
+ <td class='text' valign='top'>
+ <!-- #text-template goes here -->
+ </td>
+ </tr>
+ </table>
+ </script>
+
+ <script id="lineno-template" type="text/x-jquery-tmpl">
+ <p id='n${number}' class='${klass}'><a href='#n${number}'>${number}</a></p>
+ </script>
+
+ <script id="text-template" type="text/x-jquery-tmpl">
+ <p id='t${number}' class='${klass}'>Hello, world!</p>
+ </script>
+
+ <!-- Pull in the tests -->
+ <script type="text/javascript" src="tests.js"></script>
+
+</head>
+<body>
+ <h1 id="qunit-header">Coverage.py Javascript Test Suite</h1>
+ <h2 id="qunit-banner"></h2>
+ <div id="qunit-testrunner-toolbar"></div>
+ <h2 id="qunit-userAgent"></h2>
+ <ol id="qunit-tests"></ol>
+ <div id="qunit-fixture"></div>
+</body>
+</html>
diff --git a/tests/js/tests.js b/tests/js/tests.js
new file mode 100644
index 0000000..73b4ce2
--- /dev/null
+++ b/tests/js/tests.js
@@ -0,0 +1,204 @@
+// Tests of coverage.py HTML report chunk navigation.
+/*global coverage, test, module, equals, jQuery, $ */
+
+// Test helpers
+
+function selection_is(sel) {
+ raw_selection_is(sel, true);
+}
+
+function raw_selection_is(sel, check_highlight) {
+ var beg = sel[0], end = sel[1];
+ equals(coverage.sel_begin, beg);
+ equals(coverage.sel_end, end);
+ if (check_highlight) {
+ equals(coverage.code_container().find(".highlight").length, end-beg);
+ }
+}
+
+function build_fixture(spec) {
+ var i, data;
+ $("#fixture-template").tmpl().appendTo("#qunit-fixture");
+ for (i = 0; i < spec.length; i++) {
+ data = {number: i+1, klass: spec.substr(i, 1)};
+ $("#lineno-template").tmpl(data).appendTo("#qunit-fixture .linenos");
+ $("#text-template").tmpl(data).appendTo("#qunit-fixture .text");
+ }
+ coverage.pyfile_ready(jQuery);
+}
+
+// Tests
+
+// Zero-chunk tests
+
+module("Zero-chunk navigation", {
+ setup: function () {
+ build_fixture("wwww");
+ }
+});
+
+test("set_sel defaults", function () {
+ coverage.set_sel(2);
+ equals(coverage.sel_begin, 2);
+ equals(coverage.sel_end, 3);
+});
+
+test("No first chunk to select", function () {
+ coverage.to_first_chunk();
+});
+
+// One-chunk tests
+
+$.each([
+ ['rrrrr', [1,6]],
+ ['r', [1,2]],
+ ['wwrrrr', [3,7]],
+ ['wwrrrrww', [3,7]],
+ ['rrrrww', [1,5]]
+], function (i, params) {
+
+ // Each of these tests uses a fixture with one highlighted chunks.
+ var id = params[0];
+ var c1 = params[1];
+
+ module("One-chunk navigation - " + id, {
+ setup: function () {
+ build_fixture(id);
+ }
+ });
+
+ test("First chunk", function () {
+ coverage.to_first_chunk();
+ selection_is(c1);
+ });
+
+ test("Next chunk is first chunk", function () {
+ coverage.to_next_chunk();
+ selection_is(c1);
+ });
+
+ test("There is no next chunk", function () {
+ coverage.to_first_chunk();
+ coverage.to_next_chunk();
+ selection_is(c1);
+ });
+
+ test("There is no prev chunk", function () {
+ coverage.to_first_chunk();
+ coverage.to_prev_chunk();
+ selection_is(c1);
+ });
+});
+
+// Two-chunk tests
+
+$.each([
+ ['rrwwrrrr', [1,3], [5,9]],
+ ['rb', [1,2], [2,3]],
+ ['rbbbbbbbbbb', [1,2], [2,12]],
+ ['rrrrrrrrrrb', [1,11], [11,12]],
+ ['wrrwrrrrw', [2,4], [5,9]],
+ ['rrrbbb', [1,4], [4,7]]
+], function (i, params) {
+
+ // Each of these tests uses a fixture with two highlighted chunks.
+ var id = params[0];
+ var c1 = params[1];
+ var c2 = params[2];
+
+ module("Two-chunk navigation - " + id, {
+ setup: function () {
+ build_fixture(id);
+ }
+ });
+
+ test("First chunk", function () {
+ coverage.to_first_chunk();
+ selection_is(c1);
+ });
+
+ test("Next chunk is first chunk", function () {
+ coverage.to_next_chunk();
+ selection_is(c1);
+ });
+
+ test("Move to next chunk", function () {
+ coverage.to_first_chunk();
+ coverage.to_next_chunk();
+ selection_is(c2);
+ });
+
+ test("Move to first chunk", function () {
+ coverage.to_first_chunk();
+ coverage.to_next_chunk();
+ coverage.to_first_chunk();
+ selection_is(c1);
+ });
+
+ test("Move to previous chunk", function () {
+ coverage.to_first_chunk();
+ coverage.to_next_chunk();
+ coverage.to_prev_chunk();
+ selection_is(c1);
+ });
+
+ test("Next doesn't move after last chunk", function () {
+ coverage.to_first_chunk();
+ coverage.to_next_chunk();
+ coverage.to_next_chunk();
+ selection_is(c2);
+ });
+
+ test("Prev doesn't move before first chunk", function () {
+ coverage.to_first_chunk();
+ coverage.to_next_chunk();
+ coverage.to_prev_chunk();
+ coverage.to_prev_chunk();
+ selection_is(c1);
+ });
+
+});
+
+module("Miscellaneous");
+
+test("Jump from a line selected", function () {
+ build_fixture("rrwwrr");
+ coverage.set_sel(3);
+ coverage.to_next_chunk();
+ selection_is([5,7]);
+});
+
+// Tests of select_line_or_chunk.
+
+$.each([
+ // The data for each test: a spec for the fixture to build, and an array
+ // of the selection that will be selected by select_line_or_chunk for
+ // each line in the fixture.
+ ['rrwwrr', [[1,3], [1,3], [3,4], [4,5], [5,7], [5,7]]],
+ ['rb', [[1,2], [2,3]]],
+ ['r', [[1,2]]],
+ ['w', [[1,2]]],
+ ['www', [[1,2], [2,3], [3,4]]],
+ ['wwwrrr', [[1,2], [2,3], [3,4], [4,7], [4,7], [4,7]]],
+ ['rrrwww', [[1,4], [1,4], [1,4], [4,5], [5,6], [6,7]]],
+ ['rrrbbb', [[1,4], [1,4], [1,4], [4,7], [4,7], [4,7]]]
+], function (i, params) {
+
+ // Each of these tests uses a fixture with two highlighted chunks.
+ var id = params[0];
+ var sels = params[1];
+
+ module("Select line or chunk - " + id, {
+ setup: function () {
+ build_fixture(id);
+ }
+ });
+
+ $.each(sels, function (i, sel) {
+ i++;
+ test("Select line " + i, function () {
+ coverage.select_line_or_chunk(i);
+ raw_selection_is(sel);
+ });
+ });
+});
diff --git a/tests/modules/aa/__init__.py b/tests/modules/aa/__init__.py
new file mode 100644
index 0000000..77593d8
--- /dev/null
+++ b/tests/modules/aa/__init__.py
@@ -0,0 +1 @@
+# aa
diff --git a/tests/modules/aa/afile.odd.py b/tests/modules/aa/afile.odd.py
new file mode 100644
index 0000000..c6f49e1
--- /dev/null
+++ b/tests/modules/aa/afile.odd.py
@@ -0,0 +1 @@
+# afile.odd.py
diff --git a/tests/modules/aa/afile.py b/tests/modules/aa/afile.py
new file mode 100644
index 0000000..3f0e38d
--- /dev/null
+++ b/tests/modules/aa/afile.py
@@ -0,0 +1 @@
+# afile.py
diff --git a/tests/modules/aa/bb.odd/bfile.py b/tests/modules/aa/bb.odd/bfile.py
new file mode 100644
index 0000000..9087540
--- /dev/null
+++ b/tests/modules/aa/bb.odd/bfile.py
@@ -0,0 +1 @@
+# bfile.py
diff --git a/tests/modules/aa/bb/__init__.py b/tests/modules/aa/bb/__init__.py
new file mode 100644
index 0000000..ffbe624
--- /dev/null
+++ b/tests/modules/aa/bb/__init__.py
@@ -0,0 +1 @@
+# bb
diff --git a/tests/modules/aa/bb/bfile.odd.py b/tests/modules/aa/bb/bfile.odd.py
new file mode 100644
index 0000000..b45cba8
--- /dev/null
+++ b/tests/modules/aa/bb/bfile.odd.py
@@ -0,0 +1 @@
+# bfile.odd.py
diff --git a/tests/modules/aa/bb/bfile.py b/tests/modules/aa/bb/bfile.py
new file mode 100644
index 0000000..9087540
--- /dev/null
+++ b/tests/modules/aa/bb/bfile.py
@@ -0,0 +1 @@
+# bfile.py
diff --git a/tests/modules/aa/bb/cc/__init__.py b/tests/modules/aa/bb/cc/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/modules/aa/bb/cc/__init__.py
diff --git a/tests/modules/aa/bb/cc/cfile.py b/tests/modules/aa/bb/cc/cfile.py
new file mode 100644
index 0000000..7976475
--- /dev/null
+++ b/tests/modules/aa/bb/cc/cfile.py
@@ -0,0 +1 @@
+# cfile.py
diff --git a/tests/modules/aa/zfile.py b/tests/modules/aa/zfile.py
new file mode 100644
index 0000000..924f9b7
--- /dev/null
+++ b/tests/modules/aa/zfile.py
@@ -0,0 +1 @@
+# zfile.py
diff --git a/tests/modules/covmod1.py b/tests/modules/covmod1.py
new file mode 100644
index 0000000..b3f5e5f
--- /dev/null
+++ b/tests/modules/covmod1.py
@@ -0,0 +1,3 @@
+# covmod1.py: Simplest module for testing.
+i = 1
+i += 1
diff --git a/tests/modules/pkg1/__init__.py b/tests/modules/pkg1/__init__.py
new file mode 100644
index 0000000..2dfeb9c
--- /dev/null
+++ b/tests/modules/pkg1/__init__.py
@@ -0,0 +1,3 @@
+# This __init__.py has a module-level docstring, which is counted as a
+# statement.
+"""A simple package for testing with."""
diff --git a/tests/modules/pkg1/__main__.py b/tests/modules/pkg1/__main__.py
new file mode 100644
index 0000000..66ce595
--- /dev/null
+++ b/tests/modules/pkg1/__main__.py
@@ -0,0 +1,3 @@
+# Used in the tests for run_python_module
+import sys
+print("pkg1.__main__: passed %s" % sys.argv[1])
diff --git a/tests/modules/pkg1/p1a.py b/tests/modules/pkg1/p1a.py
new file mode 100644
index 0000000..be5fcdd
--- /dev/null
+++ b/tests/modules/pkg1/p1a.py
@@ -0,0 +1,5 @@
+import os, sys
+
+# Invoke functions in os and sys so we can see if we measure code there.
+x = sys.getcheckinterval()
+y = os.getcwd()
diff --git a/tests/modules/pkg1/p1b.py b/tests/modules/pkg1/p1b.py
new file mode 100644
index 0000000..59d6fb5
--- /dev/null
+++ b/tests/modules/pkg1/p1b.py
@@ -0,0 +1,3 @@
+x = 1
+y = 2
+z = 3
diff --git a/tests/modules/pkg1/p1c.py b/tests/modules/pkg1/p1c.py
new file mode 100644
index 0000000..a9aeef0
--- /dev/null
+++ b/tests/modules/pkg1/p1c.py
@@ -0,0 +1,3 @@
+a = 1
+b = 2
+c = 3
diff --git a/tests/modules/pkg1/runmod2.py b/tests/modules/pkg1/runmod2.py
new file mode 100644
index 0000000..b52964c
--- /dev/null
+++ b/tests/modules/pkg1/runmod2.py
@@ -0,0 +1,3 @@
+# Used in the tests for run_python_module
+import sys
+print("runmod2: passed %s" % sys.argv[1])
diff --git a/tests/modules/pkg1/sub/__init__.py b/tests/modules/pkg1/sub/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/modules/pkg1/sub/__init__.py
diff --git a/tests/modules/pkg1/sub/__main__.py b/tests/modules/pkg1/sub/__main__.py
new file mode 100644
index 0000000..b5be9f1
--- /dev/null
+++ b/tests/modules/pkg1/sub/__main__.py
@@ -0,0 +1,3 @@
+# Used in the tests for run_python_module
+import sys
+print("pkg1.sub.__main__: passed %s" % sys.argv[1])
diff --git a/tests/modules/pkg1/sub/ps1a.py b/tests/modules/pkg1/sub/ps1a.py
new file mode 100644
index 0000000..4b6a15c
--- /dev/null
+++ b/tests/modules/pkg1/sub/ps1a.py
@@ -0,0 +1,3 @@
+d = 1
+e = 2
+f = 3
diff --git a/tests/modules/pkg1/sub/runmod3.py b/tests/modules/pkg1/sub/runmod3.py
new file mode 100644
index 0000000..3a1ad15
--- /dev/null
+++ b/tests/modules/pkg1/sub/runmod3.py
@@ -0,0 +1,3 @@
+# Used in the tests for run_python_module
+import sys
+print("runmod3: passed %s" % sys.argv[1])
diff --git a/tests/modules/pkg2/__init__.py b/tests/modules/pkg2/__init__.py
new file mode 100644
index 0000000..090efbf
--- /dev/null
+++ b/tests/modules/pkg2/__init__.py
@@ -0,0 +1,2 @@
+# This is an __init__.py file, with no executable statements in it.
+# This comment shouldn't confuse the parser.
diff --git a/tests/modules/pkg2/p2a.py b/tests/modules/pkg2/p2a.py
new file mode 100644
index 0000000..b606711
--- /dev/null
+++ b/tests/modules/pkg2/p2a.py
@@ -0,0 +1,3 @@
+q = 1
+r = 1
+s = 1
diff --git a/tests/modules/pkg2/p2b.py b/tests/modules/pkg2/p2b.py
new file mode 100644
index 0000000..7a34e2c
--- /dev/null
+++ b/tests/modules/pkg2/p2b.py
@@ -0,0 +1,3 @@
+t = 1
+u = 1
+v = 1
diff --git a/tests/modules/runmod1.py b/tests/modules/runmod1.py
new file mode 100644
index 0000000..671d81e
--- /dev/null
+++ b/tests/modules/runmod1.py
@@ -0,0 +1,3 @@
+# Used in the tests for run_python_module
+import sys
+print("runmod1: passed %s" % sys.argv[1])
diff --git a/tests/modules/usepkgs.py b/tests/modules/usepkgs.py
new file mode 100644
index 0000000..93c7d90
--- /dev/null
+++ b/tests/modules/usepkgs.py
@@ -0,0 +1,4 @@
+import pkg1.p1a, pkg1.p1b
+import pkg2.p2a, pkg2.p2b
+import othermods.othera, othermods.otherb
+import othermods.sub.osa, othermods.sub.osb
diff --git a/tests/moremodules/othermods/__init__.py b/tests/moremodules/othermods/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/moremodules/othermods/__init__.py
diff --git a/tests/moremodules/othermods/othera.py b/tests/moremodules/othermods/othera.py
new file mode 100644
index 0000000..7889692
--- /dev/null
+++ b/tests/moremodules/othermods/othera.py
@@ -0,0 +1,2 @@
+o = 1
+p = 2
diff --git a/tests/moremodules/othermods/otherb.py b/tests/moremodules/othermods/otherb.py
new file mode 100644
index 0000000..2bd8a44
--- /dev/null
+++ b/tests/moremodules/othermods/otherb.py
@@ -0,0 +1,2 @@
+q = 3
+r = 4
diff --git a/tests/moremodules/othermods/sub/__init__.py b/tests/moremodules/othermods/sub/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/moremodules/othermods/sub/__init__.py
diff --git a/tests/moremodules/othermods/sub/osa.py b/tests/moremodules/othermods/sub/osa.py
new file mode 100644
index 0000000..0139d28
--- /dev/null
+++ b/tests/moremodules/othermods/sub/osa.py
@@ -0,0 +1,2 @@
+s = 5
+t = 6
diff --git a/tests/moremodules/othermods/sub/osb.py b/tests/moremodules/othermods/sub/osb.py
new file mode 100644
index 0000000..b024b14
--- /dev/null
+++ b/tests/moremodules/othermods/sub/osb.py
@@ -0,0 +1,2 @@
+u = 7
+v = 8
diff --git a/tests/osinfo.py b/tests/osinfo.py
new file mode 100644
index 0000000..25c3a7c
--- /dev/null
+++ b/tests/osinfo.py
@@ -0,0 +1,71 @@
+"""OS information for testing."""
+
+import sys
+
+if sys.version_info >= (2, 5) and sys.platform == 'win32':
+ # Windows implementation
+ def process_ram():
+ """How much RAM is this process using? (Windows)"""
+ import ctypes
+ # lifted from:
+ # lists.ubuntu.com/archives/bazaar-commits/2009-February/011990.html
+ class PROCESS_MEMORY_COUNTERS_EX(ctypes.Structure):
+ """Used by GetProcessMemoryInfo"""
+ _fields_ = [('cb', ctypes.c_ulong),
+ ('PageFaultCount', ctypes.c_ulong),
+ ('PeakWorkingSetSize', ctypes.c_size_t),
+ ('WorkingSetSize', ctypes.c_size_t),
+ ('QuotaPeakPagedPoolUsage', ctypes.c_size_t),
+ ('QuotaPagedPoolUsage', ctypes.c_size_t),
+ ('QuotaPeakNonPagedPoolUsage', ctypes.c_size_t),
+ ('QuotaNonPagedPoolUsage', ctypes.c_size_t),
+ ('PagefileUsage', ctypes.c_size_t),
+ ('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(),
+ ctypes.byref(mem_struct),
+ ctypes.sizeof(mem_struct)
+ )
+ if not ret:
+ return 0
+ return mem_struct.PrivateUsage
+
+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:
+ # get pseudo file /proc/<pid>/status
+ t = open('/proc/%d/status' % os.getpid())
+ try:
+ v = t.read()
+ finally:
+ t.close()
+ except IOError:
+ return 0 # non-Linux?
+ # get VmKey line e.g. 'VmRSS: 9999 kB\n ...'
+ i = v.index(key)
+ v = v[i:].split(None, 3)
+ if len(v) < 3:
+ return 0 # invalid format?
+ # convert Vm value to bytes
+ return int(float(v[1]) * _scale[v[2].lower()])
+
+ def process_ram():
+ """How much RAM is this process using? (Linux implementation)"""
+ return _VmB('VmRSS')
+
+
+else:
+ # Don't have an implementation, at least satisfy the interface.
+ def process_ram():
+ """How much RAM is this process using? (placebo implementation)"""
+ return 0
diff --git a/tests/qunit/jquery.tmpl.min.js b/tests/qunit/jquery.tmpl.min.js
new file mode 100644
index 0000000..7438b2c
--- /dev/null
+++ b/tests/qunit/jquery.tmpl.min.js
@@ -0,0 +1,10 @@
+/*
+ * jQuery Templates Plugin 1.0.0pre
+ * http://github.com/jquery/jquery-tmpl
+ * Requires jQuery 1.4.2
+ *
+ * Copyright Software Freedom Conservancy, Inc.
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ */
+(function(a){var r=a.fn.domManip,d="_tmplitem",q=/^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /,b={},f={},e,p={key:0,data:{}},i=0,c=0,l=[];function g(g,d,h,e){var c={data:e||(e===0||e===false)?e:d?d.data:{},_wrap:d?d._wrap:null,tmpl:null,parent:d||null,nodes:[],calls:u,nest:w,wrap:x,html:v,update:t};g&&a.extend(c,g,{nodes:[],parent:d});if(h){c.tmpl=h;c._ctnt=c._ctnt||c.tmpl(a,c);c.key=++i;(l.length?f:b)[i]=c}return c}a.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(f,d){a.fn[f]=function(n){var g=[],i=a(n),k,h,m,l,j=this.length===1&&this[0].parentNode;e=b||{};if(j&&j.nodeType===11&&j.childNodes.length===1&&i.length===1){i[d](this[0]);g=this}else{for(h=0,m=i.length;h<m;h++){c=h;k=(h>0?this.clone(true):this).get();a(i[h])[d](k);g=g.concat(k)}c=0;g=this.pushStack(g,f,i.selector)}l=e;e=null;a.tmpl.complete(l);return g}});a.fn.extend({tmpl:function(d,c,b){return a.tmpl(this[0],d,c,b)},tmplItem:function(){return a.tmplItem(this[0])},template:function(b){return a.template(b,this[0])},domManip:function(d,m,k){if(d[0]&&a.isArray(d[0])){var g=a.makeArray(arguments),h=d[0],j=h.length,i=0,f;while(i<j&&!(f=a.data(h[i++],"tmplItem")));if(f&&c)g[2]=function(b){a.tmpl.afterManip(this,b,k)};r.apply(this,g)}else r.apply(this,arguments);c=0;!e&&a.tmpl.complete(b);return this}});a.extend({tmpl:function(d,h,e,c){var i,k=!c;if(k){c=p;d=a.template[d]||a.template(null,d);f={}}else if(!d){d=c.tmpl;b[c.key]=c;c.nodes=[];c.wrapped&&n(c,c.wrapped);return a(j(c,null,c.tmpl(a,c)))}if(!d)return[];if(typeof h==="function")h=h.call(c||{});e&&e.wrapped&&n(e,e.wrapped);i=a.isArray(h)?a.map(h,function(a){return a?g(e,c,d,a):null}):[g(e,c,d,h)];return k?a(j(c,null,i)):i},tmplItem:function(b){var c;if(b instanceof a)b=b[0];while(b&&b.nodeType===1&&!(c=a.data(b,"tmplItem"))&&(b=b.parentNode));return c||p},template:function(c,b){if(b){if(typeof b==="string")b=o(b);else if(b instanceof a)b=b[0]||{};if(b.nodeType)b=a.data(b,"tmpl")||a.data(b,"tmpl",o(b.innerHTML));return typeof c==="string"?(a.template[c]=b):b}return c?typeof c!=="string"?a.template(null,c):a.template[c]||a.template(null,q.test(c)?c:a(c)):null},encode:function(a){return(""+a).split("<").join("&lt;").split(">").join("&gt;").split('"').join("&#34;").split("'").join("&#39;")}});a.extend(a.tmpl,{tag:{tmpl:{_default:{$2:"null"},open:"if($notnull_1){__=__.concat($item.nest($1,$2));}"},wrap:{_default:{$2:"null"},open:"$item.calls(__,$1,$2);__=[];",close:"call=$item.calls();__=call._.concat($item.wrap(call,__));"},each:{_default:{$2:"$index, $value"},open:"if($notnull_1){$.each($1a,function($2){with(this){",close:"}});}"},"if":{open:"if(($notnull_1) && $1a){",close:"}"},"else":{_default:{$1:"true"},open:"}else if(($notnull_1) && $1a){"},html:{open:"if($notnull_1){__.push($1a);}"},"=":{_default:{$1:"$data"},open:"if($notnull_1){__.push($.encode($1a));}"},"!":{open:""}},complete:function(){b={}},afterManip:function(f,b,d){var e=b.nodeType===11?a.makeArray(b.childNodes):b.nodeType===1?[b]:[];d.call(f,b);m(e);c++}});function j(e,g,f){var b,c=f?a.map(f,function(a){return typeof a==="string"?e.key?a.replace(/(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g,"$1 "+d+'="'+e.key+'" $2'):a:j(a,e,a._ctnt)}):e;if(g)return c;c=c.join("");c.replace(/^\s*([^<\s][^<]*)?(<[\w\W]+>)([^>]*[^>\s])?\s*$/,function(f,c,e,d){b=a(e).get();m(b);if(c)b=k(c).concat(b);if(d)b=b.concat(k(d))});return b?b:k(c)}function k(c){var b=document.createElement("div");b.innerHTML=c;return a.makeArray(b.childNodes)}function o(b){return new Function("jQuery","$item","var $=jQuery,call,__=[],$data=$item.data;with($data){__.push('"+a.trim(b).replace(/([\\'])/g,"\\$1").replace(/[\r\t\n]/g," ").replace(/\$\{([^\}]*)\}/g,"{{= $1}}").replace(/\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g,function(m,l,k,g,b,c,d){var j=a.tmpl.tag[k],i,e,f;if(!j)throw"Unknown template tag: "+k;i=j._default||[];if(c&&!/\w$/.test(b)){b+=c;c=""}if(b){b=h(b);d=d?","+h(d)+")":c?")":"";e=c?b.indexOf(".")>-1?b+h(c):"("+b+").call($item"+d:b;f=c?e:"(typeof("+b+")==='function'?("+b+").call($item):("+b+"))"}else f=e=i.$1||"null";g=h(g);return"');"+j[l?"close":"open"].split("$notnull_1").join(b?"typeof("+b+")!=='undefined' && ("+b+")!=null":"true").split("$1a").join(f).split("$1").join(e).split("$2").join(g||i.$2||"")+"__.push('"})+"');}return __;")}function n(c,b){c._wrap=j(c,true,a.isArray(b)?b:[q.test(b)?b:a(b).html()]).join("")}function h(a){return a?a.replace(/\\'/g,"'").replace(/\\\\/g,"\\"):null}function s(b){var a=document.createElement("div");a.appendChild(b.cloneNode(true));return a.innerHTML}function m(o){var n="_"+c,k,j,l={},e,p,h;for(e=0,p=o.length;e<p;e++){if((k=o[e]).nodeType!==1)continue;j=k.getElementsByTagName("*");for(h=j.length-1;h>=0;h--)m(j[h]);m(k)}function m(j){var p,h=j,k,e,m;if(m=j.getAttribute(d)){while(h.parentNode&&(h=h.parentNode).nodeType===1&&!(p=h.getAttribute(d)));if(p!==m){h=h.parentNode?h.nodeType===11?0:h.getAttribute(d)||0:0;if(!(e=b[m])){e=f[m];e=g(e,b[h]||f[h]);e.key=++i;b[i]=e}c&&o(m)}j.removeAttribute(d)}else if(c&&(e=a.data(j,"tmplItem"))){o(e.key);b[e.key]=e;h=a.data(j.parentNode,"tmplItem");h=h?h.key:0}if(e){k=e;while(k&&k.key!=h){k.nodes.push(j);k=k.parent}delete e._ctnt;delete e._wrap;a.data(j,"tmplItem",e)}function o(a){a=a+n;e=l[a]=l[a]||g(e,b[e.parent.key+n]||e.parent)}}}function u(a,d,c,b){if(!a)return l.pop();l.push({_:a,tmpl:d,item:this,data:c,options:b})}function w(d,c,b){return a.tmpl(a.template(d),c,b,this)}function x(b,d){var c=b.options||{};c.wrapped=d;return a.tmpl(a.template(b.tmpl),b.data,c,b.item)}function v(d,c){var b=this._wrap;return a.map(a(a.isArray(b)?b.join(""):b).filter(d||"*"),function(a){return c?a.innerText||a.textContent:a.outerHTML||s(a)})}function t(){var b=this.nodes;a.tmpl(null,null,null,this).insertBefore(b[0]);a(b).remove()}})(jQuery); \ No newline at end of file
diff --git a/tests/qunit/qunit.css b/tests/qunit/qunit.css
new file mode 100644
index 0000000..b3c6db5
--- /dev/null
+++ b/tests/qunit/qunit.css
@@ -0,0 +1,225 @@
+/**
+ * QUnit - A JavaScript Unit Testing Framework
+ *
+ * http://docs.jquery.com/QUnit
+ *
+ * Copyright (c) 2011 John Resig, Jörn Zaefferer
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * or GPL (GPL-LICENSE.txt) licenses.
+ */
+
+/** Font Family and Sizes */
+
+#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
+ font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
+}
+
+#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
+#qunit-tests { font-size: smaller; }
+
+
+/** Resets */
+
+#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
+ margin: 0;
+ padding: 0;
+}
+
+
+/** Header */
+
+#qunit-header {
+ padding: 0.5em 0 0.5em 1em;
+
+ color: #8699a4;
+ background-color: #0d3349;
+
+ font-size: 1.5em;
+ line-height: 1em;
+ font-weight: normal;
+
+ border-radius: 15px 15px 0 0;
+ -moz-border-radius: 15px 15px 0 0;
+ -webkit-border-top-right-radius: 15px;
+ -webkit-border-top-left-radius: 15px;
+}
+
+#qunit-header a {
+ text-decoration: none;
+ color: #c2ccd1;
+}
+
+#qunit-header a:hover,
+#qunit-header a:focus {
+ color: #fff;
+}
+
+#qunit-banner {
+ height: 5px;
+}
+
+#qunit-testrunner-toolbar {
+ padding: 0.5em 0 0.5em 2em;
+ color: #5E740B;
+ background-color: #eee;
+}
+
+#qunit-userAgent {
+ padding: 0.5em 0 0.5em 2.5em;
+ background-color: #2b81af;
+ color: #fff;
+ text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
+}
+
+
+/** Tests: Pass/Fail */
+
+#qunit-tests {
+ list-style-position: inside;
+}
+
+#qunit-tests li {
+ padding: 0.4em 0.5em 0.4em 2.5em;
+ border-bottom: 1px solid #fff;
+ list-style-position: inside;
+}
+
+#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
+ display: none;
+}
+
+#qunit-tests li strong {
+ cursor: pointer;
+}
+
+#qunit-tests li a {
+ padding: 0.5em;
+ color: #c2ccd1;
+ text-decoration: none;
+}
+#qunit-tests li a:hover,
+#qunit-tests li a:focus {
+ color: #000;
+}
+
+#qunit-tests ol {
+ margin-top: 0.5em;
+ padding: 0.5em;
+
+ background-color: #fff;
+
+ border-radius: 15px;
+ -moz-border-radius: 15px;
+ -webkit-border-radius: 15px;
+
+ box-shadow: inset 0px 2px 13px #999;
+ -moz-box-shadow: inset 0px 2px 13px #999;
+ -webkit-box-shadow: inset 0px 2px 13px #999;
+}
+
+#qunit-tests table {
+ border-collapse: collapse;
+ margin-top: .2em;
+}
+
+#qunit-tests th {
+ text-align: right;
+ vertical-align: top;
+ padding: 0 .5em 0 0;
+}
+
+#qunit-tests td {
+ vertical-align: top;
+}
+
+#qunit-tests pre {
+ margin: 0;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+#qunit-tests del {
+ background-color: #e0f2be;
+ color: #374e0c;
+ text-decoration: none;
+}
+
+#qunit-tests ins {
+ background-color: #ffcaca;
+ color: #500;
+ text-decoration: none;
+}
+
+/*** Test Counts */
+
+#qunit-tests b.counts { color: black; }
+#qunit-tests b.passed { color: #5E740B; }
+#qunit-tests b.failed { color: #710909; }
+
+#qunit-tests li li {
+ margin: 0.5em;
+ padding: 0.4em 0.5em 0.4em 0.5em;
+ background-color: #fff;
+ border-bottom: none;
+ list-style-position: inside;
+}
+
+/*** Passing Styles */
+
+#qunit-tests li li.pass {
+ color: #5E740B;
+ background-color: #fff;
+ border-left: 26px solid #C6E746;
+}
+
+#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
+#qunit-tests .pass .test-name { color: #366097; }
+
+#qunit-tests .pass .test-actual,
+#qunit-tests .pass .test-expected { color: #999999; }
+
+#qunit-banner.qunit-pass { background-color: #C6E746; }
+
+/*** Failing Styles */
+
+#qunit-tests li li.fail {
+ color: #710909;
+ background-color: #fff;
+ border-left: 26px solid #EE5757;
+}
+
+#qunit-tests > li:last-child {
+ border-radius: 0 0 15px 15px;
+ -moz-border-radius: 0 0 15px 15px;
+ -webkit-border-bottom-right-radius: 15px;
+ -webkit-border-bottom-left-radius: 15px;
+}
+
+#qunit-tests .fail { color: #000000; background-color: #EE5757; }
+#qunit-tests .fail .test-name,
+#qunit-tests .fail .module-name { color: #000000; }
+
+#qunit-tests .fail .test-actual { color: #EE5757; }
+#qunit-tests .fail .test-expected { color: green; }
+
+#qunit-banner.qunit-fail { background-color: #EE5757; }
+
+
+/** Result */
+
+#qunit-testresult {
+ padding: 0.5em 0.5em 0.5em 2.5em;
+
+ color: #2b81af;
+ background-color: #D2E0E6;
+
+ border-bottom: 1px solid white;
+}
+
+/** Fixture */
+
+#qunit-fixture {
+ position: absolute;
+ top: -10000px;
+ left: -10000px;
+}
diff --git a/tests/qunit/qunit.js b/tests/qunit/qunit.js
new file mode 100644
index 0000000..e00cca9
--- /dev/null
+++ b/tests/qunit/qunit.js
@@ -0,0 +1,1448 @@
+/**
+ * QUnit - A JavaScript Unit Testing Framework
+ *
+ * http://docs.jquery.com/QUnit
+ *
+ * Copyright (c) 2011 John Resig, Jörn Zaefferer
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * or GPL (GPL-LICENSE.txt) licenses.
+ */
+
+(function(window) {
+
+var defined = {
+ setTimeout: typeof window.setTimeout !== "undefined",
+ sessionStorage: (function() {
+ try {
+ return !!sessionStorage.getItem;
+ } catch(e){
+ return false;
+ }
+ })()
+};
+
+var testId = 0;
+
+var Test = function(name, testName, expected, testEnvironmentArg, async, callback) {
+ this.name = name;
+ this.testName = testName;
+ this.expected = expected;
+ this.testEnvironmentArg = testEnvironmentArg;
+ this.async = async;
+ this.callback = callback;
+ this.assertions = [];
+};
+Test.prototype = {
+ init: function() {
+ var tests = id("qunit-tests");
+ if (tests) {
+ var b = document.createElement("strong");
+ b.innerHTML = "Running " + this.name;
+ var li = document.createElement("li");
+ li.appendChild( b );
+ li.className = "running";
+ li.id = this.id = "test-output" + testId++;
+ tests.appendChild( li );
+ }
+ },
+ setup: function() {
+ if (this.module != config.previousModule) {
+ if ( config.previousModule ) {
+ QUnit.moduleDone( {
+ name: config.previousModule,
+ failed: config.moduleStats.bad,
+ passed: config.moduleStats.all - config.moduleStats.bad,
+ total: config.moduleStats.all
+ } );
+ }
+ config.previousModule = this.module;
+ config.moduleStats = { all: 0, bad: 0 };
+ QUnit.moduleStart( {
+ name: this.module
+ } );
+ }
+
+ config.current = this;
+ this.testEnvironment = extend({
+ setup: function() {},
+ teardown: function() {}
+ }, this.moduleTestEnvironment);
+ if (this.testEnvironmentArg) {
+ extend(this.testEnvironment, this.testEnvironmentArg);
+ }
+
+ QUnit.testStart( {
+ name: this.testName
+ } );
+
+ // allow utility functions to access the current test environment
+ // TODO why??
+ QUnit.current_testEnvironment = this.testEnvironment;
+
+ try {
+ if ( !config.pollution ) {
+ saveGlobal();
+ }
+
+ this.testEnvironment.setup.call(this.testEnvironment);
+ } catch(e) {
+ QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message );
+ }
+ },
+ run: function() {
+ if ( this.async ) {
+ QUnit.stop();
+ }
+
+ if ( config.notrycatch ) {
+ this.callback.call(this.testEnvironment);
+ return;
+ }
+ try {
+ this.callback.call(this.testEnvironment);
+ } catch(e) {
+ fail("Test " + this.testName + " died, exception and test follows", e, this.callback);
+ QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) );
+ // else next test will carry the responsibility
+ saveGlobal();
+
+ // Restart the tests if they're blocking
+ if ( config.blocking ) {
+ start();
+ }
+ }
+ },
+ teardown: function() {
+ try {
+ this.testEnvironment.teardown.call(this.testEnvironment);
+ checkPollution();
+ } catch(e) {
+ QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message );
+ }
+ },
+ finish: function() {
+ if ( this.expected && this.expected != this.assertions.length ) {
+ QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" );
+ }
+
+ var good = 0, bad = 0,
+ tests = id("qunit-tests");
+
+ config.stats.all += this.assertions.length;
+ config.moduleStats.all += this.assertions.length;
+
+ if ( tests ) {
+ var ol = document.createElement("ol");
+
+ for ( var i = 0; i < this.assertions.length; i++ ) {
+ var assertion = this.assertions[i];
+
+ var li = document.createElement("li");
+ li.className = assertion.result ? "pass" : "fail";
+ li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed");
+ ol.appendChild( li );
+
+ if ( assertion.result ) {
+ good++;
+ } else {
+ bad++;
+ config.stats.bad++;
+ config.moduleStats.bad++;
+ }
+ }
+
+ // store result when possible
+ if ( QUnit.config.reorder && defined.sessionStorage ) {
+ if (bad) {
+ sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad);
+ } else {
+ sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName);
+ }
+ }
+
+ if (bad == 0) {
+ ol.style.display = "none";
+ }
+
+ var b = document.createElement("strong");
+ b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
+
+ var a = document.createElement("a");
+ a.innerHTML = "Rerun";
+ a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
+
+ addEvent(b, "click", function() {
+ var next = b.nextSibling.nextSibling,
+ display = next.style.display;
+ next.style.display = display === "none" ? "block" : "none";
+ });
+
+ addEvent(b, "dblclick", function(e) {
+ var target = e && e.target ? e.target : window.event.srcElement;
+ if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
+ target = target.parentNode;
+ }
+ if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
+ window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
+ }
+ });
+
+ var li = id(this.id);
+ li.className = bad ? "fail" : "pass";
+ li.removeChild( li.firstChild );
+ li.appendChild( b );
+ li.appendChild( a );
+ li.appendChild( ol );
+
+ } else {
+ for ( var i = 0; i < this.assertions.length; i++ ) {
+ if ( !this.assertions[i].result ) {
+ bad++;
+ config.stats.bad++;
+ config.moduleStats.bad++;
+ }
+ }
+ }
+
+ try {
+ QUnit.reset();
+ } catch(e) {
+ fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset);
+ }
+
+ QUnit.testDone( {
+ name: this.testName,
+ failed: bad,
+ passed: this.assertions.length - bad,
+ total: this.assertions.length
+ } );
+ },
+
+ queue: function() {
+ var test = this;
+ synchronize(function() {
+ test.init();
+ });
+ function run() {
+ // each of these can by async
+ synchronize(function() {
+ test.setup();
+ });
+ synchronize(function() {
+ test.run();
+ });
+ synchronize(function() {
+ test.teardown();
+ });
+ synchronize(function() {
+ test.finish();
+ });
+ }
+ // defer when previous test run passed, if storage is available
+ var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName);
+ if (bad) {
+ run();
+ } else {
+ synchronize(run);
+ };
+ }
+
+};
+
+var QUnit = {
+
+ // call on start of module test to prepend name to all tests
+ module: function(name, testEnvironment) {
+ config.currentModule = name;
+ config.currentModuleTestEnviroment = testEnvironment;
+ },
+
+ asyncTest: function(testName, expected, callback) {
+ if ( arguments.length === 2 ) {
+ callback = expected;
+ expected = 0;
+ }
+
+ QUnit.test(testName, expected, callback, true);
+ },
+
+ test: function(testName, expected, callback, async) {
+ var name = '<span class="test-name">' + testName + '</span>', testEnvironmentArg;
+
+ if ( arguments.length === 2 ) {
+ callback = expected;
+ expected = null;
+ }
+ // is 2nd argument a testEnvironment?
+ if ( expected && typeof expected === 'object') {
+ testEnvironmentArg = expected;
+ expected = null;
+ }
+
+ if ( config.currentModule ) {
+ name = '<span class="module-name">' + config.currentModule + "</span>: " + name;
+ }
+
+ if ( !validTest(config.currentModule + ": " + testName) ) {
+ return;
+ }
+
+ var test = new Test(name, testName, expected, testEnvironmentArg, async, callback);
+ test.module = config.currentModule;
+ test.moduleTestEnvironment = config.currentModuleTestEnviroment;
+ test.queue();
+ },
+
+ /**
+ * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
+ */
+ expect: function(asserts) {
+ config.current.expected = asserts;
+ },
+
+ /**
+ * Asserts true.
+ * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
+ */
+ ok: function(a, msg) {
+ a = !!a;
+ var details = {
+ result: a,
+ message: msg
+ };
+ msg = escapeHtml(msg);
+ QUnit.log(details);
+ config.current.assertions.push({
+ result: a,
+ message: msg
+ });
+ },
+
+ /**
+ * Checks that the first two arguments are equal, with an optional message.
+ * Prints out both actual and expected values.
+ *
+ * Prefered to ok( actual == expected, message )
+ *
+ * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
+ *
+ * @param Object actual
+ * @param Object expected
+ * @param String message (optional)
+ */
+ equal: function(actual, expected, message) {
+ QUnit.push(expected == actual, actual, expected, message);
+ },
+
+ notEqual: function(actual, expected, message) {
+ QUnit.push(expected != actual, actual, expected, message);
+ },
+
+ deepEqual: function(actual, expected, message) {
+ QUnit.push(QUnit.equiv(actual, expected), actual, expected, message);
+ },
+
+ notDeepEqual: function(actual, expected, message) {
+ QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message);
+ },
+
+ strictEqual: function(actual, expected, message) {
+ QUnit.push(expected === actual, actual, expected, message);
+ },
+
+ notStrictEqual: function(actual, expected, message) {
+ QUnit.push(expected !== actual, actual, expected, message);
+ },
+
+ raises: function(block, expected, message) {
+ var actual, ok = false;
+
+ if (typeof expected === 'string') {
+ message = expected;
+ expected = null;
+ }
+
+ try {
+ block();
+ } catch (e) {
+ actual = e;
+ }
+
+ if (actual) {
+ // we don't want to validate thrown error
+ if (!expected) {
+ ok = true;
+ // expected is a regexp
+ } else if (QUnit.objectType(expected) === "regexp") {
+ ok = expected.test(actual);
+ // expected is a constructor
+ } else if (actual instanceof expected) {
+ ok = true;
+ // expected is a validation function which returns true is validation passed
+ } else if (expected.call({}, actual) === true) {
+ ok = true;
+ }
+ }
+
+ QUnit.ok(ok, message);
+ },
+
+ start: function() {
+ config.semaphore--;
+ if (config.semaphore > 0) {
+ // don't start until equal number of stop-calls
+ return;
+ }
+ if (config.semaphore < 0) {
+ // ignore if start is called more often then stop
+ config.semaphore = 0;
+ }
+ // A slight delay, to avoid any current callbacks
+ if ( defined.setTimeout ) {
+ window.setTimeout(function() {
+ if ( config.timeout ) {
+ clearTimeout(config.timeout);
+ }
+
+ config.blocking = false;
+ process();
+ }, 13);
+ } else {
+ config.blocking = false;
+ process();
+ }
+ },
+
+ stop: function(timeout) {
+ config.semaphore++;
+ config.blocking = true;
+
+ if ( timeout && defined.setTimeout ) {
+ clearTimeout(config.timeout);
+ config.timeout = window.setTimeout(function() {
+ QUnit.ok( false, "Test timed out" );
+ QUnit.start();
+ }, timeout);
+ }
+ }
+};
+
+// Backwards compatibility, deprecated
+QUnit.equals = QUnit.equal;
+QUnit.same = QUnit.deepEqual;
+
+// Maintain internal state
+var config = {
+ // The queue of tests to run
+ queue: [],
+
+ // block until document ready
+ blocking: true,
+
+ // by default, run previously failed tests first
+ // very useful in combination with "Hide passed tests" checked
+ reorder: true,
+
+ noglobals: false,
+ notrycatch: false
+};
+
+// Load paramaters
+(function() {
+ var location = window.location || { search: "", protocol: "file:" },
+ params = location.search.slice( 1 ).split( "&" ),
+ length = params.length,
+ urlParams = {},
+ current;
+
+ if ( params[ 0 ] ) {
+ for ( var i = 0; i < length; i++ ) {
+ current = params[ i ].split( "=" );
+ current[ 0 ] = decodeURIComponent( current[ 0 ] );
+ // allow just a key to turn on a flag, e.g., test.html?noglobals
+ current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
+ urlParams[ current[ 0 ] ] = current[ 1 ];
+ if ( current[ 0 ] in config ) {
+ config[ current[ 0 ] ] = current[ 1 ];
+ }
+ }
+ }
+
+ QUnit.urlParams = urlParams;
+ config.filter = urlParams.filter;
+
+ // Figure out if we're running the tests from a server or not
+ QUnit.isLocal = !!(location.protocol === 'file:');
+})();
+
+// Expose the API as global variables, unless an 'exports'
+// object exists, in that case we assume we're in CommonJS
+if ( typeof exports === "undefined" || typeof require === "undefined" ) {
+ extend(window, QUnit);
+ window.QUnit = QUnit;
+} else {
+ extend(exports, QUnit);
+ exports.QUnit = QUnit;
+}
+
+// define these after exposing globals to keep them in these QUnit namespace only
+extend(QUnit, {
+ config: config,
+
+ // Initialize the configuration options
+ init: function() {
+ extend(config, {
+ stats: { all: 0, bad: 0 },
+ moduleStats: { all: 0, bad: 0 },
+ started: +new Date,
+ updateRate: 1000,
+ blocking: false,
+ autostart: true,
+ autorun: false,
+ filter: "",
+ queue: [],
+ semaphore: 0
+ });
+
+ var tests = id( "qunit-tests" ),
+ banner = id( "qunit-banner" ),
+ result = id( "qunit-testresult" );
+
+ if ( tests ) {
+ tests.innerHTML = "";
+ }
+
+ if ( banner ) {
+ banner.className = "";
+ }
+
+ if ( result ) {
+ result.parentNode.removeChild( result );
+ }
+
+ if ( tests ) {
+ result = document.createElement( "p" );
+ result.id = "qunit-testresult";
+ result.className = "result";
+ tests.parentNode.insertBefore( result, tests );
+ result.innerHTML = 'Running...<br/>&nbsp;';
+ }
+ },
+
+ /**
+ * Resets the test setup. Useful for tests that modify the DOM.
+ *
+ * If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
+ */
+ reset: function() {
+ if ( window.jQuery ) {
+ jQuery( "#qunit-fixture" ).html( config.fixture );
+ } else {
+ var main = id( 'qunit-fixture' );
+ if ( main ) {
+ main.innerHTML = config.fixture;
+ }
+ }
+ },
+
+ /**
+ * Trigger an event on an element.
+ *
+ * @example triggerEvent( document.body, "click" );
+ *
+ * @param DOMElement elem
+ * @param String type
+ */
+ triggerEvent: function( elem, type, event ) {
+ if ( document.createEvent ) {
+ event = document.createEvent("MouseEvents");
+ event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
+ 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ elem.dispatchEvent( event );
+
+ } else if ( elem.fireEvent ) {
+ elem.fireEvent("on"+type);
+ }
+ },
+
+ // Safe object type checking
+ is: function( type, obj ) {
+ return QUnit.objectType( obj ) == type;
+ },
+
+ objectType: function( obj ) {
+ if (typeof obj === "undefined") {
+ return "undefined";
+
+ // consider: typeof null === object
+ }
+ if (obj === null) {
+ return "null";
+ }
+
+ var type = Object.prototype.toString.call( obj )
+ .match(/^\[object\s(.*)\]$/)[1] || '';
+
+ switch (type) {
+ case 'Number':
+ if (isNaN(obj)) {
+ return "nan";
+ } else {
+ return "number";
+ }
+ case 'String':
+ case 'Boolean':
+ case 'Array':
+ case 'Date':
+ case 'RegExp':
+ case 'Function':
+ return type.toLowerCase();
+ }
+ if (typeof obj === "object") {
+ return "object";
+ }
+ return undefined;
+ },
+
+ push: function(result, actual, expected, message) {
+ var details = {
+ result: result,
+ message: message,
+ actual: actual,
+ expected: expected
+ };
+
+ message = escapeHtml(message) || (result ? "okay" : "failed");
+ message = '<span class="test-message">' + message + "</span>";
+ expected = escapeHtml(QUnit.jsDump.parse(expected));
+ actual = escapeHtml(QUnit.jsDump.parse(actual));
+ var output = message + '<table><tr class="test-expected"><th>Expected: </th><td><pre>' + expected + '</pre></td></tr>';
+ if (actual != expected) {
+ output += '<tr class="test-actual"><th>Result: </th><td><pre>' + actual + '</pre></td></tr>';
+ output += '<tr class="test-diff"><th>Diff: </th><td><pre>' + QUnit.diff(expected, actual) +'</pre></td></tr>';
+ }
+ if (!result) {
+ var source = sourceFromStacktrace();
+ if (source) {
+ details.source = source;
+ output += '<tr class="test-source"><th>Source: </th><td><pre>' + escapeHtml(source) + '</pre></td></tr>';
+ }
+ }
+ output += "</table>";
+
+ QUnit.log(details);
+
+ config.current.assertions.push({
+ result: !!result,
+ message: output
+ });
+ },
+
+ url: function( params ) {
+ params = extend( extend( {}, QUnit.urlParams ), params );
+ var querystring = "?",
+ key;
+ for ( key in params ) {
+ querystring += encodeURIComponent( key ) + "=" +
+ encodeURIComponent( params[ key ] ) + "&";
+ }
+ return window.location.pathname + querystring.slice( 0, -1 );
+ },
+
+ // Logging callbacks; all receive a single argument with the listed properties
+ // run test/logs.html for any related changes
+ begin: function() {},
+ // done: { failed, passed, total, runtime }
+ done: function() {},
+ // log: { result, actual, expected, message }
+ log: function() {},
+ // testStart: { name }
+ testStart: function() {},
+ // testDone: { name, failed, passed, total }
+ testDone: function() {},
+ // moduleStart: { name }
+ moduleStart: function() {},
+ // moduleDone: { name, failed, passed, total }
+ moduleDone: function() {}
+});
+
+if ( typeof document === "undefined" || document.readyState === "complete" ) {
+ config.autorun = true;
+}
+
+addEvent(window, "load", function() {
+ QUnit.begin({});
+
+ // Initialize the config, saving the execution queue
+ var oldconfig = extend({}, config);
+ QUnit.init();
+ extend(config, oldconfig);
+
+ config.blocking = false;
+
+ var userAgent = id("qunit-userAgent");
+ if ( userAgent ) {
+ userAgent.innerHTML = navigator.userAgent;
+ }
+ var banner = id("qunit-header");
+ if ( banner ) {
+ banner.innerHTML = '<a href="' + QUnit.url({ filter: undefined }) + '"> ' + banner.innerHTML + '</a> ' +
+ '<label><input name="noglobals" type="checkbox"' + ( config.noglobals ? ' checked="checked"' : '' ) + '>noglobals</label>' +
+ '<label><input name="notrycatch" type="checkbox"' + ( config.notrycatch ? ' checked="checked"' : '' ) + '>notrycatch</label>';
+ addEvent( banner, "change", function( event ) {
+ var params = {};
+ params[ event.target.name ] = event.target.checked ? true : undefined;
+ window.location = QUnit.url( params );
+ });
+ }
+
+ var toolbar = id("qunit-testrunner-toolbar");
+ if ( toolbar ) {
+ var filter = document.createElement("input");
+ filter.type = "checkbox";
+ filter.id = "qunit-filter-pass";
+ addEvent( filter, "click", function() {
+ var ol = document.getElementById("qunit-tests");
+ if ( filter.checked ) {
+ ol.className = ol.className + " hidepass";
+ } else {
+ var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
+ ol.className = tmp.replace(/ hidepass /, " ");
+ }
+ if ( defined.sessionStorage ) {
+ if (filter.checked) {
+ sessionStorage.setItem("qunit-filter-passed-tests", "true");
+ } else {
+ sessionStorage.removeItem("qunit-filter-passed-tests");
+ }
+ }
+ });
+ if ( defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) {
+ filter.checked = true;
+ var ol = document.getElementById("qunit-tests");
+ ol.className = ol.className + " hidepass";
+ }
+ toolbar.appendChild( filter );
+
+ var label = document.createElement("label");
+ label.setAttribute("for", "qunit-filter-pass");
+ label.innerHTML = "Hide passed tests";
+ toolbar.appendChild( label );
+ }
+
+ var main = id('qunit-fixture');
+ if ( main ) {
+ config.fixture = main.innerHTML;
+ }
+
+ if (config.autostart) {
+ QUnit.start();
+ }
+});
+
+function done() {
+ config.autorun = true;
+
+ // Log the last module results
+ if ( config.currentModule ) {
+ QUnit.moduleDone( {
+ name: config.currentModule,
+ failed: config.moduleStats.bad,
+ passed: config.moduleStats.all - config.moduleStats.bad,
+ total: config.moduleStats.all
+ } );
+ }
+
+ var banner = id("qunit-banner"),
+ tests = id("qunit-tests"),
+ runtime = +new Date - config.started,
+ passed = config.stats.all - config.stats.bad,
+ html = [
+ 'Tests completed in ',
+ runtime,
+ ' milliseconds.<br/>',
+ '<span class="passed">',
+ passed,
+ '</span> tests of <span class="total">',
+ config.stats.all,
+ '</span> passed, <span class="failed">',
+ config.stats.bad,
+ '</span> failed.'
+ ].join('');
+
+ if ( banner ) {
+ banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
+ }
+
+ if ( tests ) {
+ id( "qunit-testresult" ).innerHTML = html;
+ }
+
+ if ( typeof document !== "undefined" && document.title ) {
+ // show ✖ for good, ✔ for bad suite result in title
+ // use escape sequences in case file gets loaded with non-utf-8-charset
+ document.title = (config.stats.bad ? "\u2716" : "\u2714") + " " + document.title;
+ }
+
+ QUnit.done( {
+ failed: config.stats.bad,
+ passed: passed,
+ total: config.stats.all,
+ runtime: runtime
+ } );
+}
+
+function validTest( name ) {
+ var filter = config.filter,
+ run = false;
+
+ if ( !filter ) {
+ return true;
+ }
+
+ var not = filter.charAt( 0 ) === "!";
+ if ( not ) {
+ filter = filter.slice( 1 );
+ }
+
+ if ( name.indexOf( filter ) !== -1 ) {
+ return !not;
+ }
+
+ if ( not ) {
+ run = true;
+ }
+
+ return run;
+}
+
+// so far supports only Firefox, Chrome and Opera (buggy)
+// could be extended in the future to use something like https://github.com/csnover/TraceKit
+function sourceFromStacktrace() {
+ try {
+ throw new Error();
+ } catch ( e ) {
+ if (e.stacktrace) {
+ // Opera
+ return e.stacktrace.split("\n")[6];
+ } else if (e.stack) {
+ // Firefox, Chrome
+ return e.stack.split("\n")[4];
+ }
+ }
+}
+
+function escapeHtml(s) {
+ if (!s) {
+ return "";
+ }
+ s = s + "";
+ return s.replace(/[\&"<>\\]/g, function(s) {
+ switch(s) {
+ case "&": return "&amp;";
+ case "\\": return "\\\\";
+ case '"': return '\"';
+ case "<": return "&lt;";
+ case ">": return "&gt;";
+ default: return s;
+ }
+ });
+}
+
+function synchronize( callback ) {
+ config.queue.push( callback );
+
+ if ( config.autorun && !config.blocking ) {
+ process();
+ }
+}
+
+function process() {
+ var start = (new Date()).getTime();
+
+ while ( config.queue.length && !config.blocking ) {
+ if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) {
+ config.queue.shift()();
+ } else {
+ window.setTimeout( process, 13 );
+ break;
+ }
+ }
+ if (!config.blocking && !config.queue.length) {
+ done();
+ }
+}
+
+function saveGlobal() {
+ config.pollution = [];
+
+ if ( config.noglobals ) {
+ for ( var key in window ) {
+ config.pollution.push( key );
+ }
+ }
+}
+
+function checkPollution( name ) {
+ var old = config.pollution;
+ saveGlobal();
+
+ var newGlobals = diff( config.pollution, old );
+ if ( newGlobals.length > 0 ) {
+ ok( false, "Introduced global variable(s): " + newGlobals.join(", ") );
+ }
+
+ var deletedGlobals = diff( old, config.pollution );
+ if ( deletedGlobals.length > 0 ) {
+ ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") );
+ }
+}
+
+// returns a new Array with the elements that are in a but not in b
+function diff( a, b ) {
+ var result = a.slice();
+ for ( var i = 0; i < result.length; i++ ) {
+ for ( var j = 0; j < b.length; j++ ) {
+ if ( result[i] === b[j] ) {
+ result.splice(i, 1);
+ i--;
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+function fail(message, exception, callback) {
+ if ( typeof console !== "undefined" && console.error && console.warn ) {
+ console.error(message);
+ console.error(exception);
+ console.warn(callback.toString());
+
+ } else if ( window.opera && opera.postError ) {
+ opera.postError(message, exception, callback.toString);
+ }
+}
+
+function extend(a, b) {
+ for ( var prop in b ) {
+ if ( b[prop] === undefined ) {
+ delete a[prop];
+ } else {
+ a[prop] = b[prop];
+ }
+ }
+
+ return a;
+}
+
+function addEvent(elem, type, fn) {
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, fn, false );
+ } else if ( elem.attachEvent ) {
+ elem.attachEvent( "on" + type, fn );
+ } else {
+ fn();
+ }
+}
+
+function id(name) {
+ return !!(typeof document !== "undefined" && document && document.getElementById) &&
+ document.getElementById( name );
+}
+
+// Test for equality any JavaScript type.
+// Discussions and reference: http://philrathe.com/articles/equiv
+// Test suites: http://philrathe.com/tests/equiv
+// Author: Philippe Rathé <prathe@gmail.com>
+QUnit.equiv = function () {
+
+ var innerEquiv; // the real equiv function
+ var callers = []; // stack to decide between skip/abort functions
+ var parents = []; // stack to avoiding loops from circular referencing
+
+ // Call the o related callback with the given arguments.
+ function bindCallbacks(o, callbacks, args) {
+ var prop = QUnit.objectType(o);
+ if (prop) {
+ if (QUnit.objectType(callbacks[prop]) === "function") {
+ return callbacks[prop].apply(callbacks, args);
+ } else {
+ return callbacks[prop]; // or undefined
+ }
+ }
+ }
+
+ var callbacks = function () {
+
+ // for string, boolean, number and null
+ function useStrictEquality(b, a) {
+ if (b instanceof a.constructor || a instanceof b.constructor) {
+ // to catch short annotaion VS 'new' annotation of a declaration
+ // e.g. var i = 1;
+ // var j = new Number(1);
+ return a == b;
+ } else {
+ return a === b;
+ }
+ }
+
+ return {
+ "string": useStrictEquality,
+ "boolean": useStrictEquality,
+ "number": useStrictEquality,
+ "null": useStrictEquality,
+ "undefined": useStrictEquality,
+
+ "nan": function (b) {
+ return isNaN(b);
+ },
+
+ "date": function (b, a) {
+ return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf();
+ },
+
+ "regexp": function (b, a) {
+ return QUnit.objectType(b) === "regexp" &&
+ a.source === b.source && // the regex itself
+ a.global === b.global && // and its modifers (gmi) ...
+ a.ignoreCase === b.ignoreCase &&
+ a.multiline === b.multiline;
+ },
+
+ // - skip when the property is a method of an instance (OOP)
+ // - abort otherwise,
+ // initial === would have catch identical references anyway
+ "function": function () {
+ var caller = callers[callers.length - 1];
+ return caller !== Object &&
+ typeof caller !== "undefined";
+ },
+
+ "array": function (b, a) {
+ var i, j, loop;
+ var len;
+
+ // b could be an object literal here
+ if ( ! (QUnit.objectType(b) === "array")) {
+ return false;
+ }
+
+ len = a.length;
+ if (len !== b.length) { // safe and faster
+ return false;
+ }
+
+ //track reference to avoid circular references
+ parents.push(a);
+ for (i = 0; i < len; i++) {
+ loop = false;
+ for(j=0;j<parents.length;j++){
+ if(parents[j] === a[i]){
+ loop = true;//dont rewalk array
+ }
+ }
+ if (!loop && ! innerEquiv(a[i], b[i])) {
+ parents.pop();
+ return false;
+ }
+ }
+ parents.pop();
+ return true;
+ },
+
+ "object": function (b, a) {
+ var i, j, loop;
+ var eq = true; // unless we can proove it
+ var aProperties = [], bProperties = []; // collection of strings
+
+ // comparing constructors is more strict than using instanceof
+ if ( a.constructor !== b.constructor) {
+ return false;
+ }
+
+ // stack constructor before traversing properties
+ callers.push(a.constructor);
+ //track reference to avoid circular references
+ parents.push(a);
+
+ for (i in a) { // be strict: don't ensures hasOwnProperty and go deep
+ loop = false;
+ for(j=0;j<parents.length;j++){
+ if(parents[j] === a[i])
+ loop = true; //don't go down the same path twice
+ }
+ aProperties.push(i); // collect a's properties
+
+ if (!loop && ! innerEquiv(a[i], b[i])) {
+ eq = false;
+ break;
+ }
+ }
+
+ callers.pop(); // unstack, we are done
+ parents.pop();
+
+ for (i in b) {
+ bProperties.push(i); // collect b's properties
+ }
+
+ // Ensures identical properties name
+ return eq && innerEquiv(aProperties.sort(), bProperties.sort());
+ }
+ };
+ }();
+
+ innerEquiv = function () { // can take multiple arguments
+ var args = Array.prototype.slice.apply(arguments);
+ if (args.length < 2) {
+ return true; // end transition
+ }
+
+ return (function (a, b) {
+ if (a === b) {
+ return true; // catch the most you can
+ } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || QUnit.objectType(a) !== QUnit.objectType(b)) {
+ return false; // don't lose time with error prone cases
+ } else {
+ return bindCallbacks(a, callbacks, [b, a]);
+ }
+
+ // apply transition with (1..n) arguments
+ })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1));
+ };
+
+ return innerEquiv;
+
+}();
+
+/**
+ * jsDump
+ * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
+ * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)
+ * Date: 5/15/2008
+ * @projectDescription Advanced and extensible data dumping for Javascript.
+ * @version 1.0.0
+ * @author Ariel Flesler
+ * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
+ */
+QUnit.jsDump = (function() {
+ function quote( str ) {
+ return '"' + str.toString().replace(/"/g, '\\"') + '"';
+ };
+ function literal( o ) {
+ return o + '';
+ };
+ function join( pre, arr, post ) {
+ var s = jsDump.separator(),
+ base = jsDump.indent(),
+ inner = jsDump.indent(1);
+ if ( arr.join )
+ arr = arr.join( ',' + s + inner );
+ if ( !arr )
+ return pre + post;
+ return [ pre, inner + arr, base + post ].join(s);
+ };
+ function array( arr ) {
+ var i = arr.length, ret = Array(i);
+ this.up();
+ while ( i-- )
+ ret[i] = this.parse( arr[i] );
+ this.down();
+ return join( '[', ret, ']' );
+ };
+
+ var reName = /^function (\w+)/;
+
+ var jsDump = {
+ parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance
+ var parser = this.parsers[ type || this.typeOf(obj) ];
+ type = typeof parser;
+
+ return type == 'function' ? parser.call( this, obj ) :
+ type == 'string' ? parser :
+ this.parsers.error;
+ },
+ typeOf:function( obj ) {
+ var type;
+ if ( obj === null ) {
+ type = "null";
+ } else if (typeof obj === "undefined") {
+ type = "undefined";
+ } else if (QUnit.is("RegExp", obj)) {
+ type = "regexp";
+ } else if (QUnit.is("Date", obj)) {
+ type = "date";
+ } else if (QUnit.is("Function", obj)) {
+ type = "function";
+ } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") {
+ type = "window";
+ } else if (obj.nodeType === 9) {
+ type = "document";
+ } else if (obj.nodeType) {
+ type = "node";
+ } else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) {
+ type = "array";
+ } else {
+ type = typeof obj;
+ }
+ return type;
+ },
+ separator:function() {
+ return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? '&nbsp;' : ' ';
+ },
+ indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
+ if ( !this.multiline )
+ return '';
+ var chr = this.indentChar;
+ if ( this.HTML )
+ chr = chr.replace(/\t/g,' ').replace(/ /g,'&nbsp;');
+ return Array( this._depth_ + (extra||0) ).join(chr);
+ },
+ up:function( a ) {
+ this._depth_ += a || 1;
+ },
+ down:function( a ) {
+ this._depth_ -= a || 1;
+ },
+ setParser:function( name, parser ) {
+ this.parsers[name] = parser;
+ },
+ // The next 3 are exposed so you can use them
+ quote:quote,
+ literal:literal,
+ join:join,
+ //
+ _depth_: 1,
+ // This is the list of parsers, to modify them, use jsDump.setParser
+ parsers:{
+ window: '[Window]',
+ document: '[Document]',
+ error:'[ERROR]', //when no parser is found, shouldn't happen
+ unknown: '[Unknown]',
+ 'null':'null',
+ 'undefined':'undefined',
+ 'function':function( fn ) {
+ var ret = 'function',
+ name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
+ if ( name )
+ ret += ' ' + name;
+ ret += '(';
+
+ ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join('');
+ return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' );
+ },
+ array: array,
+ nodelist: array,
+ arguments: array,
+ object:function( map ) {
+ var ret = [ ];
+ QUnit.jsDump.up();
+ for ( var key in map )
+ ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(map[key]) );
+ QUnit.jsDump.down();
+ return join( '{', ret, '}' );
+ },
+ node:function( node ) {
+ var open = QUnit.jsDump.HTML ? '&lt;' : '<',
+ close = QUnit.jsDump.HTML ? '&gt;' : '>';
+
+ var tag = node.nodeName.toLowerCase(),
+ ret = open + tag;
+
+ for ( var a in QUnit.jsDump.DOMAttrs ) {
+ var val = node[QUnit.jsDump.DOMAttrs[a]];
+ if ( val )
+ ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' );
+ }
+ return ret + close + open + '/' + tag + close;
+ },
+ functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
+ var l = fn.length;
+ if ( !l ) return '';
+
+ var args = Array(l);
+ while ( l-- )
+ args[l] = String.fromCharCode(97+l);//97 is 'a'
+ return ' ' + args.join(', ') + ' ';
+ },
+ key:quote, //object calls it internally, the key part of an item in a map
+ functionCode:'[code]', //function calls it internally, it's the content of the function
+ attribute:quote, //node calls it internally, it's an html attribute value
+ string:quote,
+ date:quote,
+ regexp:literal, //regex
+ number:literal,
+ 'boolean':literal
+ },
+ DOMAttrs:{//attributes to dump from nodes, name=>realName
+ id:'id',
+ name:'name',
+ 'class':'className'
+ },
+ HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
+ indentChar:' ',//indentation unit
+ multiline:true //if true, items in a collection, are separated by a \n, else just a space.
+ };
+
+ return jsDump;
+})();
+
+// from Sizzle.js
+function getText( elems ) {
+ var ret = "", elem;
+
+ for ( var i = 0; elems[i]; i++ ) {
+ elem = elems[i];
+
+ // Get the text from text nodes and CDATA nodes
+ if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
+ ret += elem.nodeValue;
+
+ // Traverse everything else, except comment nodes
+ } else if ( elem.nodeType !== 8 ) {
+ ret += getText( elem.childNodes );
+ }
+ }
+
+ return ret;
+};
+
+/*
+ * Javascript Diff Algorithm
+ * By John Resig (http://ejohn.org/)
+ * Modified by Chu Alan "sprite"
+ *
+ * Released under the MIT license.
+ *
+ * More Info:
+ * http://ejohn.org/projects/javascript-diff-algorithm/
+ *
+ * Usage: QUnit.diff(expected, actual)
+ *
+ * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
+ */
+QUnit.diff = (function() {
+ function diff(o, n){
+ var ns = new Object();
+ var os = new Object();
+
+ for (var i = 0; i < n.length; i++) {
+ if (ns[n[i]] == null)
+ ns[n[i]] = {
+ rows: new Array(),
+ o: null
+ };
+ ns[n[i]].rows.push(i);
+ }
+
+ for (var i = 0; i < o.length; i++) {
+ if (os[o[i]] == null)
+ os[o[i]] = {
+ rows: new Array(),
+ n: null
+ };
+ os[o[i]].rows.push(i);
+ }
+
+ for (var i in ns) {
+ if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
+ n[ns[i].rows[0]] = {
+ text: n[ns[i].rows[0]],
+ row: os[i].rows[0]
+ };
+ o[os[i].rows[0]] = {
+ text: o[os[i].rows[0]],
+ row: ns[i].rows[0]
+ };
+ }
+ }
+
+ for (var i = 0; i < n.length - 1; i++) {
+ if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null &&
+ n[i + 1] == o[n[i].row + 1]) {
+ n[i + 1] = {
+ text: n[i + 1],
+ row: n[i].row + 1
+ };
+ o[n[i].row + 1] = {
+ text: o[n[i].row + 1],
+ row: i + 1
+ };
+ }
+ }
+
+ for (var i = n.length - 1; i > 0; i--) {
+ if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&
+ n[i - 1] == o[n[i].row - 1]) {
+ n[i - 1] = {
+ text: n[i - 1],
+ row: n[i].row - 1
+ };
+ o[n[i].row - 1] = {
+ text: o[n[i].row - 1],
+ row: i - 1
+ };
+ }
+ }
+
+ return {
+ o: o,
+ n: n
+ };
+ }
+
+ return function(o, n){
+ o = o.replace(/\s+$/, '');
+ n = n.replace(/\s+$/, '');
+ var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/));
+
+ var str = "";
+
+ var oSpace = o.match(/\s+/g);
+ if (oSpace == null) {
+ oSpace = [" "];
+ }
+ else {
+ oSpace.push(" ");
+ }
+ var nSpace = n.match(/\s+/g);
+ if (nSpace == null) {
+ nSpace = [" "];
+ }
+ else {
+ nSpace.push(" ");
+ }
+
+ if (out.n.length == 0) {
+ for (var i = 0; i < out.o.length; i++) {
+ str += '<del>' + out.o[i] + oSpace[i] + "</del>";
+ }
+ }
+ else {
+ if (out.n[0].text == null) {
+ for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
+ str += '<del>' + out.o[n] + oSpace[n] + "</del>";
+ }
+ }
+
+ for (var i = 0; i < out.n.length; i++) {
+ if (out.n[i].text == null) {
+ str += '<ins>' + out.n[i] + nSpace[i] + "</ins>";
+ }
+ else {
+ var pre = "";
+
+ for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) {
+ pre += '<del>' + out.o[n] + oSpace[n] + "</del>";
+ }
+ str += " " + out.n[i].text + nSpace[i] + pre;
+ }
+ }
+ }
+
+ return str;
+ };
+})();
+
+})(this);
diff --git a/tests/stress_phystoken.tok b/tests/stress_phystoken.tok
new file mode 100644
index 0000000..8d1b6be
--- /dev/null
+++ b/tests/stress_phystoken.tok
@@ -0,0 +1,52 @@
+# Here's some random Python so that test_tokenize_myself will have some
+# stressful stuff to try. This file is .tok instead of .py so pylint won't
+# complain about it, check_eol won't look at it, etc.
+
+first_back = """\
+hey there!
+"""
+
+other_back = """
+hey \
+there
+"""
+
+lots_of_back = """\
+hey \
+there
+"""
+# This next line is supposed to have trailing whitespace:
+fake_back = """\
+ouch
+"""
+
+# Lots of difficulty happens with code like:
+#
+# fake_back = """\
+# ouch
+# """
+#
+# Ugh, the edge cases...
+
+# What about a comment like this\
+"what's this string doing here?"
+
+class C(object):
+ def there():
+ this = 5 + \
+ 7
+ that = \
+ "a continued line"
+
+cont1 = "one line of text" + \
+ "another line of text"
+
+a_long_string = \
+ "part 1" \
+ "2" \
+ "3 is longer"
+
+def hello():
+ print("Hello world!")
+
+hello()
diff --git a/tests/stress_phystoken_dos.tok b/tests/stress_phystoken_dos.tok
new file mode 100644
index 0000000..b08fd70
--- /dev/null
+++ b/tests/stress_phystoken_dos.tok
@@ -0,0 +1,52 @@
+# Here's some random Python so that test_tokenize_myself will have some
+# stressful stuff to try. This file is .tok instead of .py so pylint won't
+# complain about it, check_eol won't look at it, etc.
+
+first_back = """\
+hey there!
+"""
+
+other_back = """
+hey \
+there
+"""
+
+lots_of_back = """\
+hey \
+there
+"""
+# This next line is supposed to have trailing whitespace:
+fake_back = """\
+ouch
+"""
+
+# Lots of difficulty happens with code like:
+#
+# fake_back = """\
+# ouch
+# """
+#
+# Ugh, the edge cases...
+
+# What about a comment like this\
+"what's this string doing here?"
+
+class C(object):
+ def there():
+ this = 5 + \
+ 7
+ that = \
+ "a continued line"
+
+cont1 = "one line of text" + \
+ "another line of text"
+
+a_long_string = \
+ "part 1" \
+ "2" \
+ "3 is longer"
+
+def hello():
+ print("Hello world!")
+
+hello()
diff --git a/tests/test_api.py b/tests/test_api.py
new file mode 100644
index 0000000..559684b
--- /dev/null
+++ b/tests/test_api.py
@@ -0,0 +1,571 @@
+"""Tests for Coverage's api."""
+
+import fnmatch, os, re, sys, textwrap
+
+import coverage
+from coverage.backward import StringIO
+
+from test.coveragetest import CoverageTest
+
+
+class SingletonApiTest(CoverageTest):
+ """Tests of the old-fashioned singleton API."""
+
+ def setUp(self):
+ super(SingletonApiTest, self).setUp()
+ # These tests use the singleton module interface. Prevent it from
+ # writing .coverage files at exit.
+ coverage.use_cache(0)
+
+ def do_report_work(self, modname):
+ """Create a module named `modname`, then measure it."""
+ coverage.erase()
+
+ self.make_file(modname+".py", """\
+ a = 1
+ b = 2
+ if b == 3:
+ c = 4
+ d = 5
+ e = 6
+ f = 7
+ """)
+
+ # Import the python file, executing it.
+ self.start_import_stop(coverage, modname)
+
+ def test_simple(self):
+ coverage.erase()
+
+ self.make_file("mycode.py", """\
+ a = 1
+ b = 2
+ if b == 3:
+ c = 4
+ d = 5
+ """)
+
+ # Import the python file, executing it.
+ self.start_import_stop(coverage, "mycode")
+
+ _, statements, missing, missingtext = coverage.analysis("mycode.py")
+ self.assertEqual(statements, [1,2,3,4,5])
+ self.assertEqual(missing, [4])
+ self.assertEqual(missingtext, "4")
+
+ def test_report(self):
+ self.do_report_work("mycode2")
+ coverage.report(["mycode2.py"])
+ self.assertEqual(self.stdout(), textwrap.dedent("""\
+ Name Stmts Miss Cover Missing
+ ---------------------------------------
+ mycode2 7 3 57% 4-6
+ """))
+
+ def test_report_file(self):
+ # The file= argument of coverage.report makes the report go there.
+ self.do_report_work("mycode3")
+ fout = StringIO()
+ coverage.report(["mycode3.py"], file=fout)
+ self.assertEqual(self.stdout(), "")
+ self.assertEqual(fout.getvalue(), textwrap.dedent("""\
+ Name Stmts Miss Cover Missing
+ ---------------------------------------
+ mycode3 7 3 57% 4-6
+ """))
+
+ def test_report_default(self):
+ # Calling report() with no morfs will report on whatever was executed.
+ self.do_report_work("mycode4")
+ coverage.report()
+ rpt = re.sub(r"\s+", " ", self.stdout())
+ self.assertIn("mycode4 7 3 57% 4-6", rpt)
+
+
+class ApiTest(CoverageTest):
+ """Api-oriented tests for Coverage."""
+
+ def clean_files(self, files, pats):
+ """Remove names matching `pats` from `files`, a list of filenames."""
+ good = []
+ for f in files:
+ for pat in pats:
+ if fnmatch.fnmatch(f, pat):
+ break
+ else:
+ good.append(f)
+ return good
+
+ def assertFiles(self, files):
+ """Assert that the files here are `files`, ignoring the usual junk."""
+ here = os.listdir(".")
+ here = self.clean_files(here, ["*.pyc", "__pycache__"])
+ self.assertSameElements(here, files)
+
+ def test_unexecuted_file(self):
+ cov = coverage.coverage()
+
+ self.make_file("mycode.py", """\
+ a = 1
+ b = 2
+ if b == 3:
+ c = 4
+ d = 5
+ """)
+
+ self.make_file("not_run.py", """\
+ fooey = 17
+ """)
+
+ # Import the python file, executing it.
+ self.start_import_stop(cov, "mycode")
+
+ _, statements, missing, _ = cov.analysis("not_run.py")
+ self.assertEqual(statements, [1])
+ self.assertEqual(missing, [1])
+
+ def test_filenames(self):
+
+ self.make_file("mymain.py", """\
+ import mymod
+ a = 1
+ """)
+
+ self.make_file("mymod.py", """\
+ fooey = 17
+ """)
+
+ # Import the python file, executing it.
+ cov = coverage.coverage()
+ self.start_import_stop(cov, "mymain")
+
+ 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"])
+ self.assertEqual(os.path.basename(filename), "mymod.py")
+
+ # Import the python file, executing it again, once it's been compiled
+ # already.
+ cov = coverage.coverage()
+ self.start_import_stop(cov, "mymain")
+
+ 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"])
+ self.assertEqual(os.path.basename(filename), "mymod.py")
+
+ def test_ignore_stdlib(self):
+ self.make_file("mymain.py", """\
+ import colorsys
+ a = 1
+ hls = colorsys.rgb_to_hls(1.0, 0.5, 0.0)
+ """)
+
+ # Measure without the stdlib.
+ cov1 = coverage.coverage()
+ self.assertEqual(cov1.config.cover_pylib, False)
+ self.start_import_stop(cov1, "mymain")
+
+ # some statements were marked executed in mymain.py
+ _, statements, missing, _ = cov1.analysis("mymain.py")
+ self.assertNotEqual(statements, missing)
+ # but none were in colorsys.py
+ _, statements, missing, _ = cov1.analysis("colorsys.py")
+ self.assertEqual(statements, missing)
+
+ # Measure with the stdlib.
+ cov2 = coverage.coverage(cover_pylib=True)
+ self.start_import_stop(cov2, "mymain")
+
+ # some statements were marked executed in mymain.py
+ _, statements, missing, _ = cov2.analysis("mymain.py")
+ self.assertNotEqual(statements, missing)
+ # and some were marked executed in colorsys.py
+ _, statements, missing, _ = cov2.analysis("colorsys.py")
+ self.assertNotEqual(statements, missing)
+
+ def test_include_can_measure_stdlib(self):
+ self.make_file("mymain.py", """\
+ import colorsys, random
+ a = 1
+ r, g, b = [random.random() for _ in range(3)]
+ hls = colorsys.rgb_to_hls(r, g, b)
+ """)
+
+ # Measure without the stdlib, but include colorsys.
+ cov1 = coverage.coverage(cover_pylib=False, include=["*/colorsys.py"])
+ self.start_import_stop(cov1, "mymain")
+
+ # some statements were marked executed in colorsys.py
+ _, statements, missing, _ = cov1.analysis("colorsys.py")
+ self.assertNotEqual(statements, missing)
+ # but none were in random.py
+ _, statements, missing, _ = cov1.analysis("random.py")
+ self.assertEqual(statements, missing)
+
+ def test_exclude_list(self):
+ cov = coverage.coverage()
+ cov.clear_exclude()
+ self.assertEqual(cov.get_exclude_list(), [])
+ cov.exclude("foo")
+ self.assertEqual(cov.get_exclude_list(), ["foo"])
+ cov.exclude("bar")
+ self.assertEqual(cov.get_exclude_list(), ["foo", "bar"])
+ self.assertEqual(cov._exclude_regex('exclude'), "(foo)|(bar)")
+ cov.clear_exclude()
+ self.assertEqual(cov.get_exclude_list(), [])
+
+ def test_exclude_partial_list(self):
+ cov = coverage.coverage()
+ cov.clear_exclude(which='partial')
+ self.assertEqual(cov.get_exclude_list(which='partial'), [])
+ cov.exclude("foo", which='partial')
+ self.assertEqual(cov.get_exclude_list(which='partial'), ["foo"])
+ cov.exclude("bar", which='partial')
+ self.assertEqual(cov.get_exclude_list(which='partial'), ["foo", "bar"])
+ self.assertEqual(cov._exclude_regex(which='partial'), "(foo)|(bar)")
+ cov.clear_exclude(which='partial')
+ self.assertEqual(cov.get_exclude_list(which='partial'), [])
+
+ def test_exclude_and_partial_are_separate_lists(self):
+ cov = coverage.coverage()
+ cov.clear_exclude(which='partial')
+ cov.clear_exclude(which='exclude')
+ cov.exclude("foo", which='partial')
+ self.assertEqual(cov.get_exclude_list(which='partial'), ['foo'])
+ self.assertEqual(cov.get_exclude_list(which='exclude'), [])
+ cov.exclude("bar", which='exclude')
+ self.assertEqual(cov.get_exclude_list(which='partial'), ['foo'])
+ self.assertEqual(cov.get_exclude_list(which='exclude'), ['bar'])
+ cov.exclude("p2", which='partial')
+ cov.exclude("e2", which='exclude')
+ self.assertEqual(cov.get_exclude_list(which='partial'), ['foo', 'p2'])
+ self.assertEqual(cov.get_exclude_list(which='exclude'), ['bar', 'e2'])
+ cov.clear_exclude(which='partial')
+ self.assertEqual(cov.get_exclude_list(which='partial'), [])
+ self.assertEqual(cov.get_exclude_list(which='exclude'), ['bar', 'e2'])
+ cov.clear_exclude(which='exclude')
+ self.assertEqual(cov.get_exclude_list(which='partial'), [])
+ self.assertEqual(cov.get_exclude_list(which='exclude'), [])
+
+ def test_datafile_default(self):
+ # Default data file behavior: it's .coverage
+ self.make_file("datatest1.py", """\
+ fooey = 17
+ """)
+
+ self.assertFiles(["datatest1.py"])
+ cov = coverage.coverage()
+ self.start_import_stop(cov, "datatest1")
+ cov.save()
+ self.assertFiles(["datatest1.py", ".coverage"])
+
+ def test_datafile_specified(self):
+ # You can specify the data file name.
+ self.make_file("datatest2.py", """\
+ fooey = 17
+ """)
+
+ self.assertFiles(["datatest2.py"])
+ cov = coverage.coverage(data_file="cov.data")
+ self.start_import_stop(cov, "datatest2")
+ cov.save()
+ self.assertFiles(["datatest2.py", "cov.data"])
+
+ def test_datafile_and_suffix_specified(self):
+ # You can specify the data file name and suffix.
+ self.make_file("datatest3.py", """\
+ fooey = 17
+ """)
+
+ self.assertFiles(["datatest3.py"])
+ cov = coverage.coverage(data_file="cov.data", data_suffix="14")
+ self.start_import_stop(cov, "datatest3")
+ cov.save()
+ self.assertFiles(["datatest3.py", "cov.data.14"])
+
+ def test_datafile_from_rcfile(self):
+ # You can specify the data file name in the .coveragerc file
+ self.make_file("datatest4.py", """\
+ fooey = 17
+ """)
+ self.make_file(".coveragerc", """\
+ [run]
+ data_file = mydata.dat
+ """)
+
+ self.assertFiles(["datatest4.py", ".coveragerc"])
+ cov = coverage.coverage()
+ self.start_import_stop(cov, "datatest4")
+ cov.save()
+ self.assertFiles(["datatest4.py", ".coveragerc", "mydata.dat"])
+
+ def test_empty_reporting(self):
+ # Used to be you'd get an exception reporting on nothing...
+ cov = coverage.coverage()
+ cov.erase()
+ cov.report()
+
+ def test_start_stop_start_stop(self):
+ self.make_file("code1.py", """\
+ code1 = 1
+ """)
+ self.make_file("code2.py", """\
+ code2 = 1
+ code2 = 2
+ """)
+ cov = coverage.coverage()
+ self.start_import_stop(cov, "code1")
+ cov.save()
+ self.start_import_stop(cov, "code2")
+ _, statements, missing, _ = cov.analysis("code1.py")
+ self.assertEqual(statements, [1])
+ self.assertEqual(missing, [])
+ _, statements, missing, _ = cov.analysis("code2.py")
+ self.assertEqual(statements, [1, 2])
+ self.assertEqual(missing, [])
+
+ if 0: # expected failure
+ # for https://bitbucket.org/ned/coveragepy/issue/79
+ def test_start_save_stop(self):
+ self.make_file("code1.py", """\
+ code1 = 1
+ """)
+ self.make_file("code2.py", """\
+ code2 = 1
+ code2 = 2
+ """)
+ cov = coverage.coverage()
+ cov.start()
+ self.import_local_file("code1")
+ cov.save()
+ self.import_local_file("code2")
+ cov.stop()
+
+ _, statements, missing, _ = cov.analysis("code1.py")
+ self.assertEqual(statements, [1])
+ self.assertEqual(missing, [])
+ _, statements, missing, _ = cov.analysis("code2.py")
+ self.assertEqual(statements, [1, 2])
+ self.assertEqual(missing, [])
+
+
+
+class UsingModulesMixin(object):
+ """A mixin for importing modules from test/modules and test/moremodules."""
+
+ run_in_temp_dir = False
+
+ def setUp(self):
+ super(UsingModulesMixin, self).setUp()
+ # Parent class saves and restores sys.path, we can just modify it.
+ self.old_dir = os.getcwd()
+ os.chdir(self.nice_file(os.path.dirname(__file__), 'modules'))
+ sys.path.append(".")
+ sys.path.append("../moremodules")
+
+ def tearDown(self):
+ os.chdir(self.old_dir)
+ super(UsingModulesMixin, self).tearDown()
+
+
+class OmitIncludeTestsMixin(UsingModulesMixin):
+ """Test methods for coverage methods taking include and omit."""
+
+ def filenames_in(self, summary, filenames):
+ """Assert the `filenames` are in the keys of `summary`."""
+ for filename in filenames.split():
+ self.assertIn(filename, summary)
+
+ def filenames_not_in(self, summary, filenames):
+ """Assert the `filenames` are not in the keys of `summary`."""
+ for filename in filenames.split():
+ self.assertNotIn(filename, summary)
+
+ def test_nothing_specified(self):
+ result = self.coverage_usepkgs()
+ self.filenames_in(result, "p1a p1b p2a p2b othera otherb osa osb")
+ self.filenames_not_in(result, "p1c")
+ # Because there was no source= specified, we don't search for
+ # unexecuted files.
+
+ def test_include(self):
+ result = self.coverage_usepkgs(include=["*/p1a.py"])
+ self.filenames_in(result, "p1a")
+ self.filenames_not_in(result, "p1b p1c p2a p2b othera otherb osa osb")
+
+ def test_include_2(self):
+ result = self.coverage_usepkgs(include=["*a.py"])
+ self.filenames_in(result, "p1a p2a othera osa")
+ self.filenames_not_in(result, "p1b p1c p2b otherb osb")
+
+ def test_include_as_string(self):
+ result = self.coverage_usepkgs(include="*a.py")
+ self.filenames_in(result, "p1a p2a othera osa")
+ self.filenames_not_in(result, "p1b p1c p2b otherb osb")
+
+ def test_omit(self):
+ result = self.coverage_usepkgs(omit=["*/p1a.py"])
+ self.filenames_in(result, "p1b p2a p2b")
+ self.filenames_not_in(result, "p1a p1c")
+
+ def test_omit_2(self):
+ result = self.coverage_usepkgs(omit=["*a.py"])
+ self.filenames_in(result, "p1b p2b otherb osb")
+ self.filenames_not_in(result, "p1a p1c p2a othera osa")
+
+ def test_omit_as_string(self):
+ result = self.coverage_usepkgs(omit="*a.py")
+ self.filenames_in(result, "p1b p2b otherb osb")
+ self.filenames_not_in(result, "p1a p1c p2a othera osa")
+
+ def test_omit_and_include(self):
+ result = self.coverage_usepkgs( include=["*/p1*"], omit=["*/p1a.py"])
+ self.filenames_in(result, "p1b")
+ self.filenames_not_in(result, "p1a p1c p2a p2b")
+
+
+class SourceOmitIncludeTest(OmitIncludeTestsMixin, CoverageTest):
+ """Test using `source`, `omit` and `include` when measuring code."""
+
+ def coverage_usepkgs(self, **kwargs):
+ """Run coverage on usepkgs and return the line summary.
+
+ Arguments are passed to the `coverage.coverage` constructor.
+
+ """
+ cov = coverage.coverage(**kwargs)
+ cov.start()
+ import usepkgs # pragma: nested # pylint: disable=F0401,W0612
+ cov.stop() # pragma: nested
+ cov._harvest_data() # private! sshhh...
+ summary = cov.data.summary()
+ for k, v in list(summary.items()):
+ assert k.endswith(".py")
+ summary[k[:-3]] = v
+ return summary
+
+ def test_source_package(self):
+ lines = self.coverage_usepkgs(source=["pkg1"])
+ self.filenames_in(lines, "p1a p1b")
+ self.filenames_not_in(lines, "p2a p2b othera otherb osa osb")
+ # Because source= was specified, we do search for unexecuted files.
+ self.assertEqual(lines['p1c'], 0)
+
+ def test_source_package_dotted(self):
+ lines = self.coverage_usepkgs(source=["pkg1.p1b"])
+ self.filenames_in(lines, "p1b")
+ self.filenames_not_in(lines, "p1a p1c p2a p2b othera otherb osa osb")
+
+
+class ReportIncludeOmitTest(OmitIncludeTestsMixin, CoverageTest):
+ """Tests of the report include/omit functionality."""
+
+ def coverage_usepkgs(self, **kwargs):
+ """Try coverage.report()."""
+ cov = coverage.coverage()
+ cov.start()
+ import usepkgs # pragma: nested # pylint: disable=F0401,W0612
+ cov.stop() # pragma: nested
+ report = StringIO()
+ cov.report(file=report, **kwargs)
+ return report.getvalue()
+
+
+class XmlIncludeOmitTest(OmitIncludeTestsMixin, CoverageTest):
+ """Tests of the xml include/omit functionality.
+
+ This also takes care of the HTML and annotate include/omit, by virtue
+ of the structure of the code.
+
+ """
+
+ def coverage_usepkgs(self, **kwargs):
+ """Try coverage.xml_report()."""
+ cov = coverage.coverage()
+ cov.start()
+ import usepkgs # pragma: nested # pylint: disable=F0401,W0612
+ cov.stop() # pragma: nested
+ cov.xml_report(outfile="-", **kwargs)
+ return self.stdout()
+
+
+class AnalysisTest(CoverageTest):
+ """Test the numerical analysis of results."""
+ def test_many_missing_branches(self):
+ cov = coverage.coverage(branch=True)
+
+ self.make_file("missing.py", """\
+ def fun1(x):
+ if x == 1:
+ print("one")
+ else:
+ print("not one")
+ print("done") # pragma: nocover
+
+ def fun2(x):
+ print("x")
+
+ fun2(3)
+ """)
+
+ # Import the python file, executing it.
+ self.start_import_stop(cov, "missing")
+
+ nums = cov._analyze("missing.py").numbers
+ self.assertEqual(nums.n_files, 1)
+ self.assertEqual(nums.n_statements, 7)
+ self.assertEqual(nums.n_excluded, 1)
+ self.assertEqual(nums.n_missing, 3)
+ self.assertEqual(nums.n_branches, 2)
+ self.assertEqual(nums.n_partial_branches, 0)
+ self.assertEqual(nums.n_missing_branches, 2)
+
+
+class PluginTest(CoverageTest):
+ """Test that the API works properly the way the plugins call it.
+
+ We don't actually use the plugins, but these tests call the API the same
+ way they do.
+
+ """
+ def pretend_to_be_nose_with_cover(self, erase):
+ """This is what the nose --with-cover plugin does."""
+ cov = coverage.coverage()
+
+ self.make_file("no_biggie.py", """\
+ a = 1
+ b = 2
+ if b == 1:
+ c = 4
+ """)
+
+ if erase:
+ cov.combine()
+ cov.erase()
+ cov.load()
+ self.start_import_stop(cov, "no_biggie")
+ cov.combine()
+ cov.save()
+ cov.report(["no_biggie.py"])
+ self.assertEqual(self.stdout(), textwrap.dedent("""\
+ Name Stmts Miss Cover Missing
+ -----------------------------------------
+ no_biggie 4 1 75% 4
+ """))
+
+ def test_nose_plugin(self):
+ self.pretend_to_be_nose_with_cover(erase=False)
+
+ def test_nose_plugin_with_erase(self):
+ self.pretend_to_be_nose_with_cover(erase=True)
diff --git a/tests/test_arcs.py b/tests/test_arcs.py
new file mode 100644
index 0000000..69c054c
--- /dev/null
+++ b/tests/test_arcs.py
@@ -0,0 +1,578 @@
+"""Tests for Coverage.py's arc measurement."""
+
+import sys
+from test.coveragetest import CoverageTest
+
+
+class SimpleArcTest(CoverageTest):
+ """Tests for Coverage.py's arc measurement."""
+
+ def test_simple_sequence(self):
+ self.check_coverage("""\
+ a = 1
+ b = 2
+ """,
+ arcz=".1 12 2.")
+ self.check_coverage("""\
+ a = 1
+
+ b = 3
+ """,
+ arcz=".1 13 3.")
+ self.check_coverage("""\
+
+ a = 2
+ b = 3
+
+ c = 5
+ """,
+ arcz=".2 23 35 5-2")
+
+ def test_function_def(self):
+ self.check_coverage("""\
+ def foo():
+ a = 2
+
+ foo()
+ """,
+ arcz=".1 .2 14 2. 4.")
+
+ def test_if(self):
+ self.check_coverage("""\
+ a = 1
+ if len([]) == 0:
+ a = 3
+ assert a == 3
+ """,
+ arcz=".1 12 23 24 34 4.", arcz_missing="24")
+ self.check_coverage("""\
+ a = 1
+ if len([]) == 1:
+ a = 3
+ 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:
+ a = 2
+ else:
+ a = 4
+ assert a == 2
+ """,
+ arcz=".1 12 25 14 45 5.", arcz_missing="14 45")
+ self.check_coverage("""\
+ if len([]) == 1:
+ a = 2
+ else:
+ a = 4
+ assert a == 4
+ """,
+ arcz=".1 12 25 14 45 5.", arcz_missing="12 25")
+
+ def test_compact_if(self):
+ self.check_coverage("""\
+ a = 1
+ if len([]) == 0: a = 2
+ assert a == 2
+ """,
+ arcz=".1 12 23 3.", arcz_missing="")
+ self.check_coverage("""\
+ def fn(x):
+ if x % 2: return True
+ return False
+ a = fn(1)
+ assert a == True
+ """,
+ arcz=".1 14 45 5. .2 2. 23 3.", arcz_missing="23 3.")
+
+ def test_multiline(self):
+ # The firstlineno of the a assignment below differs among Python
+ # versions.
+ if sys.version_info >= (2, 5):
+ arcz = ".1 15 5-2"
+ else:
+ arcz = ".1 15 5-1"
+ self.check_coverage("""\
+ a = (
+ 2 +
+ 3
+ )
+ b = \\
+ 6
+ """,
+ arcz=arcz, arcz_missing="")
+
+ def test_if_return(self):
+ self.check_coverage("""\
+ def if_ret(a):
+ if a:
+ return 3
+ b = 4
+ return 5
+ x = if_ret(0) + if_ret(1)
+ assert x == 8
+ """,
+ arcz=".1 16 67 7. .2 23 24 3. 45 5.", arcz_missing=""
+ )
+
+ def test_dont_confuse_exit_and_else(self):
+ self.check_coverage("""\
+ def foo():
+ if foo:
+ a = 3
+ else:
+ a = 5
+ return a
+ assert foo() == 3 # 7
+ """,
+ arcz=".1 17 7. .2 23 36 25 56 6.", arcz_missing="25 56"
+ )
+ self.check_coverage("""\
+ def foo():
+ if foo:
+ a = 3
+ else:
+ a = 5
+ foo() # 6
+ """,
+ arcz=".1 16 6. .2 23 3. 25 5.", arcz_missing="25 5."
+ )
+
+ if 0: # expected failure
+ def test_lambdas_are_confusing_bug_90(self):
+ self.check_coverage("""\
+ a = 1
+ fn = lambda x: x
+ b = 3
+ """,
+ arcz=".1 12 .2 2-2 23 3."
+ )
+
+
+if sys.version_info >= (2, 6):
+ class WithTest(CoverageTest):
+ """Arc-measuring tests involving context managers."""
+
+ def test_with(self):
+ self.check_coverage("""\
+ def example():
+ with open("test", "w") as f: # exit
+ f.write("")
+ return 1
+
+ example()
+ """,
+ arcz=".1 .2 23 34 4. 16 6."
+ )
+
+
+class LoopArcTest(CoverageTest):
+ """Arc-measuring tests involving loops."""
+
+ def test_loop(self):
+ self.check_coverage("""\
+ for i in range(10):
+ a = i
+ assert a == 9
+ """,
+ arcz=".1 12 21 13 3.", arcz_missing="")
+ self.check_coverage("""\
+ a = -1
+ for i in range(0):
+ a = i
+ assert a == -1
+ """,
+ arcz=".1 12 23 32 24 4.", arcz_missing="23 32")
+
+ def test_nested_loop(self):
+ self.check_coverage("""\
+ for i in range(3):
+ for j in range(3):
+ a = i + j
+ assert a == 4
+ """,
+ arcz=".1 12 23 32 21 14 4.", arcz_missing="")
+
+ def test_break(self):
+ self.check_coverage("""\
+ for i in range(10):
+ a = i
+ break # 3
+ a = 99
+ assert a == 0 # 5
+ """,
+ arcz=".1 12 23 35 15 41 5.", arcz_missing="15 41")
+
+ def test_continue(self):
+ self.check_coverage("""\
+ for i in range(10):
+ a = i
+ continue # 3
+ a = 99
+ assert a == 9 # 5
+ """,
+ arcz=".1 12 23 31 15 41 5.", arcz_missing="41")
+
+ def test_nested_breaks(self):
+ self.check_coverage("""\
+ for i in range(3):
+ for j in range(3):
+ a = i + j
+ break # 4
+ if i == 2:
+ break
+ assert a == 2 and i == 2 # 7
+ """,
+ arcz=".1 12 23 34 45 25 56 51 67 17 7.", arcz_missing="17 25")
+
+ def test_while_true(self):
+ # With "while 1", the loop knows it's constant.
+ self.check_coverage("""\
+ a, i = 1, 0
+ while 1:
+ if i >= 3:
+ a = 4
+ break
+ i += 1
+ assert a == 4 and i == 3
+ """,
+ arcz=".1 12 23 34 45 36 63 57 7.",
+ )
+ # With "while True", 2.x thinks it's computation, 3.x thinks it's
+ # constant.
+ if sys.version_info >= (3, 0):
+ arcz = ".1 12 23 34 45 36 63 57 7."
+ else:
+ arcz = ".1 12 23 27 34 45 36 62 57 7."
+ self.check_coverage("""\
+ a, i = 1, 0
+ while True:
+ if i >= 3:
+ a = 4
+ break
+ i += 1
+ assert a == 4 and i == 3
+ """,
+ arcz=arcz,
+ )
+
+ def test_for_if_else_for(self):
+ self.check_coverage("""\
+ def branches_2(l):
+ if l:
+ for e in l:
+ a = 4
+ else:
+ a = 6
+
+ def branches_3(l):
+ for x in l:
+ if x:
+ for e in l:
+ a = 12
+ else:
+ a = 14
+
+ branches_2([0,1])
+ branches_3([0,1])
+ """,
+ arcz=
+ ".1 18 8G GH H. "
+ ".2 23 34 43 26 3. 6. "
+ ".9 9A 9-8 AB BC CB B9 AE E9",
+ arcz_missing="26 6."
+ )
+
+ def test_for_else(self):
+ self.check_coverage("""\
+ def forelse(seq):
+ for n in seq:
+ if n > 5:
+ break
+ else:
+ print('None of the values were greater than 5')
+ print('Done')
+ forelse([1,2])
+ forelse([1,6])
+ """,
+ arcz=".1 .2 23 32 34 47 26 67 7. 18 89 9."
+ )
+
+ def test_confusing_for_loop_bug_175(self):
+ if sys.version_info >= (3, 0):
+ # Py3 counts the list comp as a separate code object.
+ arcz = ".1 .2 2-2 12 23 34 45 53 3."
+ else:
+ arcz = ".1 12 23 34 45 53 3."
+ self.check_coverage("""\
+ o = [(1,2), (3,4)]
+ o = [a for a in o]
+ for tup in o:
+ x = tup[0]
+ y = tup[1]
+ """,
+ arcz=arcz, arcz_missing="", arcz_unpredicted="")
+ if sys.version_info >= (3, 0):
+ arcz = ".1 12 .2 2-2 23 34 42 2."
+ else:
+ arcz = ".1 12 23 34 42 2."
+ self.check_coverage("""\
+ o = [(1,2), (3,4)]
+ for tup in [a for a in o]:
+ x = tup[0]
+ y = tup[1]
+ """,
+ arcz=arcz, arcz_missing="", arcz_unpredicted="")
+
+
+class ExceptionArcTest(CoverageTest):
+ """Arc-measuring tests involving exception handling."""
+
+ def test_try_except(self):
+ self.check_coverage("""\
+ a, b = 1, 1
+ try:
+ a = 3
+ except:
+ b = 5
+ assert a == 3 and b == 1
+ """,
+ arcz=".1 12 23 36 45 56 6.", arcz_missing="45 56")
+ self.check_coverage("""\
+ a, b = 1, 1
+ try:
+ a = 3
+ raise Exception("Yikes!")
+ a = 5
+ except:
+ b = 7
+ assert a == 3 and b == 7
+ """,
+ arcz=".1 12 23 34 58 67 78 8.",
+ arcz_missing="58", arcz_unpredicted="46")
+
+ def test_hidden_raise(self):
+ self.check_coverage("""\
+ a, b = 1, 1
+ def oops(x):
+ if x % 2: raise Exception("odd")
+ try:
+ a = 5
+ oops(1)
+ a = 7
+ except:
+ b = 9
+ assert a == 5 and b == 9
+ """,
+ arcz=".1 12 .3 3-2 24 45 56 67 7A 89 9A A.",
+ arcz_missing="67 7A", arcz_unpredicted="68")
+
+ def test_except_with_type(self):
+ self.check_coverage("""\
+ a, b = 1, 1
+ def oops(x):
+ if x % 2: raise ValueError("odd")
+ def try_it(x):
+ try:
+ a = 6
+ oops(x)
+ a = 8
+ except ValueError:
+ b = 10
+ return a
+ assert try_it(0) == 8 # C
+ assert try_it(1) == 6 # D
+ """,
+ arcz=".1 12 .3 3-2 24 4C CD D. .5 56 67 78 8B 9A AB B-4",
+ arcz_missing="",
+ arcz_unpredicted="79")
+
+ def test_try_finally(self):
+ self.check_coverage("""\
+ a, c = 1, 1
+ try:
+ a = 3
+ finally:
+ c = 5
+ assert a == 3 and c == 5
+ """,
+ arcz=".1 12 23 35 56 6.", arcz_missing="")
+ self.check_coverage("""\
+ a, c, d = 1, 1, 1
+ try:
+ try:
+ a = 4
+ finally:
+ c = 6
+ except:
+ d = 8
+ assert a == 4 and c == 6 and d == 1 # 9
+ """,
+ arcz=".1 12 23 34 46 67 78 89 69 9.",
+ arcz_missing="67 78 89", arcz_unpredicted="")
+ self.check_coverage("""\
+ a, c, d = 1, 1, 1
+ try:
+ try:
+ a = 4
+ raise Exception("Yikes!")
+ a = 6
+ finally:
+ c = 8
+ except:
+ d = 10 # A
+ assert a == 4 and c == 8 and d == 10 # B
+ """,
+ arcz=".1 12 23 34 45 68 89 8B 9A AB B.",
+ arcz_missing="68 8B", arcz_unpredicted="58")
+
+ def test_finally_in_loop(self):
+ self.check_coverage("""\
+ a, c, d, i = 1, 1, 1, 99
+ try:
+ for i in range(5):
+ try:
+ a = 5
+ if i > 0:
+ raise Exception("Yikes!")
+ a = 8
+ finally:
+ c = 10
+ except:
+ d = 12 # C
+ assert a == 5 and c == 10 and d == 12 # D
+ """,
+ arcz=".1 12 23 34 3D 45 56 67 68 8A A3 AB AD BC CD D.",
+ arcz_missing="3D AD", arcz_unpredicted="7A")
+ self.check_coverage("""\
+ a, c, d, i = 1, 1, 1, 99
+ try:
+ for i in range(5):
+ try:
+ a = 5
+ if i > 10:
+ raise Exception("Yikes!")
+ a = 8
+ finally:
+ c = 10
+ except:
+ d = 12 # C
+ assert a == 8 and c == 10 and d == 1 # D
+ """,
+ arcz=".1 12 23 34 3D 45 56 67 68 8A A3 AB AD BC CD D.",
+ arcz_missing="67 AB AD BC CD", arcz_unpredicted="")
+
+
+ def test_break_in_finally(self):
+ self.check_coverage("""\
+ a, c, d, i = 1, 1, 1, 99
+ try:
+ for i in range(5):
+ try:
+ a = 5
+ if i > 0:
+ break
+ a = 8
+ finally:
+ c = 10
+ except:
+ d = 12 # C
+ assert a == 5 and c == 10 and d == 1 # D
+ """,
+ arcz=".1 12 23 34 3D 45 56 67 68 7A 8A A3 AB AD BC CD D.",
+ arcz_missing="3D AB BC CD", arcz_unpredicted="")
+
+ if 1: # expected failure
+ def test_finally_in_loop_bug_92(self):
+ self.check_coverage("""\
+ for i in range(5):
+ try:
+ j = 3
+ finally:
+ f = 5
+ g = 6
+ h = 7
+ """,
+ arcz=".1 12 23 35 56 61 17 7.",
+ arcz_missing="", arcz_unpredicted="")
+
+ if sys.version_info >= (2, 5):
+ # Try-except-finally was new in 2.5
+ def test_except_finally(self):
+ self.check_coverage("""\
+ a, b, c = 1, 1, 1
+ try:
+ a = 3
+ except:
+ b = 5
+ finally:
+ c = 7
+ assert a == 3 and b == 1 and c == 7
+ """,
+ arcz=".1 12 23 45 37 57 78 8.", arcz_missing="45 57")
+ self.check_coverage("""\
+ a, b, c = 1, 1, 1
+ def oops(x):
+ if x % 2: raise Exception("odd")
+ try:
+ a = 5
+ oops(1)
+ a = 7
+ except:
+ b = 9
+ finally:
+ c = 11
+ assert a == 5 and b == 9 and c == 11
+ """,
+ arcz=".1 12 .3 3-2 24 45 56 67 7B 89 9B BC C.",
+ arcz_missing="67 7B", arcz_unpredicted="68")
+
+
+class MiscArcTest(CoverageTest):
+ """Miscellaneous arc-measuring tests."""
+
+ def test_dict_literal(self):
+ self.check_coverage("""\
+ d = {
+ 'a': 2,
+ 'b': 3,
+ 'c': {
+ 'd': 5,
+ 'e': 6,
+ }
+ }
+ assert d
+ """,
+ arcz=".1 19 9.")
+
+
+class ExcludeTest(CoverageTest):
+ """Tests of exclusions to indicate known partial branches."""
+
+ def test_default(self):
+ # A number of forms of pragma comment are accepted.
+ self.check_coverage("""\
+ a = 1
+ if a: #pragma: no branch
+ b = 3
+ c = 4
+ if c: # pragma NOBRANCH
+ d = 6
+ e = 7
+ """,
+ [1,2,3,4,5,6,7],
+ arcz=".1 12 23 24 34 45 56 57 67 7.", arcz_missing="")
+
+ def test_custom_pragmas(self):
+ self.check_coverage("""\
+ a = 1
+ while a: # [only some]
+ c = 3
+ break
+ assert c == 5-2
+ """,
+ [1,2,3,4,5],
+ partials=["only some"],
+ arcz=".1 12 23 34 45 25 5.", arcz_missing="")
diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py
new file mode 100644
index 0000000..33f9021
--- /dev/null
+++ b/tests/test_cmdline.py
@@ -0,0 +1,702 @@
+"""Test cmdline.py for coverage."""
+
+import pprint, re, shlex, sys, textwrap
+import mock
+import coverage
+import coverage.cmdline
+from coverage.misc import ExceptionDuringRun
+
+from test.coveragetest import CoverageTest, OK, ERR
+
+
+class CmdLineTest(CoverageTest):
+ """Tests of execution paths through the command line interpreter."""
+
+ run_in_temp_dir = False
+
+ INIT_LOAD = """\
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None)
+ .load()\n"""
+
+ def model_object(self):
+ """Return a Mock suitable for use in CoverageScript."""
+ mk = mock.Mock()
+ mk.coverage.return_value = mk
+ return mk
+
+ 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,
+ _run_python_module=m.run_python_module, _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)
+ self.assertEqual(r1, ret,
+ "Wrong status: got %s, wanted %s" % (r1, ret)
+ )
+
+ # Remove all indentation, and change ".foo()" to "m2.foo()".
+ code = re.sub(r"(?m)^\s+", "", code)
+ code = re.sub(r"(?m)^\.", "m2.", code)
+ m2 = self.model_object()
+ code_obj = compile(code, "<code>", "exec")
+ eval(code_obj, globals(), { 'm2': m2 })
+ self.assert_same_method_calls(m1, m2)
+
+ def cmd_executes_same(self, args1, args2):
+ """Assert that the `args1` executes the same as `args2`."""
+ m1, r1 = self.mock_command_line(args1)
+ m2, r2 = self.mock_command_line(args2)
+ self.assertEqual(r1, r2)
+ self.assert_same_method_calls(m1, m2)
+
+ def assert_same_method_calls(self, m1, m2):
+ """Assert that `m1.method_calls` and `m2.method_calls` are the same."""
+ # Use a real equality comparison, but if it fails, use a nicer assert
+ # so we can tell what's going on. We have to use the real == first due
+ # to CmdOptionParser.__eq__
+ if m1.method_calls != m2.method_calls:
+ pp1 = pprint.pformat(m1.method_calls)
+ pp2 = pprint.pformat(m2.method_calls)
+ self.assertMultiLineEqual(pp1+'\n', pp2+'\n')
+
+ 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,
+ "Wrong status: got %s, wanted %s" % (r, ret)
+ )
+ if help_msg:
+ self.assertEqual(m.method_calls[-1],
+ ('help_fn', (help_msg,), {})
+ )
+ else:
+ self.assertEqual(m.method_calls[-1],
+ ('help_fn', (), {'topic':topic})
+ )
+
+
+class CmdLineTestTest(CmdLineTest):
+ """Tests that our CmdLineTest helpers work."""
+ def test_assert_same_method_calls(self):
+ # All the other tests here use self.cmd_executes_same in successful
+ # ways, so here we just check that it fails.
+ self.assertRaises(AssertionError, self.cmd_executes_same, "-e", "-c")
+
+
+class ClassicCmdLineTest(CmdLineTest):
+ """Tests of the classic coverage.py command line."""
+
+ def test_erase(self):
+ # coverage -e
+ self.cmd_executes("-e", """\
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None)
+ .erase()
+ """)
+ self.cmd_executes_same("-e", "--erase")
+
+ def test_execute(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=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None)
+ .load()
+ .start()
+ .run_python_file('foo.py', ['foo.py'])
+ .stop()
+ .save()
+ """)
+ # -e -x calls coverage.erase first.
+ self.cmd_executes("-e -x foo.py", """\
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None)
+ .erase()
+ .start()
+ .run_python_file('foo.py', ['foo.py'])
+ .stop()
+ .save()
+ """)
+ # --timid sets a flag, and program arguments get passed through.
+ self.cmd_executes("-x --timid foo.py abc 123", """\
+ .coverage(cover_pylib=None, data_suffix=None, timid=True, branch=None, config_file=True, source=None, include=None, omit=None)
+ .load()
+ .start()
+ .run_python_file('foo.py', ['foo.py', 'abc', '123'])
+ .stop()
+ .save()
+ """)
+ # -L sets a flag, and flags for the program don't confuse us.
+ self.cmd_executes("-x -p -L foo.py -a -b", """\
+ .coverage(cover_pylib=True, data_suffix=True, timid=None, branch=None, config_file=True, source=None, include=None, omit=None)
+ .load()
+ .start()
+ .run_python_file('foo.py', ['foo.py', '-a', '-b'])
+ .stop()
+ .save()
+ """)
+
+ # Check that long forms of flags do the same thing as short forms.
+ self.cmd_executes_same("-x f.py", "--execute f.py")
+ self.cmd_executes_same("-e -x f.py", "--erase --execute f.py")
+ self.cmd_executes_same("-x -p f.py", "-x --parallel-mode f.py")
+ self.cmd_executes_same("-x -L f.py", "-x --pylib f.py")
+
+ def test_combine(self):
+ # coverage -c
+ self.cmd_executes("-c", """\
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None)
+ .load()
+ .combine()
+ .save()
+ """)
+ self.cmd_executes_same("-c", "--combine")
+
+ def test_report(self):
+ # coverage -r [-m] [-i] [-o DIR,...] [FILE1 FILE2 ...]
+ self.cmd_executes("-r", self.INIT_LOAD + """\
+ .report(ignore_errors=None, omit=None, include=None, morfs=[],
+ show_missing=None)
+ """)
+ self.cmd_executes("-r -i", self.INIT_LOAD + """\
+ .report(ignore_errors=True, omit=None, include=None, morfs=[],
+ show_missing=None)
+ """)
+ self.cmd_executes("-r -m", self.INIT_LOAD + """\
+ .report(ignore_errors=None, omit=None, include=None, morfs=[],
+ show_missing=True)
+ """)
+ self.cmd_executes("-r -o fooey", """\
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"])
+ .load()
+ .report(ignore_errors=None, omit=["fooey"], include=None,
+ morfs=[], show_missing=None)
+ """)
+ self.cmd_executes("-r -o fooey,booey", """\
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"])
+ .load()
+ .report(ignore_errors=None, omit=["fooey", "booey"], include=None,
+ morfs=[], show_missing=None)
+ """)
+ self.cmd_executes("-r mod1", self.INIT_LOAD + """\
+ .report(ignore_errors=None, omit=None, include=None,
+ morfs=["mod1"], show_missing=None)
+ """)
+ self.cmd_executes("-r mod1 mod2 mod3", self.INIT_LOAD + """\
+ .report(ignore_errors=None, omit=None, include=None,
+ morfs=["mod1", "mod2", "mod3"], show_missing=None)
+ """)
+
+ self.cmd_executes_same("-r", "--report")
+ self.cmd_executes_same("-r -i", "-r --ignore-errors")
+ self.cmd_executes_same("-r -m", "-r --show-missing")
+ self.cmd_executes_same("-r -o f", "-r --omit=f")
+ self.cmd_executes_same("-r -o f", "-r --omit f")
+ self.cmd_executes_same("-r -o f,b", "-r --omit=f,b")
+ self.cmd_executes_same("-r -o f,b", "-r --omit f,b")
+ self.cmd_executes_same("-r -of", "-r --omit=f")
+ self.cmd_executes_same("-r -of,b", "-r --omit=f,b")
+
+ def test_annotate(self):
+ # coverage -a [-d DIR] [-i] [-o DIR,...] [FILE1 FILE2 ...]
+ self.cmd_executes("-a", self.INIT_LOAD + """\
+ .annotate(directory=None, ignore_errors=None,
+ omit=None, include=None, morfs=[])
+ """)
+ self.cmd_executes("-a -d dir1", self.INIT_LOAD + """\
+ .annotate(directory="dir1", ignore_errors=None,
+ omit=None, include=None, morfs=[])
+ """)
+ self.cmd_executes("-a -i", self.INIT_LOAD + """\
+ .annotate(directory=None, ignore_errors=True,
+ omit=None, include=None, morfs=[])
+ """)
+ self.cmd_executes("-a -o fooey", """\
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"])
+ .load()
+ .annotate(directory=None, ignore_errors=None,
+ omit=["fooey"], include=None, morfs=[])
+ """)
+ self.cmd_executes("-a -o fooey,booey", """\
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"])
+ .load()
+ .annotate(directory=None, ignore_errors=None,
+ omit=["fooey", "booey"], include=None, morfs=[])
+ """)
+ self.cmd_executes("-a mod1", self.INIT_LOAD + """\
+ .annotate(directory=None, ignore_errors=None,
+ omit=None, include=None, morfs=["mod1"])
+ """)
+ self.cmd_executes("-a mod1 mod2 mod3", self.INIT_LOAD + """\
+ .annotate(directory=None, ignore_errors=None,
+ omit=None, include=None, morfs=["mod1", "mod2", "mod3"])
+ """)
+
+ self.cmd_executes_same("-a", "--annotate")
+ self.cmd_executes_same("-a -d d1", "-a --directory=d1")
+ self.cmd_executes_same("-a -i", "-a --ignore-errors")
+ self.cmd_executes_same("-a -o f", "-a --omit=f")
+ self.cmd_executes_same("-a -o f", "-a --omit f")
+ self.cmd_executes_same("-a -o f,b", "-a --omit=f,b")
+ self.cmd_executes_same("-a -o f,b", "-a --omit f,b")
+ self.cmd_executes_same("-a -of", "-a --omit=f")
+ self.cmd_executes_same("-a -of,b", "-a --omit=f,b")
+
+ def test_html_report(self):
+ # coverage -b -d DIR [-i] [-o DIR,...] [FILE1 FILE2 ...]
+ self.cmd_executes("-b", self.INIT_LOAD + """\
+ .html_report(directory=None, ignore_errors=None, title=None,
+ omit=None, include=None, morfs=[])
+ """)
+ self.cmd_executes("-b -d dir1", self.INIT_LOAD + """\
+ .html_report(directory="dir1", ignore_errors=None, title=None,
+ omit=None, include=None, morfs=[])
+ """)
+ self.cmd_executes("-b -i", self.INIT_LOAD + """\
+ .html_report(directory=None, ignore_errors=True, title=None,
+ omit=None, include=None, morfs=[])
+ """)
+ self.cmd_executes("-b -o fooey", """\
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"])
+ .load()
+ .html_report(directory=None, ignore_errors=None, title=None,
+ omit=["fooey"], include=None, morfs=[])
+ """)
+ self.cmd_executes("-b -o fooey,booey", """\
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"])
+ .load()
+ .html_report(directory=None, ignore_errors=None, title=None,
+ omit=["fooey", "booey"], include=None, morfs=[])
+ """)
+ self.cmd_executes("-b mod1", self.INIT_LOAD + """\
+ .html_report(directory=None, ignore_errors=None, title=None,
+ omit=None, include=None, morfs=["mod1"])
+ """)
+ self.cmd_executes("-b mod1 mod2 mod3", self.INIT_LOAD + """\
+ .html_report(directory=None, ignore_errors=None, title=None,
+ omit=None, include=None, morfs=["mod1", "mod2", "mod3"])
+ """)
+
+ self.cmd_executes_same("-b", "--html")
+ self.cmd_executes_same("-b -d d1", "-b --directory=d1")
+ self.cmd_executes_same("-b -i", "-b --ignore-errors")
+ self.cmd_executes_same("-b -o f", "-b --omit=f")
+ self.cmd_executes_same("-b -o f,b", "-b --omit=f,b")
+ self.cmd_executes_same("-b -of", "-b --omit=f")
+ self.cmd_executes_same("-b -of,b", "-b --omit=f,b")
+
+ def test_help(self):
+ # coverage -h
+ self.cmd_help("-h", topic="help", ret=OK)
+ self.cmd_help("--help", topic="help", ret=OK)
+
+ def test_version(self):
+ # coverage --version
+ self.cmd_help("--version", topic="version", ret=OK)
+
+ ## Error cases
+
+ def test_argless_actions(self):
+ self.cmd_help("-e foo bar", "Unexpected arguments: foo bar")
+ self.cmd_help("-c baz quux", "Unexpected arguments: baz quux")
+
+ def test_need_action(self):
+ self.cmd_help("-p", "You must specify at least one of "
+ "-e, -x, -c, -r, -a, or -b.")
+
+ def test_bad_action_combinations(self):
+ self.cmd_help('-e -a',
+ "You can't specify the 'erase' and 'annotate' "
+ "options at the same time."
+ )
+ self.cmd_help('-e -r',
+ "You can't specify the 'erase' and 'report' "
+ "options at the same time."
+ )
+ self.cmd_help('-e -b',
+ "You can't specify the 'erase' and 'html' "
+ "options at the same time."
+ )
+ self.cmd_help('-e -c',
+ "You can't specify the 'erase' and 'combine' "
+ "options at the same time."
+ )
+ self.cmd_help('-x -a',
+ "You can't specify the 'execute' and 'annotate' "
+ "options at the same time."
+ )
+ self.cmd_help('-x -r',
+ "You can't specify the 'execute' and 'report' "
+ "options at the same time."
+ )
+ self.cmd_help('-x -b',
+ "You can't specify the 'execute' and 'html' "
+ "options at the same time."
+ )
+ self.cmd_help('-x -c',
+ "You can't specify the 'execute' and 'combine' "
+ "options at the same time."
+ )
+
+ def test_nothing_to_do(self):
+ self.cmd_help("-x", "Nothing to do.")
+
+ def test_unknown_option(self):
+ self.cmd_help("-z", "no such option: -z")
+
+
+class FakeCoverageForDebugData(object):
+ """Just enough of a fake coverage package for the 'debug data' tests."""
+ def __init__(self, summary):
+ self._summary = summary
+ self.filename = "FILENAME"
+ self.data = self
+
+ # package members
+ def coverage(self, *unused_args, **unused_kwargs):
+ """The coverage class in the package."""
+ return self
+
+ # coverage methods
+ def load(self):
+ """Fake coverage().load()"""
+ pass
+
+ # data methods
+ def has_arcs(self):
+ """Fake coverage().data.has_arcs()"""
+ return False
+
+ def summary(self, fullpath): # pylint: disable=W0613
+ """Fake coverage().data.summary()"""
+ return self._summary
+
+
+class NewCmdLineTest(CmdLineTest):
+ """Tests of the coverage.py command line."""
+
+ def test_annotate(self):
+ self.cmd_executes_same("annotate", "-a")
+ self.cmd_executes_same("annotate -i", "-a -i")
+ self.cmd_executes_same("annotate -d d1", "-a -d d1")
+ self.cmd_executes_same("annotate --omit f", "-a --omit f")
+ self.cmd_executes_same("annotate --omit f,b", "-a --omit f,b")
+ self.cmd_executes_same("annotate m1", "-a m1")
+ self.cmd_executes_same("annotate m1 m2 m3", "-a m1 m2 m3")
+
+ def test_combine(self):
+ self.cmd_executes_same("combine", "-c")
+
+ def test_debug(self):
+ self.cmd_help("debug", "What information would you like: data, sys?")
+ self.cmd_help("debug foo", "Don't know what you mean by 'foo'")
+
+ def test_debug_data(self):
+ fake = FakeCoverageForDebugData({
+ 'file1.py': 17, 'file2.py': 23,
+ })
+ self.command_line("debug data", _covpkg=fake)
+ self.assertMultiLineEqual(self.stdout(), textwrap.dedent("""\
+ -- data ---------------------------------------
+ path: FILENAME
+ has_arcs: False
+
+ 2 files:
+ file1.py: 17 lines
+ file2.py: 23 lines
+ """))
+
+ def test_debug_data_with_no_data(self):
+ fake = FakeCoverageForDebugData({})
+ self.command_line("debug data", _covpkg=fake)
+ self.assertMultiLineEqual(self.stdout(), textwrap.dedent("""\
+ -- data ---------------------------------------
+ path: FILENAME
+ has_arcs: False
+ No data collected
+ """))
+
+ def test_debug_sys(self):
+ self.command_line("debug sys")
+ out = self.stdout()
+ assert "version:" in out
+ assert "data_path:" in out
+
+ def test_erase(self):
+ self.cmd_executes_same("erase", "-e")
+
+ def test_help(self):
+ self.cmd_executes("help", ".help_fn(topic='help')")
+
+ def test_cmd_help(self):
+ self.cmd_executes("run --help",
+ ".help_fn(parser='<CmdOptionParser:run>')")
+ self.cmd_executes_same("help run", "run --help")
+
+ def test_html(self):
+ self.cmd_executes_same("html", "-b")
+ self.cmd_executes_same("html -i", "-b -i")
+ self.cmd_executes_same("html -d d1", "-b -d d1")
+ self.cmd_executes_same("html --omit f", "-b --omit f")
+ self.cmd_executes_same("html --omit f,b", "-b --omit f,b")
+ self.cmd_executes_same("html m1", "-b m1")
+ self.cmd_executes_same("html m1 m2 m3", "-b m1 m2 m3")
+ self.cmd_executes("html", self.INIT_LOAD + """\
+ .html_report(ignore_errors=None, omit=None, include=None, morfs=[],
+ directory=None, title=None)
+ """)
+ self.cmd_executes("html --title=Hello_there", self.INIT_LOAD + """\
+ .html_report(ignore_errors=None, omit=None, include=None, morfs=[],
+ directory=None, title='Hello_there')
+ """)
+
+ def test_report(self):
+ self.cmd_executes_same("report", "-r")
+ self.cmd_executes_same("report -i", "-r -i")
+ self.cmd_executes_same("report -m", "-r -m")
+ self.cmd_executes_same("report --omit f", "-r --omit f")
+ self.cmd_executes_same("report --omit f,b", "-r --omit f,b")
+ self.cmd_executes_same("report m1", "-r m1")
+ self.cmd_executes_same("report m1 m2 m3", "-r m1 m2 m3")
+
+ def test_run(self):
+ self.cmd_executes_same("run f.py", "-e -x f.py")
+ self.cmd_executes_same("run f.py -a arg -z", "-e -x f.py -a arg -z")
+ self.cmd_executes_same("run -a f.py", "-x f.py")
+ self.cmd_executes_same("run -p f.py", "-e -x -p f.py")
+ 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")
+ self.cmd_executes("run --branch foo.py", """\
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=True, config_file=True, source=None, include=None, omit=None)
+ .erase()
+ .start()
+ .run_python_file('foo.py', ['foo.py'])
+ .stop()
+ .save()
+ """)
+ self.cmd_executes("run --rcfile=myrc.rc foo.py", """\
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file="myrc.rc", source=None, include=None, omit=None)
+ .erase()
+ .start()
+ .run_python_file('foo.py', ['foo.py'])
+ .stop()
+ .save()
+ """)
+ self.cmd_executes("run --include=pre1,pre2 foo.py", """\
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=["pre1", "pre2"], omit=None)
+ .erase()
+ .start()
+ .run_python_file('foo.py', ['foo.py'])
+ .stop()
+ .save()
+ """)
+ self.cmd_executes("run --omit=opre1,opre2 foo.py", """\
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["opre1", "opre2"])
+ .erase()
+ .start()
+ .run_python_file('foo.py', ['foo.py'])
+ .stop()
+ .save()
+ """)
+ self.cmd_executes("run --include=pre1,pre2 --omit=opre1,opre2 foo.py",
+ """\
+ .coverage(cover_pylib=None, data_suffix=None, timid=None,
+ branch=None, config_file=True, source=None,
+ include=["pre1", "pre2"],
+ omit=["opre1", "opre2"])
+ .erase()
+ .start()
+ .run_python_file('foo.py', ['foo.py'])
+ .stop()
+ .save()
+ """)
+ self.cmd_executes("run --source=quux,hi.there,/home/bar foo.py",
+ """\
+ .coverage(cover_pylib=None, data_suffix=None, timid=None,
+ branch=None, config_file=True,
+ source=["quux", "hi.there", "/home/bar"], include=None,
+ omit=None)
+ .erase()
+ .start()
+ .run_python_file('foo.py', ['foo.py'])
+ .stop()
+ .save()
+ """)
+
+ def test_run_module(self):
+ self.cmd_executes("run -m mymodule", """\
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None)
+ .erase()
+ .start()
+ .run_python_module('mymodule', ['mymodule'])
+ .stop()
+ .save()
+ """)
+ self.cmd_executes("run -m mymodule -qq arg1 arg2", """\
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None)
+ .erase()
+ .start()
+ .run_python_module('mymodule', ['mymodule', '-qq', 'arg1', 'arg2'])
+ .stop()
+ .save()
+ """)
+ self.cmd_executes("run --branch -m mymodule", """\
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=True, config_file=True, source=None, include=None, omit=None)
+ .erase()
+ .start()
+ .run_python_module('mymodule', ['mymodule'])
+ .stop()
+ .save()
+ """)
+ self.cmd_executes_same("run -m mymodule", "run --module mymodule")
+
+ def test_xml(self):
+ # coverage xml [-i] [--omit DIR,...] [FILE1 FILE2 ...]
+ self.cmd_executes("xml", self.INIT_LOAD + """\
+ .xml_report(ignore_errors=None, omit=None, include=None, morfs=[],
+ outfile=None)
+ """)
+ self.cmd_executes("xml -i", self.INIT_LOAD + """\
+ .xml_report(ignore_errors=True, omit=None, include=None, morfs=[],
+ outfile=None)
+ """)
+ self.cmd_executes("xml -o myxml.foo", self.INIT_LOAD + """\
+ .xml_report(ignore_errors=None, omit=None, include=None, morfs=[],
+ outfile="myxml.foo")
+ """)
+ self.cmd_executes("xml -o -", self.INIT_LOAD + """\
+ .xml_report(ignore_errors=None, omit=None, include=None, morfs=[],
+ outfile="-")
+ """)
+ self.cmd_executes("xml --omit fooey", """\
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"])
+ .load()
+ .xml_report(ignore_errors=None, omit=["fooey"], include=None, morfs=[],
+ outfile=None)
+ """)
+ self.cmd_executes("xml --omit fooey,booey", """\
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"])
+ .load()
+ .xml_report(ignore_errors=None, omit=["fooey", "booey"], include=None,
+ morfs=[], outfile=None)
+ """)
+ self.cmd_executes("xml mod1", self.INIT_LOAD + """\
+ .xml_report(ignore_errors=None, omit=None, include=None, morfs=["mod1"],
+ outfile=None)
+ """)
+ self.cmd_executes("xml mod1 mod2 mod3", self.INIT_LOAD + """\
+ .xml_report(ignore_errors=None, omit=None, include=None,
+ morfs=["mod1", "mod2", "mod3"], outfile=None)
+ """)
+
+ def test_no_arguments_at_all(self):
+ self.cmd_help("", topic="minimum_help", ret=OK)
+
+ def test_bad_command(self):
+ self.cmd_help("xyzzy", "Unknown command: 'xyzzy'")
+
+
+class CmdLineStdoutTest(CmdLineTest):
+ """Test the command line with real stdout output."""
+
+ def test_minimum_help(self):
+ self.command_line("")
+ out = self.stdout()
+ assert "Code coverage for Python." in out
+ assert out.count("\n") < 4
+
+ def test_version(self):
+ self.command_line("--version")
+ out = self.stdout()
+ assert "ersion " in out
+ assert out.count("\n") < 4
+
+ def test_help(self):
+ self.command_line("help")
+ out = self.stdout()
+ assert "nedbatchelder.com" in out
+ assert out.count("\n") > 10
+
+ def test_cmd_help(self):
+ self.command_line("help run")
+ out = self.stdout()
+ assert "<pyfile>" in out
+ assert "--timid" in out
+ assert out.count("\n") > 10
+
+ def test_error(self):
+ self.command_line("fooey kablooey", ret=ERR)
+ out = self.stdout()
+ assert "fooey" in out
+ assert "help" in out
+
+
+class CmdMainTest(CoverageTest):
+ """Tests of coverage.cmdline.main(), using mocking for isolation."""
+
+ class CoverageScriptStub(object):
+ """A stub for coverage.cmdline.CoverageScript, used by CmdMainTest."""
+
+ def command_line(self, argv):
+ """Stub for command_line, the arg determines what it will do."""
+ if argv[0] == 'hello':
+ print("Hello, world!")
+ elif argv[0] == 'raise':
+ try:
+ raise Exception("oh noes!")
+ except:
+ raise ExceptionDuringRun(*sys.exc_info())
+ elif argv[0] == 'internalraise':
+ raise ValueError("coverage is broken")
+ elif argv[0] == 'exit':
+ sys.exit(23)
+ else:
+ raise AssertionError("Bad CoverageScriptStub: %r"% (argv,))
+ return 0
+
+ def setUp(self):
+ super(CmdMainTest, self).setUp()
+ self.old_CoverageScript = coverage.cmdline.CoverageScript
+ coverage.cmdline.CoverageScript = self.CoverageScriptStub
+
+ def tearDown(self):
+ coverage.cmdline.CoverageScript = self.old_CoverageScript
+ super(CmdMainTest, self).tearDown()
+
+ def test_normal(self):
+ ret = coverage.cmdline.main(['hello'])
+ self.assertEqual(ret, 0)
+ self.assertEqual(self.stdout(), "Hello, world!\n")
+
+ def test_raise(self):
+ ret = coverage.cmdline.main(['raise'])
+ self.assertEqual(ret, 1)
+ self.assertEqual(self.stdout(), "")
+ err = self.stderr().split('\n')
+ self.assertEqual(err[0], 'Traceback (most recent call last):')
+ self.assertEqual(err[-3], ' raise Exception("oh noes!")')
+ self.assertEqual(err[-2], 'Exception: oh noes!')
+
+ def test_internalraise(self):
+ self.assertRaisesRegexp(ValueError,
+ "coverage is broken",
+ coverage.cmdline.main, ['internalraise']
+ )
+
+ def test_exit(self):
+ ret = coverage.cmdline.main(['exit'])
+ self.assertEqual(ret, 23)
diff --git a/tests/test_codeunit.py b/tests/test_codeunit.py
new file mode 100644
index 0000000..b4caff8
--- /dev/null
+++ b/tests/test_codeunit.py
@@ -0,0 +1,103 @@
+"""Tests for coverage.codeunit"""
+
+import os, sys
+
+from coverage.codeunit import code_unit_factory
+from coverage.files import FileLocator
+
+from test.coveragetest import CoverageTest
+
+# pylint: disable=F0401
+# Unable to import 'aa' (No module named aa)
+
+class CodeUnitTest(CoverageTest):
+ """Tests for coverage.codeunit"""
+
+ run_in_temp_dir = False
+
+ def setUp(self):
+ super(CodeUnitTest, self).setUp()
+ # 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())
+ ccu = code_unit_factory("aa/bb/cc/cfile.py", FileLocator())
+ self.assertEqual(acu[0].name, "aa/afile")
+ self.assertEqual(bcu[0].name, "aa/bb/bfile")
+ self.assertEqual(ccu[0].name, "aa/bb/cc/cfile")
+ self.assertEqual(acu[0].flat_rootname(), "aa_afile")
+ self.assertEqual(bcu[0].flat_rootname(), "aa_bb_bfile")
+ self.assertEqual(ccu[0].flat_rootname(), "aa_bb_cc_cfile")
+ self.assertEqual(acu[0].source_file().read(), "# afile.py\n")
+ self.assertEqual(bcu[0].source_file().read(), "# bfile.py\n")
+ self.assertEqual(ccu[0].source_file().read(), "# cfile.py\n")
+
+ def test_odd_filenames(self):
+ acu = code_unit_factory("aa/afile.odd.py", FileLocator())
+ bcu = code_unit_factory("aa/bb/bfile.odd.py", FileLocator())
+ b2cu = code_unit_factory("aa/bb.odd/bfile.py", FileLocator())
+ self.assertEqual(acu[0].name, "aa/afile.odd")
+ self.assertEqual(bcu[0].name, "aa/bb/bfile.odd")
+ self.assertEqual(b2cu[0].name, "aa/bb.odd/bfile")
+ self.assertEqual(acu[0].flat_rootname(), "aa_afile_odd")
+ self.assertEqual(bcu[0].flat_rootname(), "aa_bb_bfile_odd")
+ self.assertEqual(b2cu[0].flat_rootname(), "aa_bb_odd_bfile")
+ self.assertEqual(acu[0].source_file().read(), "# afile.odd.py\n")
+ self.assertEqual(bcu[0].source_file().read(), "# bfile.odd.py\n")
+ self.assertEqual(b2cu[0].source_file().read(), "# bfile.py\n")
+
+ def test_modules(self):
+ import aa, aa.bb, aa.bb.cc
+ cu = code_unit_factory([aa, aa.bb, aa.bb.cc], FileLocator())
+ self.assertEqual(cu[0].name, "aa")
+ self.assertEqual(cu[1].name, "aa.bb")
+ self.assertEqual(cu[2].name, "aa.bb.cc")
+ self.assertEqual(cu[0].flat_rootname(), "aa")
+ self.assertEqual(cu[1].flat_rootname(), "aa_bb")
+ self.assertEqual(cu[2].flat_rootname(), "aa_bb_cc")
+ self.assertEqual(cu[0].source_file().read(), "# aa\n")
+ self.assertEqual(cu[1].source_file().read(), "# bb\n")
+ self.assertEqual(cu[2].source_file().read(), "") # yes, empty
+
+ def test_module_files(self):
+ import aa.afile, aa.bb.bfile, aa.bb.cc.cfile
+ cu = code_unit_factory([aa.afile, aa.bb.bfile, aa.bb.cc.cfile],
+ FileLocator())
+ self.assertEqual(cu[0].name, "aa.afile")
+ self.assertEqual(cu[1].name, "aa.bb.bfile")
+ self.assertEqual(cu[2].name, "aa.bb.cc.cfile")
+ self.assertEqual(cu[0].flat_rootname(), "aa_afile")
+ self.assertEqual(cu[1].flat_rootname(), "aa_bb_bfile")
+ self.assertEqual(cu[2].flat_rootname(), "aa_bb_cc_cfile")
+ self.assertEqual(cu[0].source_file().read(), "# afile.py\n")
+ self.assertEqual(cu[1].source_file().read(), "# bfile.py\n")
+ self.assertEqual(cu[2].source_file().read(), "# cfile.py\n")
+
+ def test_comparison(self):
+ acu = code_unit_factory("aa/afile.py", FileLocator())[0]
+ acu2 = code_unit_factory("aa/afile.py", FileLocator())[0]
+ zcu = code_unit_factory("aa/zfile.py", FileLocator())[0]
+ bcu = code_unit_factory("aa/bb/bfile.py", FileLocator())[0]
+ assert acu == acu2 and acu <= acu2 and acu >= acu2
+ assert acu < zcu and acu <= zcu and acu != zcu
+ assert zcu > acu and zcu >= acu and zcu != acu
+ assert acu < bcu and acu <= bcu and acu != bcu
+ assert bcu > acu and bcu >= acu and bcu != acu
+
+ def test_egg(self):
+ # Test that we can get files out of eggs, and read their source files.
+ # The egg1 module is installed by an action in igor.py.
+ import egg1, egg1.egg1
+ # Verify that we really imported from an egg. If we did, then the
+ # __file__ won't be an actual file, because one of the "directories"
+ # in the path is actually the .egg zip file.
+ self.assert_doesnt_exist(egg1.__file__)
+
+ cu = code_unit_factory([egg1, egg1.egg1], FileLocator())
+ self.assertEqual(cu[0].source_file().read(), "")
+ self.assertEqual(cu[1].source_file().read().split("\n")[0],
+ "# My egg file!"
+ )
diff --git a/tests/test_config.py b/tests/test_config.py
new file mode 100644
index 0000000..1ca6376
--- /dev/null
+++ b/tests/test_config.py
@@ -0,0 +1,225 @@
+# -*- coding: utf-8 -*-
+"""Test the config file handling for coverage.py"""
+
+import sys
+import coverage
+from coverage.misc import CoverageException
+
+from test.coveragetest import CoverageTest
+
+
+class ConfigTest(CoverageTest):
+ """Tests of the different sources of configuration settings."""
+
+ def test_default_config(self):
+ # Just constructing a coverage() object gets the right defaults.
+ cov = coverage.coverage()
+ self.assertFalse(cov.config.timid)
+ self.assertFalse(cov.config.branch)
+ self.assertEqual(cov.config.data_file, ".coverage")
+
+ def test_arguments(self):
+ # Arguments to the constructor are applied to the configuation.
+ cov = coverage.coverage(timid=True, data_file="fooey.dat")
+ self.assertTrue(cov.config.timid)
+ self.assertFalse(cov.config.branch)
+ self.assertEqual(cov.config.data_file, "fooey.dat")
+
+ def test_config_file(self):
+ # A .coveragerc file will be read into the configuration.
+ self.make_file(".coveragerc", """\
+ # This is just a bogus .rc file for testing.
+ [run]
+ timid = True
+ data_file = .hello_kitty.data
+ """)
+ cov = coverage.coverage()
+ self.assertTrue(cov.config.timid)
+ self.assertFalse(cov.config.branch)
+ self.assertEqual(cov.config.data_file, ".hello_kitty.data")
+
+ def test_named_config_file(self):
+ # You can name the config file what you like.
+ self.make_file("my_cov.ini", """\
+ [run]
+ timid = True
+ ; I wouldn't really use this as a data file...
+ data_file = delete.me
+ """)
+ cov = coverage.coverage(config_file="my_cov.ini")
+ self.assertTrue(cov.config.timid)
+ self.assertFalse(cov.config.branch)
+ self.assertEqual(cov.config.data_file, "delete.me")
+
+ def test_ignored_config_file(self):
+ # You can disable reading the .coveragerc file.
+ self.make_file(".coveragerc", """\
+ [run]
+ timid = True
+ data_file = delete.me
+ """)
+ cov = coverage.coverage(config_file=False)
+ self.assertFalse(cov.config.timid)
+ self.assertFalse(cov.config.branch)
+ self.assertEqual(cov.config.data_file, ".coverage")
+
+ def test_config_file_then_args(self):
+ # The arguments override the .coveragerc file.
+ self.make_file(".coveragerc", """\
+ [run]
+ timid = True
+ data_file = weirdo.file
+ """)
+ cov = coverage.coverage(timid=False, data_file=".mycov")
+ self.assertFalse(cov.config.timid)
+ self.assertFalse(cov.config.branch)
+ self.assertEqual(cov.config.data_file, ".mycov")
+
+ def test_data_file_from_environment(self):
+ # There's an environment variable for the data_file.
+ self.make_file(".coveragerc", """\
+ [run]
+ timid = True
+ data_file = weirdo.file
+ """)
+ self.set_environ("COVERAGE_FILE", "fromenv.dat")
+ cov = coverage.coverage()
+ self.assertEqual(cov.config.data_file, "fromenv.dat")
+ # But the constructor args override the env var.
+ cov = coverage.coverage(data_file="fromarg.dat")
+ self.assertEqual(cov.config.data_file, "fromarg.dat")
+
+ def test_parse_errors(self):
+ # Im-parseable values raise CoverageException
+ self.make_file(".coveragerc", """\
+ [run]
+ timid = maybe?
+ """)
+ self.assertRaises(CoverageException, coverage.coverage)
+
+ def test_environment_vars_in_config(self):
+ # Config files can have $envvars in them.
+ self.make_file(".coveragerc", """\
+ [run]
+ data_file = $DATA_FILE.fooey
+ branch = $OKAY
+ [report]
+ exclude_lines =
+ the_$$one
+ another${THING}
+ x${THING}y
+ x${NOTHING}y
+ huh$${X}what
+ """)
+ self.set_environ("DATA_FILE", "hello-world")
+ self.set_environ("THING", "ZZZ")
+ self.set_environ("OKAY", "yes")
+ cov = coverage.coverage()
+ self.assertEqual(cov.config.data_file, "hello-world.fooey")
+ self.assertEqual(cov.config.branch, True)
+ self.assertEqual(cov.config.exclude_list,
+ ["the_$one", "anotherZZZ", "xZZZy", "xy", "huh${X}what"]
+ )
+
+
+class ConfigFileTest(CoverageTest):
+ """Tests of the config file settings in particular."""
+
+ def test_config_file_settings(self):
+ # This sample file tries to use lots of variation of syntax...
+ self.make_file(".coveragerc", """\
+ # This is a settings file for coverage.py
+ [run]
+ timid = yes
+ data_file = something_or_other.dat
+ branch = 1
+ cover_pylib = TRUE
+ parallel = on
+ include = a/ , b/
+
+ [report]
+ ; these settings affect reporting.
+ exclude_lines =
+ if 0:
+
+ pragma:?\\s+no cover
+ another_tab
+
+ ignore_errors = TRUE
+ omit =
+ one, another, some_more,
+ yet_more
+ precision = 3
+
+ partial_branches =
+ pragma:?\\s+no branch
+ partial_branches_always =
+ if 0:
+ while True:
+
+ show_missing= TruE
+
+ [html]
+
+ directory = c:\\tricky\\dir.somewhere
+ extra_css=something/extra.css
+ title = Title & nums # nums!
+ [xml]
+ output=mycov.xml
+
+ [paths]
+ source =
+ .
+ /home/ned/src/
+
+ other = other, /home/ned/other, c:\\Ned\\etc
+
+ """)
+ cov = coverage.coverage()
+
+ self.assertTrue(cov.config.timid)
+ self.assertEqual(cov.config.data_file, "something_or_other.dat")
+ self.assertTrue(cov.config.branch)
+ self.assertTrue(cov.config.cover_pylib)
+ self.assertTrue(cov.config.parallel)
+
+ self.assertEqual(cov.get_exclude_list(),
+ ["if 0:", r"pragma:?\s+no cover", "another_tab"]
+ )
+ self.assertTrue(cov.config.ignore_errors)
+ self.assertEqual(cov.config.include, ["a/", "b/"])
+ self.assertEqual(cov.config.omit,
+ ["one", "another", "some_more", "yet_more"]
+ )
+ self.assertEqual(cov.config.precision, 3)
+
+ self.assertEqual(cov.config.partial_list,
+ [r"pragma:?\s+no branch"]
+ )
+ self.assertEqual(cov.config.partial_always_list,
+ ["if 0:", "while True:"]
+ )
+ self.assertTrue(cov.config.show_missing)
+ self.assertEqual(cov.config.html_dir, r"c:\tricky\dir.somewhere")
+ self.assertEqual(cov.config.extra_css, "something/extra.css")
+ self.assertEqual(cov.config.html_title, "Title & nums # nums!")
+
+ self.assertEqual(cov.config.xml_output, "mycov.xml")
+
+ self.assertEqual(cov.config.paths, {
+ 'source': ['.', '/home/ned/src/'],
+ 'other': ['other', '/home/ned/other', 'c:\\Ned\\etc']
+ })
+
+ if sys.version_info[:2] != (3,1):
+ def test_one(self):
+ # This sample file tries to use lots of variation of syntax...
+ self.make_file(".coveragerc", """\
+ [html]
+ title = tabblo & «ταБЬℓσ» # numbers
+ """)
+ cov = coverage.coverage()
+
+ self.assertEqual(cov.config.html_title,
+ "tabblo & «ταБЬℓσ» # numbers"
+ )
diff --git a/tests/test_coverage.py b/tests/test_coverage.py
new file mode 100644
index 0000000..0918dfe
--- /dev/null
+++ b/tests/test_coverage.py
@@ -0,0 +1,1730 @@
+"""Tests for Coverage."""
+# http://nedbatchelder.com/code/coverage
+
+import sys
+import coverage
+from coverage.misc import CoverageException
+from test.coveragetest import CoverageTest
+
+
+class TestCoverageTest(CoverageTest):
+ """Make sure our complex self.check_coverage method works."""
+
+ def test_successful_coverage(self):
+ # The simplest run possible.
+ self.check_coverage("""\
+ a = 1
+ b = 2
+ """,
+ [1,2]
+ )
+ # You can provide a list of possible statement matches.
+ self.check_coverage("""\
+ a = 1
+ b = 2
+ """,
+ ([100], [1,2], [1723,47]),
+ )
+ # You can specify missing lines.
+ self.check_coverage("""\
+ a = 1
+ if a == 2:
+ a = 3
+ """,
+ [1,2,3],
+ missing="3",
+ )
+ # You can specify a list of possible missing lines.
+ self.check_coverage("""\
+ a = 1
+ if a == 2:
+ a = 3
+ """,
+ [1,2,3],
+ missing=("47-49", "3", "100,102")
+ )
+
+ def test_failed_coverage(self):
+ # If the lines are wrong, the message shows right and wrong.
+ self.assertRaisesRegexp(AssertionError,
+ r"\[1, 2] != \[1]",
+ self.check_coverage, """\
+ a = 1
+ b = 2
+ """,
+ [1]
+ )
+ # If the list of lines possibilities is wrong, the msg shows right.
+ self.assertRaisesRegexp(AssertionError,
+ r"None of the lines choices matched \[1, 2]",
+ self.check_coverage, """\
+ a = 1
+ b = 2
+ """,
+ ([1], [2])
+ )
+ # If the missing lines are wrong, the message shows right and wrong.
+ self.assertRaisesRegexp(AssertionError,
+ r"'3' != '37'",
+ self.check_coverage, """\
+ a = 1
+ if a == 2:
+ a = 3
+ """,
+ [1,2,3],
+ missing="37",
+ )
+ # If the missing lines possibilities are wrong, the msg shows right.
+ self.assertRaisesRegexp(AssertionError,
+ r"None of the missing choices matched '3'",
+ self.check_coverage, """\
+ a = 1
+ if a == 2:
+ a = 3
+ """,
+ [1,2,3],
+ missing=("37", "4-10"),
+ )
+
+
+class BasicCoverageTest(CoverageTest):
+ """The simplest tests, for quick smoke testing of fundamental changes."""
+
+ def test_simple(self):
+ self.check_coverage("""\
+ a = 1
+ b = 2
+
+ c = 4
+ # Nothing here
+ d = 6
+ """,
+ [1,2,4,6], report="4 0 100%")
+
+ def test_indentation_wackiness(self):
+ # Partial final lines are OK.
+ self.check_coverage("""\
+ import sys
+ if not sys.path:
+ a = 1
+ """,
+ [1,2,3], "3")
+
+ def test_multiline_initializer(self):
+ self.check_coverage("""\
+ d = {
+ 'foo': 1+2,
+ 'bar': (lambda x: x+1)(1),
+ 'baz': str(1),
+ }
+
+ e = { 'foo': 1, 'bar': 2 }
+ """,
+ [1,7], "")
+
+ def test_list_comprehension(self):
+ self.check_coverage("""\
+ l = [
+ 2*i for i in range(10)
+ if i > 5
+ ]
+ assert l == [12, 14, 16, 18]
+ """,
+ [1,5], "")
+
+
+class SimpleStatementTest(CoverageTest):
+ """Testing simple single-line statements."""
+
+ def test_expression(self):
+ # Bare expressions as statements are tricky: some implementations
+ # optimize some of them away. All implementations seem to count
+ # the implicit return at the end as executable.
+ self.check_coverage("""\
+ 12
+ 23
+ """,
+ ([1,2],[2]), "")
+ self.check_coverage("""\
+ 12
+ 23
+ a = 3
+ """,
+ ([1,2,3],[3]), "")
+ self.check_coverage("""\
+ 1 + 2
+ 1 + \\
+ 2
+ """,
+ ([1,2], [2]), "")
+ self.check_coverage("""\
+ 1 + 2
+ 1 + \\
+ 2
+ a = 4
+ """,
+ ([1,2,4], [4]), "")
+
+ def test_assert(self):
+ self.check_coverage("""\
+ assert (1 + 2)
+ assert (1 +
+ 2)
+ assert (1 + 2), 'the universe is broken'
+ assert (1 +
+ 2), \\
+ 'something is amiss'
+ """,
+ [1,2,4,5], "")
+
+ def test_assignment(self):
+ # Simple variable assignment
+ self.check_coverage("""\
+ a = (1 + 2)
+ b = (1 +
+ 2)
+ c = \\
+ 1
+ """,
+ [1,2,4], "")
+
+ def test_assign_tuple(self):
+ self.check_coverage("""\
+ a = 1
+ a,b,c = 7,8,9
+ assert a == 7 and b == 8 and c == 9
+ """,
+ [1,2,3], "")
+
+ def test_attribute_assignment(self):
+ # Attribute assignment
+ self.check_coverage("""\
+ class obj: pass
+ o = obj()
+ o.foo = (1 + 2)
+ o.foo = (1 +
+ 2)
+ o.foo = \\
+ 1
+ """,
+ [1,2,3,4,6], "")
+
+ def test_list_of_attribute_assignment(self):
+ self.check_coverage("""\
+ class obj: pass
+ o = obj()
+ o.a, o.b = (1 + 2), 3
+ o.a, o.b = (1 +
+ 2), (3 +
+ 4)
+ o.a, o.b = \\
+ 1, \\
+ 2
+ """,
+ [1,2,3,4,7], "")
+
+ def test_augmented_assignment(self):
+ self.check_coverage("""\
+ a = 1
+ a += 1
+ a += (1 +
+ 2)
+ a += \\
+ 1
+ """,
+ [1,2,3,5], "")
+
+ def test_triple_string_stuff(self):
+ self.check_coverage("""\
+ a = '''
+ a multiline
+ string.
+ '''
+ b = '''
+ long expression
+ ''' + '''
+ on many
+ lines.
+ '''
+ c = len('''
+ long expression
+ ''' +
+ '''
+ on many
+ lines.
+ ''')
+ """,
+ [1,5,11], "")
+
+ def test_pass(self):
+ # pass is tricky: if it's the only statement in a block, then it is
+ # "executed". But if it is not the only statement, then it is not.
+ self.check_coverage("""\
+ if 1==1:
+ pass
+ """,
+ [1,2], "")
+ self.check_coverage("""\
+ def foo():
+ pass
+ foo()
+ """,
+ [1,2,3], "")
+ self.check_coverage("""\
+ def foo():
+ "doc"
+ pass
+ foo()
+ """,
+ ([1,3,4], [1,4]), "")
+ self.check_coverage("""\
+ class Foo:
+ def foo(self):
+ pass
+ Foo().foo()
+ """,
+ [1,2,3,4], "")
+ self.check_coverage("""\
+ class Foo:
+ def foo(self):
+ "Huh?"
+ pass
+ Foo().foo()
+ """,
+ ([1,2,4,5], [1,2,5]), "")
+
+ def test_del(self):
+ self.check_coverage("""\
+ d = { 'a': 1, 'b': 1, 'c': 1, 'd': 1, 'e': 1 }
+ del d['a']
+ del d[
+ 'b'
+ ]
+ del d['c'], \\
+ d['d'], \\
+ d['e']
+ assert(len(d.keys()) == 0)
+ """,
+ [1,2,3,6,9], "")
+
+ if sys.version_info < (3, 0): # Print statement is gone in Py3k.
+ def test_print(self):
+ self.check_coverage("""\
+ print "hello, world!"
+ print ("hey: %d" %
+ 17)
+ print "goodbye"
+ print "hello, world!",
+ print ("hey: %d" %
+ 17),
+ print "goodbye",
+ """,
+ [1,2,4,5,6,8], "")
+
+ def test_raise(self):
+ self.check_coverage("""\
+ try:
+ raise Exception(
+ "hello %d" %
+ 17)
+ except:
+ pass
+ """,
+ [1,2,5,6], "")
+
+ def test_return(self):
+ self.check_coverage("""\
+ def fn():
+ a = 1
+ return a
+
+ x = fn()
+ assert(x == 1)
+ """,
+ [1,2,3,5,6], "")
+ self.check_coverage("""\
+ def fn():
+ a = 1
+ return (
+ a +
+ 1)
+
+ x = fn()
+ assert(x == 2)
+ """,
+ [1,2,3,7,8], "")
+ self.check_coverage("""\
+ def fn():
+ a = 1
+ return (a,
+ a + 1,
+ a + 2)
+
+ x,y,z = fn()
+ assert x == 1 and y == 2 and z == 3
+ """,
+ [1,2,3,7,8], "")
+
+ def test_yield(self):
+ self.check_coverage("""\
+ from __future__ import generators
+ def gen():
+ yield 1
+ yield (2+
+ 3+
+ 4)
+ yield 1, \\
+ 2
+ a,b,c = gen()
+ assert a == 1 and b == 9 and c == (1,2)
+ """,
+ [1,2,3,4,7,9,10], "")
+
+ def test_break(self):
+ self.check_coverage("""\
+ for x in range(10):
+ a = 2 + x
+ break
+ a = 4
+ assert a == 2
+ """,
+ [1,2,3,4,5], "4")
+
+ def test_continue(self):
+ self.check_coverage("""\
+ for x in range(10):
+ a = 2 + x
+ continue
+ a = 4
+ assert a == 11
+ """,
+ [1,2,3,4,5], "4")
+
+ if 0: # expected failure
+ # Peephole optimization of jumps to jumps can mean that some statements
+ # never hit the line tracer. The behavior is different in different
+ # versions of Python, so don't run this test:
+ def test_strange_unexecuted_continue(self):
+ self.check_coverage("""\
+ a = b = c = 0
+ for n in range(100):
+ if n % 2:
+ if n % 4:
+ a += 1
+ continue # <-- This line may not be hit.
+ else:
+ 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:
+ if n % 3:
+ a += 1
+ continue # <-- This line is always hit.
+ else:
+ b += 1
+ c += 1
+ 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 test_import(self):
+ self.check_coverage("""\
+ import string
+ from sys import path
+ a = 1
+ """,
+ [1,2,3], "")
+ self.check_coverage("""\
+ import string
+ if 1 == 2:
+ from sys import path
+ a = 1
+ """,
+ [1,2,3,4], "3")
+ self.check_coverage("""\
+ import string, \\
+ os, \\
+ re
+ from sys import path, \\
+ stdout
+ a = 1
+ """,
+ [1,4,6], "")
+ self.check_coverage("""\
+ import sys, sys as s
+ assert s.path == sys.path
+ """,
+ [1,2], "")
+ self.check_coverage("""\
+ import sys, \\
+ sys as s
+ assert s.path == sys.path
+ """,
+ [1,3], "")
+ self.check_coverage("""\
+ from sys import path, \\
+ path as p
+ assert p == path
+ """,
+ [1,3], "")
+ self.check_coverage("""\
+ from sys import \\
+ *
+ assert len(path) > 0
+ """,
+ [1,3], "")
+
+ def test_global(self):
+ self.check_coverage("""\
+ g = h = i = 1
+ def fn():
+ global g
+ global h, \\
+ i
+ g = h = i = 2
+ fn()
+ assert g == 2 and h == 2 and i == 2
+ """,
+ [1,2,6,7,8], "")
+ self.check_coverage("""\
+ g = h = i = 1
+ def fn():
+ global g; g = 2
+ fn()
+ assert g == 2 and h == 1 and i == 1
+ """,
+ [1,2,3,4,5], "")
+
+ if sys.version_info < (3, 0):
+ # In Python 2.x, exec is a statement.
+ def test_exec(self):
+ self.check_coverage("""\
+ a = b = c = 1
+ exec "a = 2"
+ exec ("b = " +
+ "c = " +
+ "2")
+ assert a == 2 and b == 2 and c == 2
+ """,
+ [1,2,3,6], "")
+ self.check_coverage("""\
+ vars = {'a': 1, 'b': 1, 'c': 1}
+ exec "a = 2" in vars
+ exec ("b = " +
+ "c = " +
+ "2") in vars
+ assert vars['a'] == 2 and vars['b'] == 2 and vars['c'] == 2
+ """,
+ [1,2,3,6], "")
+ self.check_coverage("""\
+ globs = {}
+ locs = {'a': 1, 'b': 1, 'c': 1}
+ exec "a = 2" in globs, locs
+ exec ("b = " +
+ "c = " +
+ "2") in globs, locs
+ assert locs['a'] == 2 and locs['b'] == 2 and locs['c'] == 2
+ """,
+ [1,2,3,4,7], "")
+ else:
+ # In Python 3.x, exec is a function.
+ def test_exec(self):
+ self.check_coverage("""\
+ a = b = c = 1
+ exec("a = 2")
+ exec("b = " +
+ "c = " +
+ "2")
+ assert a == 2 and b == 2 and c == 2
+ """,
+ [1,2,3,6], "")
+ self.check_coverage("""\
+ vars = {'a': 1, 'b': 1, 'c': 1}
+ exec("a = 2", vars)
+ exec("b = " +
+ "c = " +
+ "2", vars)
+ assert vars['a'] == 2 and vars['b'] == 2 and vars['c'] == 2
+ """,
+ [1,2,3,6], "")
+ self.check_coverage("""\
+ globs = {}
+ locs = {'a': 1, 'b': 1, 'c': 1}
+ exec("a = 2", globs, locs)
+ exec("b = " +
+ "c = " +
+ "2", globs, locs)
+ assert locs['a'] == 2 and locs['b'] == 2 and locs['c'] == 2
+ """,
+ [1,2,3,4,7], "")
+
+ def test_extra_doc_string(self):
+ self.check_coverage("""\
+ a = 1
+ "An extra docstring, should be a comment."
+ b = 3
+ assert (a,b) == (1,3)
+ """,
+ [1,3,4], "")
+ self.check_coverage("""\
+ a = 1
+ "An extra docstring, should be a comment."
+ b = 3
+ 123 # A number for some reason: ignored
+ 1+1 # An expression: executed.
+ c = 6
+ assert (a,b,c) == (1,3,6)
+ """,
+ ([1,3,6,7], [1,3,5,6,7], [1,3,4,5,6,7]), "")
+
+
+class CompoundStatementTest(CoverageTest):
+ """Testing coverage of multi-line compound statements."""
+
+ def test_statement_list(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 test_if(self):
+ self.check_coverage("""\
+ a = 1
+ if a == 1:
+ x = 3
+ assert x == 3
+ if (a ==
+ 1):
+ x = 7
+ assert x == 7
+ """,
+ [1,2,3,4,5,7,8], "")
+ self.check_coverage("""\
+ a = 1
+ if a == 1:
+ x = 3
+ else:
+ y = 5
+ assert x == 3
+ """,
+ [1,2,3,5,6], "5")
+ self.check_coverage("""\
+ a = 1
+ if a != 1:
+ x = 3
+ else:
+ y = 5
+ assert y == 5
+ """,
+ [1,2,3,5,6], "3")
+ self.check_coverage("""\
+ a = 1; b = 2
+ if a == 1:
+ if b == 2:
+ x = 4
+ else:
+ y = 6
+ else:
+ z = 8
+ assert x == 4
+ """,
+ [1,2,3,4,6,8,9], "6-8")
+
+ def test_elif(self):
+ self.check_coverage("""\
+ a = 1; b = 2; c = 3;
+ if a == 1:
+ x = 3
+ elif b == 2:
+ y = 5
+ else:
+ z = 7
+ assert x == 3
+ """,
+ [1,2,3,4,5,7,8], "4-7", report="7 3 57% 4-7")
+ self.check_coverage("""\
+ a = 1; b = 2; c = 3;
+ if a != 1:
+ x = 3
+ elif b == 2:
+ y = 5
+ else:
+ z = 7
+ assert y == 5
+ """,
+ [1,2,3,4,5,7,8], "3, 7", report="7 2 71% 3, 7")
+ self.check_coverage("""\
+ a = 1; b = 2; c = 3;
+ if a != 1:
+ x = 3
+ elif b != 2:
+ y = 5
+ else:
+ z = 7
+ assert z == 7
+ """,
+ [1,2,3,4,5,7,8], "3, 5", report="7 2 71% 3, 5")
+
+ def test_elif_no_else(self):
+ self.check_coverage("""\
+ a = 1; b = 2; c = 3;
+ if a == 1:
+ x = 3
+ elif b == 2:
+ y = 5
+ assert x == 3
+ """,
+ [1,2,3,4,5,6], "4-5", report="6 2 67% 4-5")
+ self.check_coverage("""\
+ a = 1; b = 2; c = 3;
+ if a != 1:
+ x = 3
+ elif b == 2:
+ y = 5
+ assert y == 5
+ """,
+ [1,2,3,4,5,6], "3", report="6 1 83% 3")
+
+ def test_elif_bizarre(self):
+ self.check_coverage("""\
+ def f(self):
+ if self==1:
+ x = 3
+ elif self.m('fred'):
+ x = 5
+ elif (g==1) and (b==2):
+ x = 7
+ elif self.m('fred')==True:
+ x = 9
+ elif ((g==1) and (b==2))==True:
+ x = 11
+ else:
+ x = 13
+ """,
+ [1,2,3,4,5,6,7,8,9,10,11,13], "2-13")
+
+ def test_split_if(self):
+ self.check_coverage("""\
+ a = 1; b = 2; c = 3;
+ if \\
+ a == 1:
+ x = 3
+ elif \\
+ b == 2:
+ y = 5
+ else:
+ z = 7
+ assert x == 3
+ """,
+ [1,2,4,5,7,9,10], "5-9")
+ self.check_coverage("""\
+ a = 1; b = 2; c = 3;
+ if \\
+ a != 1:
+ x = 3
+ elif \\
+ b == 2:
+ y = 5
+ else:
+ z = 7
+ assert y == 5
+ """,
+ [1,2,4,5,7,9,10], "4, 9")
+ self.check_coverage("""\
+ a = 1; b = 2; c = 3;
+ if \\
+ a != 1:
+ x = 3
+ elif \\
+ b != 2:
+ y = 5
+ else:
+ z = 7
+ assert z == 7
+ """,
+ [1,2,4,5,7,9,10], "4, 7")
+
+ def test_pathological_split_if(self):
+ self.check_coverage("""\
+ a = 1; b = 2; c = 3;
+ if (
+ a == 1
+ ):
+ x = 3
+ elif (
+ b == 2
+ ):
+ y = 5
+ else:
+ z = 7
+ assert x == 3
+ """,
+ [1,2,5,6,9,11,12], "6-11")
+ self.check_coverage("""\
+ a = 1; b = 2; c = 3;
+ if (
+ a != 1
+ ):
+ x = 3
+ elif (
+ b == 2
+ ):
+ y = 5
+ else:
+ z = 7
+ assert y == 5
+ """,
+ [1,2,5,6,9,11,12], "5, 11")
+ self.check_coverage("""\
+ a = 1; b = 2; c = 3;
+ if (
+ a != 1
+ ):
+ x = 3
+ elif (
+ b != 2
+ ):
+ y = 5
+ else:
+ z = 7
+ assert z == 7
+ """,
+ [1,2,5,6,9,11,12], "5, 9")
+
+ def test_absurd_split_if(self):
+ self.check_coverage("""\
+ a = 1; b = 2; c = 3;
+ if a == 1 \\
+ :
+ x = 3
+ elif b == 2 \\
+ :
+ y = 5
+ else:
+ z = 7
+ assert x == 3
+ """,
+ [1,2,4,5,7,9,10], "5-9")
+ self.check_coverage("""\
+ a = 1; b = 2; c = 3;
+ if a != 1 \\
+ :
+ x = 3
+ elif b == 2 \\
+ :
+ y = 5
+ else:
+ z = 7
+ assert y == 5
+ """,
+ [1,2,4,5,7,9,10], "4, 9")
+ self.check_coverage("""\
+ a = 1; b = 2; c = 3;
+ if a != 1 \\
+ :
+ x = 3
+ elif b != 2 \\
+ :
+ y = 5
+ else:
+ z = 7
+ assert z == 7
+ """,
+ [1,2,4,5,7,9,10], "4, 7")
+
+ if sys.version_info >= (2, 4):
+ # In 2.4 and up, constant if's were compiled away.
+ def test_constant_if(self):
+ self.check_coverage("""\
+ if 1:
+ a = 2
+ assert a == 2
+ """,
+ [2,3], "")
+
+ def test_while(self):
+ self.check_coverage("""\
+ a = 3; b = 0
+ while a:
+ b += 1
+ a -= 1
+ assert a == 0 and b == 3
+ """,
+ [1,2,3,4,5], "")
+ self.check_coverage("""\
+ a = 3; b = 0
+ while a:
+ b += 1
+ break
+ b = 99
+ assert a == 3 and b == 1
+ """,
+ [1,2,3,4,5,6], "5")
+
+ def test_while_else(self):
+ # Take the else branch.
+ self.check_coverage("""\
+ a = 3; b = 0
+ while a:
+ b += 1
+ a -= 1
+ else:
+ b = 99
+ assert a == 0 and b == 99
+ """,
+ [1,2,3,4,6,7], "")
+ # Don't take the else branch.
+ self.check_coverage("""\
+ a = 3; b = 0
+ while a:
+ b += 1
+ a -= 1
+ break
+ b = 123
+ else:
+ b = 99
+ assert a == 2 and b == 1
+ """,
+ [1,2,3,4,5,6,8,9], "6-8")
+
+ def test_split_while(self):
+ self.check_coverage("""\
+ a = 3; b = 0
+ while \\
+ a:
+ b += 1
+ a -= 1
+ assert a == 0 and b == 3
+ """,
+ [1,2,4,5,6], "")
+ self.check_coverage("""\
+ a = 3; b = 0
+ while (
+ a
+ ):
+ b += 1
+ a -= 1
+ assert a == 0 and b == 3
+ """,
+ [1,2,5,6,7], "")
+
+ def test_for(self):
+ self.check_coverage("""\
+ a = 0
+ for i in [1,2,3,4,5]:
+ a += i
+ assert a == 15
+ """,
+ [1,2,3,4], "")
+ self.check_coverage("""\
+ a = 0
+ for i in [1,
+ 2,3,4,
+ 5]:
+ a += i
+ assert a == 15
+ """,
+ [1,2,5,6], "")
+ self.check_coverage("""\
+ a = 0
+ for i in [1,2,3,4,5]:
+ a += i
+ break
+ a = 99
+ assert a == 1
+ """,
+ [1,2,3,4,5,6], "5")
+
+ def test_for_else(self):
+ self.check_coverage("""\
+ a = 0
+ for i in range(5):
+ a += i+1
+ else:
+ a = 99
+ assert a == 99
+ """,
+ [1,2,3,5,6], "")
+ self.check_coverage("""\
+ a = 0
+ for i in range(5):
+ a += i+1
+ break
+ a = 99
+ else:
+ a = 123
+ assert a == 1
+ """,
+ [1,2,3,4,5,7,8], "5-7")
+
+ def test_split_for(self):
+ self.check_coverage("""\
+ a = 0
+ for \\
+ i in [1,2,3,4,5]:
+ a += i
+ assert a == 15
+ """,
+ [1,2,4,5], "")
+ self.check_coverage("""\
+ a = 0
+ for \\
+ i in [1,
+ 2,3,4,
+ 5]:
+ a += i
+ assert a == 15
+ """,
+ [1,2,6,7], "")
+
+ def test_try_except(self):
+ self.check_coverage("""\
+ a = 0
+ try:
+ a = 1
+ except:
+ a = 99
+ assert a == 1
+ """,
+ [1,2,3,4,5,6], "4-5")
+ self.check_coverage("""\
+ a = 0
+ try:
+ a = 1
+ raise Exception("foo")
+ except:
+ a = 99
+ assert a == 99
+ """,
+ [1,2,3,4,5,6,7], "")
+ self.check_coverage("""\
+ a = 0
+ try:
+ a = 1
+ raise Exception("foo")
+ except ImportError:
+ a = 99
+ except:
+ a = 123
+ assert a == 123
+ """,
+ [1,2,3,4,5,6,7,8,9], "6")
+ self.check_coverage("""\
+ a = 0
+ try:
+ a = 1
+ raise IOError("foo")
+ except ImportError:
+ a = 99
+ except IOError:
+ a = 17
+ except:
+ a = 123
+ assert a == 17
+ """,
+ [1,2,3,4,5,6,7,8,9,10,11], "6, 9-10")
+ self.check_coverage("""\
+ a = 0
+ try:
+ a = 1
+ except:
+ a = 99
+ else:
+ a = 123
+ assert a == 123
+ """,
+ [1,2,3,4,5,7,8], "4-5")
+ self.check_coverage("""\
+ a = 0
+ try:
+ a = 1
+ raise Exception("foo")
+ except:
+ a = 99
+ else:
+ a = 123
+ assert a == 99
+ """,
+ [1,2,3,4,5,6,8,9], "8")
+
+ def test_try_finally(self):
+ self.check_coverage("""\
+ a = 0
+ try:
+ a = 1
+ finally:
+ a = 99
+ assert a == 99
+ """,
+ [1,2,3,5,6], "")
+ self.check_coverage("""\
+ a = 0; b = 0
+ try:
+ a = 1
+ try:
+ raise Exception("foo")
+ finally:
+ b = 123
+ except:
+ a = 99
+ assert a == 99 and b == 123
+ """,
+ [1,2,3,4,5,7,8,9,10], "")
+
+ def test_function_def(self):
+ self.check_coverage("""\
+ a = 99
+ def foo():
+ ''' docstring
+ '''
+ return 1
+
+ a = foo()
+ assert a == 1
+ """,
+ [1,2,5,7,8], "")
+ self.check_coverage("""\
+ def foo(
+ a,
+ b
+ ):
+ ''' docstring
+ '''
+ return a+b
+
+ x = foo(17, 23)
+ assert x == 40
+ """,
+ [1,7,9,10], "")
+ self.check_coverage("""\
+ def foo(
+ a = (lambda x: x*2)(10),
+ b = (
+ lambda x:
+ x+1
+ )(1)
+ ):
+ ''' docstring
+ '''
+ return a+b
+
+ x = foo()
+ assert x == 22
+ """,
+ [1,10,12,13], "")
+
+ def test_class_def(self):
+ self.check_coverage("""\
+ # A comment.
+ class theClass:
+ ''' the docstring.
+ Don't be fooled.
+ '''
+ 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], "")
+
+
+class ExcludeTest(CoverageTest):
+ """Tests of the exclusion feature to mark lines as not covered."""
+
+ def test_default(self):
+ # A number of forms of pragma comment are accepted.
+ self.check_coverage("""\
+ a = 1
+ b = 2 # pragma: no cover
+ c = 3
+ d = 4 #pragma NOCOVER
+ e = 5
+ """,
+ [1,3,5]
+ )
+
+ def test_simple(self):
+ self.check_coverage("""\
+ a = 1; b = 2
+
+ if 0:
+ a = 4 # -cc
+ """,
+ [1,3], "", excludes=['-cc'])
+
+ def test_two_excludes(self):
+ self.check_coverage("""\
+ a = 1; b = 2
+
+ if a == 99:
+ a = 4 # -cc
+ b = 5
+ c = 6 # -xx
+ assert a == 1 and b == 2
+ """,
+ [1,3,5,7], "5", excludes=['-cc', '-xx'])
+
+ def test_excluding_if_suite(self):
+ self.check_coverage("""\
+ a = 1; b = 2
+
+ if 0:
+ a = 4
+ b = 5
+ c = 6
+ assert a == 1 and b == 2
+ """,
+ [1,7], "", excludes=['if 0:'])
+
+ def test_excluding_if_but_not_else_suite(self):
+ self.check_coverage("""\
+ a = 1; b = 2
+
+ if 0:
+ a = 4
+ b = 5
+ c = 6
+ else:
+ a = 8
+ b = 9
+ assert a == 8 and b == 9
+ """,
+ [1,8,9,10], "", excludes=['if 0:'])
+
+ def test_excluding_else_suite(self):
+ self.check_coverage("""\
+ a = 1; b = 2
+
+ if 1==1:
+ a = 4
+ b = 5
+ c = 6
+ else: #pragma: NO COVER
+ a = 8
+ b = 9
+ assert a == 4 and b == 5 and c == 6
+ """,
+ [1,3,4,5,6,10], "", excludes=['#pragma: NO COVER'])
+ self.check_coverage("""\
+ a = 1; b = 2
+
+ if 1==1:
+ 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
+ """,
+ [1,3,4,5,6,17], "", excludes=['#pragma: NO COVER'])
+
+ def test_excluding_elif_suites(self):
+ self.check_coverage("""\
+ a = 1; b = 2
+
+ if 1==1:
+ a = 4
+ b = 5
+ c = 6
+ elif 1==0: #pragma: NO COVER
+ a = 8
+ b = 9
+ else:
+ a = 11
+ b = 12
+ assert a == 4 and b == 5 and c == 6
+ """,
+ [1,3,4,5,6,11,12,13], "11-12", excludes=['#pragma: NO COVER'])
+
+ def test_excluding_oneline_if(self):
+ self.check_coverage("""\
+ def foo():
+ a = 2
+ if 0: x = 3 # no cover
+ b = 4
+
+ foo()
+ """,
+ [1,2,4,6], "", excludes=["no cover"])
+
+ def test_excluding_a_colon_not_a_suite(self):
+ self.check_coverage("""\
+ def foo():
+ l = list(range(10))
+ a = l[:3] # no cover
+ b = 4
+
+ foo()
+ """,
+ [1,2,4,6], "", excludes=["no cover"])
+
+ def test_excluding_for_suite(self):
+ self.check_coverage("""\
+ a = 0
+ for i in [1,2,3,4,5]: #pragma: NO COVER
+ a += i
+ assert a == 15
+ """,
+ [1,4], "", excludes=['#pragma: NO COVER'])
+ self.check_coverage("""\
+ a = 0
+ for i in [1,
+ 2,3,4,
+ 5]: #pragma: NO COVER
+ a += i
+ assert a == 15
+ """,
+ [1,6], "", excludes=['#pragma: NO COVER'])
+ self.check_coverage("""\
+ a = 0
+ for i in [1,2,3,4,5
+ ]: #pragma: NO COVER
+ a += i
+ break
+ a = 99
+ assert a == 1
+ """,
+ [1,7], "", excludes=['#pragma: NO COVER'])
+
+ def test_excluding_for_else(self):
+ self.check_coverage("""\
+ a = 0
+ for i in range(5):
+ a += i+1
+ break
+ a = 99
+ else: #pragma: NO COVER
+ a = 123
+ assert a == 1
+ """,
+ [1,2,3,4,5,8], "5", excludes=['#pragma: NO COVER'])
+
+ def test_excluding_while(self):
+ self.check_coverage("""\
+ a = 3; b = 0
+ while a*b: #pragma: NO COVER
+ b += 1
+ break
+ b = 99
+ assert a == 3 and b == 0
+ """,
+ [1,6], "", excludes=['#pragma: NO COVER'])
+ self.check_coverage("""\
+ a = 3; b = 0
+ while (
+ a*b
+ ): #pragma: NO COVER
+ b += 1
+ break
+ b = 99
+ assert a == 3 and b == 0
+ """,
+ [1,8], "", excludes=['#pragma: NO COVER'])
+
+ def test_excluding_while_else(self):
+ self.check_coverage("""\
+ a = 3; b = 0
+ while a:
+ b += 1
+ break
+ b = 99
+ else: #pragma: NO COVER
+ b = 123
+ assert a == 3 and b == 1
+ """,
+ [1,2,3,4,5,8], "5", excludes=['#pragma: NO COVER'])
+
+ def test_excluding_try_except(self):
+ self.check_coverage("""\
+ a = 0
+ try:
+ a = 1
+ except: #pragma: NO COVER
+ a = 99
+ assert a == 1
+ """,
+ [1,2,3,6], "", excludes=['#pragma: NO COVER'])
+ self.check_coverage("""\
+ a = 0
+ try:
+ a = 1
+ raise Exception("foo")
+ except:
+ a = 99
+ assert a == 99
+ """,
+ [1,2,3,4,5,6,7], "", excludes=['#pragma: NO COVER'])
+ self.check_coverage("""\
+ a = 0
+ try:
+ a = 1
+ raise Exception("foo")
+ except ImportError: #pragma: NO COVER
+ a = 99
+ except:
+ a = 123
+ assert a == 123
+ """,
+ [1,2,3,4,7,8,9], "", excludes=['#pragma: NO COVER'])
+ self.check_coverage("""\
+ a = 0
+ try:
+ a = 1
+ except: #pragma: NO COVER
+ a = 99
+ else:
+ a = 123
+ assert a == 123
+ """,
+ [1,2,3,7,8], "", excludes=['#pragma: NO COVER'])
+ self.check_coverage("""\
+ a = 0
+ try:
+ a = 1
+ raise Exception("foo")
+ except:
+ a = 99
+ else: #pragma: NO COVER
+ a = 123
+ assert a == 99
+ """,
+ [1,2,3,4,5,6,9], "", excludes=['#pragma: NO COVER'])
+
+ def test_excluding_try_except_pass(self):
+ self.check_coverage("""\
+ a = 0
+ try:
+ a = 1
+ except: #pragma: NO COVER
+ x = 2
+ assert a == 1
+ """,
+ [1,2,3,6], "", excludes=['#pragma: NO COVER'])
+ self.check_coverage("""\
+ a = 0
+ try:
+ a = 1
+ raise Exception("foo")
+ except ImportError: #pragma: NO COVER
+ x = 2
+ except:
+ a = 123
+ assert a == 123
+ """,
+ [1,2,3,4,7,8,9], "", excludes=['#pragma: NO COVER'])
+ self.check_coverage("""\
+ a = 0
+ try:
+ a = 1
+ except: #pragma: NO COVER
+ x = 2
+ else:
+ a = 123
+ assert a == 123
+ """,
+ [1,2,3,7,8], "", excludes=['#pragma: NO COVER'])
+ self.check_coverage("""\
+ a = 0
+ try:
+ a = 1
+ raise Exception("foo")
+ except:
+ a = 99
+ else: #pragma: NO COVER
+ x = 2
+ assert a == 99
+ """,
+ [1,2,3,4,5,6,9], "", excludes=['#pragma: NO COVER'])
+
+ def test_excluding_if_pass(self):
+ # From a comment on the coverage page by Michael McNeil Forbes:
+ self.check_coverage("""\
+ def f():
+ if False: # pragma: no cover
+ pass # This line still reported as missing
+ if False: # pragma: no cover
+ x = 1 # Now it is skipped.
+
+ f()
+ """,
+ [1,7], "", excludes=["no cover"])
+
+ def test_excluding_function(self):
+ self.check_coverage("""\
+ def fn(foo): #pragma: NO COVER
+ a = 1
+ b = 2
+ c = 3
+
+ x = 1
+ assert x == 1
+ """,
+ [6,7], "", excludes=['#pragma: NO COVER'])
+
+ def test_excluding_method(self):
+ self.check_coverage("""\
+ 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], "", excludes=['#pragma: NO COVER'])
+
+ def test_excluding_class(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
+ """,
+ [8,9], "", excludes=['#pragma: NO COVER'])
+
+
+if sys.version_info >= (2, 4):
+ class Py24Test(CoverageTest):
+ """Tests of new syntax in Python 2.4."""
+
+ def test_function_decorators(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], "")
+
+ def test_function_decorators_with_args(self):
+ self.check_coverage("""\
+ def boost_by(extra):
+ def decorator(func):
+ def wrapper(arg):
+ 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], "")
+
+ def test_double_function_decorators(self):
+ self.check_coverage("""\
+ def require_int(func):
+ def wrapper(arg):
+ assert isinstance(arg, int)
+ return func(arg)
+ return wrapper
+
+ def boost_by(extra):
+ def decorator(func):
+ def wrapper(arg):
+ 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],
+ [1,2,3,4,5,7,8,9,10,11,12,14, 17,19,21, 24,26]), "")
+
+
+if sys.version_info >= (2, 5):
+ class Py25Test(CoverageTest):
+ """Tests of new syntax in Python 2.5."""
+
+ def test_with_statement(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"
+ raise Exception("Boo!")
+ except:
+ desc = "caught"
+ """,
+ [1,3,4,5,7,8,10,11,12,13,15,16,17,18,19,20], "")
+
+ def test_try_except_finally(self):
+ self.check_coverage("""\
+ a = 0; b = 0
+ try:
+ a = 1
+ except:
+ a = 99
+ finally:
+ b = 2
+ assert a == 1 and b == 2
+ """,
+ [1,2,3,4,5,7,8], "4-5")
+ self.check_coverage("""\
+ a = 0; b = 0
+ try:
+ a = 1
+ raise Exception("foo")
+ except:
+ a = 99
+ finally:
+ b = 2
+ assert a == 99 and b == 2
+ """,
+ [1,2,3,4,5,6,8,9], "")
+ self.check_coverage("""\
+ a = 0; b = 0
+ try:
+ a = 1
+ raise Exception("foo")
+ except ImportError:
+ a = 99
+ except:
+ a = 123
+ finally:
+ b = 2
+ assert a == 123 and b == 2
+ """,
+ [1,2,3,4,5,6,7,8,10,11], "6")
+ self.check_coverage("""\
+ a = 0; b = 0
+ try:
+ a = 1
+ raise IOError("foo")
+ except ImportError:
+ a = 99
+ except IOError:
+ a = 17
+ except:
+ a = 123
+ finally:
+ b = 2
+ assert a == 17 and b == 2
+ """,
+ [1,2,3,4,5,6,7,8,9,10,12,13], "6, 9-10")
+ self.check_coverage("""\
+ a = 0; b = 0
+ try:
+ a = 1
+ except:
+ a = 99
+ else:
+ a = 123
+ finally:
+ b = 2
+ assert a == 123 and b == 2
+ """,
+ [1,2,3,4,5,7,9,10], "4-5")
+ self.check_coverage("""\
+ a = 0; b = 0
+ try:
+ a = 1
+ raise Exception("foo")
+ except:
+ a = 99
+ else:
+ a = 123
+ finally:
+ b = 2
+ 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."""
+
+ def test_not_singleton(self):
+ # You *can* create another coverage object.
+ coverage.coverage()
+ coverage.coverage()
+
+
+class ReportingTest(CoverageTest):
+ """Tests of some reporting behavior."""
+
+ def test_no_data_to_report_on_annotate(self):
+ # Reporting with no data produces a nice message and no output dir.
+ self.assertRaisesRegexp(
+ CoverageException, "No data to report.",
+ self.command_line, "annotate -d ann"
+ )
+ self.assert_doesnt_exist("ann")
+
+ def test_no_data_to_report_on_html(self):
+ # Reporting with no data produces a nice message and no output dir.
+ self.assertRaisesRegexp(
+ CoverageException, "No data to report.",
+ self.command_line, "html -d htmlcov"
+ )
+ self.assert_doesnt_exist("htmlcov")
+
+ def test_no_data_to_report_on_xml(self):
+ # Reporting with no data produces a nice message.
+ self.assertRaisesRegexp(
+ CoverageException, "No data to report.",
+ self.command_line, "xml"
+ )
+ # Currently, this leaves an empty coverage.xml file... :(
diff --git a/tests/test_data.py b/tests/test_data.py
new file mode 100644
index 0000000..9281ccc
--- /dev/null
+++ b/tests/test_data.py
@@ -0,0 +1,146 @@
+"""Tests for coverage.data"""
+
+from coverage.backward import pickle
+from coverage.data import CoverageData
+from coverage.files import PathAliases
+
+from test.coveragetest import CoverageTest
+
+
+DATA_1 = { 'a.py': {1:None, 2:None}, 'b.py': {3:None} }
+SUMMARY_1 = { 'a.py':2, 'b.py':1 }
+MEASURED_FILES_1 = [ 'a.py', 'b.py' ]
+A_PY_LINES_1 = [1,2]
+B_PY_LINES_1 = [3]
+
+DATA_2 = { 'a.py': {1:None, 5:None}, 'c.py': {17:None} }
+SUMMARY_1_2 = { 'a.py':3, 'b.py':1, 'c.py':1 }
+MEASURED_FILES_1_2 = [ 'a.py', 'b.py', 'c.py' ]
+
+ARC_DATA_3 = { 'x.py': {(1,2):None, (2,3):None}, 'y.py': {(17,23):None} }
+X_PY_ARCS_3 = [(1,2), (2,3)]
+Y_PY_ARCS_3 = [(17,23)]
+
+
+class DataTest(CoverageTest):
+ """Test cases for coverage.data."""
+
+ def assert_summary(self, covdata, summary, fullpath=False):
+ """Check that the summary of `covdata` is `summary`."""
+ self.assertEqual(covdata.summary(fullpath), summary)
+
+ def assert_measured_files(self, covdata, measured):
+ """Check that `covdata`'s measured files are `measured`."""
+ self.assertSameElements(covdata.measured_files(), measured)
+
+ def test_reading_empty(self):
+ covdata = CoverageData()
+ covdata.read()
+ self.assert_summary(covdata, {})
+
+ def test_adding_data(self):
+ covdata = CoverageData()
+ covdata.add_line_data(DATA_1)
+ self.assert_summary(covdata, SUMMARY_1)
+ self.assert_measured_files(covdata, MEASURED_FILES_1)
+
+ def test_touch_file(self):
+ covdata = CoverageData()
+ covdata.add_line_data(DATA_1)
+ covdata.touch_file('x.py')
+ self.assert_measured_files(covdata, MEASURED_FILES_1 + ['x.py'])
+
+ 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)
+
+ def test_combining(self):
+ covdata1 = CoverageData()
+ covdata1.add_line_data(DATA_1)
+ covdata1.write(suffix='1')
+
+ covdata2 = CoverageData()
+ covdata2.add_line_data(DATA_2)
+ covdata2.write(suffix='2')
+
+ covdata3 = CoverageData()
+ covdata3.combine_parallel_data()
+ self.assert_summary(covdata3, SUMMARY_1_2)
+ self.assert_measured_files(covdata3, MEASURED_FILES_1_2)
+
+ def test_erasing(self):
+ covdata1 = CoverageData()
+ covdata1.add_line_data(DATA_1)
+ covdata1.write()
+ covdata1.erase()
+ self.assert_summary(covdata1, {})
+
+ covdata2 = CoverageData()
+ covdata2.read()
+ self.assert_summary(covdata2, {})
+
+ def test_file_format(self):
+ # Write with CoverageData, then read the pickle explicitly.
+ 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(), MEASURED_FILES_1)
+ self.assertSameElements(lines['a.py'], A_PY_LINES_1)
+ self.assertSameElements(lines['b.py'], B_PY_LINES_1)
+ # If not measuring branches, there's no arcs entry.
+ self.assertEqual(data.get('arcs', 'not there'), 'not there')
+
+ def test_file_format_with_arcs(self):
+ # Write with CoverageData, then read the pickle explicitly.
+ 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)
+ self.assertSameElements(arcs['y.py'], Y_PY_ARCS_3)
+
+ def test_combining_with_aliases(self):
+ covdata1 = CoverageData()
+ covdata1.add_line_data({
+ '/home/ned/proj/src/a.py': {1:None, 2:None},
+ '/home/ned/proj/src/sub/b.py': {3:None},
+ })
+ covdata1.write(suffix='1')
+
+ covdata2 = CoverageData()
+ covdata2.add_line_data({
+ r'c:\ned\test\a.py': {4:None, 5:None},
+ r'c:\ned\test\sub\b.py': {6:None},
+ })
+ covdata2.write(suffix='2')
+
+ covdata3 = CoverageData()
+ aliases = PathAliases()
+ aliases.add("/home/ned/proj/src/", "./")
+ aliases.add(r"c:\ned\test", "./")
+ covdata3.combine_parallel_data(aliases=aliases)
+ self.assert_summary(
+ covdata3, { './a.py':4, './sub/b.py':2 }, fullpath=True
+ )
+ self.assert_measured_files(covdata3, [ './a.py', './sub/b.py' ])
diff --git a/tests/test_execfile.py b/tests/test_execfile.py
new file mode 100644
index 0000000..e7d7041
--- /dev/null
+++ b/tests/test_execfile.py
@@ -0,0 +1,116 @@
+"""Tests for coverage.execfile"""
+
+import os, sys
+
+from coverage.execfile import run_python_file, run_python_module
+from coverage.misc import NoSource
+
+from test.coveragetest import CoverageTest
+
+here = os.path.dirname(__file__)
+
+class RunFileTest(CoverageTest):
+ """Test cases for `run_python_file`."""
+
+ def test_run_python_file(self):
+ 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__")
+
+ # It should seem to come from a file named try_execfile.py
+ dunder_file = os.path.basename(mod_globs['__file__'])
+ self.assertEqual(dunder_file, "try_execfile.py")
+
+ # It should have its correct module data.
+ self.assertEqual(mod_globs['__doc__'],
+ "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"])
+
+ # __builtins__ should have the right values, like open().
+ self.assertEqual(mod_globs['__builtins__.has_open'], True)
+
+ def test_no_extra_file(self):
+ # Make sure that running a file doesn't create an extra compiled file.
+ self.make_file("xxx", """\
+ desc = "a non-.py file!"
+ """)
+
+ self.assertEqual(os.listdir("."), ["xxx"])
+ run_python_file("xxx", ["xxx"])
+ self.assertEqual(os.listdir("."), ["xxx"])
+
+ def test_universal_newlines(self):
+ # Make sure we can read any sort of line ending.
+ pylines = """# try newlines|print('Hello, world!')|""".split('|')
+ for nl in ('\n', '\r\n', '\r'):
+ fpy = open('nl.py', 'wb')
+ try:
+ fpy.write(nl.join(pylines).encode('utf-8'))
+ finally:
+ fpy.close()
+ run_python_file('nl.py', ['nl.py'])
+ self.assertEqual(self.stdout(), "Hello, world!\n"*3)
+
+ def test_missing_final_newline(self):
+ # Make sure we can deal with a Python file with no final newline.
+ self.make_file("abrupt.py", """\
+ if 1:
+ a = 1
+ print("a is %r" % a)
+ #""")
+ abrupt = open("abrupt.py").read()
+ self.assertEqual(abrupt[-1], '#')
+ run_python_file("abrupt.py", ["abrupt.py"])
+ self.assertEqual(self.stdout(), "a is 1\n")
+
+ def test_no_such_file(self):
+ self.assertRaises(NoSource, run_python_file, "xyzzy.py", [])
+
+
+class RunModuleTest(CoverageTest):
+ """Test run_python_module."""
+
+ run_in_temp_dir = False
+
+ def setUp(self):
+ super(RunModuleTest, self).setUp()
+ # Parent class saves and restores sys.path, we can just modify it.
+ sys.path.append(self.nice_file(os.path.dirname(__file__), 'modules'))
+
+ def test_runmod1(self):
+ run_python_module("runmod1", ["runmod1", "hello"])
+ self.assertEqual(self.stdout(), "runmod1: passed hello\n")
+
+ def test_runmod2(self):
+ run_python_module("pkg1.runmod2", ["runmod2", "hello"])
+ self.assertEqual(self.stdout(), "runmod2: passed hello\n")
+
+ def test_runmod3(self):
+ run_python_module("pkg1.sub.runmod3", ["runmod3", "hello"])
+ self.assertEqual(self.stdout(), "runmod3: passed hello\n")
+
+ def test_pkg1_main(self):
+ run_python_module("pkg1", ["pkg1", "hello"])
+ self.assertEqual(self.stdout(), "pkg1.__main__: passed hello\n")
+
+ def test_pkg1_sub_main(self):
+ run_python_module("pkg1.sub", ["pkg1.sub", "hello"])
+ self.assertEqual(self.stdout(), "pkg1.sub.__main__: passed hello\n")
+
+ def test_no_such_module(self):
+ self.assertRaises(NoSource, run_python_module, "i_dont_exist", [])
+ self.assertRaises(NoSource, run_python_module, "i.dont_exist", [])
+ self.assertRaises(NoSource, run_python_module, "i.dont.exist", [])
+
+ def test_no_main(self):
+ self.assertRaises(NoSource, run_python_module, "pkg2", ["pkg2", "hi"])
diff --git a/tests/test_farm.py b/tests/test_farm.py
new file mode 100644
index 0000000..f25d610
--- /dev/null
+++ b/tests/test_farm.py
@@ -0,0 +1,366 @@
+"""Run tests in the farm subdirectory. Designed for nose."""
+
+import difflib, filecmp, fnmatch, glob, os, re, shutil, sys
+from nose.plugins.skip import SkipTest
+
+from test.backtest import run_command, execfile # pylint: disable=W0622
+
+from coverage.control import _TEST_NAME_FILE
+
+
+def test_farm(clean_only=False):
+ """A test-generating function for nose to find and run."""
+ for fname in glob.glob("test/farm/*/*.py"):
+ case = FarmTestCase(fname, clean_only)
+ yield (case,)
+
+
+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
+ coverage -a white.py
+ ''', rundir="out")
+ compare("out", "gold", "*,cover")
+ clean("out")
+
+ 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)
+ self.clean_only = clean_only
+ self.dont_clean = dont_clean
+
+ def cd(self, newdir):
+ """Change the current directory, and return the old one."""
+ cwd = os.getcwd()
+ os.chdir(newdir)
+ return cwd
+
+ def addtopath(self, directory):
+ """Add `directory` to the path, and return the old path."""
+ oldpath = sys.path[:]
+ if directory is not None:
+ 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.
+
+ """
+ if _TEST_NAME_FILE:
+ f = open(_TEST_NAME_FILE, "w")
+ f.write(self.description.replace("/", "_"))
+ f.close()
+
+ cwd = self.cd(self.dir)
+
+ # Prepare a dictionary of globals for the run.py files to use.
+ fns = """
+ copy run runfunc compare contains doesnt_contain clean skip
+ """.split()
+ if self.clean_only:
+ glo = dict([(fn, self.noop) for fn in fns])
+ glo['clean'] = self.clean
+ else:
+ glo = dict([(fn, getattr(self, fn)) for fn in fns])
+ if self.dont_clean: # pragma: not covered
+ glo['clean'] = self.noop
+
+ old_mods = dict(sys.modules)
+ try:
+ execfile(self.runpy, glo)
+ finally:
+ self.cd(cwd)
+ # Remove any new modules imported during the test run. This lets us
+ # import the same source files for more than one test.
+ to_del = [m for m in sys.modules if m not in old_mods]
+ for m in to_del:
+ del sys.modules[m]
+
+ def run_fully(self): # pragma: not covered
+ """Run as a full test case, with setUp and tearDown."""
+ self.setUp()
+ try:
+ self()
+ finally:
+ self.tearDown()
+
+ 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)]
+ return files
+
+ def setUp(self):
+ """Test set up, run by nose before __call__."""
+
+ # 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.
+ if not self.dont_clean: # pragma: part covered
+ self.clean_only = True
+ self()
+
+ # Restore the original sys.path
+ 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."""
+
+ if os.path.exists(dst):
+ shutil.rmtree(dst)
+ shutil.copytree(src, dst)
+
+ 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)
+ if outfile:
+ fout = open(outfile, "a+")
+ try:
+ for cmd in cmds.split("\n"):
+ cmd = cmd.strip()
+ if not cmd:
+ continue
+ retcode, output = run_command(cmd)
+ print(output.rstrip())
+ if outfile:
+ fout.write(output)
+ if retcode:
+ raise Exception("command exited abnormally")
+ finally:
+ if outfile:
+ fout.close()
+ self.cd(cwd)
+
+ 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:
+ fn()
+ finally:
+ self.cd(cwd)
+ self.restorepath(oldpath)
+
+ def compare(self, dir1, dir2, file_pattern=None, size_within=0,
+ 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]))
+ while version_suff:
+ trydir = dir2 + '_v' + version_suff
+ if os.path.exists(trydir):
+ dir2 = trydir
+ break
+ version_suff = version_suff[:-1]
+
+ 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.
+ wrong_size = []
+ for f in diff_files:
+ left = open(os.path.join(dir1, f), "rb").read()
+ right = open(os.path.join(dir2, f), "rb").read()
+ size_l, size_r = len(left), len(right)
+ big, little = max(size_l, size_r), min(size_l, size_r)
+ if (big - little) / float(little) > size_within/100.0:
+ # print "%d %d" % (big, little)
+ # print "Left: ---\n%s\n-----\n%s" % (left, right)
+ wrong_size.append(f)
+ assert not wrong_size, (
+ "File sizes differ between %s and %s: %s" % (
+ dir1, dir2, wrong_size
+ ))
+ else:
+ # filecmp only compares in binary mode, but we want text mode. So
+ # look through the list of different files, and compare them
+ # ourselves.
+ text_diff = []
+ for f in diff_files:
+ left = open(os.path.join(dir1, f), "rU").readlines()
+ right = open(os.path.join(dir2, f), "rU").readlines()
+ if scrubs:
+ left = self._scrub(left, scrubs)
+ right = self._scrub(right, scrubs)
+ if left != right:
+ text_diff.append(f)
+ print("".join(list(difflib.Differ().compare(left, right))))
+ assert not text_diff, "Files differ: %s" % text_diff
+
+ if not left_extra:
+ assert not left_only, "Files in %s only: %s" % (dir1, left_only)
+ if not right_extra:
+ assert not right_only, "Files in %s only: %s" % (dir2, right_only)
+
+ 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:
+ for rgx_find, rgx_replace in scrubs:
+ s = re.sub(rgx_find, rgx_replace, s)
+ scrubbed.append(s)
+ return scrubbed
+
+ 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:
+ assert s in text, "Missing content in %s: %r" % (filename, s)
+
+ 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:
+ assert s not in text, "Forbidden content in %s: %r" % (filename, s)
+
+ def clean(self, cleandir):
+ """Clean `cleandir` by removing it and all its children completely."""
+ # rmtree gives mysterious failures on Win7, so retry a "few" times.
+ # I've seen it take over 100 tries, so, 1000! This is probably the
+ # most unpleasant hack I've written in a long time...
+ tries = 1000
+ while tries: # pragma: part covered
+ if os.path.exists(cleandir):
+ try:
+ shutil.rmtree(cleandir)
+ except OSError: # pragma: not covered
+ if tries == 1:
+ raise
+ else:
+ tries -= 1
+ continue
+ break
+
+ def skip(self, msg=None):
+ """Skip the current test."""
+ raise SkipTest(msg)
+
+
+def main(): # pragma: not covered
+ """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 = 'help'
+ try:
+ op = sys.argv[1]
+ except IndexError:
+ pass
+
+ if op == 'run':
+ # Run the test for real.
+ case = FarmTestCase(sys.argv[2])
+ case.run_fully()
+ elif op == 'out':
+ # Run the test, but don't clean up, so we can examine the output.
+ case = FarmTestCase(sys.argv[2], dont_clean=True)
+ case.run_fully()
+ elif op == 'clean':
+ # Run all the tests, but just clean.
+ for test in test_farm(clean_only=True):
+ test[0].run_fully()
+ else:
+ print(main.__doc__)
+
+# So that we can run just one farm run.py at a time.
+if __name__ == '__main__':
+ main()
diff --git a/tests/test_files.py b/tests/test_files.py
new file mode 100644
index 0000000..051b274
--- /dev/null
+++ b/tests/test_files.py
@@ -0,0 +1,169 @@
+"""Tests for files.py"""
+
+import os
+
+from coverage.files import FileLocator, TreeMatcher, FnmatchMatcher
+from coverage.files import PathAliases, find_python_files, abs_file
+from coverage.backward import set # pylint: disable=W0622
+from coverage.misc import CoverageException
+
+from test.coveragetest import CoverageTest
+
+
+class FileLocatorTest(CoverageTest):
+ """Tests of `FileLocator`."""
+
+ def abs_path(self, p):
+ """Return the absolute path for `p`."""
+ return os.path.join(os.getcwd(), os.path.normpath(p))
+
+ def test_simple(self):
+ self.make_file("hello.py")
+ fl = FileLocator()
+ self.assertEqual(fl.relative_filename("hello.py"), "hello.py")
+ a = self.abs_path("hello.py")
+ self.assertNotEqual(a, "hello.py")
+ self.assertEqual(fl.relative_filename(a), "hello.py")
+
+ def test_peer_directories(self):
+ self.make_file("sub/proj1/file1.py")
+ self.make_file("sub/proj2/file2.py")
+ a1 = self.abs_path("sub/proj1/file1.py")
+ a2 = self.abs_path("sub/proj2/file2.py")
+ d = os.path.normpath("sub/proj1")
+ os.chdir(d)
+ fl = FileLocator()
+ self.assertEqual(fl.relative_filename(a1), "file1.py")
+ self.assertEqual(fl.relative_filename(a2), a2)
+
+ def test_filepath_contains_absolute_prefix_twice(self):
+ # https://bitbucket.org/ned/coveragepy/issue/194
+ # Build a path that has two pieces matching the absolute path prefix.
+ # Technically, this test doesn't do that on Windows, but drive
+ # letters make that impractical to acheive.
+ fl = FileLocator()
+ d = abs_file(os.curdir)
+ trick = os.path.splitdrive(d)[1].lstrip(os.path.sep)
+ rel = os.path.join('sub', trick, 'file1.py')
+ self.assertEqual(fl.relative_filename(abs_file(rel)), rel)
+
+
+class MatcherTest(CoverageTest):
+ """Tests of file matchers."""
+
+ def test_tree_matcher(self):
+ file1 = self.make_file("sub/file1.py")
+ file2 = self.make_file("sub/file2.c")
+ file3 = self.make_file("sub2/file3.h")
+ file4 = self.make_file("sub3/file4.py")
+ file5 = self.make_file("sub3/file5.c")
+ fl = FileLocator()
+ tm = TreeMatcher([
+ fl.canonical_filename("sub"),
+ fl.canonical_filename(file4),
+ ])
+ self.assertTrue(tm.match(fl.canonical_filename(file1)))
+ self.assertTrue(tm.match(fl.canonical_filename(file2)))
+ self.assertFalse(tm.match(fl.canonical_filename(file3)))
+ self.assertTrue(tm.match(fl.canonical_filename(file4)))
+ self.assertFalse(tm.match(fl.canonical_filename(file5)))
+
+ def test_fnmatch_matcher(self):
+ file1 = self.make_file("sub/file1.py")
+ file2 = self.make_file("sub/file2.c")
+ file3 = self.make_file("sub2/file3.h")
+ file4 = self.make_file("sub3/file4.py")
+ file5 = self.make_file("sub3/file5.c")
+ fl = FileLocator()
+ fnm = FnmatchMatcher(["*.py", "*/sub2/*"])
+ self.assertTrue(fnm.match(fl.canonical_filename(file1)))
+ self.assertFalse(fnm.match(fl.canonical_filename(file2)))
+ self.assertTrue(fnm.match(fl.canonical_filename(file3)))
+ self.assertTrue(fnm.match(fl.canonical_filename(file4)))
+ self.assertFalse(fnm.match(fl.canonical_filename(file5)))
+
+
+class PathAliasesTest(CoverageTest):
+ """Tests for coverage/files.py:PathAliases"""
+
+ def test_noop(self):
+ aliases = PathAliases()
+ self.assertEqual(aliases.map('/ned/home/a.py'), '/ned/home/a.py')
+
+ def test_nomatch(self):
+ aliases = PathAliases()
+ aliases.add('/home/*/src', './mysrc')
+ self.assertEqual(aliases.map('/home/foo/a.py'), '/home/foo/a.py')
+
+ def test_wildcard(self):
+ aliases = PathAliases()
+ aliases.add('/ned/home/*/src', './mysrc')
+ self.assertEqual(aliases.map('/ned/home/foo/src/a.py'), './mysrc/a.py')
+ aliases = PathAliases()
+ aliases.add('/ned/home/*/src/', './mysrc')
+ self.assertEqual(aliases.map('/ned/home/foo/src/a.py'), './mysrc/a.py')
+
+ def test_no_accidental_match(self):
+ aliases = PathAliases()
+ aliases.add('/home/*/src', './mysrc')
+ self.assertEqual(aliases.map('/home/foo/srcetc'), '/home/foo/srcetc')
+
+ def test_multiple_patterns(self):
+ aliases = PathAliases()
+ aliases.add('/home/*/src', './mysrc')
+ aliases.add('/lib/*/libsrc', './mylib')
+ self.assertEqual(aliases.map('/home/foo/src/a.py'), './mysrc/a.py')
+ self.assertEqual(aliases.map('/lib/foo/libsrc/a.py'), './mylib/a.py')
+
+ def test_cant_have_wildcard_at_end(self):
+ aliases = PathAliases()
+ self.assertRaisesRegexp(
+ CoverageException, "Pattern must not end with wildcards.",
+ aliases.add, "/ned/home/*", "fooey"
+ )
+ self.assertRaisesRegexp(
+ CoverageException, "Pattern must not end with wildcards.",
+ aliases.add, "/ned/home/*/", "fooey"
+ )
+ self.assertRaisesRegexp(
+ CoverageException, "Pattern must not end with wildcards.",
+ aliases.add, "/ned/home/*/*/", "fooey"
+ )
+
+ def test_no_accidental_munging(self):
+ aliases = PathAliases()
+ aliases.add(r'c:\Zoo\boo', 'src/')
+ aliases.add('/home/ned$', 'src/')
+ self.assertEqual(aliases.map(r'c:\Zoo\boo\foo.py'), 'src/foo.py')
+ self.assertEqual(aliases.map(r'/home/ned$/foo.py'), 'src/foo.py')
+
+ def test_paths_are_os_corrected(self):
+ aliases = PathAliases()
+ aliases.add('/home/ned/*/src', './mysrc')
+ aliases.add(r'c:\ned\src', './mysrc')
+ mapped = aliases.map(r'C:\Ned\src\sub\a.py')
+ self.assertEqual(mapped, './mysrc/sub/a.py')
+
+ aliases = PathAliases()
+ aliases.add('/home/ned/*/src', r'.\mysrc')
+ aliases.add(r'c:\ned\src', r'.\mysrc')
+ mapped = aliases.map(r'/home/ned/foo/src/sub/a.py')
+ self.assertEqual(mapped, r'.\mysrc\sub\a.py')
+
+
+class FindPythonFilesTest(CoverageTest):
+ """Tests of `find_python_files`."""
+
+ def test_find_python_files(self):
+ self.make_file("sub/a.py")
+ self.make_file("sub/b.py")
+ self.make_file("sub/x.c") # nope: not .py
+ self.make_file("sub/ssub/__init__.py")
+ self.make_file("sub/ssub/s.py")
+ self.make_file("sub/ssub/~s.py") # nope: editor effluvia
+ self.make_file("sub/lab/exp.py") # nope: no __init__.py
+ py_files = set(find_python_files("sub"))
+ self.assert_same_files(py_files, [
+ "sub/a.py", "sub/b.py",
+ "sub/ssub/__init__.py", "sub/ssub/s.py",
+ ])
diff --git a/tests/test_html.py b/tests/test_html.py
new file mode 100644
index 0000000..66d1bd7
--- /dev/null
+++ b/tests/test_html.py
@@ -0,0 +1,297 @@
+# -*- coding: utf-8 -*-
+"""Tests that HTML generation is awesome."""
+
+import os.path, re, sys
+import coverage
+from coverage.misc import NotPython, NoSource
+
+from test.coveragetest import CoverageTest
+
+class HtmlTestHelpers(CoverageTest):
+ """Methods that help with HTML tests."""
+
+ def create_initial_files(self):
+ """Create the source files we need to run these tests."""
+ self.make_file("main_file.py", """\
+ import helper1, helper2
+ helper1.func1(12)
+ helper2.func2(12)
+ """)
+ self.make_file("helper1.py", """\
+ def func1(x):
+ if x % 2:
+ print("odd")
+ """)
+ self.make_file("helper2.py", """\
+ def func2(x):
+ print("x is %d" % x)
+ """)
+
+ def run_coverage(self, covargs=None, htmlargs=None):
+ """Run coverage on main_file.py, and create an HTML report."""
+ self.clean_local_file_imports()
+ cov = coverage.coverage(**(covargs or {}))
+ self.start_import_stop(cov, "main_file")
+ cov.html_report(**(htmlargs or {}))
+
+ def remove_html_files(self):
+ """Remove the HTML files created as part of the HTML report."""
+ os.remove("htmlcov/index.html")
+ os.remove("htmlcov/main_file.html")
+ os.remove("htmlcov/helper1.html")
+ os.remove("htmlcov/helper2.html")
+
+
+class HtmlDeltaTest(HtmlTestHelpers, CoverageTest):
+ """Tests of the HTML delta speed-ups."""
+
+ def setUp(self):
+ super(HtmlDeltaTest, self).setUp()
+
+ # At least one of our tests monkey-patches the version of coverage,
+ # so grab it here to restore it later.
+ self.real_coverage_version = coverage.__version__
+
+ self.maxDiff = None
+
+ def tearDown(self):
+ coverage.__version__ = self.real_coverage_version
+ super(HtmlDeltaTest, self).tearDown()
+
+ def test_html_created(self):
+ # Test basic HTML generation: files should be created.
+ self.create_initial_files()
+ self.run_coverage()
+
+ self.assert_exists("htmlcov/index.html")
+ self.assert_exists("htmlcov/main_file.html")
+ self.assert_exists("htmlcov/helper1.html")
+ self.assert_exists("htmlcov/helper2.html")
+ self.assert_exists("htmlcov/style.css")
+ self.assert_exists("htmlcov/coverage_html.js")
+
+ def test_html_delta_from_source_change(self):
+ # HTML generation can create only the files that have changed.
+ # In this case, helper1 changes because its source is different.
+ self.create_initial_files()
+ self.run_coverage()
+ index1 = open("htmlcov/index.html").read()
+ self.remove_html_files()
+
+ # Now change a file and do it again
+ self.make_file("helper1.py", """\
+ def func1(x): # A nice function
+ if x % 2:
+ print("odd")
+ """)
+
+ self.run_coverage()
+
+ # Only the changed files should have been created.
+ self.assert_exists("htmlcov/index.html")
+ self.assert_exists("htmlcov/helper1.html")
+ self.assert_doesnt_exist("htmlcov/main_file.html")
+ self.assert_doesnt_exist("htmlcov/helper2.html")
+ index2 = open("htmlcov/index.html").read()
+ self.assertMultiLineEqual(index1, index2)
+
+ def test_html_delta_from_coverage_change(self):
+ # HTML generation can create only the files that have changed.
+ # In this case, helper1 changes because its coverage is different.
+ self.create_initial_files()
+ self.run_coverage()
+ self.remove_html_files()
+
+ # Now change a file and do it again
+ self.make_file("main_file.py", """\
+ import helper1, helper2
+ helper1.func1(23)
+ helper2.func2(23)
+ """)
+
+ self.run_coverage()
+
+ # Only the changed files should have been created.
+ self.assert_exists("htmlcov/index.html")
+ self.assert_exists("htmlcov/helper1.html")
+ self.assert_exists("htmlcov/main_file.html")
+ self.assert_doesnt_exist("htmlcov/helper2.html")
+
+ def test_html_delta_from_settings_change(self):
+ # HTML generation can create only the files that have changed.
+ # In this case, everything changes because the coverage settings have
+ # changed.
+ self.create_initial_files()
+ self.run_coverage(covargs=dict(omit=[]))
+ index1 = open("htmlcov/index.html").read()
+ self.remove_html_files()
+
+ self.run_coverage(covargs=dict(omit=['xyzzy*']))
+
+ # All the files have been reported again.
+ self.assert_exists("htmlcov/index.html")
+ self.assert_exists("htmlcov/helper1.html")
+ self.assert_exists("htmlcov/main_file.html")
+ self.assert_exists("htmlcov/helper2.html")
+ index2 = open("htmlcov/index.html").read()
+ self.assertMultiLineEqual(index1, index2)
+
+ def test_html_delta_from_coverage_version_change(self):
+ # HTML generation can create only the files that have changed.
+ # In this case, everything changes because the coverage version has
+ # changed.
+ self.create_initial_files()
+ self.run_coverage()
+ index1 = open("htmlcov/index.html").read()
+ self.remove_html_files()
+
+ # "Upgrade" coverage.py!
+ coverage.__version__ = "XYZZY"
+
+ self.run_coverage()
+
+ # All the files have been reported again.
+ self.assert_exists("htmlcov/index.html")
+ self.assert_exists("htmlcov/helper1.html")
+ self.assert_exists("htmlcov/main_file.html")
+ self.assert_exists("htmlcov/helper2.html")
+ index2 = open("htmlcov/index.html").read()
+ fixed_index2 = index2.replace("XYZZY", self.real_coverage_version)
+ self.assertMultiLineEqual(index1, fixed_index2)
+
+
+class HtmlTitleTests(HtmlTestHelpers, CoverageTest):
+ """Tests of the HTML title support."""
+
+ def test_default_title(self):
+ self.create_initial_files()
+ self.run_coverage()
+ index = open("htmlcov/index.html").read()
+ self.assertIn("<title>Coverage report</title>", index)
+ self.assertIn("<h1>Coverage report:", index)
+
+ def test_title_set_in_config_file(self):
+ self.create_initial_files()
+ self.make_file(".coveragerc", "[html]\ntitle = Metrics & stuff!\n")
+ self.run_coverage()
+ index = open("htmlcov/index.html").read()
+ self.assertIn("<title>Metrics &amp; stuff!</title>", index)
+ self.assertIn("<h1>Metrics &amp; stuff!:", index)
+
+ if sys.version_info[:2] != (3,1):
+ def test_non_ascii_title_set_in_config_file(self):
+ self.create_initial_files()
+ self.make_file(".coveragerc",
+ "[html]\ntitle = «ταБЬℓσ» numbers"
+ )
+ self.run_coverage()
+ index = open("htmlcov/index.html").read()
+ self.assertIn(
+ "<title>&#171;&#964;&#945;&#1041;&#1068;&#8467;&#963;&#187;"
+ " numbers", index
+ )
+ self.assertIn(
+ "<h1>&#171;&#964;&#945;&#1041;&#1068;&#8467;&#963;&#187;"
+ " numbers", index
+ )
+
+ def test_title_set_in_args(self):
+ self.create_initial_files()
+ self.make_file(".coveragerc", "[html]\ntitle = Good title\n")
+ self.run_coverage(htmlargs=dict(title="«ταБЬℓσ» & stüff!"))
+ index = open("htmlcov/index.html").read()
+ self.assertIn(
+ "<title>&#171;&#964;&#945;&#1041;&#1068;&#8467;&#963;&#187;"
+ " &amp; st&#252;ff!</title>", index
+ )
+ self.assertIn(
+ "<h1>&#171;&#964;&#945;&#1041;&#1068;&#8467;&#963;&#187;"
+ " &amp; st&#252;ff!:", index
+ )
+
+
+class HtmlWithUnparsableFilesTest(CoverageTest):
+ """Test the behavior when measuring unparsable files."""
+
+ def test_dotpy_not_python(self):
+ self.make_file("innocuous.py", "a = 1")
+ cov = coverage.coverage()
+ self.start_import_stop(cov, "innocuous")
+ self.make_file("innocuous.py", "<h1>This isn't python!</h1>")
+ self.assertRaisesRegexp(
+ NotPython,
+ "Couldn't parse '.*innocuous.py' as Python source: '.*' at line 1",
+ cov.html_report
+ )
+
+ def test_dotpy_not_python_ignored(self):
+ self.make_file("innocuous.py", "a = 2")
+ cov = coverage.coverage()
+ self.start_import_stop(cov, "innocuous")
+ self.make_file("innocuous.py", "<h1>This isn't python!</h1>")
+ cov.html_report(ignore_errors=True)
+ self.assert_exists("htmlcov/index.html")
+ # this would be better as a glob, if the html layout changes:
+ self.assert_doesnt_exist("htmlcov/innocuous.html")
+
+ def test_dothtml_not_python(self):
+ # We run a .html file, and when reporting, we can't parse it as
+ # Python. Since it wasn't .py, no error is reported.
+
+ # Run an "html" file
+ self.make_file("innocuous.html", "a = 3")
+ self.run_command("coverage run innocuous.html")
+ # Before reporting, change it to be an HTML file.
+ self.make_file("innocuous.html", "<h1>This isn't python at all!</h1>")
+ output = self.run_command("coverage html")
+ self.assertEqual(output.strip(), "No data to report.")
+
+ def test_execed_liar_ignored(self):
+ # Jinja2 sets __file__ to be a non-Python file, and then execs code.
+ # If that file contains non-Python code, a TokenError shouldn't
+ # have been raised when writing the HTML report.
+ if sys.version_info < (3, 0):
+ source = "exec compile('','','exec') in {'__file__': 'liar.html'}"
+ else:
+ source = "exec(compile('','','exec'), {'__file__': 'liar.html'})"
+ self.make_file("liar.py", source)
+ self.make_file("liar.html", "{# Whoops, not python code #}")
+ cov = coverage.coverage()
+ self.start_import_stop(cov, "liar")
+ cov.html_report()
+ self.assert_exists("htmlcov/index.html")
+
+ def test_execed_liar_ignored_indentation_error(self):
+ # Jinja2 sets __file__ to be a non-Python file, and then execs code.
+ # If that file contains untokenizable code, we shouldn't get an
+ # exception.
+ if sys.version_info < (3, 0):
+ source = "exec compile('','','exec') in {'__file__': 'liar.html'}"
+ else:
+ source = "exec(compile('','','exec'), {'__file__': 'liar.html'})"
+ self.make_file("liar.py", source)
+ # Tokenize will raise an IndentationError if it can't dedent.
+ self.make_file("liar.html", "0\n 2\n 1\n")
+ cov = coverage.coverage()
+ self.start_import_stop(cov, "liar")
+ cov.html_report()
+ self.assert_exists("htmlcov/index.html")
+
+
+class HtmlTest(CoverageTest):
+ """Moar HTML tests."""
+
+ def test_missing_source_file_incorrect_message(self):
+ # https://bitbucket.org/ned/coveragepy/issue/60
+ self.make_file("thefile.py", "import sub.another\n")
+ self.make_file("sub/__init__.py", "")
+ self.make_file("sub/another.py", "print('another')\n")
+ cov = coverage.coverage()
+ self.start_import_stop(cov, 'thefile')
+ os.remove("sub/another.py")
+
+ missing_file = os.path.join(self.temp_dir, "sub", "another.py")
+ self.assertRaisesRegexp(NoSource,
+ "(?i)No source for code: '%s'" % re.escape(missing_file),
+ cov.html_report
+ )
diff --git a/tests/test_misc.py b/tests/test_misc.py
new file mode 100644
index 0000000..a32030c
--- /dev/null
+++ b/tests/test_misc.py
@@ -0,0 +1,73 @@
+"""Tests of miscellaneous stuff."""
+
+from coverage.misc import Hasher, file_be_gone
+from coverage import __version__, __url__
+from test.coveragetest import CoverageTest
+
+class HasherTest(CoverageTest):
+ """Test our wrapper of md5 hashing."""
+
+ def test_string_hashing(self):
+ h1 = Hasher()
+ h1.update("Hello, world!")
+ h2 = Hasher()
+ h2.update("Goodbye!")
+ h3 = Hasher()
+ h3.update("Hello, world!")
+ self.assertNotEqual(h1.digest(), h2.digest())
+ self.assertEqual(h1.digest(), h3.digest())
+
+ def test_dict_hashing(self):
+ h1 = Hasher()
+ h1.update({'a': 17, 'b': 23})
+ h2 = Hasher()
+ h2.update({'b': 23, 'a': 17})
+ self.assertEqual(h1.digest(), h2.digest())
+
+
+class RemoveFileTest(CoverageTest):
+ """Tests of misc.file_be_gone."""
+
+ def test_remove_nonexistent_file(self):
+ # it's ok to try to remove a file that doesn't exist.
+ file_be_gone("not_here.txt")
+
+ def test_remove_actual_file(self):
+ # it really does remove a file that does exist.
+ self.make_file("here.txt", "We are here, we are here, we are here!")
+ file_be_gone("here.txt")
+ self.assert_doesnt_exist("here.txt")
+
+ def test_actual_errors(self):
+ # Errors can still happen.
+ # ". is a directory" on Unix, or "Access denied" on Windows
+ self.assertRaises(OSError, file_be_gone, ".")
+
+
+class SetupPyTest(CoverageTest):
+ """Tests of setup.py"""
+
+ run_in_temp_dir = False
+
+ def test_metadata(self):
+ status, output = self.run_command_status(
+ "python setup.py --description --version --url --author"
+ )
+ self.assertEqual(status, 0)
+ out = output.splitlines()
+ self.assertIn("measurement", out[0])
+ self.assertEqual(out[1], __version__)
+ self.assertEqual(out[2], __url__)
+ self.assertIn("Ned Batchelder", out[3])
+
+ def test_more_metadata(self):
+ from setup import setup_args
+
+ classifiers = setup_args['classifiers']
+ self.assertGreater(len(classifiers), 7)
+ self.assertTrue(classifiers[-1].startswith("Development Status ::"))
+
+ long_description = setup_args['long_description'].splitlines()
+ self.assertGreater(len(long_description), 7)
+ self.assertNotEqual(long_description[0].strip(), "")
+ self.assertNotEqual(long_description[-1].strip(), "")
diff --git a/tests/test_oddball.py b/tests/test_oddball.py
new file mode 100644
index 0000000..113328b
--- /dev/null
+++ b/tests/test_oddball.py
@@ -0,0 +1,386 @@
+"""Oddball cases for testing coverage.py"""
+
+import os, sys
+import coverage
+
+from test.coveragetest import CoverageTest
+from test import osinfo
+
+class ThreadingTest(CoverageTest):
+ """Tests of the threading support."""
+
+ def test_threading(self):
+ self.check_coverage("""\
+ import threading
+
+ 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()
+ other.join()
+ """,
+ [1,3,4,6,7,9,10,12,13,14,15], "10")
+
+ def test_thread_run(self):
+ self.check_coverage("""\
+ import threading
+
+ class TestThread(threading.Thread):
+ def run(self):
+ self.a = 5
+ self.do_work()
+ self.a = 7
+
+ def do_work(self):
+ self.a = 10
+
+ thd = TestThread()
+ thd.start()
+ thd.join()
+ """,
+ [1,3,4,5,6,7,9,10,12,13,14], "")
+
+
+class RecursionTest(CoverageTest):
+ """Check what happens when recursive code gets near limits."""
+
+ def test_short_recursion(self):
+ # We can definitely get close to 500 stack frames.
+ self.check_coverage("""\
+ def recur(n):
+ if n == 0:
+ return 0
+ else:
+ return recur(n-1)+1
+
+ recur(495) # We can get at least this many stack frames.
+ i = 8 # and this line will be traced
+ """,
+ [1,2,3,5,7,8], "")
+
+ def test_long_recursion(self):
+ # We can't finish a very deep recursion, but we don't crash.
+ self.assertRaises(RuntimeError, self.check_coverage,
+ """\
+ def recur(n):
+ if n == 0:
+ return 0
+ else:
+ return recur(n-1)+1
+
+ recur(100000) # This is definitely too many frames.
+ """,
+ [1,2,3,5,7], "")
+
+ def test_long_recursion_recovery(self):
+ # Test the core of bug 93: http://bitbucket.org/ned/coveragepy/issue/93
+ # When recovering from a stack overflow, the Python trace function is
+ # disabled, but the C trace function is not. So if we're using a
+ # Python trace function, we won't trace anything after the stack
+ # overflow, and there should be a warning about it. If we're using
+ # the C trace function, only line 3 will be missing, and all else
+ # will be traced.
+
+ self.make_file("recur.py", """\
+ def recur(n):
+ if n == 0:
+ return 0 # never hit
+ else:
+ return recur(n-1)+1
+
+ try:
+ recur(100000) # This is definitely too many frames.
+ except RuntimeError:
+ i = 10
+ i = 11
+ """)
+
+ cov = coverage.coverage()
+ self.start_import_stop(cov, "recur")
+
+ pytrace = (cov.collector.tracer_name() == "PyTracer")
+ expected_missing = [3]
+ if pytrace:
+ expected_missing += [9,10,11]
+
+ _, statements, missing, _ = cov.analysis("recur.py")
+ self.assertEqual(statements, [1,2,3,5,7,8,9,10,11])
+ self.assertEqual(missing, expected_missing)
+
+ # We can get a warning about the stackoverflow effect on the tracing
+ # function only if we have sys.gettrace
+ if pytrace and hasattr(sys, "gettrace"):
+ self.assertEqual(cov._warnings,
+ ["Trace function changed, measurement is likely wrong: None"]
+ )
+ else:
+ self.assertEqual(cov._warnings, [])
+
+
+class MemoryLeakTest(CoverageTest):
+ """Attempt the impossible: test that memory doesn't leak.
+
+ Note: this test is truly unusual, and may fail unexpectedly.
+ In particular, it is known to fail on PyPy if test_oddball.py is run in
+ isolation: https://bitbucket.org/ned/coveragepy/issue/186
+
+ """
+
+ def test_for_leaks(self):
+ lines = list(range(301, 315))
+ lines.remove(306)
+ # Ugly string mumbo jumbo to get 300 blank lines at the beginning..
+ code = """\
+ # blank line\n""" * 300 + """\
+ def once(x):
+ if x % 100 == 0:
+ raise Exception("100!")
+ elif x % 2:
+ return 10
+ else:
+ return 11
+ i = 0 # Portable loop without alloc'ing memory.
+ while i < ITERS:
+ try:
+ once(i)
+ except:
+ pass
+ i += 1
+ """
+ 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.assertTrue(ram_growth < 100000, "RAM grew by %d" % (ram_growth))
+
+
+class PyexpatTest(CoverageTest):
+ """Pyexpat screws up tracing. Make sure we've counter-defended properly."""
+
+ def test_pyexpat(self):
+ # pyexpat calls the trace function explicitly (inexplicably), and does
+ # it wrong for exceptions. Parsing a DOCTYPE for some reason throws
+ # an exception internally, and triggers its wrong behavior. This test
+ # checks that our fake PyTrace_RETURN hack in tracer.c works. It will
+ # also detect if the pyexpat bug is fixed unbeknownst to us, meaning
+ # we'd see two RETURNs where there should only be one.
+
+ self.make_file("trydom.py", """\
+ import xml.dom.minidom
+
+ XML = '''\\
+ <!DOCTYPE fooey SYSTEM "http://www.example.com/example.dtd">
+ <root><child/><child/></root>
+ '''
+
+ def foo():
+ dom = xml.dom.minidom.parseString(XML)
+ assert len(dom.getElementsByTagName('child')) == 2
+ a = 11
+
+ foo()
+ """)
+
+ self.make_file("outer.py", "\n"*100 + "import trydom\na = 102\n")
+
+ cov = coverage.coverage()
+ cov.erase()
+
+ # Import the python file, executing it.
+ self.start_import_stop(cov, "outer")
+
+ _, 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, [])
+
+
+class ExceptionTest(CoverageTest):
+ """I suspect different versions of Python deal with exceptions differently
+ in the trace function.
+ """
+
+ def test_exception(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
+ # function which relies on scope announcements to track which files to
+ # trace.
+ #
+ # This test is designed to sniff this out. Each function in the call
+ # 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:
+ a = 3
+ calls[0](calls[1:])
+ a = 5
+ except:
+ a = 7
+ """)
+
+ self.make_file("doit.py", "\n"*300 + """\
+ def doit(calls):
+ try:
+ calls[0](calls[1:])
+ except:
+ a = 5
+ """)
+
+ # Import all the modules before starting coverage, so the def lines
+ # won't be in all the results.
+ for mod in "oops fly catch doit".split():
+ self.import_local_file(mod)
+
+ # Each run nests the functions differently to get different
+ # combinations of catching exceptions and letting them fly.
+ runs = [
+ ("doit fly oops", {
+ 'doit.py': [302,303,304,305],
+ 'fly.py': [102,103],
+ 'oops.py': [2,3],
+ }),
+ ("doit catch oops", {
+ 'doit.py': [302,303],
+ 'catch.py': [202,203,204,206,207],
+ 'oops.py': [2,3],
+ }),
+ ("doit fly catch oops", {
+ 'doit.py': [302,303],
+ 'fly.py': [102,103,104],
+ 'catch.py': [202,203,204,206,207],
+ 'oops.py': [2,3],
+ }),
+ ("doit catch fly oops", {
+ 'doit.py': [302,303],
+ 'catch.py': [202,203,204,206,207],
+ 'fly.py': [102,103],
+ '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:]) # pragma: nested
+ cov.stop() # pragma: nested
+
+ # Clean the line data and compare to expected results.
+ # The filenames are absolute, so keep just the base.
+ cov._harvest_data() # private! sshhh...
+ lines = cov.data.line_data()
+ clean_lines = {}
+ for f, llist in lines.items():
+ if f == __file__:
+ # ignore this file.
+ continue
+ clean_lines[os.path.basename(f)] = llist
+ self.assertEqual(clean_lines, lines_expected)
+
+
+if sys.version_info >= (2, 5):
+ 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
+ # monkeypatch to be sure I'm getting the doctest module that users
+ # of coverage will get. Deleting the imported module here is
+ # enough: when the test imports doctest again, it will get a fresh
+ # copy without the monkeypatch.
+ del sys.modules['doctest']
+
+ def test_doctest(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")
+ 'arg'
+ >>> return_arg_or_void("None")
+ 'None'
+ """
+ if arg is None:
+ return "Void"
+ else:
+ return arg
+
+ import doctest, sys
+ doctest.testmod(sys.modules[__name__]) # we're not __main__ :(
+ ''',
+ [1,11,12,14,16,17], "")
+
+
+if hasattr(sys, 'gettrace'):
+ class GettraceTest(CoverageTest):
+ """Tests that we work properly with `sys.gettrace()`."""
+ def test_round_trip(self):
+ self.check_coverage('''\
+ import sys
+ def foo(n):
+ return 3*n
+ def bar(n):
+ return 5*n
+ a = foo(6)
+ sys.settrace(sys.gettrace())
+ a = bar(8)
+ ''',
+ [1,2,3,4,5,6,7,8], "")
+
+ def test_multi_layers(self):
+ self.check_coverage('''\
+ import sys
+ def level1():
+ a = 3
+ level2()
+ b = 5
+ def level2():
+ c = 7
+ sys.settrace(sys.gettrace())
+ d = 9
+ e = 10
+ level1()
+ f = 12
+ ''',
+ [1,2,3,4,5,6,7,8,9,10,11,12], "")
diff --git a/tests/test_parser.py b/tests/test_parser.py
new file mode 100644
index 0000000..6ccef20
--- /dev/null
+++ b/tests/test_parser.py
@@ -0,0 +1,131 @@
+"""Tests for Coverage.py's code parsing."""
+
+import textwrap
+from test.coveragetest import CoverageTest
+from coverage.parser import CodeParser
+
+
+class ParserTest(CoverageTest):
+ """Tests for Coverage.py's code parsing."""
+
+ run_in_temp_dir = False
+
+ def parse_source(self, text):
+ """Parse `text` as source, and return the `CodeParser` used."""
+ text = textwrap.dedent(text)
+ cp = CodeParser(text=text, exclude="nocover")
+ cp.parse_source()
+ return cp
+
+ def test_exit_counts(self):
+ cp = self.parse_source("""\
+ # check some basic branch counting
+ class Foo:
+ def foo(self, a):
+ if a:
+ return 5
+ else:
+ return 7
+
+ class Bar:
+ pass
+ """)
+ self.assertEqual(cp.exit_counts(), {
+ 2:1, 3:1, 4:2, 5:1, 7:1, 9:1, 10:1
+ })
+
+ def test_try_except(self):
+ cp = self.parse_source("""\
+ try:
+ a = 2
+ except ValueError:
+ a = 4
+ except ZeroDivideError:
+ a = 6
+ except:
+ a = 8
+ b = 9
+ """)
+ 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
+ """)
+ 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:
+ a = 2
+ else: # nocover
+ a = 4
+ b = 5
+ """)
+ self.assertEqual(cp.exit_counts(), { 1:1, 2:1, 5:1 })
+ cp = self.parse_source("""\
+ def foo():
+ if fooey:
+ a = 3
+ else:
+ a = 5
+ b = 6
+ """)
+ self.assertEqual(cp.exit_counts(), { 1:1, 2:2, 3:1, 5:1, 6:1 })
+ cp = self.parse_source("""\
+ def foo():
+ if fooey:
+ a = 3
+ else: # nocover
+ a = 5
+ b = 6
+ """)
+ self.assertEqual(cp.exit_counts(), { 1:1, 2:1, 3:1, 6:1 })
+
+
+class ParserFileTest(CoverageTest):
+ """Tests for Coverage.py's code parsing from files."""
+
+ def parse_file(self, filename):
+ """Parse `text` as source, and return the `CodeParser` used."""
+ cp = CodeParser(filename=filename, exclude="nocover")
+ cp.parse_source()
+ return cp
+
+ def test_line_endings(self):
+ text = """\
+ # check some basic branch counting
+ class Foo:
+ def foo(self, a):
+ if a:
+ return 5
+ else:
+ return 7
+
+ class Bar:
+ pass
+ """
+ counts = { 2:1, 3:1, 4:2, 5:1, 7:1, 9:1, 10:1 }
+ name_endings = (("unix", "\n"), ("dos", "\r\n"), ("mac", "\r"))
+ for fname, newline in name_endings:
+ fname = fname + ".py"
+ self.make_file(fname, text, newline=newline)
+ cp = self.parse_file(fname)
+ self.assertEqual(cp.exit_counts(), counts)
+
+ def test_encoding(self):
+ self.make_file("encoded.py", """\
+ coverage = "\xe7\xf6v\xear\xe3g\xe9"
+ """)
+ cp = self.parse_file("encoded.py")
+ cp.exit_counts()
diff --git a/tests/test_phystokens.py b/tests/test_phystokens.py
new file mode 100644
index 0000000..e4834e4
--- /dev/null
+++ b/tests/test_phystokens.py
@@ -0,0 +1,79 @@
+"""Tests for Coverage.py's improved tokenizer."""
+
+import os, re
+from test.coveragetest import CoverageTest
+from coverage.phystokens import source_token_lines
+
+
+SIMPLE = """\
+# yay!
+def foo():
+ say('two = %d' % 2)
+"""
+
+MIXED_WS = """\
+def hello():
+ a="Hello world!"
+\tb="indented"
+"""
+
+HERE = os.path.split(__file__)[0]
+
+
+class PhysTokensTest(CoverageTest):
+ """Tests for Coverage.py's improver tokenizer."""
+
+ run_in_temp_dir = False
+
+ def check_tokenization(self, source):
+ """Tokenize `source`, then put it back together, should be the same."""
+ tokenized = ""
+ for line in source_token_lines(source):
+ text = "".join([t for _,t in line])
+ tokenized += text + "\n"
+ # source_token_lines doesn't preserve trailing spaces, so trim all that
+ # before comparing.
+ source = source.replace('\r\n', '\n')
+ source = re.sub(r"(?m)[ \t]+$", "", source)
+ tokenized = re.sub(r"(?m)[ \t]+$", "", tokenized)
+ self.assertMultiLineEqual(source, tokenized)
+
+ def check_file_tokenization(self, fname):
+ """Use the contents of `fname` for `check_tokenization`."""
+ self.check_tokenization(open(fname).read())
+
+ def test_simple(self):
+ self.assertEqual(list(source_token_lines(SIMPLE)),
+ [
+ [('com', "# yay!")],
+ [('key', 'def'), ('ws', ' '), ('nam', 'foo'), ('op', '('),
+ ('op', ')'), ('op', ':')],
+ [('ws', ' '), ('nam', 'say'), ('op', '('),
+ ('str', "'two = %d'"), ('ws', ' '), ('op', '%'),
+ ('ws', ' '), ('num', '2'), ('op', ')')]
+ ])
+ self.check_tokenization(SIMPLE)
+
+ def test_tab_indentation(self):
+ # Mixed tabs and spaces...
+ self.assertEqual(list(source_token_lines(MIXED_WS)),
+ [
+ [('key', 'def'), ('ws', ' '), ('nam', 'hello'), ('op', '('),
+ ('op', ')'), ('op', ':')],
+ [('ws', ' '), ('nam', 'a'), ('op', '='),
+ ('str', '"Hello world!"')],
+ [('ws', ' '), ('nam', 'b'), ('op', '='),
+ ('str', '"indented"')],
+ ])
+
+ def test_tokenize_real_file(self):
+ # Check the tokenization of a real file (large, btw).
+ real_file = os.path.join(HERE, "test_coverage.py")
+ self.check_file_tokenization(real_file)
+
+ def test_stress(self):
+ # Check the tokenization of a stress-test file.
+ stress = os.path.join(HERE, "stress_phystoken.tok")
+ self.check_file_tokenization(stress)
+ stress = os.path.join(HERE, "stress_phystoken_dos.tok")
+ self.check_file_tokenization(stress)
diff --git a/tests/test_process.py b/tests/test_process.py
new file mode 100644
index 0000000..bf22cc9
--- /dev/null
+++ b/tests/test_process.py
@@ -0,0 +1,575 @@
+"""Tests for process behavior of coverage.py."""
+
+import glob, os, sys, textwrap
+from nose.plugins.skip import SkipTest
+import coverage
+
+from test.coveragetest import CoverageTest
+
+here = os.path.dirname(__file__)
+
+class ProcessTest(CoverageTest):
+ """Tests of the per-process behavior of coverage.py."""
+
+ def number_of_data_files(self):
+ """Return the number of coverage data files in this directory."""
+ num = 0
+ for f in os.listdir('.'):
+ if f.startswith('.coverage.') or f == '.coverage':
+ num += 1
+ return num
+
+ def test_save_on_exit(self):
+ self.make_file("mycode.py", """\
+ h = "Hello"
+ w = "world"
+ """)
+
+ self.assert_doesnt_exist(".coverage")
+ self.run_command("coverage -x mycode.py")
+ self.assert_exists(".coverage")
+
+ def test_environment(self):
+ # Checks that we can import modules from the test directory at all!
+ self.make_file("mycode.py", """\
+ import covmod1
+ import covmodzip1
+ a = 1
+ print ('done')
+ """)
+
+ self.assert_doesnt_exist(".coverage")
+ out = self.run_command("coverage -x mycode.py")
+ self.assert_exists(".coverage")
+ self.assertEqual(out, 'done\n')
+
+ def test_combine_parallel_data(self):
+ self.make_file("b_or_c.py", """\
+ import sys
+ a = 1
+ if sys.argv[1] == 'b':
+ b = 1
+ else:
+ c = 1
+ d = 1
+ print ('done')
+ """)
+
+ out = self.run_command("coverage -x -p b_or_c.py b")
+ self.assertEqual(out, 'done\n')
+ self.assert_doesnt_exist(".coverage")
+
+ out = self.run_command("coverage -x -p b_or_c.py c")
+ self.assertEqual(out, 'done\n')
+ self.assert_doesnt_exist(".coverage")
+
+ # After two -p runs, there should be two .coverage.machine.123 files.
+ self.assertEqual(self.number_of_data_files(), 2)
+
+ # Combine the parallel coverage data files into .coverage .
+ self.run_command("coverage -c")
+ self.assert_exists(".coverage")
+
+ # After combining, there should be only the .coverage file.
+ self.assertEqual(self.number_of_data_files(), 1)
+
+ # Read the coverage file and see that b_or_c.py has all 7 lines
+ # executed.
+ data = coverage.CoverageData()
+ data.read_file(".coverage")
+ self.assertEqual(data.summary()['b_or_c.py'], 7)
+
+ def test_combine_parallel_data_in_two_steps(self):
+ self.make_file("b_or_c.py", """\
+ import sys
+ a = 1
+ if sys.argv[1] == 'b':
+ b = 1
+ else:
+ c = 1
+ d = 1
+ print ('done')
+ """)
+
+ out = self.run_command("coverage -x -p b_or_c.py b")
+ self.assertEqual(out, 'done\n')
+ self.assert_doesnt_exist(".coverage")
+ self.assertEqual(self.number_of_data_files(), 1)
+
+ # Combine the (one) parallel coverage data file into .coverage .
+ self.run_command("coverage -c")
+ self.assert_exists(".coverage")
+ self.assertEqual(self.number_of_data_files(), 1)
+
+ out = self.run_command("coverage -x -p b_or_c.py c")
+ self.assertEqual(out, 'done\n')
+ self.assert_exists(".coverage")
+ self.assertEqual(self.number_of_data_files(), 2)
+
+ # Combine the parallel coverage data files into .coverage .
+ self.run_command("coverage -c")
+ self.assert_exists(".coverage")
+
+ # After combining, there should be only the .coverage file.
+ self.assertEqual(self.number_of_data_files(), 1)
+
+ # Read the coverage file and see that b_or_c.py has all 7 lines
+ # executed.
+ data = coverage.CoverageData()
+ data.read_file(".coverage")
+ self.assertEqual(data.summary()['b_or_c.py'], 7)
+
+ def test_combine_with_rc(self):
+ self.make_file("b_or_c.py", """\
+ import sys
+ a = 1
+ if sys.argv[1] == 'b':
+ b = 1
+ else:
+ c = 1
+ d = 1
+ print ('done')
+ """)
+
+ self.make_file(".coveragerc", """\
+ [run]
+ parallel = true
+ """)
+
+ out = self.run_command("coverage run b_or_c.py b")
+ self.assertEqual(out, 'done\n')
+ self.assert_doesnt_exist(".coverage")
+
+ out = self.run_command("coverage run b_or_c.py c")
+ self.assertEqual(out, 'done\n')
+ self.assert_doesnt_exist(".coverage")
+
+ # After two runs, there should be two .coverage.machine.123 files.
+ self.assertEqual(self.number_of_data_files(), 2)
+
+ # Combine the parallel coverage data files into .coverage .
+ self.run_command("coverage combine")
+ self.assert_exists(".coverage")
+ self.assert_exists(".coveragerc")
+
+ # After combining, there should be only the .coverage file.
+ self.assertEqual(self.number_of_data_files(), 1)
+
+ # Read the coverage file and see that b_or_c.py has all 7 lines
+ # executed.
+ data = coverage.CoverageData()
+ data.read_file(".coverage")
+ self.assertEqual(data.summary()['b_or_c.py'], 7)
+
+ # Reporting should still work even with the .rc file
+ out = self.run_command("coverage report")
+ self.assertMultiLineEqual(out, textwrap.dedent("""\
+ Name Stmts Miss Cover
+ ----------------------------
+ b_or_c 7 0 100%
+ """))
+
+ def test_combine_with_aliases(self):
+ self.make_file("d1/x.py", """\
+ a = 1
+ b = 2
+ print("%s %s" % (a, b))
+ """)
+
+ self.make_file("d2/x.py", """\
+ # 1
+ # 2
+ # 3
+ c = 4
+ d = 5
+ print("%s %s" % (c, d))
+ """)
+
+ self.make_file(".coveragerc", """\
+ [run]
+ parallel = True
+
+ [paths]
+ source =
+ src
+ */d1
+ */d2
+ """)
+
+ out = self.run_command("coverage run " + os.path.normpath("d1/x.py"))
+ self.assertEqual(out, '1 2\n')
+ out = self.run_command("coverage run " + os.path.normpath("d2/x.py"))
+ self.assertEqual(out, '4 5\n')
+
+ self.assertEqual(self.number_of_data_files(), 2)
+
+ self.run_command("coverage combine")
+ self.assert_exists(".coverage")
+
+ # After combining, there should be only the .coverage file.
+ self.assertEqual(self.number_of_data_files(), 1)
+
+ # Read the coverage data file and see that the two different x.py
+ # files have been combined together.
+ data = coverage.CoverageData()
+ data.read_file(".coverage")
+ summary = data.summary(fullpath=True)
+ self.assertEqual(len(summary), 1)
+ actual = os.path.normcase(os.path.abspath(list(summary.keys())[0]))
+ expected = os.path.normcase(os.path.abspath('src/x.py'))
+ self.assertEqual(actual, expected)
+ self.assertEqual(list(summary.values())[0], 6)
+
+ def test_missing_source_file(self):
+ # Check what happens if the source is missing when reporting happens.
+ self.make_file("fleeting.py", """\
+ s = 'goodbye, cruel world!'
+ """)
+
+ self.run_command("coverage run fleeting.py")
+ os.remove("fleeting.py")
+ out = self.run_command("coverage html -d htmlcov")
+ self.assertRegexpMatches(out, "No source for code: '.*fleeting.py'")
+ self.assertNotIn("Traceback", out)
+
+ # It happens that the code paths are different for *.py and other
+ # files, so try again with no extension.
+ self.make_file("fleeting", """\
+ s = 'goodbye, cruel world!'
+ """)
+
+ self.run_command("coverage run fleeting")
+ os.remove("fleeting")
+ status, out = self.run_command_status("coverage html -d htmlcov", 1)
+ self.assertRegexpMatches(out, "No source for code: '.*fleeting'")
+ self.assertNotIn("Traceback", out)
+ self.assertEqual(status, 1)
+
+ def test_running_missing_file(self):
+ status, out = self.run_command_status("coverage run xyzzy.py", 1)
+ self.assertRegexpMatches(out, "No file to run: .*xyzzy.py")
+ self.assertNotIn("raceback", out)
+ self.assertNotIn("rror", out)
+ self.assertEqual(status, 1)
+
+ def test_code_throws(self):
+ self.make_file("throw.py", """\
+ def f1():
+ raise Exception("hey!")
+
+ def f2():
+ f1()
+
+ f2()
+ """)
+
+ # The important thing is for "coverage run" and "python" to report the
+ # same traceback.
+ status, out = self.run_command_status("coverage run throw.py", 1)
+ out2 = self.run_command("python throw.py")
+ if '__pypy__' in sys.builtin_module_names:
+ # Pypy has an extra frame in the traceback for some reason
+ lines2 = out2.splitlines()
+ out2 = "".join([l+"\n" for l in lines2 if "toplevel" not in l])
+ self.assertMultiLineEqual(out, out2)
+
+ # But also make sure that the output is what we expect.
+ self.assertIn('File "throw.py", line 5, in f2', out)
+ self.assertIn('raise Exception("hey!")', out)
+ self.assertNotIn('coverage', out)
+ self.assertEqual(status, 1)
+
+ def test_code_exits(self):
+ self.make_file("exit.py", """\
+ import sys
+ def f1():
+ print("about to exit..")
+ sys.exit(17)
+
+ def f2():
+ f1()
+
+ f2()
+ """)
+
+ # The important thing is for "coverage run" and "python" to have the
+ # same output. No traceback.
+ status, out = self.run_command_status("coverage run exit.py", 17)
+ status2, out2 = self.run_command_status("python exit.py", 17)
+ self.assertMultiLineEqual(out, out2)
+ self.assertMultiLineEqual(out, "about to exit..\n")
+ self.assertEqual(status, status2)
+ self.assertEqual(status, 17)
+
+ def test_code_exits_no_arg(self):
+ self.make_file("exit_none.py", """\
+ import sys
+ def f1():
+ print("about to exit quietly..")
+ sys.exit()
+
+ f1()
+ """)
+ status, out = self.run_command_status("coverage run exit_none.py", 0)
+ status2, out2 = self.run_command_status("python exit_none.py", 0)
+ self.assertMultiLineEqual(out, out2)
+ self.assertMultiLineEqual(out, "about to exit quietly..\n")
+ self.assertEqual(status, status2)
+ self.assertEqual(status, 0)
+
+ def test_coverage_run_is_like_python(self):
+ tryfile = os.path.join(here, "try_execfile.py")
+ self.make_file("run_me.py", open(tryfile).read())
+ out = self.run_command("coverage run run_me.py")
+ out2 = self.run_command("python run_me.py")
+ self.assertMultiLineEqual(out, out2)
+
+ if sys.version_info >= (2, 6): # Doesn't work in 2.5, and I don't care!
+ def test_coverage_run_dashm_is_like_python_dashm(self):
+ # These -m commands assume the coverage tree is on the path.
+ out = self.run_command("coverage run -m test.try_execfile")
+ out2 = self.run_command("python -m test.try_execfile")
+ self.assertMultiLineEqual(out, out2)
+
+ if 0: # Expected failure
+ # For https://bitbucket.org/ned/coveragepy/issue/207
+ def test_coverage_run_dashm_is_like_python_dashm_with__main__207(self):
+ self.make_file("package/__init__.py") # empty
+ self.make_file("package/__main__.py", "#\n") # empty
+ out = self.run_command("coverage run -m package")
+ out2 = self.run_command("python -m package")
+ self.assertMultiLineEqual(out, out2)
+
+ if hasattr(os, 'fork'):
+ def test_fork(self):
+ self.make_file("fork.py", """\
+ import os
+
+ def child():
+ print('Child!')
+
+ def main():
+ ret = os.fork()
+
+ if ret == 0:
+ child()
+ else:
+ os.waitpid(ret, 0)
+
+ main()
+ """)
+
+ out = self.run_command("coverage run -p fork.py")
+ self.assertEqual(out, 'Child!\n')
+ self.assert_doesnt_exist(".coverage")
+
+ # After running the forking program, there should be two
+ # .coverage.machine.123 files.
+ self.assertEqual(self.number_of_data_files(), 2)
+
+ # Combine the parallel coverage data files into .coverage .
+ self.run_command("coverage -c")
+ self.assert_exists(".coverage")
+
+ # After combining, there should be only the .coverage file.
+ self.assertEqual(self.number_of_data_files(), 1)
+
+ # Read the coverage file and see that b_or_c.py has all 7 lines
+ # executed.
+ data = coverage.CoverageData()
+ data.read_file(".coverage")
+ self.assertEqual(data.summary()['fork.py'], 9)
+
+ def test_warnings(self):
+ self.make_file("hello.py", """\
+ import sys, os
+ print("Hello")
+ """)
+ out = self.run_command("coverage run --source=sys,xyzzy,quux hello.py")
+
+ self.assertIn("Hello\n", out)
+ self.assertIn(textwrap.dedent("""\
+ Coverage.py warning: Module sys has no Python source.
+ Coverage.py warning: Module xyzzy was never imported.
+ Coverage.py warning: Module quux was never imported.
+ Coverage.py warning: No data was collected.
+ """), out)
+
+ def test_warnings_during_reporting(self):
+ # While fixing issue #224, the warnings were being printed far too
+ # often. Make sure they're not any more.
+ self.make_file("hello.py", """\
+ import sys, os, the_other
+ print("Hello")
+ """)
+ self.make_file("the_other.py", """\
+ print("What?")
+ """)
+ self.make_file(".coveragerc", """\
+ [run]
+ source =
+ .
+ xyzzy
+ """)
+
+ self.run_command("coverage run hello.py")
+ out = self.run_command("coverage html")
+ self.assertEqual(out.count("Module xyzzy was never imported."), 0)
+
+ def test_warnings_if_never_run(self):
+ out = self.run_command("coverage run i_dont_exist.py")
+ self.assertIn("No file to run: 'i_dont_exist.py'", out)
+ self.assertNotIn("warning", out)
+ self.assertNotIn("Exception", out)
+
+ out = self.run_command("coverage run -m no_such_module")
+ self.assertTrue(
+ ("No module named no_such_module" in out) or
+ ("No module named 'no_such_module'" in out)
+ )
+ self.assertNotIn("warning", out)
+ self.assertNotIn("Exception", out)
+
+ if sys.version_info >= (3, 0): # This only works on 3.x for now.
+ # It only works with the C tracer,
+ c_tracer = os.getenv('COVERAGE_TEST_TRACER', 'c') == 'c'
+ # and if we aren't measuring ourselves.
+ metacov = os.getenv('COVERAGE_COVERAGE', '') != ''
+ if c_tracer and not metacov: # pragma: not covered
+ def test_fullcoverage(self):
+ # fullcoverage is a trick to get stdlib modules measured from
+ # the very beginning of the process. Here we import os and
+ # then check how many lines are measured.
+ self.make_file("getenv.py", """\
+ import os
+ print("FOOEY == %s" % os.getenv("FOOEY"))
+ """)
+
+ fullcov = os.path.join(
+ os.path.dirname(coverage.__file__), "fullcoverage"
+ )
+ self.set_environ("FOOEY", "BOO")
+ self.set_environ("PYTHONPATH", fullcov)
+ out = self.run_command("python -m coverage run -L getenv.py")
+ self.assertEqual(out, "FOOEY == BOO\n")
+ data = coverage.CoverageData()
+ data.read_file(".coverage")
+ # The actual number of executed lines in os.py when it's
+ # imported is 120 or so. Just running os.getenv executes
+ # about 5.
+ self.assertGreater(data.summary()['os.py'], 50)
+
+
+class AliasedCommandTests(CoverageTest):
+ """Tests of the version-specific command aliases."""
+
+ def test_major_version_works(self):
+ # "coverage2" works on py2
+ cmd = "coverage%d" % sys.version_info[0]
+ out = self.run_command(cmd)
+ self.assertIn("Code coverage for Python", out)
+
+ def test_wrong_alias_doesnt_work(self):
+ # "coverage3" doesn't work on py2
+ badcmd = "coverage%d" % (5 - sys.version_info[0])
+ out = self.run_command(badcmd)
+ self.assertNotIn("Code coverage for Python", out)
+
+ def test_specific_alias_works(self):
+ # "coverage-2.7" works on py2.7
+ cmd = "coverage-%d.%d" % sys.version_info[:2]
+ out = self.run_command(cmd)
+ self.assertIn("Code coverage for Python", out)
+
+
+class FailUnderTest(CoverageTest):
+ """Tests of the --fail-under switch."""
+
+ def setUp(self):
+ super(FailUnderTest, self).setUp()
+ self.make_file("fifty.py", """\
+ # I have 50% coverage!
+ a = 1
+ if a > 2:
+ b = 3
+ c = 4
+ """)
+ st, _ = self.run_command_status("coverage run fifty.py", 0)
+ self.assertEqual(st, 0)
+
+ def test_report(self):
+ st, _ = self.run_command_status("coverage report --fail-under=50", 0)
+ self.assertEqual(st, 0)
+ st, _ = self.run_command_status("coverage report --fail-under=51", 2)
+ self.assertEqual(st, 2)
+
+ def test_html_report(self):
+ st, _ = self.run_command_status("coverage html --fail-under=50", 0)
+ self.assertEqual(st, 0)
+ st, _ = self.run_command_status("coverage html --fail-under=51", 2)
+ self.assertEqual(st, 2)
+
+ def test_xml_report(self):
+ st, _ = self.run_command_status("coverage xml --fail-under=50", 0)
+ self.assertEqual(st, 0)
+ st, _ = self.run_command_status("coverage xml --fail-under=51", 2)
+ self.assertEqual(st, 2)
+
+
+class ProcessStartupTest(CoverageTest):
+ """Test that we can measure coverage in subprocesses."""
+
+ def setUp(self):
+ super(ProcessStartupTest, self).setUp()
+ # Find a place to put a .pth file.
+ pth_contents = "import coverage; coverage.process_startup()\n"
+ for d in sys.path: # pragma: part covered
+ g = glob.glob(os.path.join(d, "*.pth"))
+ if g:
+ pth_path = os.path.join(d, "subcover.pth")
+ pth = open(pth_path, "w")
+ try:
+ try:
+ pth.write(pth_contents)
+ self.pth_path = pth_path
+ break
+ except (IOError, OSError): # pragma: not covered
+ pass
+ finally:
+ pth.close()
+ else: # pragma: not covered
+ raise Exception("Couldn't find a place for the .pth file")
+
+ def tearDown(self):
+ super(ProcessStartupTest, self).tearDown()
+ # Clean up the .pth file we made.
+ os.remove(self.pth_path)
+
+ def test_subprocess_with_pth_files(self): # pragma: not covered
+ if os.environ.get('COVERAGE_COVERAGE', ''):
+ raise SkipTest(
+ "Can't test subprocess pth file suppport during metacoverage"
+ )
+ # Main will run sub.py
+ self.make_file("main.py", """\
+ import os
+ os.system("python sub.py")
+ """)
+ # sub.py will write a few lines.
+ self.make_file("sub.py", """\
+ f = open("out.txt", "w")
+ f.write("Hello, world!\\n")
+ f.close()
+ """)
+ self.make_file("coverage.ini", """\
+ [run]
+ data_file = .mycovdata
+ """)
+ self.set_environ("COVERAGE_PROCESS_START", "coverage.ini")
+ import main # pylint: disable=F0401,W0612
+
+ self.assertEqual(open("out.txt").read(), "Hello, world!\n")
+ # Read the data from .coverage
+ data = coverage.CoverageData()
+ data.read_file(".mycovdata")
+ self.assertEqual(data.summary()['sub.py'], 3)
diff --git a/tests/test_results.py b/tests/test_results.py
new file mode 100644
index 0000000..3caa5a6
--- /dev/null
+++ b/tests/test_results.py
@@ -0,0 +1,60 @@
+"""Tests for Coverage.py's results analysis."""
+
+from coverage.results import Numbers
+from test.coveragetest import CoverageTest
+
+
+class NumbersTest(CoverageTest):
+ """Tests for Coverage.py's numeric measurement summaries."""
+
+ run_in_temp_dir = False
+
+ def test_basic(self):
+ n1 = Numbers(n_files=1, n_statements=200, n_missing=20)
+ self.assertEqual(n1.n_statements, 200)
+ 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)
+ n3 = n1 + n2
+ self.assertEqual(n3.n_files, 2)
+ self.assertEqual(n3.n_statements, 210)
+ self.assertEqual(n3.n_executed, 182)
+ self.assertEqual(n3.n_missing, 28)
+ self.assertAlmostEqual(n3.pc_covered, 86.666666666)
+
+ def test_sum(self):
+ n1 = Numbers(n_files=1, n_statements=200, n_missing=20)
+ n2 = Numbers(n_files=1, n_statements=10, n_missing=8)
+ n3 = sum([n1, n2])
+ self.assertEqual(n3.n_files, 2)
+ self.assertEqual(n3.n_statements, 210)
+ self.assertEqual(n3.n_executed, 182)
+ self.assertEqual(n3.n_missing, 28)
+ self.assertAlmostEqual(n3.pc_covered, 86.666666666)
+
+ def test_pc_covered_str(self):
+ n0 = Numbers(n_files=1, n_statements=1000, n_missing=0)
+ n1 = Numbers(n_files=1, n_statements=1000, n_missing=1)
+ n999 = Numbers(n_files=1, n_statements=1000, n_missing=999)
+ n1000 = Numbers(n_files=1, n_statements=1000, n_missing=1000)
+ self.assertEqual(n0.pc_covered_str, "100")
+ self.assertEqual(n1.pc_covered_str, "99")
+ self.assertEqual(n999.pc_covered_str, "1")
+ self.assertEqual(n1000.pc_covered_str, "0")
+
+ def test_pc_covered_str_precision(self):
+ assert Numbers._precision == 0
+ Numbers.set_precision(1)
+ n0 = Numbers(n_files=1, n_statements=10000, n_missing=0)
+ n1 = Numbers(n_files=1, n_statements=10000, n_missing=1)
+ n9999 = Numbers(n_files=1, n_statements=10000, n_missing=9999)
+ n10000 = Numbers(n_files=1, n_statements=10000, n_missing=10000)
+ self.assertEqual(n0.pc_covered_str, "100.0")
+ self.assertEqual(n1.pc_covered_str, "99.9")
+ self.assertEqual(n9999.pc_covered_str, "0.1")
+ self.assertEqual(n10000.pc_covered_str, "0.0")
+ Numbers.set_precision(0)
diff --git a/tests/test_summary.py b/tests/test_summary.py
new file mode 100644
index 0000000..5bb903a
--- /dev/null
+++ b/tests/test_summary.py
@@ -0,0 +1,298 @@
+"""Test text-based summary reporting for coverage.py"""
+
+import os, re, sys
+
+import coverage
+from coverage.backward import StringIO
+
+from test.coveragetest import CoverageTest
+
+class SummaryTest(CoverageTest):
+ """Tests of the text summary reporting for coverage.py."""
+
+ def setUp(self):
+ super(SummaryTest, self).setUp()
+ self.make_file("mycode.py", """\
+ import covmod1
+ import covmodzip1
+ a = 1
+ print ('done')
+ """)
+ # Parent class saves and restores sys.path, we can just modify it.
+ sys.path.append(self.nice_file(os.path.dirname(__file__), 'modules'))
+
+ def report_from_command(self, cmd):
+ """Return the report from the `cmd`, with some convenience added."""
+ report = self.run_command(cmd).replace('\\', '/')
+ self.assertNotIn("error", report.lower())
+ return report
+
+ def line_count(self, report):
+ """How many lines are in `report`?"""
+ self.assertEqual(report.split('\n')[-1], "")
+ return len(report.split('\n')) - 1
+
+ def last_line_squeezed(self, report):
+ """Return the last line of `report` with the spaces squeezed down."""
+ last_line = report.split('\n')[-2]
+ return re.sub(r"\s+", " ", last_line)
+
+ def test_report(self):
+ out = self.run_command("coverage -x mycode.py")
+ self.assertEqual(out, 'done\n')
+ report = self.report_from_command("coverage -r")
+
+ # Name Stmts Miss Cover
+ # ---------------------------------------------------------------------
+ # c:/ned/coverage/trunk/test/modules/covmod1 2 0 100%
+ # c:/ned/coverage/trunk/test/zipmods.zip/covmodzip1 2 0 100%
+ # mycode 4 0 100%
+ # ---------------------------------------------------------------------
+ # TOTAL 8 0 100%
+
+ self.assertNotIn("/coverage/__init__/", report)
+ self.assertIn("/test/modules/covmod1 ", report)
+ self.assertIn("/test/zipmods.zip/covmodzip1 ", report)
+ self.assertIn("mycode ", report)
+ self.assertEqual(self.last_line_squeezed(report), "TOTAL 8 0 100%")
+
+ def test_report_just_one(self):
+ # Try reporting just one module
+ self.run_command("coverage -x mycode.py")
+ report = self.report_from_command("coverage -r mycode.py")
+
+ # Name Stmts Miss Cover
+ # ----------------------------
+ # mycode 4 0 100%
+
+ self.assertEqual(self.line_count(report), 3)
+ self.assertNotIn("/coverage/", report)
+ self.assertNotIn("/test/modules/covmod1 ", report)
+ self.assertNotIn("/test/zipmods.zip/covmodzip1 ", report)
+ self.assertIn("mycode ", report)
+ self.assertEqual(self.last_line_squeezed(report), "mycode 4 0 100%")
+
+ def test_report_omitting(self):
+ # Try reporting while omitting some modules
+ 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 Miss Cover
+ # ----------------------------
+ # mycode 4 0 100%
+
+ self.assertEqual(self.line_count(report), 3)
+ self.assertNotIn("/coverage/", report)
+ self.assertNotIn("/test/modules/covmod1 ", report)
+ self.assertNotIn("/test/zipmods.zip/covmodzip1 ", report)
+ self.assertIn("mycode ", report)
+ self.assertEqual(self.last_line_squeezed(report), "mycode 4 0 100%")
+
+ def test_report_including(self):
+ # Try reporting while including some modules
+ self.run_command("coverage run mycode.py")
+ report = self.report_from_command("coverage report --include=mycode*")
+
+ # Name Stmts Miss Cover
+ # ----------------------------
+ # mycode 4 0 100%
+
+ self.assertEqual(self.line_count(report), 3)
+ self.assertNotIn("/coverage/", report)
+ self.assertNotIn("/test/modules/covmod1 ", report)
+ self.assertNotIn("/test/zipmods.zip/covmodzip1 ", report)
+ self.assertIn("mycode ", report)
+ self.assertEqual(self.last_line_squeezed(report), "mycode 4 0 100%")
+
+ def test_report_branches(self):
+ self.make_file("mybranch.py", """\
+ def branch(x):
+ if x:
+ print("x")
+ return x
+ branch(1)
+ """)
+ out = self.run_command("coverage run --branch mybranch.py")
+ self.assertEqual(out, 'x\n')
+ report = self.report_from_command("coverage report")
+
+ # Name Stmts Miss Branch BrMiss Cover
+ # --------------------------------------------
+ # mybranch 5 0 2 1 85%
+
+ self.assertEqual(self.line_count(report), 3)
+ self.assertIn("mybranch ", report)
+ self.assertEqual(self.last_line_squeezed(report),
+ "mybranch 5 0 2 1 86%")
+
+ def test_dotpy_not_python(self):
+ # We run a .py file, and when reporting, we can't parse it as Python.
+ # We should get an error message in the report.
+
+ self.run_command("coverage run mycode.py")
+ self.make_file("mycode.py", "This isn't python at all!")
+ report = self.report_from_command("coverage -r mycode.py")
+
+ # pylint: disable=C0301
+ # Name Stmts Miss Cover
+ # ----------------------------
+ # mycode NotPython: Couldn't parse '/tmp/test_cover/63354509363/mycode.py' as Python source: 'invalid syntax' at line 1
+
+ last = self.last_line_squeezed(report)
+ # The actual file name varies run to run.
+ last = re.sub(r"parse '.*mycode.py", "parse 'mycode.py", last)
+ # The actual error message varies version to version
+ last = re.sub(r": '.*' at", ": 'error' at", last)
+ self.assertEqual(last,
+ "mycode NotPython: "
+ "Couldn't parse 'mycode.py' as Python source: "
+ "'error' at line 1"
+ )
+
+ def test_dotpy_not_python_ignored(self):
+ # We run a .py file, and when reporting, we can't parse it as Python,
+ # but we've said to ignore errors, so there's no error reported.
+ self.run_command("coverage run mycode.py")
+ self.make_file("mycode.py", "This isn't python at all!")
+ report = self.report_from_command("coverage -r -i mycode.py")
+
+ # Name Stmts Miss Cover
+ # ----------------------------
+
+ self.assertEqual(self.line_count(report), 2)
+
+ def test_dothtml_not_python(self):
+ # We run a .html file, and when reporting, we can't parse it as
+ # Python. Since it wasn't .py, no error is reported.
+
+ # Run an "html" file
+ self.make_file("mycode.html", "a = 1")
+ self.run_command("coverage run mycode.html")
+ # Before reporting, change it to be an HTML file.
+ self.make_file("mycode.html", "<h1>This isn't python at all!</h1>")
+ report = self.report_from_command("coverage -r mycode.html")
+
+ # Name Stmts Miss Cover
+ # ----------------------------
+
+ self.assertEqual(self.line_count(report), 2)
+
+ def get_report(self, cov):
+ """Get the report from `cov`, and canonicalize it."""
+ repout = StringIO()
+ cov.report(file=repout, show_missing=False)
+ report = repout.getvalue().replace('\\', '/')
+ report = re.sub(r" +", " ", report)
+ return report
+
+ def test_bug_156_file_not_run_should_be_zero(self):
+ # https://bitbucket.org/ned/coveragepy/issue/156
+ self.make_file("mybranch.py", """\
+ def branch(x):
+ if x:
+ print("x")
+ return x
+ branch(1)
+ """)
+ self.make_file("main.py", """\
+ print("y")
+ """)
+ cov = coverage.coverage(branch=True, source=["."])
+ cov.start()
+ import main # pragma: nested # pylint: disable=F0401,W0612
+ cov.stop() # pragma: nested
+ report = self.get_report(cov).splitlines()
+ self.assertIn("mybranch 5 5 2 2 0%", report)
+
+ def run_TheCode_and_report_it(self):
+ """A helper for the next few tests."""
+ cov = coverage.coverage()
+ cov.start()
+ import TheCode # pragma: nested # pylint: disable=F0401,W0612
+ cov.stop() # pragma: nested
+ return self.get_report(cov)
+
+ def test_bug_203_mixed_case_listed_twice_with_rc(self):
+ self.make_file("TheCode.py", "a = 1\n")
+ self.make_file(".coveragerc", "[run]\nsource = .\n")
+
+ report = self.run_TheCode_and_report_it()
+
+ self.assertIn("TheCode", report)
+ self.assertNotIn("thecode", report)
+
+ def test_bug_203_mixed_case_listed_twice(self):
+ self.make_file("TheCode.py", "a = 1\n")
+
+ report = self.run_TheCode_and_report_it()
+
+ self.assertIn("TheCode", report)
+ self.assertNotIn("thecode", report)
+
+
+class SummaryTest2(CoverageTest):
+ """Another bunch of summary tests."""
+ # This class exists because tests naturally clump into classes based on the
+ # needs of their setUp and tearDown, rather than the product features they
+ # are testing. There's probably a better way to organize these.
+
+ run_in_temp_dir = False
+
+ def setUp(self):
+ super(SummaryTest2, self).setUp()
+ # Parent class saves and restores sys.path, we can just modify it.
+ this_dir = os.path.dirname(__file__)
+ sys.path.append(self.nice_file(this_dir, 'modules'))
+ sys.path.append(self.nice_file(this_dir, 'moremodules'))
+
+ def test_empty_files(self):
+ # Shows that empty files like __init__.py are listed as having zero
+ # statements, not one statement.
+ cov = coverage.coverage()
+ cov.start()
+ import usepkgs # pragma: nested # pylint: disable=F0401,W0612
+ cov.stop() # pragma: nested
+
+ repout = StringIO()
+ cov.report(file=repout, show_missing=False)
+
+ report = repout.getvalue().replace('\\', '/')
+ report = re.sub(r"\s+", " ", report)
+ self.assertIn("test/modules/pkg1/__init__ 1 0 100%", report)
+ self.assertIn("test/modules/pkg2/__init__ 0 0 100%", report)
+
+
+class ReportingReturnValue(CoverageTest):
+ """Tests of reporting functions returning values."""
+
+ def run_coverage(self):
+ """Run coverage on doit.py and return the coverage object."""
+ self.make_file("doit.py", """\
+ a = 1
+ b = 2
+ c = 3
+ d = 4
+ if a > 10:
+ f = 6
+ g = 7
+ """)
+
+ cov = coverage.coverage()
+ self.start_import_stop(cov, "doit")
+ return cov
+
+ def test_report(self):
+ cov = self.run_coverage()
+ val = cov.report(include="*/doit.py")
+ self.assertAlmostEqual(val, 85.7, 1)
+
+ def test_html(self):
+ cov = self.run_coverage()
+ val = cov.html_report(include="*/doit.py")
+ self.assertAlmostEqual(val, 85.7, 1)
+
+ def test_xml(self):
+ cov = self.run_coverage()
+ val = cov.xml_report(include="*/doit.py")
+ self.assertAlmostEqual(val, 85.7, 1)
diff --git a/tests/test_templite.py b/tests/test_templite.py
new file mode 100644
index 0000000..0435c54
--- /dev/null
+++ b/tests/test_templite.py
@@ -0,0 +1,204 @@
+"""Tests for coverage.templite."""
+
+from coverage.templite import Templite
+import unittest
+
+# pylint: disable=W0612,E1101
+# Disable W0612 (Unused variable) and
+# E1101 (Instance of 'foo' has no 'bar' member)
+
+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():
+ setattr(self, n, v)
+
+
+class TempliteTest(unittest.TestCase):
+ """Tests for Templite."""
+
+ def try_render(self, text, ctx, result):
+ """Render `text` through `ctx`, and it had better be `result`."""
+ self.assertEqual(Templite(text).render(ctx), result)
+
+ def test_passthrough(self):
+ # Strings without variables are passed through unchanged.
+ self.assertEqual(Templite("Hello").render(), "Hello")
+ self.assertEqual(
+ Templite("Hello, 20% fun time!").render(),
+ "Hello, 20% fun time!"
+ )
+
+ def test_variables(self):
+ # Variables use {{var}} syntax.
+ self.try_render("Hello, {{name}}!", {'name':'Ned'}, "Hello, Ned!")
+
+ def test_pipes(self):
+ # Variables can be filtered with pipes.
+ data = {
+ 'name': 'Ned',
+ 'upper': lambda x: x.upper(),
+ '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!")
+
+ def test_reusability(self):
+ # A single Templite can be used more than once with different data.
+ globs = {
+ '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!")
+
+ def test_attribute(self):
+ # Variables' attributes can be accessed with dots.
+ obj = AnyOldObject(a="Ay")
+ self.try_render("{{obj.a}}", locals(), "Ay")
+
+ 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):
+ """A class to try out member function access."""
+ def ditto(self):
+ """Return twice the .txt attribute."""
+ return self.txt + self.txt
+ obj = WithMemberFns(txt="Once")
+ self.try_render("{{obj.ditto}}", locals(), "OnceOnce")
+
+ def test_item_access(self):
+ # Variables' items can be used.
+ d = {'a':17, 'b':23}
+ self.try_render("{{d.a}} < {{d.b}}", locals(), "17 < 23")
+
+ def test_loops(self):
+ # Loops work like in Django.
+ nums = [1,2,3,4]
+ self.try_render(
+ "Look: {% for n in nums %}{{n}}, {% endfor %}done.",
+ locals(),
+ "Look: 1, 2, 3, 4, done."
+ )
+ # Loop iterables can be filtered.
+ def rev(l):
+ """Return the reverse of `l`."""
+ l = l[:]
+ l.reverse()
+ return l
+
+ self.try_render(
+ "Look: {% for n in nums|rev %}{{n}}, {% endfor %}done.",
+ locals(),
+ "Look: 4, 3, 2, 1, done."
+ )
+
+ def test_empty_loops(self):
+ self.try_render(
+ "Empty: {% for n in nums %}{{n}}, {% endfor %}done.",
+ {'nums':[]},
+ "Empty: done."
+ )
+
+ def test_multiline_loops(self):
+ self.try_render(
+ "Look: \n{% for n in nums %}\n{{n}}, \n{% endfor %}done.",
+ {'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 "
+ "{% for n in nums %}{{n}}{% endfor %}",
+ {'nums': [1,2,3]},
+ "123 and 123"
+ )
+
+ def test_comments(self):
+ # Single-line comments work:
+ self.try_render(
+ "Hello, {# Name goes here: #}{{name}}!",
+ {'name':'Ned'}, "Hello, Ned!"
+ )
+ # and so do multi-line comments:
+ self.try_render(
+ "Hello, {# Name\ngoes\nhere: #}{{name}}!",
+ {'name':'Ned'}, "Hello, Ned!"
+ )
+
+ def test_if(self):
+ self.try_render(
+ "Hi, {% if ned %}NED{% endif %}{% if ben %}BEN{% endif %}!",
+ {'ned': 1, 'ben': 0},
+ "Hi, NED!"
+ )
+ self.try_render(
+ "Hi, {% if ned %}NED{% endif %}{% if ben %}BEN{% endif %}!",
+ {'ned': 0, 'ben': 1},
+ "Hi, BEN!"
+ )
+ self.try_render(
+ "Hi, {% if ned %}NED{% if ben %}BEN{% endif %}{% endif %}!",
+ {'ned': 0, 'ben': 0},
+ "Hi, !"
+ )
+ self.try_render(
+ "Hi, {% if ned %}NED{% if ben %}BEN{% endif %}{% endif %}!",
+ {'ned': 1, 'ben': 0},
+ "Hi, NED!"
+ )
+ self.try_render(
+ "Hi, {% if ned %}NED{% if ben %}BEN{% endif %}{% endif %}!",
+ {'ned': 1, 'ben': 1},
+ "Hi, NEDBEN!"
+ )
+
+ def test_loop_if(self):
+ self.try_render(
+ "@{% for n in nums %}{% if n %}Z{% endif %}{{n}}{% endfor %}!",
+ {'nums': [0,1,2]},
+ "@0Z1Z2!"
+ )
+ self.try_render(
+ "X{%if nums%}@{% for n in nums %}{{n}}{% endfor %}{%endif%}!",
+ {'nums': [0,1,2]},
+ "X@012!"
+ )
+ self.try_render(
+ "X{%if nums%}@{% for n in nums %}{{n}}{% endfor %}{%endif%}!",
+ {'nums': []},
+ "X!"
+ )
+
+ def test_nested_loops(self):
+ self.try_render(
+ "@{% for n in nums %}"
+ "{% for a in abc %}{{a}}{{n}}{% endfor %}"
+ "{% endfor %}!",
+ {'nums': [0,1,2], 'abc': ['a', 'b', 'c']},
+ "@a0b0c0a1b1c1a2b2c2!"
+ )
+
+ def test_exception_during_evaluation(self):
+ # TypeError: Couldn't evaluate {{ foo.bar.baz }}:
+ # 'NoneType' object is unsubscriptable
+ self.assertRaises(TypeError, self.try_render,
+ "Hey {{foo.bar.baz}} there", {'foo': None}, "Hey ??? there"
+ )
+
+ def test_bogus_tag_syntax(self):
+ self.assertRaises(SyntaxError, self.try_render,
+ "Huh: {% bogus %}!!{% endbogus %}??", {}, ""
+ )
diff --git a/tests/test_testing.py b/tests/test_testing.py
new file mode 100644
index 0000000..c2d1453
--- /dev/null
+++ b/tests/test_testing.py
@@ -0,0 +1,192 @@
+# -*- coding: utf-8 -*-
+"""Tests that our test infrastructure is really working!"""
+
+import os, sys
+from coverage.backward import to_bytes, rpartition
+from test.backunittest import TestCase
+from test.coveragetest import CoverageTest
+
+from coverage.backward import set # pylint: disable=W0622
+
+class TestingTest(TestCase):
+ """Tests of helper methods on `backunittest.TestCase`."""
+
+ run_in_temp_dir = False
+
+ def please_raise(self, exc, msg):
+ """Raise an exception for testing assertRaisesRegexp."""
+ raise exc(msg)
+
+ def please_succeed(self):
+ """A simple successful method for testing assertRaisesRegexp."""
+ return "All is well"
+
+ def test_assert_same_elements(self):
+ self.assertSameElements(set(), set())
+ self.assertSameElements(set([1,2,3]), set([3,1,2]))
+ self.assertRaises(AssertionError, self.assertSameElements,
+ set([1,2,3]), set()
+ )
+ self.assertRaises(AssertionError, self.assertSameElements,
+ set([1,2,3]), set([4,5,6])
+ )
+
+ def test_assert_regexp_matches(self):
+ self.assertRegexpMatches("hello", "hel*o")
+ self.assertRegexpMatches("Oh, hello there!", "hel*o")
+ self.assertRaises(AssertionError, self.assertRegexpMatches,
+ "hello there", "^hello$"
+ )
+
+ def test_assert_multiline_equal(self):
+ self.assertMultiLineEqual("hello", "hello")
+ self.assertRaises(AssertionError, self.assertMultiLineEqual,
+ "hello there", "Hello there"
+ )
+ self.assertRaises(AssertionError, self.assertMultiLineEqual,
+ "hello\nthere", "hello\nThere"
+ )
+ # With messages also.
+ self.assertMultiLineEqual("hi", "hi", "it's ok")
+ self.assertRaisesRegexp(
+ AssertionError, "my message",
+ self.assertMultiLineEqual, "xyz", "abc", "my message"
+ )
+
+ def test_assert_raises_regexp(self):
+ # Raising the right error with the right message passes.
+ self.assertRaisesRegexp(
+ ZeroDivisionError, "Wow! Zero!",
+ self.please_raise, ZeroDivisionError, "Wow! Zero!"
+ )
+ # Raising the right error with a match passes.
+ self.assertRaisesRegexp(
+ ZeroDivisionError, "Zero",
+ self.please_raise, ZeroDivisionError, "Wow! Zero!"
+ )
+ # Raising the right error with a mismatch fails.
+ self.assertRaises(AssertionError,
+ self.assertRaisesRegexp, ZeroDivisionError, "XYZ",
+ self.please_raise, ZeroDivisionError, "Wow! Zero!"
+ )
+ # Raising the right error with a mismatch fails.
+ self.assertRaises(AssertionError,
+ self.assertRaisesRegexp, ZeroDivisionError, "XYZ",
+ self.please_raise, ZeroDivisionError, "Wow! Zero!"
+ )
+ # Raising the wrong error raises the error itself.
+ self.assertRaises(ZeroDivisionError,
+ self.assertRaisesRegexp, IOError, "Wow! Zero!",
+ self.please_raise, ZeroDivisionError, "Wow! Zero!"
+ )
+ # Raising no error fails.
+ self.assertRaises(AssertionError,
+ self.assertRaisesRegexp, ZeroDivisionError, "XYZ",
+ self.please_succeed
+ )
+
+ def test_assert_true(self):
+ self.assertTrue(True)
+ self.assertRaises(AssertionError, self.assertTrue, False)
+
+ def test_assert_false(self):
+ self.assertFalse(False)
+ self.assertRaises(AssertionError, self.assertFalse, True)
+
+ def test_assert_in(self):
+ self.assertIn("abc", "hello abc")
+ self.assertIn("abc", ["xyz", "abc", "foo"])
+ self.assertIn("abc", {'abc': 1, 'xyz': 2})
+ self.assertRaises(AssertionError, self.assertIn, "abc", "xyz")
+ self.assertRaises(AssertionError, self.assertIn, "abc", ["x", "xabc"])
+ self.assertRaises(AssertionError, self.assertIn, "abc", {'x':'abc'})
+
+ def test_assert_not_in(self):
+ self.assertRaises(AssertionError, self.assertNotIn, "abc", "hello abc")
+ self.assertRaises(AssertionError,
+ self.assertNotIn, "abc", ["xyz", "abc", "foo"]
+ )
+ self.assertRaises(AssertionError,
+ self.assertNotIn, "abc", {'abc': 1, 'xyz': 2}
+ )
+ self.assertNotIn("abc", "xyz")
+ self.assertNotIn("abc", ["x", "xabc"])
+ self.assertNotIn("abc", {'x':'abc'})
+
+ def test_assert_greater(self):
+ self.assertGreater(10, 9)
+ self.assertGreater("xyz", "abc")
+ self.assertRaises(AssertionError, self.assertGreater, 9, 10)
+ self.assertRaises(AssertionError, self.assertGreater, 10, 10)
+ self.assertRaises(AssertionError, self.assertGreater, "abc", "xyz")
+ self.assertRaises(AssertionError, self.assertGreater, "xyz", "xyz")
+
+
+class CoverageTestTest(CoverageTest):
+ """Test the methods in `CoverageTest`."""
+
+ def file_text(self, fname):
+ """Return the text read from a file."""
+ return open(fname, "rb").read().decode('ascii')
+
+ def test_make_file(self):
+ # A simple file.
+ self.make_file("fooey.boo", "Hello there")
+ self.assertEqual(open("fooey.boo").read(), "Hello there")
+ # A file in a sub-directory
+ self.make_file("sub/another.txt", "Another")
+ self.assertEqual(open("sub/another.txt").read(), "Another")
+ # A second file in that sub-directory
+ self.make_file("sub/second.txt", "Second")
+ self.assertEqual(open("sub/second.txt").read(), "Second")
+ # A deeper directory
+ self.make_file("sub/deeper/evenmore/third.txt")
+ self.assertEqual(open("sub/deeper/evenmore/third.txt").read(), "")
+
+ def test_make_file_newline(self):
+ self.make_file("unix.txt", "Hello\n")
+ self.assertEqual(self.file_text("unix.txt"), "Hello\n")
+ self.make_file("dos.txt", "Hello\n", newline="\r\n")
+ self.assertEqual(self.file_text("dos.txt"), "Hello\r\n")
+ self.make_file("mac.txt", "Hello\n", newline="\r")
+ self.assertEqual(self.file_text("mac.txt"), "Hello\r")
+
+ def test_make_file_non_ascii(self):
+ self.make_file("unicode.txt", "tabblo: «ταБЬℓσ»")
+ self.assertEqual(
+ open("unicode.txt", "rb").read(),
+ to_bytes("tabblo: «ταБЬℓσ»")
+ )
+
+ def test_file_exists(self):
+ self.make_file("whoville.txt", "We are here!")
+ self.assert_exists("whoville.txt")
+ self.assert_doesnt_exist("shadow.txt")
+ self.assertRaises(
+ AssertionError, self.assert_doesnt_exist, "whoville.txt"
+ )
+ self.assertRaises(AssertionError, self.assert_exists, "shadow.txt")
+
+ def test_sub_python_is_this_python(self):
+ # Try it with a python command.
+ os.environ['COV_FOOBAR'] = 'XYZZY'
+ self.make_file("showme.py", """\
+ import os, sys
+ print(sys.executable)
+ print(os.__file__)
+ print(os.environ['COV_FOOBAR'])
+ """)
+ out = self.run_command("python showme.py").splitlines()
+ self.assertEqual(out[0], sys.executable)
+ self.assertEqual(out[1], os.__file__)
+ self.assertEqual(out[2], 'XYZZY')
+
+ # Try it with a "coverage debug sys" command.
+ out = self.run_command("coverage debug sys").splitlines()
+ # "environment: COV_FOOBAR = XYZZY" or "COV_FOOBAR = XYZZY"
+ executable = [l for l in out if "executable:" in l][0]
+ executable = executable.split(":", 1)[1].strip()
+ self.assertEqual(executable, sys.executable)
+ environ = [l for l in out if "COV_FOOBAR" in l][0]
+ _, _, environ = rpartition(environ, ":")
+ self.assertEqual(environ.strip(), "COV_FOOBAR = XYZZY")
diff --git a/tests/test_xml.py b/tests/test_xml.py
new file mode 100644
index 0000000..204b586
--- /dev/null
+++ b/tests/test_xml.py
@@ -0,0 +1,84 @@
+"""Tests for XML reports from coverage.py."""
+
+import os, re
+import coverage
+
+from test.coveragetest import CoverageTest
+
+class XmlReportTest(CoverageTest):
+ """Tests of the XML reports from coverage.py."""
+
+ def run_mycode(self):
+ """Run mycode.py, so we can report on it."""
+ self.make_file("mycode.py", "print('hello')\n")
+ self.run_command("coverage run mycode.py")
+
+ def test_default_file_placement(self):
+ self.run_mycode()
+ self.run_command("coverage xml")
+ self.assert_exists("coverage.xml")
+
+ def test_argument_affects_xml_placement(self):
+ self.run_mycode()
+ self.run_command("coverage xml -o put_it_there.xml")
+ self.assert_doesnt_exist("coverage.xml")
+ self.assert_exists("put_it_there.xml")
+
+ def test_config_affects_xml_placement(self):
+ self.run_mycode()
+ self.make_file(".coveragerc", "[xml]\noutput = xml.out\n")
+ self.run_command("coverage xml")
+ self.assert_doesnt_exist("coverage.xml")
+ self.assert_exists("xml.out")
+
+ def test_no_data(self):
+ # https://bitbucket.org/ned/coveragepy/issue/210
+ self.run_command("coverage xml")
+ self.assert_doesnt_exist("coverage.xml")
+
+ def test_no_source(self):
+ # Written while investigating a bug, might as well keep it.
+ # https://bitbucket.org/ned/coveragepy/issue/208
+ self.make_file("innocuous.py", "a = 4")
+ cov = coverage.coverage()
+ self.start_import_stop(cov, "innocuous")
+ os.remove("innocuous.py")
+ cov.xml_report(ignore_errors=True)
+ self.assert_exists("coverage.xml")
+
+ def run_doit(self):
+ """Construct a simple sub-package."""
+ self.make_file("sub/__init__.py")
+ self.make_file("sub/doit.py", "print('doit!')")
+ self.make_file("main.py", "import sub.doit")
+ cov = coverage.coverage()
+ self.start_import_stop(cov, "main")
+ return cov
+
+ def test_filename_format_showing_everything(self):
+ cov = self.run_doit()
+ cov.xml_report(outfile="-")
+ xml = self.stdout()
+ doit_line = re_line(xml, "class.*doit")
+ self.assertIn('filename="sub/doit.py"', doit_line)
+
+ def test_filename_format_including_filename(self):
+ cov = self.run_doit()
+ cov.xml_report(["sub/doit.py"], outfile="-")
+ xml = self.stdout()
+ doit_line = re_line(xml, "class.*doit")
+ self.assertIn('filename="sub/doit.py"', doit_line)
+
+ def test_filename_format_including_module(self):
+ cov = self.run_doit()
+ import sub.doit # pylint: disable=F0401
+ cov.xml_report([sub.doit], outfile="-")
+ xml = self.stdout()
+ doit_line = re_line(xml, "class.*doit")
+ self.assertIn('filename="sub/doit.py"', doit_line)
+
+
+def re_line(text, pat):
+ """Return the one line in `text` that matches regex `pat`."""
+ lines = [l for l in text.splitlines() if re.search(pat, l)]
+ return lines[0]
diff --git a/tests/try_execfile.py b/tests/try_execfile.py
new file mode 100644
index 0000000..9bbabd1
--- /dev/null
+++ b/tests/try_execfile.py
@@ -0,0 +1,34 @@
+"""Test file for run_python_file."""
+
+import os, pprint, sys
+
+DATA = "xyzzy"
+
+import __main__
+
+def my_function(a):
+ """A function to force execution of module-level values."""
+ return "my_fn(%r)" % a
+
+FN_VAL = my_function("fooey")
+
+try:
+ pkg = __package__
+except NameError:
+ pkg = "*No __package__*"
+
+globals_to_check = {
+ '__name__': __name__,
+ '__file__': __file__,
+ '__doc__': __doc__,
+ '__builtins__.has_open': hasattr(__builtins__, 'open'),
+ '__builtins__.dir': dir(__builtins__),
+ '__package__': pkg,
+ 'DATA': DATA,
+ 'FN_VAL': FN_VAL,
+ '__main__.DATA': getattr(__main__, "DATA", "nothing"),
+ 'argv': sys.argv,
+ 'path': [os.path.normcase(p) for p in sys.path],
+}
+
+pprint.pprint(globals_to_check)